# DMOSorter.java

    /*
    ** Module   : DMOSorter.java
    ** Abstract : DMO comparator which uses a list of directional sort criteria
    **
    ** Copyright (c) 2004-2020, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- --JPRM-- ---------------------------------Description----------------------------------
    ** 001 ECF 20080510  @38478  Created initial version. DMO comparator which
    **                           uses a list of directional sort criteria.
    ** 002 ECF 20080708  @39080  Reworked FieldWorker inner class. Fixed CCE
    **                           when FieldWorker was specified for a DMO's
    **                           primary key.
    ** 003 ECF 20080806  @39322  Added support for 64-bit primary keys.
    **                           Changed primary key sorters to use Long
    **                           instead of Integer.
    ** 004 CA  20080819  @39455  Support API change in FieldReference.
    ** 005 ECF 20200906          New ORM implementation.
    */

    /*
    ** This program is free software: you can redistribute it and/or modify
    ** it under the terms of the GNU Affero General Public License as
    ** published by the Free Software Foundation, either version 3 of the
    ** License, or (at your option) any later version.
    **
    ** This program is distributed in the hope that it will be useful,
    ** but WITHOUT ANY WARRANTY; without even the implied warranty of
    ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    ** GNU Affero General Public License for more details.
    **
    ** You may find a copy of the GNU Affero GPL version 3 at the following
    ** location: https://www.gnu.org/licenses/agpl-3.0.en.html
    **
    ** Additional terms under GNU Affero GPL version 3 section 7:
    **
    **   Under Section 7 of the GNU Affero GPL version 3, the following additional
    **   terms apply to the works covered under the License.  These additional terms
    **   are non-permissive additional terms allowed under Section 7 of the GNU
    **   Affero GPL version 3 and may not be removed by you.
    **
    **   0. Attribution Requirement.
    **
    **     You must preserve all legal notices or author attributions in the covered
    **     work or Appropriate Legal Notices displayed by works containing the covered
    **     work.  You may not remove from the covered work any author or developer
    **     credit already included within the covered work.
    **
    **   1. No License To Use Trademarks.
    **
    **     This license does not grant any license or rights to use the trademarks
    **     Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
    **     of Golden Code Development Corporation. You are not authorized to use the
    **     name Golden Code, FWD, or the names of any author or contributor, for
    **     publicity purposes without written authorization.
    **
    **   2. No Misrepresentation of Affiliation.
    **
    **     You may not represent yourself as Golden Code Development Corporation or FWD.
    **
    **     You may not represent yourself for publicity purposes as associated with
    **     Golden Code Development Corporation, FWD, or any author or contributor to
    **     the covered work, without written authorization.
    **
    **   3. No Misrepresentation of Source or Origin.
    **
    **     You may not represent the covered work as solely your work.  All modified
    **     versions of the covered work must be marked in a reasonable way to make it
    **     clear that the modified work is not originating from Golden Code Development
    **     Corporation or FWD.  All modified versions must contain the notices of
    **     attribution required in this license.
    */

    package com.goldencode.p2j.persist;

    import java.util.*;

    /**
     * A sorter which orders data model objects (DMOs) according to a list of
     * {@link SortCriterion sort criteria}.  Each sort criterion specifies an
     * individual property of the DMO and the sort direction to be applied to that
     * property.  As two DMO instances are {@link
     * #compare(Record, Record) compared}, the sort criteria are applied
     * in order, until a difference in sort order is detected, or the list of
     * criteria is exhausted, at which point the DMOs are determined to sort
     * equivalently.
     */
    public final class DMOSorter
    implements Comparator<Record>
    {
       /** Workers for each sorted DMO property */
       private final ArrayList<FieldWorker> workers =
          new ArrayList<FieldWorker>();

       /**
        * Constructor which specifies DMO type and sort criteria.
        *
        * @param   database
        *          The associated database.
        * @param   dmoIface
        *          DMO interface which defines access methods for DMO properties.
        * @param   sortList
        *          List of sort criteria which define target properties and sort
        *          direction for each property.
        */
       public DMOSorter(Database database,
                        Class<? extends DataModelObject> dmoIface,
                        List<SortCriterion> sortList)
       {
          Iterator<SortCriterion> iter = sortList.iterator();
          while (iter.hasNext())
          {
             SortCriterion crit = iter.next();
             FieldWorker fw = new FieldWorker(database, dmoIface, crit);
             workers.add(fw);
          }

          workers.trimToSize();
       }

       /**
        * Compare two <code>Record</code> instances to determine relative
        * sort order, according to the sort criteria specified for this sorter.
        * The sort criteria are applied in order, until a difference in sort order
        * is detected, or the list of criteria is exhausted, at which point the
        * DMOs are determined to sort equivalently.
        * <p>
        * The comparison of each property uses the natural order of the data,
        * except that unknown value always sorts high in an ascending sort.  The
        * result is inverted for descending sorts (meaning unknown value sorts low
        * in a descending sort.
        *
        * @param   p1
        *          First DMO to compare.  Must not be <code>null</code>.
        * @param   p2
        *          Second DMO to compare.  Must not be <code>null</code>.
        *
        * @return  Positive value if <code>p1</code> &gt; <code>p2</code>;
        *          negative value if <code>p1</code> &lt; <code>p2</code>;
        *          <code>0</code> if <code>p1</code> and <code>p2</code> sort
        *          equivalently.
        */
       public int compare(Record p1, Record p2)
       {
          Iterator<FieldWorker> iter = workers.iterator();
          while (iter.hasNext())
          {
             FieldWorker fw = iter.next();
             int result = fw.compare(p1, p2);
             if (result != 0)
             {
                return result;
             }
          }

          return 0;
       }

       /**
        * Helper comparator implementation which compares a particular property of
        * a DMO, adjusted for sort direction.
        */
       private static class FieldWorker
       implements Comparator<Record>
       {
          /** Ascending primary key sorter */
          private static final BasicSorter<Long> ASC_PK_SORTER =
             new BasicSorter<Long>(true);

          /** Descending primary key sorter */
          private static final BasicSorter<Long> DESC_PK_SORTER =
             new BasicSorter<Long>(false);

          /** Worker object which performs a sort of DMO property data */
          private final BasicSorter sorter;

          /** Accessor object for DMO property */
          private final FieldReference field;

          /**
           * Constructor which accepts a DMO type and single property sort
           * criterion.
           *
           * @param   database
           *          The associated database.
           * @param   dmoIface
           *          DMO type.
           * @param   criterion
           *          Sort criterion which specifies property and direction of
           *          sort.
           */
          FieldWorker(Database database, Class<? extends DataModelObject> dmoIface, SortCriterion criterion)
          {
             boolean ascending = criterion.isAscending();
             String property = criterion.getUnqualifiedName();

             this.sorter = (criterion.isPrimaryKey()
                            ? (ascending ? ASC_PK_SORTER : DESC_PK_SORTER)
                            : DataSorter.getInstance(ascending));
             this.field = new FieldReference(database, dmoIface, property);
          }

          /**
           * Compare two <code>Record</code> instances to determine relative
           * sort order, according to the sort criterion specified for this
           * worker.  The comparison uses the natural order of the property data,
           * except that unknown value always sorts high in an ascending sort.
           * The result is inverted for descending sorts (meaning unknown value
           * sorts low in a descending sort.
           *
           * @param   p1
           *          First DMO to compare.  Must not be <code>null</code>.
           * @param   p2
           *          Second DMO to compare.  Must not be <code>null</code>.
           *
           * @return  Positive value if <code>p1</code> &gt; <code>p2</code>;
           *          negative value if <code>p1</code> &lt; <code>p2</code>;
           *          <code>0</code> if <code>p1</code> and <code>p2</code> sort
           *          equivalently.
           */
          public int compare(Record p1, Record p2)
          {
             Comparable c1 = (Comparable) field.get(p1);
             Comparable c2 = (Comparable) field.get(p2);

             return sorter.compare(c1, c2);
          }
       }
    }