001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.juneau.objecttools; 018 019import static org.apache.juneau.commons.utils.CollectionUtils.*; 020import static org.apache.juneau.commons.utils.Utils.*; 021 022import java.lang.reflect.*; 023import java.util.*; 024 025import org.apache.juneau.*; 026 027/** 028 * POJO model sorter. 029 * 030 * <p> 031 * This class is designed to sort arrays and collections of maps or beans. 032 * </p> 033 * 034 * <h5 class='section'>Example:</h5> 035 * <p class='bjava'> 036 * MyBean[] <jv>arrayOfBeans</jv> = ...; 037 * ObjectSorter <jv>sorter</jv> = ObjectSorter.<jsm>create</jsm>(); 038 * 039 * <jc>// Returns a list of beans sorted accordingly.</jc> 040 * List<MyBean> <jv>result</jv> = <jv>sorter</jv>.run(<jv>arrayOfBeans</jv>, <js>"foo,bar-"</js>); 041 * </p> 042 * <p> 043 * The tool can be used against the following data types: 044 * </p> 045 * <ul> 046 * <li>Arrays/collections of maps or beans. 047 * </ul> 048 * <p> 049 * The arguments are a simple comma-delimited list of property names optionally suffixed with <js>'+'</js> and <js>'-'</js> to 050 * denote ascending/descending order. 051 * </p> 052 * 053 * <h5 class='section'>See Also:</h5><ul> 054 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/ObjectTools">Object Tools</a> 055 056 * </ul> 057 */ 058@SuppressWarnings({ "unchecked", "rawtypes" }) 059public class ObjectSorter implements ObjectTool<SortArgs> { 060 private static class SortEntry implements Comparable { 061 Object o; 062 ClassMeta<?> cm; 063 BeanSession bs; 064 065 Object sortVal; 066 boolean isDesc; 067 068 SortEntry(BeanSession bs, Object o) { 069 this.o = o; 070 this.bs = bs; 071 this.cm = bs.getClassMetaForObject(o); 072 } 073 074 @Override 075 public int compareTo(Object o) { 076 if (isDesc) 077 return cmp(((SortEntry)o).sortVal, this.sortVal); 078 return cmp(this.sortVal, ((SortEntry)o).sortVal); 079 } 080 081 void setSort(String sortCol, boolean isDesc) { 082 this.isDesc = isDesc; 083 084 if (cm == null) 085 sortVal = null; 086 else if (cm.isMap()) 087 sortVal = ((Map)o).get(sortCol); 088 else if (cm.isBean()) 089 sortVal = bs.toBeanMap(o).get(sortCol); 090 else 091 sortVal = null; 092 } 093 } 094 095 /** 096 * Default reusable searcher. 097 */ 098 public static final ObjectSorter DEFAULT = new ObjectSorter(); 099 100 /** 101 * Static creator. 102 * 103 * @return A new {@link ObjectSorter} object. 104 */ 105 public static ObjectSorter create() { 106 return new ObjectSorter(); 107 } 108 109 @Override /* Overridden from ObjectTool */ 110 public Object run(BeanSession session, Object input, SortArgs args) { 111 if (input == null) 112 return null; 113 114 // If sort or view isn't empty, then we need to make sure that all entries in the 115 // list are maps. 116 var sort = args.getSort(); 117 118 if (sort.isEmpty()) 119 return input; 120 121 var type = session.getClassMetaForObject(input); 122 123 if (! type.isCollectionOrArray()) 124 return input; 125 126 var l = (ArrayList<SortEntry>)null; 127 128 if (type.isArray()) { 129 var size = Array.getLength(input); 130 l = listOfSize(size); 131 for (var i = 0; i < size; i++) 132 l.add(new SortEntry(session, Array.get(input, i))); 133 } else /* isCollection() */ { 134 var c = (Collection)input; 135 l = listOfSize(c.size()); 136 List<SortEntry> l2 = l; 137 c.forEach(x -> l2.add(new SortEntry(session, x))); 138 } 139 140 // We reverse the list and sort last to first. 141 var columns = toList(sort.keySet()); 142 Collections.reverse(columns); 143 144 var l3 = l; 145 columns.forEach(c -> { 146 final boolean isDesc = sort.get(c); 147 l3.forEach(se -> se.setSort(c, isDesc)); 148 Collections.sort(l3); 149 }); 150 151 var l2 = listOfSize(l.size()); 152 l.forEach(x -> l2.add(x.o)); 153 154 return l2; 155 } 156 157 /** 158 * Convenience method for executing the sorter. 159 * 160 * @param <R> The return type. 161 * @param input The input. 162 * @param sortArgs The sort arguments. See {@link SortArgs} for format. 163 * @return A list of maps/beans matching the 164 */ 165 public <R> List<R> run(Object input, String sortArgs) { 166 var r = run(BeanContext.DEFAULT_SESSION, input, SortArgs.create(sortArgs)); 167 if (r instanceof List r2) 168 return r2; 169 if (r instanceof Collection r2) 170 return toList(r2); 171 if (isArray(r)) 172 return l((R[])r); 173 return null; 174 } 175}