You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@velocity.apache.org by Nathan Bubna <na...@esha.com> on 2004/07/22 02:12:55 UTC

Re: New SortTool - some vel internal quetions

Brett Sutton said:
> Here is a feature complete release of my SortTool. If you want it I'm happy
> to donate it to the velocity community and have placed it under the apache
> license.
>
> What it needs now is some testing in wide variety of situations.
> I would be appreciative of any one willing to perform testing and report
> bugs/enh request which I will be happy to fix.
...

this is long overdue, but i've finally finished playing around with
thiscontribution.  it's a great idea for a tool, so i've been playing with it
off and on, but it's been a busy summer and i just haven't had much time to do
so.  anyway...

i do have some issues with the implementation.  the biggest is that it had a
dependency on Velocity 1.4 stuff.  at this point, i'm not willing to accept
such a dependency without better cause than this.  i'd prefer to see the use
of commons-beanutils to handle property retrieval stuff.

on a lesser note, i'm not excited about the iterator/enumeration support.  i
don't think people should be sorting those, and don't want to encourage it.
that's just not what they're for.  if someone really needs to do that, they
can extend the tool and do it themselves.

and on an even lesser note, i don't see any reason to support multiple keys
for indicating whether the sort order should be ascending or descending.
let's just stick with ':asc' and ':desc' as property suffixes to indicate
order.

so, with all that said, i've attached a pretty heavily revised version of the
tool.  the basic idea is still there and the template API is pretty much the
same, but the internals are pretty different (after some months of tweaking
for an hour here and an hour there :).

i'm pretty happy with it and am ready to commit it as is, but i thought i'd
throw it back out here for any interested parties to comment on and give
suggestions first. (Brett, i'd particularly like to hear what you think of it.
i hope you don't mind my meddling. ;)

Nathan Bubna
nathan@esha.com

(it's also included below in case the attachment gets stripped.)

/*
 * Copyright 2004 The Apache Software Foundation.
 *
 * Licensed 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.
 */

package org.apache.velocity.tools.generic;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.apache.commons.beanutils.PropertyUtils;

/**
 * SortTool allows a user to sort a collection (or array, iterator, etc)
 * on any arbitary set of properties exposed by the objects contained
 * within the collection.
 *
 * <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>
 *
 * @author S. Brett Sutton
 * @author Nathan Bubna
 * @since VelocityTools 1.2
 * @version $Id: SortTool.java,v 1.3 2004/02/18 20:11:07 nbubna Exp $
 */
public class SortTool
{

    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
        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)
        {
            //TODO: log this
            return null;
        }
    }


    /**
     * Does all of the comparisons
     */
    public class PropertiesComparator implements Comparator
    {
        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 properties)
        {
            this.properties = properties;

            // 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(left);
                    // 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
                {
                    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);
        }
    }

}


RE: New SortTool - some vel internal quetions

Posted by Brett Sutton <bs...@idatam.com.au>.
No, I'm cool with the rework.

Thanks for the input.
Brett.


-----Original Message-----
From: Nathan Bubna [mailto:nathan@esha.com] 
Sent: Thursday, 22 July 2004 10:13 AM
To: Velocity Developers List
Subject: Re: New SortTool - some vel internal quetions

Brett Sutton said:
> Here is a feature complete release of my SortTool. If you want it I'm
happy
> to donate it to the velocity community and have placed it under the apache
> license.
>
> What it needs now is some testing in wide variety of situations.
> I would be appreciative of any one willing to perform testing and report
> bugs/enh request which I will be happy to fix.
...

this is long overdue, but i've finally finished playing around with
thiscontribution.  it's a great idea for a tool, so i've been playing with
it
off and on, but it's been a busy summer and i just haven't had much time to
do
so.  anyway...

i do have some issues with the implementation.  the biggest is that it had a
dependency on Velocity 1.4 stuff.  at this point, i'm not willing to accept
such a dependency without better cause than this.  i'd prefer to see the use
of commons-beanutils to handle property retrieval stuff.

on a lesser note, i'm not excited about the iterator/enumeration support.  i
don't think people should be sorting those, and don't want to encourage it.
that's just not what they're for.  if someone really needs to do that, they
can extend the tool and do it themselves.

and on an even lesser note, i don't see any reason to support multiple keys
for indicating whether the sort order should be ascending or descending.
let's just stick with ':asc' and ':desc' as property suffixes to indicate
order.

so, with all that said, i've attached a pretty heavily revised version of
the
tool.  the basic idea is still there and the template API is pretty much the
same, but the internals are pretty different (after some months of tweaking
for an hour here and an hour there :).

i'm pretty happy with it and am ready to commit it as is, but i thought i'd
throw it back out here for any interested parties to comment on and give
suggestions first. (Brett, i'd particularly like to hear what you think of
it.
i hope you don't mind my meddling. ;)

Nathan Bubna
nathan@esha.com

(it's also included below in case the attachment gets stripped.)

/*
 * Copyright 2004 The Apache Software Foundation.
 *
 * Licensed 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.
 */

package org.apache.velocity.tools.generic;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.apache.commons.beanutils.PropertyUtils;

/**
 * SortTool allows a user to sort a collection (or array, iterator, etc)
 * on any arbitary set of properties exposed by the objects contained
 * within the collection.
 *
 * <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>
 *
 * @author S. Brett Sutton
 * @author Nathan Bubna
 * @since VelocityTools 1.2
 * @version $Id: SortTool.java,v 1.3 2004/02/18 20:11:07 nbubna Exp $
 */
public class SortTool
{

    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
        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)
        {
            //TODO: log this
            return null;
        }
    }


    /**
     * Does all of the comparisons
     */
    public class PropertiesComparator implements Comparator
    {
        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 properties)
        {
            this.properties = properties;

            // 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(left);
                    // 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
                {
                    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);
        }
    }

}




---------------------------------------------------------------------
To unsubscribe, e-mail: velocity-dev-unsubscribe@jakarta.apache.org
For additional commands, e-mail: velocity-dev-help@jakarta.apache.org