# Property.java

    /*
    ** Module   : Property.java
    ** Abstract : Enhance DMO manipulation performance by caching data from Property annotation.
    **
    ** Copyright (c) 2020-2024, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- ---------------------------------------Description----------------------------------------
    ** 001 OM  20200110 First revision.
    ** 002 SVL 20201111 Convert "reserved null" to true null for LABEL and COLUMN-LABEL.
    **     OM  20201218 Fixed implementation for error and rejected attributes/hidden fields.
    **     CA  20210831 Track the getter method for TempRecord reserved properties.
    **     OM  20220722 Added property identifier.
    **     CA  20221114 Added flag to mark a denormalized extent property.
    **     OM  20220706 The static constant is prefered to String literal.
    ** 003 HC  20230118 Eliminated some of the uses of String.toUpperCase and/or String.toLowerCase
    **                  for performance.
    **     CA  20221114 Added flag to mark a denormalized extent property.
    ** 004 CA  20240320 Save the the property meta instance as back-reference in the orm.Property instance.
    */

    /*
    ** 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 com.goldencode.p2j.persist.*;
    import com.goldencode.p2j.util.*;
    import java.lang.reflect.*;

    /**
     * The purpose of this class is to enhance DMO manipulation performance. It does this in several ways:
     *
     * <ul>
     *    <li>caches the values from DMO {@link com.goldencode.p2j.persist.annotation.Property} annotation,
     *          avoiding this way the super cost of slow access via the slow map implementation and
     *          {@code sun.reflect.annotation.AnnotationInvocationHandler#hashCodeImpl};</li>
     *    <li>carries extra information not available in annotation: the annotated method and the type
     *          of the property;</li>
     *    <li>computes and caches some boolean properties ({@code _isCharacter}, {@code _isDatetimeTz})
     *          so the callers do not need to compare the type each time;</li>
     *    <li>it makes sure the {@code Property} annotation is processed only once. This is especially important
     *          as some costing operations like {@code intern()} -ing the primary string attributes happens
     *          a single time, when the target DMO is loaded (usually at server startup);</li>
     *    <li>the class is immutable. This allows all members to be public and be accessed directly, without
     *          the extra CPU clocks for invoking a (virtual) method.</li>
     * </ul>
     *
     * Notes:
     * <br>* A single object of this type is created for a specified field, when the DMO interface is parsed
     *       for the first time. Having two or more of these objects associated with same property of a DMO
     *       is a programmatic error.
     * <br>* These objects do not hold a reference to their parent {@link DmoMeta}. If ever needed in future it
     *       can be easily added.
     * <br>* Objects of this class isolate the access to DMO interfaces and
     *       {@code com.goldencode.p2j.persist.annotation} package.
     */
    public class Property
    {
       /**
        * The id of this field in the sequence of the fields of the buffer. It is needed by the
        * dynamic {@code bufferField(int)} attribute of the {@code BufferImpl} for
        * emulating the {@code BUFFER-FIELD(integer)} buffer handle attribute.
        */
       public final int id;

       /**
        * The identifier of the property. In case there are denormalized fields in the parent table,
        * {@code propId != id}, otherwise they are equals.
        */
       public final int propId;

       /** The name of the DMO property. */
       public final String name;

       /** The name of the legacy field associated with the DMO property. */
       public final String legacy;

       /** The name of the legacy field associated with the DMO property in lower case. */
       public final String legacyLower;

       /** The name of the column in the SQL database, associated with the DMO property. */
       public final String column;

       /**
        * Extent for array (extent) fields (defaults to 0 for scalar fields). Will be set to the
        * original extent for both normalized and denormalized fields.
        */
       public final int extent;

       /** Flag indicating if this extent property has been denormalized. */
       public final boolean denormalized;

       /**
        * One-based index in the legacy extent field associated with a DMO property. Will be set only
        * for field with custom extents.
        */
       public final int index;

       /** Mandatory FORMAT attribute of this field. */
       public final String format;

       /** FORMAT-SA attribute of this field. */
       public final String format_sa;

       /** HELP attribute of this field. */
       public final String help;

       /** HELP-SA attribute of this field. */
       public final String help_sa;

       /** INITIAL attribute of this field. */
       public final String initial;

       /** INITIAL-SA attribute of this field. */
       public final String initial_sa;

       /** INITIAL-NULL pseudo-attribute of this field. */
       public final boolean initialNull;

       /** LABEL attribute of this field. */
       public final String label;

       /** LABEL-SA option of this table. */
       public final String label_sa;

       /** COLUMN-LABEL attribute of this field. */
       public final String columnLabel;

       /** COLUMN-LABEL-SA attribute of this field. */
       public final String columnLabel_sa;

       /** MANDATORY attribute of this field. */
       public final boolean mandatory;

       /** Mandatory ORDER attribute from the original schema. */
       public final int order;

       /** POSITION attribute from the original schema. */
       public final int position;

       /** VALIDATE-MESSAGE attribute of this field. */
       public final String validateMessage;

       /** VALMSG-SA option of this table. */
       public final String valmsg_sa;

       /** VALIDATE-EXPRESSION attribute of this field. */
       public final String validateExpression;

       /** DESCRIPTION attribute of this field. */
       public final String desc;

       /** MAX-WIDTH attribute of this field. -1 if not defined  */
       public final int width;

       /** Original property name (not exposed in DMO) for denormalized field with extent. */
       public final String original;

       /** Flag the case sensitive character fields. Other datatypes are not affected. */
       public final boolean caseSensitive;

       /** Scale for decimal fields (decimals, precision, in 4GL terms). Other datatypes are not affected. */
       public final int scale;

       /** Flag indicating the field should not be included in serialized output. */
       public final boolean serializeHidden;

       /** Name of field in serialized output. */
       public final String serializeName;

       /** XML schema data type of field. */
       public final String xmlDataType;

       /** Name of element or attribute representing field in XML output. */
       public final String xmlNodeName;

       /** Node type of field in XML output. */
       public final String xmlNodeType;

       /** Code page of the CLOB field. */
       public final String codePage;

       /** Name of the source field specified by LIKE option (either at the table- or field-level). */
       public final String like;

       /** VIEW-AS field property */
       public final String viewAs;

       /** CAN-READ option of this field. */
       public final String canRead;

       /** CAN-WRITE option of this field. */
       public final String canWrite;

       /** Lowercase type of this property. */
       public final String _ablType;

       /** The FWD class that of the property. */
       public final Class<? /*extends BaseDataType*/> _fwdType;

       /** Quick test if this property is a character (maybe add other similar). */
       public final boolean _isCharacter;

       /** Quick test if this property is a character (maybe add other similar). */
       public final boolean _isDatetimeTz;

       /** The method which was annotated. This is only visible in orm package. */
       public final Method annMethod;

       /** The back-reference to the meta structure. */
       PropertyMeta meta;

       /**
        * The constructor that initialize this immutable object.
        *
        * @param   source
        *          The {@code Property} annotation taken from DMO interface.
        * @param   annotatedMethod
        *          The method which was annotated.
        */
       Property(com.goldencode.p2j.persist.annotation.Property source, Method annotatedMethod)
       {
          this.id = source.id();
          this.denormalized = source.propertyId() != Integer.MIN_VALUE;
          this.propId = !this.denormalized ? this.id : source.propertyId();
          this.name = source.name().intern();
          this.legacy = source.legacy().intern();
          this.legacyLower = this.legacy.toLowerCase().intern();
          this.column = source.column().intern();
          this.extent = source.extent();
          this.index = source.index();
          this.format = source.format();
          this.format_sa = source.format_sa();
          this.help = source.help();
          this.help_sa = source.help_sa();
          this.initial = source.initial().intern();
          this.initial_sa = source.initial_sa();
          this.initialNull = source.initialNull();
          this.label = convertReservedNull(source.label());
          this.label_sa = source.label_sa();
          this.columnLabel = convertReservedNull(source.columnLabel());
          this.columnLabel_sa = source.columnLabel_sa();
          this.mandatory = source.mandatory();
          this.order = source.order();
          this.position = source.position();
          this.validateMessage = source.validateMessage();
          this.valmsg_sa = source.valmsg_sa();
          this.validateExpression = source.validateExpression();
          this.desc = source.desc();
          this.width = source.width();
          this.original = source.original().intern();
          this.caseSensitive = source.caseSensitive();
          this.scale = source.scale();
          this.serializeHidden = source.serializeHidden();
          this.serializeName = source.serializeName();
          this.xmlDataType = source.xmlDataType();
          this.xmlNodeName = source.xmlNodeName();
          this.xmlNodeType = source.xmlNodeType();
          this.codePage = source.codePage();
          this.like = source.like();
          this.viewAs = source.viewAs();
          this.canRead = source.canRead();
          this.canWrite = source.canWrite();

          annMethod = annotatedMethod;
          this._fwdType = annotatedMethod.getReturnType();
          _isCharacter = _fwdType == character.class;
          _isDatetimeTz = _fwdType == datetimetz.class;
          _ablType = getAblType();
       }

       /**
        * Creates a new {@code Property} object. This is a constructor dedicated to building reserved properties
        * which are not declared in DMO interface.
        *
        * @param   id
        *          The id of this field in the sequence of the fields of the buffer.
        * @param   name
        *          The name of the DMO property.
        * @param   legacy
        *          The name of the legacy field associated with the DMO property.
        * @param   column
        *          The name of the column in the SQL database, associated with the DMO property.
        * @param   mandatory
        *          Flag for not-null fields.
        * @param   type
        *          property type
        * @param   initialNull
        *          The property has {@code null} as default value.
        * @param   mthd
        *          The getter method for this property.
        */
       Property(int id,
                String name,
                String legacy,
                String column,
                boolean mandatory,
                Class<?> type,
                boolean initialNull,
                Method mthd)
       {
          this.id = this.propId = id; // this constructor is only invoked for reserved properties which have
                                      // negative and equal values for both [id] and [propId]
          this.denormalized = false;
          this.name = name.intern();
          this.legacy = (legacy == null) ? null : legacy.intern();
          this.legacyLower = (legacy == null) ? null : this.legacy.toLowerCase().intern();
          this.column = column.intern();
          this.extent = 0;
          this.index = 0;
          this.format = "";
          this.initial = "";
          this.initialNull = initialNull;
          this.label = "";
          this.columnLabel = "";
          this.mandatory = mandatory;
          this.order = 0;
          this.position = 0;
          this.validateMessage = "";
          this.validateExpression = "";
          this.original = "";
          this.caseSensitive = false;
          this.scale = 0;
          this.serializeHidden = false;
          this.serializeName = "";
          this.xmlDataType = "";
          this.xmlNodeName = "";
          this.xmlNodeType = XmlNode.ELEMENT;
          this.codePage = "";
          this.like = "";
          this.help = "";
          this.format_sa = "";
          this.initial_sa = "";
          this.label_sa = "";
          this.columnLabel_sa = "";
          this.valmsg_sa = "";
          this.help_sa = "";
          this.viewAs = "";
          this.canRead = "*";
          this.canWrite = "*";
          this.desc = null;
          this.width = 0;

          annMethod = mthd;
          this._fwdType = type;
          _isCharacter = (type == character.class);
          _isDatetimeTz = false;                    // no dtzs here
          _ablType = getAblType();
       }

       /**
        * Get the {@link #meta property meta} instance.
        *
        * @return   See above.
        */
       public PropertyMeta getMeta()
       {
          return meta;
       }

       /**
        * Converts "reserved null" to true <code>null</code>. Returns <code>null</code> if the <code>value</code>
        * equals "reserved null" for {@link com.goldencode.p2j.persist.annotation.Property} annotation. Otherwise
        * returns the <code>value</code> itself.
        *
        * @param  value
        *         Value to check.
        *
        * @return see above.
        */
       private String convertReservedNull(String value)
       {
          return com.goldencode.p2j.persist.annotation.Property.NULL_STRING.equals(value) ? null : value;
       }

       /**
        * Computes the ABL type name from the Java type. Usually this is the same as java class (because FWD makes
        * an exception here from standard class naming), but there is also an exception (@code datetime-tz)
        * because the character {@code -} is not permitted in a Java identifier.
        *
        * @return  The ABL type name mapped by the specified {@code type}.
        */
       private String getAblType()
       {
          // a bit of a hack to get the proper ABL type name
          String strType = _isDatetimeTz ? "datetime-tz" : _fwdType.getSimpleName().toLowerCase();
          return strType.intern();
       }

       /**
        * Obtain a short description of the object used in debugging.
        *
        * @return  a short description of the object used in debugging.
        */
       @Override
       public String toString()
       {
          StringBuilder sb = new StringBuilder();
          sb.append("ORM-Property{").append(name);
          if (extent > 0)
          {
             sb.append("[").append(extent).append("]");
          }
          sb.append(":").append(_ablType).append(" OE=").append(legacy)
            .append(" SQL=").append(column).append(" fid=").append(id).append(" pid=").append(propId).append("}");
          return sb.toString();
       }
    }