# RelationInfo.java

    /*
    ** Module   : RelationInfo.java
    ** Abstract : Object which encapsulates information about foreign relations
    **
    ** Copyright (c) 2006-2024, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- --JPRM-- -----------------------------Description-----------------------------
    ** 001 ECF 20060302   @25009 Created initial version. A descriptor object for foreign key
    **                           relations.
    ** 002 ECF 20061105   @31096 Added data and methods used to track inverse foreign key
    **                           associations. Needed when nulling out foreign keys during record
    **                           deletion.
    ** 003 ECF 20061114   @31142 Added getForeignProperties() method. This was needed to support
    **                           fuller foreign association synchronization.
    ** 004 ECF 20080310   @37484 Integrated generics.
    ** 005 ECF 20150328          Added support for cross-schema natural joins.
    ** 006 OM  20200401          Moved to orm package.
    ** 007 TJD 20240123          Java 17 compatibility updates
    */

    /*
    ** 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.orm;

    import java.util.*;
    import com.goldencode.p2j.persist.*;
    import com.goldencode.p2j.persist.Record;
    import com.goldencode.p2j.util.*;

    /**
     * A descriptor object for a primary/foreign key relation between two tables.
     * Stores the following data about this relation:
     * <ul>
     *   <li>interface of the local (referring) DMO
     *   <li>implementation class of the local DMO
     *   <li>interface of the foreign (referent) DMO
     *   <li>implementation class of the foreign DMO
     *   <li>name of the schema of the foreign DMO
     *   <li>the properties in common between the DMOs, which comprise the legacy
     *       foreign key in the pre-conversion application
     * </ul>
     * <p>
     * Property data is stored in a map whose keys are the local DMO's property
     * names, and whose values are the pairings of local and foreign property
     * names, and whether case is ignored when comparing the property values
     * (for character property types only).
     * <p>
     * Instances of this class are created at database initialization (read from
     * the dmo index file).  The information stored within these instances is
     * used to construct HQL query statements for the following purposes:
     * <ul>
     *   <li>in normal queries to search for records by foreign key
     *   <li>by the record buffer to synchronize foreign keys with application
     *       changes to properties which comprise legacy keys between tables
     * </ul>
     * <p>
     * It is also used to null out foreign records referenced by a DMO, when the
     * foreign record is deleted.
     *
     * @see  AssociationSyncher
     * @see  AbstractJoin
     * @see  ForeignNuller
     */
    public final class RelationInfo
    {
       /** Local (referring) DMO interface */
       private final Class<? extends DataModelObject> localIface;

       /** Local (referring) DMO implementation class */
       private final Class<? extends Record> localClass;

       /** Foreign (referent) DMO interface */
       private final Class<? extends DataModelObject> foreignIface;

       /** Foreign (referent) DMO implementation class */
       private final Class<? extends Record> foreignClass;

       /** Foreign (referent) schema name */
       private final String foreignSchema;

       /** Common properties which define legacy foreign key */
       private final Map<String, PropertyData> properties = new LinkedHashMap<>();

       /** Is relation uniquely constrained at the local table? */
       private boolean unique = false;

       /**
        * Constructor.
        *
        * @param   localIface
        *          Local (referring) DMO interface.
        * @param   localClass
        *          Local (referring) DMO implementation class.
        * @param   foreignIface
        *          Foreign (referent) DMO interface.
        * @param   foreignClass
        *          Foreign (referent) DMO implementation class.
        */
       public RelationInfo(Class<? extends DataModelObject> localIface,
                           Class<? extends Record> localClass,
                           Class<? extends DataModelObject> foreignIface,
                           Class<? extends Record> foreignClass,
                           String foreignSchema)
       {
          this.localIface = localIface;
          this.localClass = localClass;
          this.foreignIface = foreignIface;
          this.foreignClass = foreignClass;
          this.foreignSchema = foreignSchema;
       }

       /**
        * Add a pair of properties which, alone or in combination with other pairs, define the legacy
        * key between related database tables.
        *
        * @param   localName
        *          Name of the local DMO's property.
        * @param   foreignName
        *          Name of the foreign DMO's related property.
        * @param   caseSensitive
        *          {@code true} if comparisons of these properties must take case into account, else
        *          {@code false} (meaningful for character properties only).
        */
       public void addPropertyData(String localName, String foreignName, boolean caseSensitive)
       {
          properties.put(localName, new PropertyData(localName, foreignName, !caseSensitive));
       }

       /**
        * Indicate whether the pair of properties indexed by the specified, local property name are
        * recorded as case insensitive properties.
        *
        * @param   property
        *          Name of property in the local DMO class.
        *
        * @return  {@code true} if {@code property} represents a case insensitive, character property.
        */
       public boolean isIgnoreCase(String property)
       {
          return  properties.get(property).ignoreCase;
       }

       /**
        * Get the interface of the local (referring) DMO.
        *
        * @return   Local interface.
        */
       public Class<? extends DataModelObject> getLocalInterface()
       {
          return localIface;
       }

       /**
        * Get the implementation class of the local (referring) DMO.
        *
        * @return   Local class.
        */
       public Class<? extends Record> getLocalClass()
       {
          return localClass;
       }

       /**
        * Get the alias of the local (referring) DMO, which is its unqualified
        * interface name with the first letter lowercased.
        *
        * @return   Local alias.
        */
       public String getLocalAlias()
       {
          String name = getLocalInterfaceName();

          return Character.toLowerCase(name.charAt(0)) + name.substring(1);
       }

       /**
        * Get the unqualified name of the local (referring) DMO's interface.
        *
        * @return   Local interface name.
        */
       public String getLocalInterfaceName()
       {
          return Utils.getUnqualifiedName(localIface);
       }

       /**
        * Get the unqualified name of the local (referring) DMO's implementation
        * class.
        *
        * @return   Local class name.
        */
       public String getLocalClassName()
       {
          return Utils.getUnqualifiedName(localClass);
       }

       /**
        * Get the interface of the foreign (referent) DMO.
        *
        * @return   Foreign interface.
        */
       public Class<? extends DataModelObject> getForeignInterface()
       {
          return foreignIface;
       }

       /**
        * Get the implementation class of the foreign (referent) DMO.
        *
        * @return   Foreign class.
        */
       public Class<? extends Record> getForeignClass()
       {
          return foreignClass;
       }

       /**
        * Get the alias of the foreign (referent) DMO, which is its unqualified
        * interface name with the first letter lowercased.
        *
        * @return   Foreign alias.
        */
       public String getForeignAlias()
       {
          String name = getForeignInterfaceName();

          return Character.toLowerCase(name.charAt(0)) + name.substring(1);
       }

       /**
        * Get the unqualified name of the foreign (referent) DMO's interface.
        *
        * @return   Foreign interface name.
        */
       public String getForeignInterfaceName()
       {
          return Utils.getUnqualifiedName(foreignIface);
       }

       /**
        * Get the unqualified name of the foreign (referent) DMO's implementation
        * class.
        *
        * @return   Foreign class name.
        */
       public String getForeignClassName()
       {
          return Utils.getUnqualifiedName(foreignClass);
       }

       /**
        * Get the name of the foreign class' schema.
        *
        * @return  Name of the foreign schema.
        */
       public String getForeignSchema()
       {
          return foreignSchema;
       }

       /**
        * Get an iterator on all of the legacy key property names for the foreign
        * (referent) DMO.  Iteration will be in the same order in which the
        * properties were added to this instance.
        *
        * @return  Foreign property name iterator.
        */
       public Iterator<String> foreignProperties()
       {
          return new Iterator<String>()
          {
             private final Iterator<PropertyData> iter = properties.values().iterator();
             public boolean hasNext() { return iter.hasNext(); }
             public String next() { return iter.next().foreign; }
             public void remove() { throw new UnsupportedOperationException(); }
          };
       }

       /**
        * Get the set of all of the legacy key property names for the foreign
        * (referent) DMO.  Iteration over the set's elements will be in the same
        * order in which the properties were added to this instance.
        *
        * @return  Read-only set of local property names.
        */
       public Set<String> getForeignProperties()
       {
          Set<String> set = new AbstractSet<String>()
          {
             public Iterator<String> iterator() { return foreignProperties(); }
             public int size() { return properties.size(); }
          };

          return Collections.unmodifiableSet(set);
       }

       /**
        * Get an iterator on all of the legacy key property names for the local
        * (referring) DMO.  Iteration will be in the same order in which the
        * properties were added to this instance.
        *
        * @return  Local property name iterator.
        */
       public Iterator<String> localProperties()
       {
          return properties.keySet().iterator();
       }

       /**
        * Get the set all of the legacy key property names for the local
        * (referring) DMO.  Iteration over the set's elements will be in the same
        * order in which the properties were added to this instance.
        *
        * @return  Read-only set of local property names.
        */
       public Set<String> getLocalProperties()
       {
          return Collections.unmodifiableSet(properties.keySet());
       }

       /**
        * Indicate whether the relation described by this object is uniquely
        * constrained on the local side.  That is, indicate whether the local DMO
        * has a one-to-one relation with the foreign DMO.
        *
        * @return  <code>true</code> if the relation with the foreign DMO is
        *          one-to-one;  <code>false</code> if it is many-to-one.
        */
       public boolean isUnique()
       {
          return unique;
       }

       /**
        * Record the fact that the relation described by this object is uniquely
        * constrained on the local side.  That is, record that the local DMO has
        * a one-to-one relation with the foreign DMO (vs. a many-to-one relation).
        */
       void setUnique()
       {
          unique = true;
       }

       /**
        * Container object for data which defines a local/foreign property pair.
        * <ul>
        *   <li>property name in the local DMO
        *   <li>corresponding property name in the foreign DMO
        *   <li>ignore case flag (relevant for character properties only)
        * </ul>
        */
       private static class PropertyData
       {
          /** Local property name */
          private final String local;

          /** Foreign property name */
          private final String foreign;

          /** Ignore case flag */
          private final boolean ignoreCase;

          /**
           * Constructor
           *
           * @param   local
           *          Local property name.
           * @param   foreign
           *          Foreign property name
           * @param   ignoreCase
           *          Ignore case flag.
           */
          PropertyData(String local, String foreign, boolean ignoreCase)
          {
             this.local = local;
             this.foreign = foreign;
             this.ignoreCase = ignoreCase;
          }
       }
    }