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