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);
      }
   }
}