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&lt;MyBean&gt; <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}