P2JField.java

/*
** Module   : P2JField.java
** Abstract : Container for DMO field definitions.
**
** Copyright (c) 2013-2024, Golden Code Development Corporation.
**
** -#- -I- --Date-- ---------------------------------------Description---------------------------------------
** 001 SVL 20130624 Created initial version.
** 002 OM  20140411 Added caseSensitive support.
** 003 ECF 20140914 Added hashCode and equals implementations.
** 004 SVL 20190614 Added code page.
** 005 CA  20190724 The hashCode and equals must use the legacy field name case-insensitive.
** 006 CA  20191203 The field's help and serialize options must be used to uniquely identify it.
** 007 CA  20200724 The 'initial' BDT is mutable, keep a copy, not the exact reference.
**     OM  20200701 Made class immutable. Added debugging support.
**     OM  20220609 Added new attributes/options.
**     OM  20220914 Added MAX-WIDTH support for CHARACTER fields.
** 008 SR  20230524 Changed toLowerCase().hashCode() into hashCodeCaseInsensitive() in computeHash().
** 009 AD  20240403 Added more complete method to get string representation, toStringComplete.
*/

/*
** 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 com.goldencode.p2j.convert.ParmType;
import com.goldencode.p2j.util.*;
import com.goldencode.util.StringHelper;
import java.util.*;

/** Non-mutable container for DMO field definitions. */
public class P2JField
{
   /** Field name. */
   private final String name;
   
   /** Field type. */
   private final ParmType type;
   
   /** Extent size. */
   private final int extent;
   
   /** Field format. */
   private final String format;
   
   /** Initial field value. */
   private final BaseDataType initial;
   
   /** Field label. */
   private final String label;
   
   /** Column label. */
   private final String columnLabel;
   
   /** Flag for case-sensitive character fields. Ignored for all other datatypes. */
   private final boolean caseSensitive;
   
   /** Code page for the CLOB field. */
   private final String codePage;
   
   /** HELP attribute of this field. */
   private final String help;
   
   /** Flag indicating the field should not be included in serialized output. */
   private final boolean serializeHidden;
   
   /** Name of field in serialized output. */
   private final String serializeName;
   
   /** XML schema data type of field. */
   private final String xmlDataType;
   
   /** Name of element or attribute representing field in XML output. */
   private final String xmlNodeName;
   
   /** Node type of field in XML output. */
   private final String xmlNodeType;
   
   /** {@code true} for mandatory (not null) fields. */
   private final boolean mandatory;
   
   /** The {@code scale}, for {@code DECIMAL} fields. */
   private final int scale;
   
   /** The {@code MAX-WIDTH}, for CHARACTER fields. */
   private final int maxWidth;
   
   /** The READ-ONLY attribute of the field. */
   private final boolean readOnly;
   
   /** The VALIDATE-EXPRESSION for this field. */
   private final String validateExpression;
   
   /** The VALIDATE-MESSAGE for this field. */
   private final String validateMessage;
   
   /** Precomputed hash code. */
   private final int hash;
   
   /**
    * Convenience constructor.
    *
    * @param   name
    *          Field name.
    * @param   type
    *          Field type.
    * @param   extent
    *          Extent size.
    * @param   format
    *          Field format.
    * @param   initial
    *          Initial value.
    * @param   label
    *          Field label.
    * @param   columnLabel
    *          Column label.
    * @param   caseSensitive
    *          {@code true} for case-sensitive character fields.
    * @param   codePage
    *          Code page for the CLOB field.
    * @param   help
    *          Field's help option.
    * @param   serializeHidden
    *          Field's serialize-hidden option.
    * @param   serializeName
    *          Field's serialize-name option.
    * @param   xmlDataType
    *          Field's xml-data-type option.
    * @param   xmlNodeName
    *          Field's xml-node-name option.
    * @param   xmlNodeType
    *          Field's xml-node-type option.
    * @param   mandatory
    *          {@code true} for mandatory (not-null) fields.
    * @param   scale
    *          The {@code scale}, for decimal fields.
    * @param   maxWidth
    *          The {@code MAX-WIDTH}, for {@code CHARACTER} fields.
    */
   public P2JField(String name,
                   ParmType type,
                   long extent,
                   String format,
                   BaseDataType initial,
                   String label,
                   String columnLabel,
                   boolean caseSensitive,
                   String codePage,
                   String help,
                   boolean serializeHidden,
                   String serializeName,
                   String xmlDataType,
                   String xmlNodeName,
                   String xmlNodeType,
                   boolean mandatory,
                   int scale,
                   int maxWidth)
   {
      this(name, type, extent, format, initial, label, columnLabel, caseSensitive, codePage, help,
           serializeHidden, serializeName, xmlDataType, xmlNodeName, xmlNodeType, mandatory, scale,
           maxWidth, false, null, null);
   }
   
   /**
    * Convenience constructor.
    *
    * @param   name
    *          Field name.
    * @param   type
    *          Field type.
    * @param   extent
    *          Extent size.
    * @param   format
    *          Field format.
    * @param   initial
    *          Initial value.
    * @param   label
    *          Field label.
    * @param   columnLabel
    *          Column label.
    * @param   caseSensitive
    *          {@code true} for case sensitive character fields.
    * @param   codePage
    *          Code page for the CLOB field.
    * @param   help
    *          Field's help option.
    * @param   serializeHidden
    *          Field's serialize-hidden option.
    * @param   serializeName
    *          Field's serialize-name option.
    * @param   xmlDataType
    *          Field's xml-data-type option.
    * @param   xmlNodeName
    *          Field's xml-node-name option.
    * @param   xmlNodeType
    *          Field's xml-node-type option.
    * @param   mandatory
    *          {@code true} for mandatory (not-null) fields.
    * @param   scale
    *          The {@code scale}, for decimal fields.
    * @param   maxWidth
    *          The {@code MAX-WIDTH}, for CHARACTER fields.
    * @param   readOnly
    *          The {@code read-only} attribute.
    * @param   validateExpression
    *          The validate expression, if any.
    * @param   validateMessage
    *          The validate message, if any.
    */
   public P2JField(String name,
                   ParmType type,
                   long extent,
                   String format,
                   BaseDataType initial,
                   String label,
                   String columnLabel,
                   boolean caseSensitive,
                   String codePage,
                   String help,
                   boolean serializeHidden,
                   String serializeName,
                   String xmlDataType,
                   String xmlNodeName,
                   String xmlNodeType,
                   boolean mandatory,
                   int scale,
                   int maxWidth,
                   boolean readOnly,
                   String validateExpression,
                   String validateMessage)
   {
      this.name = name;
      this.type = type;
      this.extent = (int) extent;
      this.format = format;
      this.initial = initial == null ? null : initial.duplicate(); // avoid altering
      this.label = label;
      this.columnLabel = columnLabel;
      this.caseSensitive = caseSensitive;
      this.codePage = codePage;
      this.help = help == null || help.isEmpty() ? null : help;
      this.serializeHidden = serializeHidden;
      this.serializeName = serializeName == null || serializeName.isEmpty() ? null : serializeName;
      this.xmlDataType = xmlDataType == null || xmlDataType.isEmpty() ? null : xmlDataType;
      this.xmlNodeName = xmlNodeName == null || xmlNodeName.isEmpty() ? null : xmlNodeName;
      this.xmlNodeType = xmlNodeType == null || xmlNodeType.isEmpty() ? null : xmlNodeType;
      this.mandatory = mandatory;
      this.readOnly = readOnly;
      this.validateExpression = validateExpression;
      this.validateMessage = validateMessage;
      this.scale = scale;
      this.maxWidth = maxWidth;
      
      hash = computeHash();
   }
   
   /**
    * Get field name.
    *
    * @return  field name.
    */
   public String getName()
   {
      return name;
   }
   
   /**
    * Get field type.
    *
    * @return  field type.
    */
   public ParmType getType()
   {
      return type;
   }
   
   /**
    * Get extent size.
    *
    * @return  extent size.
    */
   public int getExtent()
   {
      return extent;
   }
   
   /**
    * Get field format.
    *
    * @return  field format.
    */
   public String getFormat()
   {
      return format;
   }
   
   /**
    * Get initial value.
    *
    * @return  initial value.
    */
   public BaseDataType getInitial()
   {
      return initial == null ? null : initial.duplicate(); // avoid altering
   }
   
   /**
    * Get field label.
    *
    * @return  field label.
    */
   public String getLabel()
   {
      return label;
   }
   
   /**
    * Get column label.
    *
    * @return  column label.
    */
   public String getColumnLabel()
   {
      return columnLabel;
   }
   
   /**
    * Check if this is a case sensitive field. It only make sense for character datatypes.
    *
    * @return  {@code true} if this is a case-sensitive character field.
    */
   public boolean isCaseSensitive()
   {
      return caseSensitive;
   }
   
   /**
    * Get code page for the CLOB field.
    *
    * @return  code page for the CLOB field.
    */
   public String getCodePage()
   {
      return codePage;
   }
   
   /**
    * Checks if this is a mandatory (not null) field.
    * 
    * @return  {@code true} if this is a mandatory (not null) field.
    */
   public boolean isMandatory()
   {
      return mandatory;
   }
   
   /**
    * Get the field's {@link #help} option.
    * 
    * @return   See above.
    */
   public String getHelp()
   {
      return help;
   }
   
   /**
    * Get the field's {@link #serializeHidden} option.
    * 
    * @return   See above.
    */
   public boolean isSerializeHidden()
   {
      return serializeHidden;
   }
   
   /**
    * Get the field's {@link #serializeName} option.
    * 
    * @return   See above.
    */
   public String getSerializeName()
   {
      return serializeName;
   }
   
   /**
    * Get the field's {@link #xmlDataType} option.
    * 
    * @return   See above.
    */
   public String getXmlDataType()
   {
      return xmlDataType;
   }
   
   /**
    * Get the field's {@link #xmlNodeName} option.
    * 
    * @return   See above.
    */
   public String getXmlNodeName()
   {
      return xmlNodeName;
   }
   
   /**
    * Get the field's {@link #xmlNodeType} option.
    * 
    * @return   See above.
    */
   public String getXmlNodeType()
   {
      return xmlNodeType;
   }
   
   /**
    * Obtains the {@code scale}. Only valid for decimal fields.
    * 
    * @return  the {@code scale}, for decimal fields.
    */
   public int getScale()
   {
      return scale;
   }
   
   /**
    * Obtains the {@code MAX-WIDTH} attribute. Only valid for character fields.
    * 
    * @return  the {@code MAX-WIDTH}, for character fields.
    */
   public int getMaxWidth()
   {
      return maxWidth;
   }
   
   /**
    * Obtain the READ-ONLY attribute.
    *
    * @return  the READ-ONLY attribute.
    */
   public boolean isReadOnly()
   {
      return readOnly;
   }
   
   /**
    * Obtain the VALIDATE-EXPRESSION attribute.
    *
    * @return  the VALIDATE-EXPRESSION attribute.
    */
   public String getValidateExpression()
   {
      return validateExpression;
   }
   
   /**
    * Obtain the VALIDATE-MESSAGE attribute.
    *
    * @return  the VALIDATE-MESSAGE attribute.
    */
   public String getValidateMessage()
   {
      return validateMessage;
   }
   
   /**
    * Produce a hash code from the internal data of this object, which is consistent with the
    * implementation of {@link #equals(Object)}.
    * 
    * @return  Hash code.
    */
   @Override
   public int hashCode()
   {
      return hash;
   }
   
   /**
    * Test equivalence of this object and the given object in a way that is consistent with the
    * implementation of {@link #hashCode()}.
    * 
    * @return  {@code true} if this object is equivalent with {@code o}, else {@code false}.
    */
   @Override
   public boolean equals(Object o)
   {
      boolean result;
      if (this == o)
      {
         result = true;
      }
      else if (!(o instanceof P2JField))
      {
         result = false;
      }
      else
      {
         P2JField that = (P2JField) o;
         result = ((this.name == null && that.name == null) ||
               (this.name != null && this.name.equalsIgnoreCase(that.name))) &&
               Objects.equals(this.type, that.type) &&
               this.extent == that.extent &&
               this.caseSensitive == that.caseSensitive &&
               this.mandatory == that.mandatory &&
               this.scale == that.scale &&
               this.maxWidth == that.maxWidth &&
               Objects.equals(this.format, that.format) &&
               Objects.equals(this.initial, that.initial) &&
               Objects.equals(this.label, that.label) &&
               Objects.equals(this.columnLabel, that.columnLabel) &&
               Objects.equals(this.codePage, that.codePage) &&
               Objects.equals(this.help, that.help) &&
               this.readOnly == that.readOnly &&
               this.serializeHidden == that.serializeHidden &&
               Objects.equals(this.validateExpression, that.validateExpression) &&
               Objects.equals(this.validateMessage, that.validateMessage) &&
               Objects.equals(this.serializeName, that.serializeName) &&
               Objects.equals(this.xmlDataType, that.xmlDataType) &&
               Objects.equals(this.xmlNodeName, that.xmlNodeName) &&
               Objects.equals(this.xmlNodeType, that.xmlNodeType);
      }
      
      return result;
   }
   
   /**
    * Get a string representation of a P2JField object that contains all/most information about the object.
    * The point of creating the method was to get a string representation unique for every distinct object.
    * 
    * @return  a string representation of all/most of the objects fields.
    */
   public String toStringComplete()
   {
      String str = "P2JField:{\n" + 
         "name: " + name + ",\n" +
         "type: " + type + ",\n" +
         "extent: " + extent + ",\n" +
         "format: " + format + ",\n" +
         "initial: " + initial + ",\n" +
         "label: " + label + ",\n" +
         "columnLabel: " + columnLabel + ",\n" +
         "caseSensitive: " + caseSensitive + ",\n" +
         "codePage: " + codePage + ",\n" +
         "help: " + help + ",\n" +
         "serializeHidden: " + serializeHidden + ",\n" +
         "serializeName: " + serializeName + ",\n" +
         "xmlDataType: " + xmlDataType + ",\n" +
         "xmlNodeType: " + xmlNodeType + ",\n" +
         "xmlNodeName: " + xmlNodeName + ",\n" +
         "mandatory: " + mandatory + ",\n" +
         "scale: " + scale + ",\n" +
         "maxWidth: " + maxWidth + ",\n" +
         "readOnly: " + readOnly + ",\n" +
         "validateExpression: " + validateExpression + ",\n" +
         "validateMessage: " + validateMessage + "}\n";
      
      return str;
   }
   
   /**
    * Obtain a short (incomplete) description of the object held. To be used only in debug routines.
    * 
    * @return  a short description of the most important attributes of the object.
    */
   @Override
   public String toString()
   {
      StringBuilder sb = new StringBuilder("P2JField{");
      sb.append(name).append(":").append(type);
      
      if (caseSensitive)
      {
         sb.append("CS");
      }
      else if (scale > 0)
      {
         sb.append("(x.").append(scale).append(")");
      }
      
      if (maxWidth > 0)
      {
         sb.append("(#").append(maxWidth).append(")");
      }
      
      if (mandatory)
      {
         sb.append("!");
      }
      
      if (extent > 0)
      {
         sb.append("[").append(extent).append("]");
      }
      sb.append("}");
      
      return sb.toString();
   }
   
   /**
    * Precompute the hashing value for this immutable object.
    * 
    * @return  A hashing value based on all members of the object.
    */
   private int computeHash()
   {
      int hash = 17;
      hash = 37 * hash + StringHelper.hashCodeCaseInsensitive(name);
      hash = 37 * hash + type.hashCode();
      hash = 37 * hash + extent;
      hash = 37 * hash + (format != null ? format.hashCode() : 0);
      hash = 37 * hash + (initial != null ? initial.hashCode() : 0);
      hash = 37 * hash + (label != null ? label.hashCode() : 0);
      hash = 37 * hash + (columnLabel != null ? columnLabel.hashCode() : 0);
      hash = 37 * hash + (caseSensitive ? 1 : 0);
      hash = 37 * hash + (codePage != null ? codePage.hashCode() : 0);
      hash = 37 * hash + (help != null ? help.hashCode() : 0);
      hash = 37 * hash + Boolean.hashCode(serializeHidden);
      hash = 37 * hash + (serializeName != null ? serializeName.hashCode() : 0);
      hash = 37 * hash + (xmlDataType != null ? xmlDataType.hashCode() : 0);
      hash = 37 * hash + (xmlNodeName != null ? xmlNodeName.hashCode() : 0);
      hash = 37 * hash + (xmlNodeType != null ? xmlNodeType.hashCode() : 0);
      hash = 37 * hash + (mandatory ? 1 : 0);
      hash = 37 * hash + (readOnly ? 1 : 0);
      hash = 37 * hash + (validateExpression != null ? validateExpression.hashCode() : 0);
      hash = 37 * hash + (validateMessage != null ? validateMessage.hashCode() : 0);
      hash = 37 * hash + maxWidth;
      return 37 * hash + scale;
   }
}