[tools] reenginering:
 - create a new CollectionTool ($collection) containing collection sorting and string splitting methods
 - make toLocale(Object) a public method of LocaleConfig
 - deprecate SortTool and ConversionTool (splitting goes to CollectionTool, number parsing is redundant with NumberTool, toLocale is now in LocaleConfig, date conversions are redundant with DateTool ones)


git-svn-id: https://svn.apache.org/repos/asf/velocity/tools/trunk@1770544 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/velocity-tools-generic/src/main/java/org/apache/velocity/tools/generic/CollectionTool.java b/velocity-tools-generic/src/main/java/org/apache/velocity/tools/generic/CollectionTool.java
new file mode 100644
index 0000000..c9d2ca2
--- /dev/null
+++ b/velocity-tools-generic/src/main/java/org/apache/velocity/tools/generic/CollectionTool.java
@@ -0,0 +1,528 @@
+package org.apache.velocity.tools.generic;
+
+/*
+ * 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
+ *
+ *   http://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.
+ */
+
+import org.apache.commons.beanutils.PropertyUtils;
+import org.apache.velocity.tools.config.DefaultKey;
+
+import java.lang.reflect.Array;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * <p>CollectionTool allows a user to sort a collection (or array, iterator, etc)
+ * on any arbitrary set of properties exposed by the objects contained
+ * within the collection, and to generate arrays by splitting strings.
+ * </p>
+ *
+ * <p>The sort tool is specifically designed to use within a #foreach
+ * but you may find other uses for it.</p>
+ *
+ * <p>The sort tool can handle all of the collection types supported by
+ * #foreach and the same constraints apply as well as the following.
+ * Every object in the collection must support the set of properties
+ * selected to sort on. Each property which is to be sorted on must
+ * return one of the follow:
+ * <ul>
+ *   <li>Primitive type: e.g. int, char, long etc</li>
+ *   <li>Standard Object: e.g. String, Integer, Long etc</li>
+ *   <li>Object which implements the Comparable interface.</li>
+ * </ul>
+ * </p>
+ *
+ * <p>During the sort operation all properties are compared by calling
+ * compareTo() with the exception of Strings for which
+ * compareToIgnoreCase() is called.</p>
+ *
+ * <p>The sort is performed by calling Collections.sort() after
+ * marshalling the collection to sort into an appropriate collection type.
+ * The original collection will not be re-ordered; a new list containing
+ * the sorted elements will always be returned.</p>
+ *
+ * <p>The tool is used as follows:
+ * <pre>
+ * Single Property Sort
+ * #foreach($obj in $sorter.sort($objects, "name"))
+ *   $obj.name Ordinal= $obj.ordinal
+ * #end
+ * End
+ *
+ * Multiple Property Sort
+ * #foreach($obj in $sorter.sort($objects, ["name", "ordinal"]))
+ *   $obj.name, $obj.ordinal
+ * #end
+ * End
+ * </pre>
+ *
+ * The sort method takes two parameters a collection and a property name
+ * or an array of property names. The property names and corresponding
+ * methods must conform to java bean standards since commons-beanutils
+ * is used to extract the property values.</p>
+ *
+ * <p>By default the sort tool sorts ascending, you can override this by
+ * adding a sort type suffix to any property name.</p>
+ *
+ * <p>The supported suffixes are:
+ * <pre>
+ * For ascending
+ * :asc
+ * For descending
+ * :desc
+ *
+ * Example
+ * #foreach($obj in $sorter.sort($objects, ["name:asc", "ordinal:desc"]))
+ *   $obj.name, $obj.ordinal
+ * #end
+ * </pre><p>
+ *
+ * <p>This will sort first by Name in ascending order and then by Ordinal
+ * in descending order, of course you could have left the :asc off of the
+ * 'Name' property as ascending is always the default.</p>
+ *
+ * <p><pre>
+ * Example tools.xml config (if you want to use this with VelocityView):
+ * &lt;tools&gt;
+ *   &lt;toolbox scope="application"&gt;
+ *     &lt;tool class="org.apache.velocity.tools.generic.SortTool"/&gt;
+ *   &lt;/toolbox&gt;
+ * &lt;/tools&gt;
+ * </pre></p>
+ *
+ * @author S. Brett Sutton
+ * @author Nathan Bubna
+ * @since VelocityTools 3.0
+ * @version $Id$
+ */
+@DefaultKey("collection")
+public class CollectionTool extends SafeConfig
+{
+    public static final String STRINGS_DELIMITER_FORMAT_KEY = "stringsDelimiter";
+    public static final String STRINGS_TRIM_KEY = "trimStrings";
+
+    public static final String DEFAULT_STRINGS_DELIMITER = ",";
+    public static final boolean DEFAULT_STRINGS_TRIM = true;
+
+    private String stringsDelimiter = DEFAULT_STRINGS_DELIMITER;
+    private boolean stringsTrim = DEFAULT_STRINGS_TRIM;
+
+    /**
+     * Sets the delimiter used for separating values in a single String value.
+     * The default string delimiter is a comma.
+     *
+     * @see #split(String)
+     */
+    protected final void setStringsDelimiter(String stringsDelimiter)
+    {
+        this.stringsDelimiter = stringsDelimiter;
+    }
+
+    public final String getStringsDelimiter()
+    {
+        return this.stringsDelimiter;
+    }
+
+    /**
+     * Sets whether strings should be trimmed when separated from
+     * a delimited string value.
+     * The default is true.
+     *
+     * @see #split(String)
+     */
+    protected final void setStringsTrim(boolean stringsTrim)
+    {
+        this.stringsTrim = stringsTrim;
+    }
+
+    public final boolean getStringsTrim()
+    {
+        return this.stringsTrim;
+    }
+
+    /**
+     * Does the actual configuration. This is protected, so
+     * subclasses may share the same ValueParser and call configure
+     * at any time, while preventing templates from doing so when
+     * configure(Map) is locked.
+     */
+    protected void configure(ValueParser values)
+    {
+        super.configure(values);
+
+        String delimiter = values.getString(STRINGS_DELIMITER_FORMAT_KEY);
+        if (delimiter != null)
+        {
+            setStringsDelimiter(delimiter);
+        }
+
+        Boolean trim = values.getBoolean(STRINGS_TRIM_KEY);
+        if (trim != null)
+        {
+            setStringsTrim(trim);
+        }
+    }
+
+    /**
+     * @param value the value to be converted
+     * @return an array of String objects containing all of the values
+     *         derived from the specified array, Collection, or delimited String
+     */
+    public String[] split(String value)
+    {
+        if (value == null)
+        {
+            return null;
+        }
+        String[] values;
+        if (value.indexOf(this.stringsDelimiter) < 0)
+        {
+            values = new String[] { value };
+        }
+        else
+        {
+            values = value.split(this.stringsDelimiter);
+        }
+        if (this.stringsTrim)
+        {
+            for (int i=0,l=values.length; i < l; i++)
+            {
+                values[i] = values[i].trim();
+            }
+        }
+        return values;
+    }
+
+    /**
+     * Sorts a Collection using a Comparator. A defensive copy is made
+     * of the Collection beforehand, so the original Collection is left
+     * untouched.
+     *
+     * @param c The Collection to sort.
+     * @param comparator The comparator to use for sorting.
+     * @return A copy of the original Collection,
+     *         sorted using the supplied Comparator.
+     * @since VelocityTools 2.0.1
+     */
+    public <T> Collection<T> sort(final Collection<T> c,
+                                  final Comparator<T> comparator)
+    {
+        final ArrayList<T> list = new ArrayList<T>(c);
+        Collections.sort(list, comparator);
+        return list;
+    }
+
+    /**
+     * Sorts an array using a Comparator. A defensive copy is made
+     * of the array beforehand, so the original array is left
+     * untouched.
+     *
+     * @param a The array to sort.
+     * @param comparator The comparator to use for sorting.
+     * @return A copy of the original array,
+     *         sorted using the supplied Comparator.
+     * @since VelocityTools 2.0.1
+     */
+    public <T> T[] sort(final T[] a, final Comparator<T> comparator)
+    {
+        final T[] copy = a.clone();
+        Arrays.sort(copy, comparator);
+        return copy;
+    }
+
+    /**
+     * Sorts a Map's values using a Comparator. A defensive copy is made
+     * of the values beforehand, so the original Map is left
+     * untouched.
+     *
+     * @param map The Map whose values should be sorted.
+     * @param comparator The comparator to use for sorting.
+     * @return A copy of the original Map's values,
+     *         sorted using the supplied Comparator.
+     * @since VelocityTools 2.0.1
+     */
+    public <T> Collection<T> sort(final Map<?,T> map,
+                                  final Comparator<T> comparator)
+    {
+        return sort(map.values(), comparator);
+    }
+
+    /**
+     * Sorts a Collection (or array, or Map's values)
+     * using a Comparator. A defensive copy is made
+     * of the original beforehand, so the original is left
+     * untouched. Unsupported collection objects result in
+     * a <code>null</code> return value.
+     *
+     * @param o The Collection to sort.
+     * @param comparator The comparator to use for sorting.
+     * @return A copy of the original Collection,
+     *         sorted using the supplied Comparator.
+     * @since VelocityTools 2.0.1
+     */
+    public Collection<?> sort(final Object o,
+                              final Comparator<?> comparator)
+    {
+        if (o instanceof Collection)
+        {
+            return sort((Collection<?>)o, comparator);
+        }
+        else if (o instanceof Object[])
+        {
+            return sort((Object[])o, comparator);
+        }
+        else if (o instanceof Map)
+        {
+            return sort((Map<?,?>)o, comparator);
+        }
+        else
+        {
+            // the object type is not supported
+            getLog().error("object type not supported: {}", o == null ? "null" : o.getClass().getName());
+            return null;
+        }
+    }
+
+    public Collection sort(Collection collection)
+    {
+        return sort(collection, (List)null);
+    }
+
+    public Collection sort(Object[] array)
+    {
+        return sort(array, (List)null);
+    }
+
+    public Collection sort(Map map)
+    {
+        return sort(map, (List)null);
+    }
+
+    public Collection sort(Object object)
+    {
+        if (object instanceof Collection)
+        {
+            return sort((Collection)object, (List)null);
+        }
+        else if (object instanceof Object[])
+        {
+            return sort((Object[])object, (List)null);
+        }
+        else if (object instanceof Map)
+        {
+            return sort((Map)object, (List)null);
+        }
+        // the object type is not supported
+        getLog().error("object type not supported: {}", object == null ? "null" : object.getClass().getName());
+        return null;
+    }
+
+    /**
+     * Sorts the collection on a single property.
+     *
+     * @param object the collection to be sorted.
+     * @param property the property to sort on.
+     */
+    public Collection sort(Object object, String property)
+    {
+        List properties = new ArrayList(1);
+        properties.add(property);
+
+        if (object instanceof Collection)
+        {
+            return sort((Collection)object, properties);
+        }
+        else if (object instanceof Object[])
+        {
+            return sort((Object[])object, properties);
+        }
+        else if (object instanceof Map)
+        {
+            return sort((Map)object, properties);
+        }
+        // the object type is not supported
+        getLog().error("object type not supported: {}", object == null ? "null" : object.getClass().getName());
+        return null;
+    }
+
+    public Collection sort(Collection collection, List properties)
+    {
+        List list = new ArrayList(collection.size());
+        list.addAll(collection);
+        return internalSort(list, properties);
+    }
+
+    public Collection sort(Map map, List properties)
+    {
+        return sort(map.values(), properties);
+    }
+
+    public Collection sort(Object[] array, List properties)
+    {
+        return internalSort(Arrays.asList(array), properties);
+    }
+
+    protected Collection internalSort(List list, List properties)
+    {
+        try
+        {
+            if (properties == null)
+            {
+                Collections.sort(list);
+            } else {
+                Collections.sort(list, new PropertiesComparator(properties));
+            }
+            return list;
+        }
+        catch (Exception e)
+        {
+            getLog().error("exception encountered while sorting: {}", e.getMessage());
+            return null;
+        }
+    }
+
+
+    /**
+     * Does all of the comparisons
+     */
+    public static class PropertiesComparator
+        implements Comparator, java.io.Serializable
+    {
+        private static final int TYPE_ASCENDING = 1;
+        private static final int TYPE_DESCENDING = -1;
+
+        public static final String TYPE_ASCENDING_SHORT = "asc";
+        public static final String TYPE_DESCENDING_SHORT = "desc";
+
+        List properties;
+        int[] sortTypes;
+
+        public PropertiesComparator(List props)
+        {
+            // copy the list so we can safely drop :asc and :desc suffixes
+            this.properties = new ArrayList(props.size());
+            this.properties.addAll(props);
+
+            // determine ascending/descending
+            sortTypes = new int[properties.size()];
+
+            for (int i = 0; i < properties.size(); i++)
+            {
+                if (properties.get(i) == null)
+                {
+                    throw new IllegalArgumentException("Property " + i
+                            + "is null, sort properties may not be null.");
+                }
+
+                // determine if the property contains a sort type
+                // e.g "Name:asc" means sort by property Name ascending
+                String prop = properties.get(i).toString();
+                int colonIndex = prop.indexOf(':');
+                if (colonIndex != -1)
+                {
+                    String sortType = prop.substring(colonIndex + 1);
+                    properties.set(i, prop.substring(0, colonIndex));
+
+                    if (TYPE_ASCENDING_SHORT.equalsIgnoreCase(sortType))
+                    {
+                        sortTypes[i] = TYPE_ASCENDING;
+                    }
+                    else if (TYPE_DESCENDING_SHORT.equalsIgnoreCase(sortType))
+                    {
+                        sortTypes[i] = TYPE_DESCENDING;
+                    }
+                    else
+                    {
+                        //FIXME: log this
+                        // invalide property sort type. use default instead.
+                        sortTypes[i] = TYPE_ASCENDING;
+                    }
+                }
+                else
+                {
+                    // default sort type is ascending.
+                    sortTypes[i] = TYPE_ASCENDING;
+                }
+            }
+        }
+
+        public int compare(Object lhs, Object rhs)
+        {
+            for (int i = 0; i < properties.size(); i++)
+            {
+                int comparison = 0;
+                String property = (String)properties.get(i);
+
+                // properties must be comparable
+                Comparable left = getComparable(lhs, property);
+                Comparable right = getComparable(rhs, property);
+
+                if (left == null && right != null)
+                {
+                    // find out how right feels about left being null
+                    comparison = right.compareTo(null);
+                    // and reverse that (if it works)
+                    comparison *= -1;
+                }
+                else if (left instanceof String)
+                {
+                    //TODO: make it optional whether or not case is ignored
+                    comparison = ((String)left).compareToIgnoreCase((String)right);
+                }
+                else if (left != null)
+                {
+                    comparison = left.compareTo(right);
+                }
+
+                // return the first difference we find
+                if (comparison != 0)
+                {
+                    // multiplied by the sort direction, of course
+                    return comparison * sortTypes[i];
+                }
+            }
+            return 0;
+        }
+    }
+
+    /**
+     * Safely retrieves the comparable value for the specified property
+     * from the specified object. Subclasses that wish to perform more
+     * advanced, efficient, or just different property retrieval methods
+     * should override this method to do so.
+     */
+    protected static Comparable getComparable(Object object, String property)
+    {
+        try
+        {
+            return (Comparable)PropertyUtils.getProperty(object, property);
+        }
+        catch (Exception e)
+        {
+            throw new IllegalArgumentException("Could not retrieve comparable value for '"
+                                               + property + "' from " + object + ": " + e);
+        }
+    }
+
+}
diff --git a/velocity-tools-generic/src/main/java/org/apache/velocity/tools/generic/ConversionTool.java b/velocity-tools-generic/src/main/java/org/apache/velocity/tools/generic/ConversionTool.java
index 3d93678..b897e31 100644
--- a/velocity-tools-generic/src/main/java/org/apache/velocity/tools/generic/ConversionTool.java
+++ b/velocity-tools-generic/src/main/java/org/apache/velocity/tools/generic/ConversionTool.java
@@ -53,10 +53,13 @@
  * @author Nathan Bubna
  * @version $Revision$ $Date: 2007-02-26 11:24:39 -0800 (Mon, 26 Feb 2007) $
  * @since VelocityTools 2.0
+ * @deprecated use NumberTool for numbers formatting/parsing, DateTool for date/time formatting/parsing,
+ * or CollectionTool for toStrings().
  */
 
 @DefaultKey("convert")
 @SkipSetters
+@Deprecated
 public class ConversionTool extends LocaleConfig implements Serializable
 {
     public static final String STRINGS_DELIMITER_FORMAT_KEY = "stringsDelimiter";
@@ -107,6 +110,7 @@
      * The default string delimiter is a comma.
      *
      * @see #parseStringList
+     * @deprecated use {@link CollectionTool#setStringsDelimiter(String)}
      */
     protected final void setStringsDelimiter(String stringsDelimiter)
     {
@@ -130,6 +134,10 @@
         this.stringsTrim = stringsTrim;
     }
 
+    /**
+     * @deprecated use {@link CollectionTool#getStringsTrim()}
+     * @return strings trim
+     */
     public final boolean getStringsTrim()
     {
         return this.stringsTrim;
@@ -140,6 +148,10 @@
         this.numberFormat = format;
     }
 
+    /**
+     * @deprecated use {@link NumberTool} format
+     * @return number format
+     */
     public final String getNumberFormat()
     {
         return this.numberFormat;
@@ -150,6 +162,10 @@
         this.dateFormat = format;
     }
 
+    /**
+     * @deprecated use {@link DateTool#getDateFormat()}
+     * @return date format
+     */
     public final String getDateFormat()
     {
         return this.dateFormat;
@@ -244,6 +260,7 @@
      * @param value the object to be converted
      * @return a {@link Locale} for the specified value or
      *         <code>null</code> if the value is null or the conversion failed
+     * @deprecated use {@link DateTool}.toLocale(Object)
      */
     public Locale toLocale(Object value)
     {
@@ -268,6 +285,7 @@
      * @param value the date to convert
      * @return the object as a {@link Date} or <code>null</code> if no
      *         conversion is possible
+     * @deprecated use {@link DateTool#toDate(Object)}
      */
     public Date toDate(Object value)
     {
@@ -284,6 +302,11 @@
         return parseDate(s);
     }
 
+    /**
+     * @param value
+     * @return calendar
+     * @deprecated use {@link DateTool#toCalendar(Object)}
+     */
     public Calendar toCalendar(Object value)
     {
         if (value == null)
@@ -310,6 +333,7 @@
      * @param value the value to be converted
      * @return an array of String objects containing all of the values
      *         derived from the specified array, Collection, or delimited String
+     * @deprecated use {@link CollectionTool#split(String)}
      */
     public String[] toStrings(Object value)
     {
@@ -695,6 +719,7 @@
      * @param value the string to parse
      * @return the string as a {@link Number} or <code>null</code> if no
      *         conversion is possible
+     * @deprecated use {@link NumberTool#toNumber(Object)}
      */
     public Number parseNumber(String value)
     {
@@ -711,6 +736,7 @@
      * @return the string as a {@link Number} or <code>null</code> if no
      *         conversion is possible
      * @see #parseNumber(String value, String format, Object locale)
+     * @deprecated use {@link NumberTool#toNumber(String, Object)}
      */
     public Number parseNumber(String value, String format)
     {
@@ -726,6 +752,7 @@
      * @return the string as a {@link Number} or <code>null</code> if no
      *         conversion is possible
      * @see java.text.NumberFormat#parse
+     * @deprecated use {@link NumberTool#toNumber(String, Object, Locale)}
      */
     public Number parseNumber(String value, Object locale)
     {
@@ -742,6 +769,7 @@
      * @return the string as a {@link Number} or <code>null</code> if no
      *         conversion is possible
      * @see java.text.NumberFormat#parse
+     * @deprecated use {@link NumberTool#toNumber(String, Object, Locale)}
      */
     public Number parseNumber(String value, String format, Object locale)
     {
@@ -764,6 +792,7 @@
      * @param value the date to convert
      * @return the object as a {@link Date} or <code>null</code> if no
      *         conversion is possible
+     * @deprecated use {@link DateTool#toDate(Object)}
      */
     public Date parseDate(String value)
     {
@@ -780,6 +809,7 @@
      * @return the string as a {@link Date} or <code>null</code> if no
      *         conversion is possible
      * @see ConversionUtils#toDate(String str, String format, Locale locale, TimeZone timezone)
+     * @deprecated use {@link DateTool#toDate(String, Object)}
      */
     public Date parseDate(String value, String format)
     {
@@ -795,6 +825,7 @@
      * @return the string as a {@link Date} or <code>null</code> if no
      *         conversion is possible
      * @see java.text.SimpleDateFormat#parse
+     * @deprecated use {@link DateTool#toDate(String, Object, Locale)}}
      */
     public Date parseDate(String value, Object locale)
     {
@@ -811,6 +842,7 @@
      * @return the string as a {@link Date} or <code>null</code> if no
      *         conversion is possible
      * @see java.text.SimpleDateFormat#parse
+     * @deprecated use {@link DateTool#toDate(String, Object, Locale)}}
      */
     public Date parseDate(String value, String format, Object locale)
     {
@@ -829,6 +861,7 @@
      *         conversion is possible
      * @see #getDateFormat
      * @see java.text.SimpleDateFormat#parse
+     * @deprecated use {@link DateTool#toDate(String, Object, Locale, TimeZone)}}
      */
     public Date parseDate(String value, String format,
                           Object locale, TimeZone timezone)
diff --git a/velocity-tools-generic/src/main/java/org/apache/velocity/tools/generic/LocaleConfig.java b/velocity-tools-generic/src/main/java/org/apache/velocity/tools/generic/LocaleConfig.java
index 26dbf4c..738c2f5 100644
--- a/velocity-tools-generic/src/main/java/org/apache/velocity/tools/generic/LocaleConfig.java
+++ b/velocity-tools-generic/src/main/java/org/apache/velocity/tools/generic/LocaleConfig.java
@@ -20,6 +20,8 @@
  */
 
 import java.util.Locale;
+
+import org.apache.velocity.tools.ConversionUtils;
 import org.apache.velocity.tools.ToolContext;
 
 /**
@@ -71,4 +73,24 @@
         this.locale = locale;
     }
 
+    /**
+     * @param value the object to be converted
+     * @return a {@link Locale} for the specified value or
+     *         <code>null</code> if the value is null or the conversion failed
+     * @since VelocityTools 3.0
+     */
+    public Locale toLocale(Object value)
+    {
+        if (value == null)
+        {
+            return null;
+        }
+        else if (value instanceof Locale)
+        {
+            return (Locale)value;
+        }
+        return ConversionUtils.toLocale(String.valueOf(value));
+    }
+
+
 }
diff --git a/velocity-tools-generic/src/main/java/org/apache/velocity/tools/generic/SortTool.java b/velocity-tools-generic/src/main/java/org/apache/velocity/tools/generic/SortTool.java
index b4099f3..116cd9c 100644
--- a/velocity-tools-generic/src/main/java/org/apache/velocity/tools/generic/SortTool.java
+++ b/velocity-tools-generic/src/main/java/org/apache/velocity/tools/generic/SortTool.java
@@ -112,300 +112,10 @@
  * @author Nathan Bubna
  * @since VelocityTools 1.2
  * @version $Id$
+ * @deprecated use CollectionTool sort methods
  */
 @DefaultKey("sorter")
-public class SortTool extends SafeConfig
+@Deprecated
+public class SortTool extends CollectionTool
 {
-    /**
-     * Sorts a Collection using a Comparator. A defensive copy is made
-     * of the Collection beforehand, so the original Collection is left
-     * untouched.
-     *
-     * @param c The Collection to sort.
-     * @param comparator The comparator to use for sorting.
-     * @return A copy of the original Collection,
-     *         sorted using the supplied Comparator.
-     * @since VelocityTools 2.0.1
-     */
-    public <T> Collection<T> sort(final Collection<T> c,
-                                  final Comparator<T> comparator)
-    {
-        final ArrayList<T> list = new ArrayList<T>(c);
-        Collections.sort(list, comparator);
-        return list;
-    }
-
-    /**
-     * Sorts an array using a Comparator. A defensive copy is made
-     * of the array beforehand, so the original array is left
-     * untouched.
-     *
-     * @param a The array to sort.
-     * @param comparator The comparator to use for sorting.
-     * @return A copy of the original array,
-     *         sorted using the supplied Comparator.
-     * @since VelocityTools 2.0.1
-     */
-    public <T> T[] sort(final T[] a, final Comparator<T> comparator)
-    {
-        final T[] copy = a.clone();
-        Arrays.sort(copy, comparator);
-        return copy;
-    }
-
-    /**
-     * Sorts a Map's values using a Comparator. A defensive copy is made
-     * of the values beforehand, so the original Map is left
-     * untouched.
-     *
-     * @param map The Map whose values should be sorted.
-     * @param comparator The comparator to use for sorting.
-     * @return A copy of the original Map's values,
-     *         sorted using the supplied Comparator.
-     * @since VelocityTools 2.0.1
-     */
-    public <T> Collection<T> sort(final Map<?,T> map,
-                                  final Comparator<T> comparator)
-    {
-        return sort(map.values(), comparator);
-    }
-
-    /**
-     * Sorts a Collection (or array, or Map's values)
-     * using a Comparator. A defensive copy is made
-     * of the original beforehand, so the original is left
-     * untouched. Unsupported collection objects result in
-     * a <code>null</code> return value.
-     *
-     * @param o The Collection to sort.
-     * @param comparator The comparator to use for sorting.
-     * @return A copy of the original Collection,
-     *         sorted using the supplied Comparator.
-     * @since VelocityTools 2.0.1
-     */
-    public Collection<?> sort(final Object o,
-                              final Comparator<?> comparator)
-    {
-        if (o instanceof Collection)
-        {
-            return sort((Collection<?>)o, comparator);
-        }
-        else if (o instanceof Object[])
-        {
-            return sort((Object[])o, comparator);
-        }
-        else if (o instanceof Map)
-        {
-            return sort((Map<?,?>)o, comparator);
-        }
-        else
-        {
-            // the object type is not supported
-            getLog().error("object type not supported: {}", o == null ? "null" : o.getClass().getName());
-            return null;
-        }
-    }
-
-    public Collection sort(Collection collection)
-    {
-        return sort(collection, (List)null);
-    }
-
-    public Collection sort(Object[] array)
-    {
-        return sort(array, (List)null);
-    }
-
-    public Collection sort(Map map)
-    {
-        return sort(map, (List)null);
-    }
-
-    /**
-     * Sorts the collection on a single property.
-     *
-     * @param object the collection to be sorted.
-     * @param property the property to sort on.
-     */
-    public Collection sort(Object object, String property)
-    {
-        List properties = new ArrayList(1);
-        properties.add(property);
-
-        if (object instanceof Collection)
-        {
-            return sort((Collection)object, properties);
-        }
-        else if (object instanceof Object[])
-        {
-            return sort((Object[])object, properties);
-        }
-        else if (object instanceof Map)
-        {
-            return sort((Map)object, properties);
-        }
-        // the object type is not supported
-        getLog().error("object type not supported: {}", object == null ? "null" : object.getClass().getName());
-        return null;
-    }
-
-    public Collection sort(Collection collection, List properties)
-    {
-        List list = new ArrayList(collection.size());
-        list.addAll(collection);
-        return internalSort(list, properties);
-    }
-
-    public Collection sort(Map map, List properties)
-    {
-        return sort(map.values(), properties);
-    }
-
-    public Collection sort(Object[] array, List properties)
-    {
-        return internalSort(Arrays.asList(array), properties);
-    }
-
-    protected Collection internalSort(List list, List properties)
-    {
-        try
-        {
-            if (properties == null)
-            {
-                Collections.sort(list);
-            } else {
-                Collections.sort(list, new PropertiesComparator(properties));
-            }
-            return list;
-        }
-        catch (Exception e)
-        {
-            getLog().error("exception encountered while sorting: {}", e.getMessage());
-            return null;
-        }
-    }
-
-
-    /**
-     * Does all of the comparisons
-     */
-    public static class PropertiesComparator
-        implements Comparator, java.io.Serializable
-    {
-        private static final int TYPE_ASCENDING = 1;
-        private static final int TYPE_DESCENDING = -1;
-
-        public static final String TYPE_ASCENDING_SHORT = "asc";
-        public static final String TYPE_DESCENDING_SHORT = "desc";
-
-        List properties;
-        int[] sortTypes;
-
-        public PropertiesComparator(List props)
-        {
-            // copy the list so we can safely drop :asc and :desc suffixes
-            this.properties = new ArrayList(props.size());
-            this.properties.addAll(props);
-
-            // determine ascending/descending
-            sortTypes = new int[properties.size()];
-
-            for (int i = 0; i < properties.size(); i++)
-            {
-                if (properties.get(i) == null)
-                {
-                    throw new IllegalArgumentException("Property " + i
-                            + "is null, sort properties may not be null.");
-                }
-
-                // determine if the property contains a sort type
-                // e.g "Name:asc" means sort by property Name ascending
-                String prop = properties.get(i).toString();
-                int colonIndex = prop.indexOf(':');
-                if (colonIndex != -1)
-                {
-                    String sortType = prop.substring(colonIndex + 1);
-                    properties.set(i, prop.substring(0, colonIndex));
-
-                    if (TYPE_ASCENDING_SHORT.equalsIgnoreCase(sortType))
-                    {
-                        sortTypes[i] = TYPE_ASCENDING;
-                    }
-                    else if (TYPE_DESCENDING_SHORT.equalsIgnoreCase(sortType))
-                    {
-                        sortTypes[i] = TYPE_DESCENDING;
-                    }
-                    else
-                    {
-                        //FIXME: log this
-                        // invalide property sort type. use default instead.
-                        sortTypes[i] = TYPE_ASCENDING;
-                    }
-                }
-                else
-                {
-                    // default sort type is ascending.
-                    sortTypes[i] = TYPE_ASCENDING;
-                }
-            }
-        }
-
-        public int compare(Object lhs, Object rhs)
-        {
-            for (int i = 0; i < properties.size(); i++)
-            {
-                int comparison = 0;
-                String property = (String)properties.get(i);
-
-                // properties must be comparable
-                Comparable left = getComparable(lhs, property);
-                Comparable right = getComparable(rhs, property);
-
-                if (left == null && right != null)
-                {
-                    // find out how right feels about left being null
-                    comparison = right.compareTo(null);
-                    // and reverse that (if it works)
-                    comparison *= -1;
-                }
-                else if (left instanceof String)
-                {
-                    //TODO: make it optional whether or not case is ignored
-                    comparison = ((String)left).compareToIgnoreCase((String)right);
-                }
-                else if (left != null)
-                {
-                    comparison = left.compareTo(right);
-                }
-
-                // return the first difference we find
-                if (comparison != 0)
-                {
-                    // multiplied by the sort direction, of course
-                    return comparison * sortTypes[i];
-                }
-            }
-            return 0;
-        }
-    }
-
-    /**
-     * Safely retrieves the comparable value for the specified property
-     * from the specified object. Subclasses that wish to perform more
-     * advanced, efficient, or just different property retrieval methods
-     * should override this method to do so.
-     */
-    protected static Comparable getComparable(Object object, String property)
-    {
-        try
-        {
-            return (Comparable)PropertyUtils.getProperty(object, property);
-        }
-        catch (Exception e)
-        {
-            throw new IllegalArgumentException("Could not retrieve comparable value for '"
-                                               + property + "' from " + object + ": " + e);
-        }
-    }
-
 }