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.common.utils.Utils.*; 020import static org.apache.juneau.internal.CollectionUtils.*; 021 022import java.lang.reflect.*; 023import java.util.*; 024 025import org.apache.juneau.*; 026import org.apache.juneau.common.utils.*; 027 028/** 029 * POJO model sorter. 030 * 031 * <p> 032 * This class is designed to sort arrays and collections of maps or beans. 033 * </p> 034 * 035 * <h5 class='section'>Example:</h5> 036 * <p class='bjava'> 037 * MyBean[] <jv>arrayOfBeans</jv> = ...; 038 * ObjectSorter <jv>sorter</jv> = ObjectSorter.<jsm>create</jsm>(); 039 * 040 * <jc>// Returns a list of beans sorted accordingly.</jc> 041 * List<MyBean> <jv>result</jv> = <jv>sorter</jv>.run(<jv>arrayOfBeans</jv>, <js>"foo,bar-"</js>); 042 * </p> 043 * <p> 044 * The tool can be used against the following data types: 045 * </p> 046 * <ul> 047 * <li>Arrays/collections of maps or beans. 048 * </ul> 049 * <p> 050 * The arguments are a simple comma-delimited list of property names optionally suffixed with <js>'+'</js> and <js>'-'</js> to 051 * denote ascending/descending order. 052 * </p> 053 * 054 * <h5 class='section'>See Also:</h5><ul> 055 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/ObjectTools">Object Tools</a> 056 057 * </ul> 058 */ 059@SuppressWarnings({"unchecked","rawtypes"}) 060public class ObjectSorter implements ObjectTool<SortArgs> { 061 062 //----------------------------------------------------------------------------------------------------------------- 063 // Static 064 //----------------------------------------------------------------------------------------------------------------- 065 066 /** 067 * Default reusable searcher. 068 */ 069 public static final ObjectSorter DEFAULT = new ObjectSorter(); 070 071 /** 072 * Static creator. 073 * 074 * @return A new {@link ObjectSorter} object. 075 */ 076 public static ObjectSorter create() { 077 return new ObjectSorter(); 078 } 079 080 //----------------------------------------------------------------------------------------------------------------- 081 // Instance 082 //----------------------------------------------------------------------------------------------------------------- 083 084 /** 085 * Convenience method for executing the sorter. 086 * 087 * @param <R> The return type. 088 * @param input The input. 089 * @param sortArgs The sort arguments. See {@link SortArgs} for format. 090 * @return A list of maps/beans matching the 091 */ 092 public <R> List<R> run(Object input, String sortArgs) { 093 Object r = run(BeanContext.DEFAULT_SESSION, input, SortArgs.create(sortArgs)); 094 if (r instanceof List) 095 return (List<R>)r; 096 if (r instanceof Collection) 097 return new ArrayList<R>((Collection)r); 098 if (isArray(r)) 099 return Arrays.asList((R[])r); 100 return null; 101 } 102 103 @Override /* ObjectTool */ 104 public Object run(BeanSession session, Object input, SortArgs args) { 105 if (input == null) 106 return null; 107 108 // If sort or view isn't empty, then we need to make sure that all entries in the 109 // list are maps. 110 Map<String,Boolean> sort = args.getSort(); 111 112 if (sort.isEmpty()) 113 return input; 114 115 ClassMeta type = session.getClassMetaForObject(input); 116 117 if (! type.isCollectionOrArray()) 118 return input; 119 120 ArrayList<SortEntry> l = null; 121 122 if (type.isArray()) { 123 int size = Array.getLength(input); 124 l = Utils.listOfSize(size); 125 for (int i = 0; i < size; i++) 126 l.add(new SortEntry(session, Array.get(input, i))); 127 } else /* isCollection() */ { 128 Collection c = (Collection)input; 129 l = Utils.listOfSize(c.size()); 130 List<SortEntry> l2 = l; 131 c.forEach(x -> l2.add(new SortEntry(session, x))); 132 } 133 134 // We reverse the list and sort last to first. 135 List<String> columns = listFrom(sort.keySet()); 136 Collections.reverse(columns); 137 138 List<SortEntry> l3 = l; 139 columns.forEach(c -> { 140 final boolean isDesc = sort.get(c); 141 l3.forEach(se -> se.setSort(c, isDesc)); 142 Collections.sort(l3); 143 }); 144 145 List<Object> l2 = Utils.listOfSize(l.size()); 146 l.forEach(x -> l2.add(x.o)); 147 148 return l2; 149 } 150 151 private static class SortEntry implements Comparable { 152 Object o; 153 ClassMeta<?> cm; 154 BeanSession bs; 155 156 Object sortVal; 157 boolean isDesc; 158 159 SortEntry(BeanSession bs, Object o) { 160 this.o = o; 161 this.bs = bs; 162 this.cm = bs.getClassMetaForObject(o); 163 } 164 165 void setSort(String sortCol, boolean isDesc) { 166 this.isDesc = isDesc; 167 168 if (cm == null) 169 sortVal = null; 170 else if (cm.isMap()) 171 sortVal = ((Map)o).get(sortCol); 172 else if (cm.isBean()) 173 sortVal = bs.toBeanMap(o).get(sortCol); 174 else 175 sortVal = null; 176 } 177 178 @Override 179 public int compareTo(Object o) { 180 if (isDesc) 181 return Utils.compare(((SortEntry)o).sortVal, this.sortVal); 182 return Utils.compare(this.sortVal, ((SortEntry)o).sortVal); 183 } 184 } 185}