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