AttributeComparator.java

/*
 * Copyright (c) 2002-2022, City of Paris
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *  1. Redistributions of source code must retain the above copyright notice
 *     and the following disclaimer.
 *
 *  2. Redistributions in binary form must reproduce the above copyright notice
 *     and the following disclaimer in the documentation and/or other materials
 *     provided with the distribution.
 *
 *  3. Neither the name of 'Mairie de Paris' nor 'Lutece' nor the names of its
 *     contributors may be used to endorse or promote products derived from
 *     this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * License 1.0
 */
package fr.paris.lutece.util.sort;

import fr.paris.lutece.portal.service.util.AppLogService;

import java.io.Serializable;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import java.util.Comparator;
import java.util.Locale;

/**
 * This class provide Attribute Comparator
 */
public class AttributeComparator implements Comparator<Object>, Serializable
{
    private static final long serialVersionUID = 8552197766086300259L;
    private String _strSortedAttribute;
    private boolean _bIsASC;

    /**
     * Constructor
     * 
     * @param strSortedAttribute
     *            the name of the attribute on which the sort will be made
     * @param bIsASC
     *            true for the ASC order, false for the DESC order
     */
    public AttributeComparator( String strSortedAttribute, boolean bIsASC )
    {
        _strSortedAttribute = strSortedAttribute;
        _bIsASC = bIsASC;
    }

    /**
     * Constructor
     * 
     * @param strSortedAttribute
     *            the name of the attribute on which the sort will be made
     */
    public AttributeComparator( String strSortedAttribute )
    {
        _strSortedAttribute = strSortedAttribute;
        _bIsASC = true;
    }

    /**
     * Compare two objects o1 and o2.
     * 
     * @param o1
     *            Object
     * @param o2
     *            Object
     * @return &lt; 0 if o1 is before o2 in the alphabetical order 0 if o1 equals o2 &gt; 0 if o1 is after o2
     */
    @Override
    public int compare( Object o1, Object o2 )
    {
        int nStatus = 0;

        Method method1 = getMethod( o1 );
        Method method2 = getMethod( o2 );

        if ( method1 == null || method2 == null || method1.getReturnType( ) != method2.getReturnType( ) )
        {
            return 0;
        }

        try
        {
            nStatus = compareReturnTypes( o1, o2, method1, method2 );
        }
        catch( IllegalArgumentException | IllegalAccessException | InvocationTargetException e )
        {
            AppLogService.error( e.getMessage( ), e );
        }

        if ( !_bIsASC )
        {
            nStatus = nStatus * ( -1 );
        }

        return nStatus;
    }

    private int compareReturnTypes( Object o1, Object o2, Method method1, Method method2 ) throws IllegalAccessException, InvocationTargetException
    {
        int nStatus = 0;
        Object oRet1 = method1.invoke( o1 );
        Object oRet2 = method2.invoke( o2 );

        String strReturnType = method1.getReturnType( ).getName( );
        Class<?> returnType = method1.getReturnType( );

        if ( oRet1 == null )
        {
            if ( oRet2 == null )
            {
                nStatus = 0;
            }
            else
            {
                nStatus = -1;
            }
        }
        else
        {
            if ( oRet2 == null )
            {
                nStatus = 1;
            }
            else
            {
                if ( strReturnType.equals( "java.lang.String" ) )
                {
                    nStatus = ( (String) oRet1 ).toLowerCase( Locale.ENGLISH ).compareTo( ( (String) oRet2 ).toLowerCase( Locale.ENGLISH ) );
                }
                else
                    if ( returnType.isPrimitive( ) || isComparable( returnType ) )
                    {
                        nStatus = ( (Comparable) oRet1 ).compareTo( (Comparable) oRet2 );
                    }
                    else
                        if ( returnType.isEnum( ) )
                        {
                            nStatus = oRet1.toString( ).compareTo( oRet2.toString( ) );
                        }
            }
        }

        return nStatus;
    }

    /**
     * Return the getter method of the object obj for the attribute _strSortedAttribute
     * 
     * @param obj
     *            the object
     * @return method Method of the object obj for the attribute _strSortedAttribute
     */
    private Method getMethod( Object obj )
    {
        Method method = null;
        String strFirstLetter = _strSortedAttribute.substring( 0, 1 ).toUpperCase( );

        String strMethodName = "get" + strFirstLetter + _strSortedAttribute.substring( 1, _strSortedAttribute.length( ) );

        try
        {
            method = obj.getClass( ).getMethod( strMethodName );
        }
        catch( Exception e )
        {
            AppLogService.error( e.getMessage( ), e );
        }

        return method;
    }

    /**
     * Returns <code>true</code> if the class implements {@link Comparable} or extends a super class that implements {@link Comparable}, <code>false</code>
     * otherwise.
     * 
     * @param clazz
     *            the class
     * @return <code>true</code> if the class implements {@link Comparable}, <code>false</code> otherwise.
     */
    private boolean isComparable( Class<?> clazz )
    {
        for ( Class<?> interfac : clazz.getInterfaces( ) )
        {
            if ( interfac.equals( Comparable.class ) )
            {
                return true;
            }
        }

        // The class might be extending a super class that implements {@link Comparable}
        Class<?> superClass = clazz.getSuperclass( );

        if ( superClass != null )
        {
            return isComparable( superClass );
        }

        return false;
    }
}