# PropertyDefinition.java

    /*
    ** Module   : PropertyDefinition.java
    ** Abstract : A container for a DMO property's metadata.
    **
    ** Copyright (c) 2013-2022, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- ---------------------------------------Description---------------------------------------
    ** 001 CA  20130618 Created initial version.
    ** 002 ECF 20140623 Removed Hibernate dependency. This update simplifies the use of the type
    **                  variable; this is a short-term workaround which breaks some result set
    **                  metadata functionality (not currently in use), but it needs further analysis/work.
    ** 003 ECF 20140626 Simplified type check.
    ** 004 SVL 20140709 Added legacyName field.
    ** 005 CA  20190812 Added toString().
    ** 006 IAS 20200908 Rework (de)serialization.
    ** 007 IAS 20200922 Get rid of possible NPE on serialization.
    **     SVL 20201030 Added format, label and columnLabel fields.
    **     OM  20201120 Added SCHEMA-MARSHAL implementation.
    **     CA  20220909 Always marshal the schema, otherwise the remote side will not be able to rebuild the table.
    **     IAS 20221029 Added support for missed properties.
    **     IAS 20221102 Re-worked serialization for SCHEMA-MARSHAL == NONE.
    */

    /*
    ** 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.io.*;
    import java.util.*;

    import com.goldencode.p2j.persist.orm.*;
    import com.goldencode.p2j.util.*;

    import static com.goldencode.p2j.persist.AbstractTempTable.*;
    import static com.goldencode.util.NativeTypeSerializer.*;

    /**
     * Container for a DMO property's metadata: name, type and extent.  Used to send the metadata of a
     * DMO to a remote side, when remote appserver calls are involved.
     *
     * TODO: add the other schema attributes of a property definition: help, initial, mandatory, validateMessage,
     *       validationExpression, description, decimals (for decimal type), caseSensitive (for character type)
     *       codePage, serializeName, xmlNodeName, xmlDataType, etc.
     *       All these will be serialized only if {@code schemaMarshalLevel == SM_FULL}.
     */
    public class PropertyDefinition
    implements Externalizable
    {
       /** Map of primitive names to their classes. */
       private static final Map<String, Class<?>> primitiveClasses = new HashMap<>();

       /** Default value for no-extent properties. */
       private static final int NO_EXTENT = -2;

       /** The P2J wrapper type of this property. */
       private Class<?> type;

       /** The extent of this property, or {@link #NO_EXTENT} if the property has no extent. */
       private int extent;

       /** The name of this property. */
       private String name;

       /** The legacy name of this property. */
       private String legacyName;

       /** The format of this property. */
       private String format;

       /** The label of this property. */
       private String label;

       /** The column label of this property. */
       private String columnLabel;

       /** HELP attribute of this field. */
       private String help;

       /** INITIAL attribute of this field. */
       private String initial;

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

       /** Flag indicating the field is case-sensitive. */
       private boolean caseSensitive;

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

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

       /**
        * The SCHEMA-MARSHAL level for the parent  temp-table. It is never {@code SM_DEFAULT} when object is to
        * be serialized. Always {@code SM_DEFAULT} for a read object.
        */
       private int schemaMarshalLevel = SM_DEFAULT;

       static
       {
           primitiveClasses.put("byte", byte.class);
           primitiveClasses.put("short", short.class);
           primitiveClasses.put("char", char.class);
           primitiveClasses.put("int", int.class);
           primitiveClasses.put("long", long.class);
           primitiveClasses.put("float", float.class);
           primitiveClasses.put("double", double.class);
       }

       /**
        * Default c'tor, explicitly added to allow instances of this class to be created on
        * deserialization. Not for public use.
        */
       public PropertyDefinition()
       {
       }

       /**
        * Create a new, no-extent, property definition.
        *
        * @param    name
        *           The name of this property.
        * @param    type
        *           The type of this property.
        *
        * @throws   ClassNotFoundException
        *           If the given <code>type</code> can not be resolved to a valid class.
        */
       public PropertyDefinition(String name, String type)
       throws ClassNotFoundException
       {
          this.name = name;
          Class<?> cls = primitiveClasses.containsKey(type) ? primitiveClasses.get(type) : Class.forName(type);
          verifyType(cls);
          this.type = cls;
          this.extent = NO_EXTENT;
       }

       /**
        * Create a new property definition.
        *
        * @param    name
        *           The name of this property.
        * @param    type
        *           The type of this property.
        * @param    extent
        *           The extent of this property.
        *
        * @throws   ClassNotFoundException
        *           If the given {@code type} can not be resolved to a valid class.
        */
       public PropertyDefinition(String name, String type, int extent)
       throws ClassNotFoundException
       {
          this(name, type);
          this.extent = extent;
       }

       /**
        * Create a new, no-extent, property definition.
        *
        * @param    name
        *           The name of this property.
        * @param    type
        *           A class specifying the type of this property.
        */
       public PropertyDefinition(String name, Class<?> type)
       {
          this(name, type, null);
       }

       /**
        * Create a new, no-extent, property definition.
        *
        * @param    name
        *           The name of this property.
        * @param    type
        *           A class specifying the type of this property.
        * @param    legacyName
        *           The legacy name of this property.
        */
       public PropertyDefinition(String name, Class<?> type, String legacyName)
       {
          this(name, type, legacyName, null, null, null);
       }

       /**
        * Create a new property definition.
        *
        * @param    name
        *           The name of this property.
        * @param    type
        *           A class specifying the type of this property.
        * @param    extent
        *           The extent of this property.
        */
       public PropertyDefinition(String name, Class<?> type, int extent)
       {
          this(name, type, extent, null);
       }

       /**
        * Create a new property definition.
        *
        * @param    name
        *           The name of this property.
        * @param    type
        *           A class specifying the type of this property.
        * @param    extent
        *           The extent of this property.
        * @param    legacyName
        *           The legacy name of this property.
        */
       public PropertyDefinition(String name, Class<?> type, int extent, String legacyName)
       {
          this(name, type, extent, legacyName, null, null, null);
       }

       /**
        * Create a new, no-extent, property definition.
        *
        * @param    name
        *           The name of this property.
        * @param    type
        *           A class specifying the type of this property.
        * @param    legacyName
        *           The legacy name of this property.
        * @param    format
        *           The format of this property.
        * @param    label
        *           The label of this property.
        * @param    columnLabel
        *           The column label of this property.
        */
       public PropertyDefinition(String name,
                                 Class<?> type,
                                 String legacyName,
                                 String format,
                                 String label,
                                 String columnLabel)
       {
          this(name, type, NO_EXTENT, legacyName, format, label, columnLabel);
       }

       /**
        * Create a new property definition.
        *
        * @param    name
        *           The name of this property.
        * @param    type
        *           A class specifying the type of this property.
        * @param    legacyName
        *           The legacy name of this property.
        * @param    format
        *           The format of this property.
        * @param    label
        *           The label of this property.
        * @param    columnLabel
        *           The column label of this property.
        */
       public PropertyDefinition(String name,
                                 Class<?> type,
                                 int extent,
                                 String legacyName,
                                 String format,
                                 String label,
                                 String columnLabel)
       {
          this.name = name;
          verifyType(type);
          this.type = type;
          this.extent = extent;
          this.legacyName = legacyName;
          this.format = format;
          this.label = label;
          this.columnLabel = columnLabel;
       }

       /**
        * Create a new property definition.
        *
        * @param    prop
        *           The ORM field <code>Property</code>.
        */
       public PropertyDefinition(Property prop)
       {
          this.name = prop.name;
          verifyType(prop._fwdType);
          this.type = prop._fwdType;
          int extent = prop.index > 0 ? 0 : prop.extent;
          this.extent = extent == 0 ? NO_EXTENT : extent;
          this.legacyName = prop.legacy;
          this.format = prop.format;
          this.label = prop.label;
          this.columnLabel = prop.columnLabel;
          this.help = prop.help;
          this.initial = prop.initial;
          this.serializeHidden = prop.serializeHidden;
          this.caseSensitive = prop.caseSensitive;
          this.xmlNodeType = prop.xmlNodeType;
          this.codePage = prop.codePage;
       }
       /**
        * Get the type of this property.
        *
        * @return   See above.
        */
       public Class<?> getType()
       {
          return type;
       }

       /**
        * Get the extent of this property.
        *
        * @return   See above.
        */
       public int getExtent()
       {
          return extent;
       }

       /**
        * Get the name of this property.
        *
        * @return   See above.
        */
       public String getName()
       {
          return name;
       }

       /**
        * Check if this property is an extent property.
        *
        * @return   <code>true</code> if this property is an extent property.
        */
       public boolean isExtent()
       {
          return extent != NO_EXTENT;
       }

       /**
        * Get legacy name of this property.
        *
        * @return legacy name of this property.
        */
       public String getLegacyName()
       {
          return legacyName;
       }

       /**
        * Get format of this property.
        *
        * @return format of this property.
        */
       public String getFormat()
       {
          return format;
       }

       /**
        * Get label of this property.
        *
        * @return label of this property.
        */
       public String getLabel()
       {
          return label;
       }

       /**
        * Get column label of this property.
        *
        * @return column label of this property.
        */
       public String getColumnLabel()
       {
          return columnLabel;
       }

       /**
        * Get help text of this property.
        *
        * @return help text of this property.
        */
       public String getHelp()
       {
          return help;
       }

       /**
        * Get initial value of this property.
        *
        * @return initial value of this property.
        */
       public String getInitial()
       {
          return initial;
       }

       /**
        * Get 'serialize hidden' flag of this property.
        *
        * @return 'serialize hidden' flag of this property.
        */
       public boolean isSerializeHidden()
       {
          return serializeHidden;
       }

       /**
        * Get 'case-sensitive' flag of this property.
        *
        * @return 'case-sensitive' flag of this property.
        */
       public boolean isCaseSensitive()
       {
          return caseSensitive;
       }

       /**
        * Get XML node type of this property.
        *
        * @return XML node type of this property.
        */
       public String getXmlNodeType()
       {
          return xmlNodeType;
       }

       /**
        * Get code page of this property.
        *
        * @return code page of this property.
        */
       public String getCodePage()
       {
          return codePage;
       }

       /**
        * Send the property definition to the specified output destination.
        *
        * @param    out
        *           The output destination to which the property definition will be sent.
        *
        * @throws   IOException
        *           In case of I/O errors.
        */
       public final void writeExternal(ObjectOutput out)
       throws IOException
       {
          if (schemaMarshalLevel == SM_NONE)
          {
             throw new IllegalStateException("Should not be called if schemaMarshalLevel == SM_NONE");
          }
          boolean full = schemaMarshalLevel == SM_FULL || schemaMarshalLevel == SM_DEFAULT;

          writeString(out, name);
          writeString(out, legacyName);
          writeString(out, type == null ? null : type.getName());
          out.writeInt(extent);
          writeString(out, initial);
          writeString(out, xmlNodeType);
          writeString(out, codePage);
          int flags = (serializeHidden ? 1 : 0) | (caseSensitive ? 2 : 0) | (full ? 4 : 0);
          out.writeByte(flags);
          if (!full )
          {
             return;
          }
          writeString(out, format);
          writeString(out, label);
          writeString(out, columnLabel);
          writeString(out, help);
       }

       /**
        * Read the property definition from the specified input source.
        *
        * @param    in
        *           The input source from which the property definition will be read.
        *
        * @throws   IOException
        *           In case of I/O errors.
        *
        * @throws  ClassNotFoundException
        *          If the class of property could not be found/loaded.
        */
       public final void readExternal(ObjectInput in)
       throws IOException,
              ClassNotFoundException
       {
          name = readString(in);
          legacyName = readString(in);
          String tn = readString(in);
          type = (tn == null) ? null : Class.forName(tn);
          extent = in.readInt();
          initial = readString(in);
          xmlNodeType = readString(in);
          codePage = readString(in);
          int flags = in.readByte();
          serializeHidden = (flags & 1) != 0;
          caseSensitive = (flags & 2) != 0;
          boolean full = (flags & 4) != 0;
          if (!full)
          {
             return;
          }
          format = readString(in);
          label = readString(in);
          columnLabel = readString(in);
          help = readString(in);
       }

       /**
        * Verify the given type is not null.
        *
        * @param    type
        *           The type to check.
        *
        * @throws   NullPointerException
        *           If <code>type</code> is null.
        */
       private void verifyType(Class<?> type)
       {
          if (type == null)
          {
             throw new NullPointerException("Type may not be null");
          }
       }

       /**
        * Get a string representation of this property.
        *
        * @return    See above.
        */
       @Override
       public String toString()
       {
          StringBuilder sb = new StringBuilder();
          sb.append(name).append("[legacy: ").append(legacyName).append("]: ").append(type.getSimpleName());
          if (extent != NO_EXTENT && extent != 0)
          {
             sb.append("[").append(extent).append("]");
          }

          return sb.toString();
       }

       /**
        * Configures the SCHEMA-MARSHAL level used for serialization of the property.
        *
        * @param   schemaMarshalLevel
        *          The new SCHEMA-MARSHAL level. See {@link AbstractTempTable} static constants for details.
        */
       public void setMarshalLevel(int schemaMarshalLevel)
       {
          this.schemaMarshalLevel = schemaMarshalLevel;
       }
    }