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