content: /*****************************************************************
   *   Licensed to the Apache Software Foundation (ASF) under one
   *  or more contributor license agreements.  See the NOTICE file
   *  distributed with this work for additional information
   *  regarding copyright ownership.  The ASF licenses this file
   *  to you under the Apache License, Version 2.0 (the
   *  "License"); you may not use this file except in compliance
   *  with the License.  You may obtain a copy of the License at
   *
   *    https://www.apache.org/licenses/LICENSE-2.0
   *
   *  Unless required by applicable law or agreed to in writing,
   *  software distributed under the License is distributed on an
   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
   *  KIND, either express or implied.  See the License for the
   *  specific language governing permissions and limitations
   *  under the License.
   ****************************************************************/
  
  package org.apache.cayenne.query;
  
  import java.io.PrintWriter;
  import java.io.Serializable;
  import java.io.StringWriter;
  import java.util.ArrayList;
  import java.util.Collection;
  import java.util.Collections;
  import java.util.Comparator;
  import java.util.List;
  
  import org.apache.cayenne.configuration.ConfigurationNodeVisitor;
  import org.apache.cayenne.configuration.EmptyConfigurationNodeVisitor;
  import org.apache.cayenne.exp.Expression;
  import org.apache.cayenne.exp.ExpressionException;
  import org.apache.cayenne.exp.parser.ASTDbPath;
  import org.apache.cayenne.exp.parser.ASTObjPath;
  import org.apache.cayenne.util.ConversionUtil;
  import org.apache.cayenne.util.Util;
  import org.apache.cayenne.util.XMLEncoder;
  import org.apache.cayenne.util.XMLSerializable;
  
  /**
   * Defines object sorting criteria, used either for in-memory sorting of object
   * lists or as a specification for building <em>ORDER BY</em> clause of a
   * SelectQuery query. Note that in case of in-memory sorting, Ordering can be
   * used with any JavaBeans, not just Persistent objects.
   */
  public class Ordering implements Comparator<Object>, Serializable, XMLSerializable {
  
  	private static final long serialVersionUID = -9167074787055881422L;
  
  	protected String sortSpecString;
  	protected transient Expression sortSpec;
  	protected SortOrder sortOrder;
  	protected boolean pathExceptionSuppressed = false;
  	protected boolean nullSortedFirst = true;
  
  	/**
  	 * Orders a given list of objects, using a List of Orderings applied
  	 * according the default iteration order of the Orderings list. I.e. each
  	 * Ordering with lower index is more significant than any other Ordering
  	 * with higher index. List being ordered is modified in place.
  	 *
  	 * @param objects elements to sort
  	 * @param orderings list of Orderings to be applied
  	 */
  	@SuppressWarnings("unchecked")
  	public static void orderList(List<?> objects, List<? extends Ordering> orderings) {
  		if(objects == null || orderings == null || orderings.isEmpty()) {
  			return;
  		}
  		Comparator<Object> comparator = orderings.get(0);
  		for(int i=1; i<orderings.size(); i++) {
  			comparator = comparator.thenComparing(orderings.get(i));
  		}
  		objects.sort(comparator);
  	}
  
  	/**
  	 * Orders a given list of objects, using a List of Orderings applied
  	 * according the default iteration order of the Orderings list. I.e. each
  	 * Ordering with lower index is more significant than any other Ordering
  	 * with higher index.
  	 *
  	 * @param objects elements to sort
  	 * @param orderings list of Orderings to be applied
  	 * @return new List with ordered elements
  	 *
  	 * @since 4.0
  	 */
  	public static <E> List<E> orderedList(final Collection<E> objects, List<? extends Ordering> orderings) {
  		List<E> newList = new ArrayList<>(objects);
  		
  		orderList(newList, orderings);
  		
  		return newList;
  	}
  	
  	public Ordering() {
  	}
  
  	/**
  	 * Create an ordering instance with a provided path and ascending sorting
  	 * strategy.
  	 * 
  	 * @since 4.0
  	 */
  	public Ordering(String sortPathSpec) {
  		this(sortPathSpec, SortOrder.ASCENDING);
  	}
  
  	/**
  	 * @since 3.0
  	 */
  	public Ordering(String sortPathSpec, SortOrder sortOrder) {
  		setSortSpecString(sortPathSpec);
  		setSortOrder(sortOrder);
  	}
  
  	/**
  	 * @since 4.0
  	 */
  	public Ordering(Expression sortSpec) {
  		this(sortSpec, SortOrder.ASCENDING);
  	}
  
  	/**
  	 * @since 4.0
  	 */
  	public Ordering(Expression sortSpec, SortOrder sortOrder) {
  		setSortSpec(sortSpec);
  		setSortOrder(sortOrder);
  	}
  
  	@Override
  	public boolean equals(Object object) {
  		if (this == object) {
  			return true;
  		}
  
  		if (!(object instanceof Ordering)) {
  			return false;
  		}
  
  		Ordering o = (Ordering) object;
  
  		if (!Util.nullSafeEquals(sortSpecString, o.sortSpecString)) {
  			return false;
  		}
  
  		if (sortOrder != o.sortOrder) {
  			return false;
  		}
  
  		if (pathExceptionSuppressed != o.pathExceptionSuppressed) {
  			return false;
  		}
  
  		if (nullSortedFirst != o.nullSortedFirst) {
  			return false;
  		}
  
  		return true;
  	}
  
  	@Override
  	public int hashCode() {
  		int result = sortSpecString != null ? sortSpecString.hashCode() : 0;
  		result = 31 * result + (sortOrder != null ? sortOrder.hashCode() : 0);
  		result = 31 * result + (pathExceptionSuppressed ? 1 : 0);
  		result = 31 * result + (nullSortedFirst ? 1 : 0);
  		return result;
  	}
  
  	/**
  	 * Sets sortSpec to be an expression represented by string argument.
  	 * 
  	 * @since 1.1
  	 */
  	public void setSortSpecString(String sortSpecString) {
  		if (!Util.nullSafeEquals(this.sortSpecString, sortSpecString)) {
  			this.sortSpecString = sortSpecString;
  			this.sortSpec = null;
  		}
  	}
  
  	/**
  	 * Sets sort order for whether nulls are at the top or bottom of the
  	 * resulting list. Default is true.
  	 * Affects only in-memory sorting.
  	 * 
  	 * @param nullSortedFirst
  	 *            true sorts nulls to the top of the list, false sorts nulls to
  	 *            the bottom
  	 */
  	public void setNullSortedFirst(boolean nullSortedFirst) {
  		this.nullSortedFirst = nullSortedFirst;
  	}
  
  	/**
  	 * Get sort order for nulls.
  	 * 
  	 * @return true if nulls are sorted to the top of the list, false if sorted
  	 *         to the bottom
  	 */
  	public boolean isNullSortedFirst() {
  		return nullSortedFirst;
  	}
  
  	/**
  	 * Sets whether a path with a null in the middle is ignored. For example, a
  	 * sort from <code>painting</code> on <code>artist.name</code> would by
  	 * default throw an exception if the artist was null. If set to true, then
  	 * this is treated just like a null value. Default is false.
  	 * 
  	 * @param pathExceptionSuppressed
  	 *            true to suppress exceptions and sort as null
  	 */
  	public void setPathExceptionSupressed(boolean pathExceptionSuppressed) {
  		this.pathExceptionSuppressed = pathExceptionSuppressed;
  	}
  
  	/**
  	 * Is a path with a null in the middle is ignored.
  	 * 
  	 * @return true is exception is suppressed and sorted as null
  	 */
  	public boolean isPathExceptionSuppressed() {
  		return pathExceptionSuppressed;
  	}
  
  	/**
  	 * Returns sortSpec string representation.
  	 * 
  	 * @since 1.1
  	 */
  	public String getSortSpecString() {
  		return sortSpecString;
  	}
  
  	/**
  	 * Sets the sort order for this ordering.
  	 * 
  	 * @since 3.0
  	 */
  	public void setSortOrder(SortOrder order) {
  		this.sortOrder = order;
  	}
  
  	/** Returns true if sorting is done in ascending order. */
  	public boolean isAscending() {
  		return sortOrder == null || sortOrder == SortOrder.ASCENDING || sortOrder == SortOrder.ASCENDING_INSENSITIVE;
  	}
  
  	/**
  	 * Returns true if the sorting is done in descending order.
  	 * 
  	 * @since 3.0
  	 */
  	public boolean isDescending() {
  		return !isAscending();
  	}
  
  	/**
  	 * If the sort order is DESCENDING or DESCENDING_INSENSITIVE, sets the sort
  	 * order to ASCENDING or ASCENDING_INSENSITIVE, respectively.
  	 * 
  	 * @since 3.0
  	 */
  	public void setAscending() {
  		if (sortOrder == null || sortOrder == SortOrder.DESCENDING) {
  			setSortOrder(SortOrder.ASCENDING);
  		} else if (sortOrder == SortOrder.DESCENDING_INSENSITIVE) {
  			setSortOrder(SortOrder.ASCENDING_INSENSITIVE);
  		}
  	}
  
  	/**
  	 * If the sort order is ASCENDING or ASCENDING_INSENSITIVE, sets the sort
  	 * order to DESCENDING or DESCENDING_INSENSITIVE, respectively.
  	 * 
  	 * @since 3.0
  	 */
  	public void setDescending() {
  		if (sortOrder == null || sortOrder == SortOrder.ASCENDING) {
  			setSortOrder(SortOrder.DESCENDING);
  		} else if (sortOrder == SortOrder.ASCENDING_INSENSITIVE) {
  			setSortOrder(SortOrder.DESCENDING_INSENSITIVE);
  		}
  	}
  
  	/** Returns true if the sorting is case insensitive */
  	public boolean isCaseInsensitive() {
  		return !isCaseSensitive();
  	}
  
  	/**
  	 * Returns true if the sorting is case sensitive.
  	 * 
  	 * @since 3.0
  	 */
  	public boolean isCaseSensitive() {
  		return sortOrder == null || sortOrder == SortOrder.ASCENDING || sortOrder == SortOrder.DESCENDING;
  	}
  
  	/**
  	 * If the sort order is ASCENDING or DESCENDING, sets the sort order to
  	 * ASCENDING_INSENSITIVE or DESCENDING_INSENSITIVE, respectively.
  	 * 
  	 * @since 3.0
  	 */
  	public void setCaseInsensitive() {
  		if (sortOrder == null || sortOrder == SortOrder.ASCENDING) {
  			setSortOrder(SortOrder.ASCENDING_INSENSITIVE);
  		} else if (sortOrder == SortOrder.DESCENDING) {
  			setSortOrder(SortOrder.DESCENDING_INSENSITIVE);
  		}
  	}
  
  	/**
  	 * If the sort order is ASCENDING_INSENSITIVE or DESCENDING_INSENSITIVE,
  	 * sets the sort order to ASCENDING or DESCENDING, respectively.
  	 * 
  	 * @since 3.0
  	 */
  	public void setCaseSensitive() {
  		if (sortOrder == null || sortOrder == SortOrder.ASCENDING_INSENSITIVE) {
  			setSortOrder(SortOrder.ASCENDING);
  		} else if (sortOrder == SortOrder.DESCENDING_INSENSITIVE) {
  			setSortOrder(SortOrder.DESCENDING);
  		}
  	}
  
  	/**
  	 * Returns the expression defining a ordering Java Bean property.
  	 */
  	public Expression getSortSpec() {
  		if (sortSpecString == null) {
  			return null;
  		}
  
  		// compile on demand .. since orderings can only be paths, avoid the
  		// overhead of
  		// Expression.fromString, and parse them manually
  		if (sortSpec == null) {
  
  			if (sortSpecString.startsWith(ASTDbPath.DB_PREFIX)) {
  				sortSpec = new ASTDbPath(sortSpecString.substring(ASTDbPath.DB_PREFIX.length()));
  			} else if (sortSpecString.startsWith(ASTObjPath.OBJ_PREFIX)) {
  				sortSpec = new ASTObjPath(sortSpecString.substring(ASTObjPath.OBJ_PREFIX.length()));
  			} else {
  				sortSpec = new ASTObjPath(sortSpecString);
  			}
  		}
  
  		return sortSpec;
  	}
  
  	/**
  	 * Sets the expression defining a ordering Java Bean property.
  	 */
  	public void setSortSpec(Expression sortSpec) {
  		this.sortSpec = sortSpec;
  		this.sortSpecString = (sortSpec != null) ? sortSpec.toString() : null;
  	}
  
  	/**
  	 * Orders the given list of objects according to the ordering that this
  	 * object specifies. List is modified in-place.
  	 * 
  	 * @param objects
  	 *            a List of objects to be sorted
  	 */
  	public void orderList(List<?> objects) {
  		Collections.sort(objects, this);
  	}
  
  	/**
  	 * @since 4.0
  	 */
  	public <E> List<E> orderedList(Collection<E> objects) {
  		List<E> newList = new ArrayList<>(objects);
  		
  		orderList(newList);
  		
  		return newList;
  	}
  
  	/**
  	 * Comparable interface implementation. Can compare two Java Beans based on
  	 * the stored expression.
  	 */
  	@Override
  	public int compare(Object o1, Object o2) {
  		Expression exp = getSortSpec();
  		Object value1 = null;
  		Object value2 = null;
  		try {
  			value1 = exp.evaluate(o1);
  		} catch (ExpressionException e) {
  			if (pathExceptionSuppressed && e.getCause() instanceof org.apache.cayenne.reflect.UnresolvablePathException) {
  				// do nothing, we expect this
  			} else {
  				// re-throw
  				throw e;
  			}
  		}
  
  		try {
  			value2 = exp.evaluate(o2);
  		} catch (ExpressionException e) {
  			if (pathExceptionSuppressed && e.getCause() instanceof org.apache.cayenne.reflect.UnresolvablePathException) {
  				// do nothing, we expect this
  			} else {
  				// rethrow
  				throw e;
  			}
  		}
  
  		if (value1 == null && value2 == null) {
  			return 0;
  		} else if (value1 == null) {
  			return nullSortedFirst ? -1 : 1;
  		} else if (value2 == null) {
  			return nullSortedFirst ? 1 : -1;
  		}
  
  		if (isCaseInsensitive()) {
  			// TODO: to upper case should probably be defined as a separate
  			// expression
  			// type
  			value1 = ConversionUtil.toUpperCase(value1);
  			value2 = ConversionUtil.toUpperCase(value2);
  		}
  
  		int compareResult = ConversionUtil.toComparable(value1).compareTo(ConversionUtil.toComparable(value2));
  		return (isAscending()) ? compareResult : -compareResult;
  	}
  
  	/**
  	 * Encodes itself as a query ordering.
  	 * 
  	 * @since 1.1
  	 */
  	@Override
  	public void encodeAsXML(XMLEncoder encoder, ConfigurationNodeVisitor delegate) {
  		encoder.start("ordering")
  				.attribute("descending", isDescending())
  				.attribute("ignore-case", isCaseInsensitive())
  				.nested(getSortSpec(), delegate)
  				.end();
  	}
  
  	@Override
  	public String toString() {
  		StringWriter buffer = new StringWriter();
  		PrintWriter pw = new PrintWriter(buffer);
  		XMLEncoder encoder = new XMLEncoder(pw);
  		encodeAsXML(encoder, new EmptyConfigurationNodeVisitor());
  		pw.close();
  		buffer.flush();
  		return buffer.toString();
  	}
  
  	/**
  	 * Returns sort order for this ordering
  	 * 
  	 * @since 3.1
  	 */
  	public SortOrder getSortOrder() {
  		return sortOrder;
  	}
  	
  	
  	 /**
  	  * Returns Orderings with this Ordering followed by the provided
  	  * next Ordering.
  	  * 
  	  * @param nextOrdering the next Ordering to chain to this
  	  * @return a new Orderings with both Ordering
  	  * @since 4.1
  	  */
  	 public Orderings then(Ordering nextOrdering) {
  	 	return new Orderings(this, nextOrdering);
  	 }
  
  	 /**
  	  * Returns Orderings with this Ordering followed by the provided
  	  * list of next Orderings.
  	  * 
  	  * @param nextOrderings the next Orderings to chain to this
  	  * @return an array of sort orderings
  	  * @since 4.1
  	  */
  	 public Orderings then(Orderings nextOrderings) {
  	 	Orderings newOrderings = new Orderings(this);
  	 	
  	 	return newOrderings.then(nextOrderings);
  	 }
  	 
  	 /**
  	  * @see Orderings#then(Orderings)
  	  * @param nextOrderings the next Orderings to chain to this
  	  * @return an array of sort orderings
  	  * @since 4.1
  	  */
  	 public Orderings then(List<Ordering> nextOrderings) {
  	 	Orderings newOrderings = new Orderings(this);
  	 	
  	 	return newOrderings.then(nextOrderings);
  	 }
  }
  