ReservedProperty.java

/*
** Module   : ReservedProperty.java
** Abstract : Pseudo-annotations for special temp-table fields in order to handle all DMO properties in a
**            unified way.
**
** Copyright (c) 2020-2022, Golden Code Development Corporation.
**
** -#- -I- --Date-- ---------------------------------------Description---------------------------------------
** 001 OM  20200110 Added constants and method implementations.
** 002 CA  20200922 Added getByName(), to resolve the reserved property for a 'hidden field' in 4GL.
**     OM  20201001 Improved DMO manipulation performance by caching slow Property annotation access.
**     OM  20201218 Fixed implementation for error and rejected attributes/hidden fields.
**     CA  20210831 Track the getter method for TempRecord reserved properties.
**     OM  20211020 Added _DATASOURCE_ROWID property.
**     OM  20220212 Added isReservedProperty static utility method for testing reserved properties names.
**     OM  20220713 Decreased constructor's visibility to private.
*/

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

/**
 * These are reserved {@code Property}/es which may take part of a SQL record of a temporary table:
 * <ul>
 *    <li>{@code id} - the record identifier. FWD-specific;</li>
 *    <li>{@code _multiplex} - the temp-table multiplexer. FWD-specific, allows multiple users
 *          to store same-structure records in a single TEMP-TABLE;</li>
 *    <li>{@code parent__id} - the foreign-key for composite table to its parent;</li>
 *    <li>{@code list__index} - extent index for composite table;</li>
 *
 *    <li>{@code _errorFlag}, {@code _originRowid}, {@code _dataSourceRowid}, {@code _errorString},
 *        {@code _peerRowid}, and {@code _rowState} - the before-table specific hidden fields.</li>
 * </ul>
 */
public class ReservedProperty
extends Property
{
   /** The id of the {@code parent__id} reserved property. */
   public static final int ID_PARENT__ID = -2;
   
   /** The id of the {@code list__index} reserved property. */
   public static final int ID_LIST__INDEX = -3;
   
   /** The id of the {@code _row_state} reserved property. */
   public static final int ID_ROW_STATE = -4;
   
   /** The id of the {@code _peer_rowid} reserved property. */
   public static final int ID_PEER_ROWID = -5;
   
   /** The id of the {@code _error_string} reserved property. */
   public static final int ID_ERROR_STRING = -6;
   
   /** The id of the {@code _datasource-rowid} reserved property. */
   public static final int ID_DATASOURCE_ROWID = -7;
   
   /** The id of the {@code _origin_rowid} reserved property. */
   public static final int ID_ORIGIN_ROWID = -8;
   
   /** The id of the {@code _error_flag} reserved property. */
   public static final int ID_ERROR_FLAG = -9;
   
   /** The id of the {@code _multiplex} reserved property. */
   public static final int ID_MULTIPLEX = -10;
   
   /** The id of the {@code primary_key} reserved property. */
   public static final int ID_PRIMARY_KEY = -11;
   
   /** The {@code id} reserved (by FWD) property. */
   public static final ReservedProperty ID = new ReservedProperty(
         ID_PRIMARY_KEY, Session.PK, null, Session.PK, true, Long.class, false);
   
   /** The {@code _multiplex} reserved (by FWD) property. */
   public static final ReservedProperty _MULTIPLEX = new ReservedProperty(
         ID_MULTIPLEX, TemporaryBuffer.MULTIPLEX_FIELD_NAME, null, TemporaryBuffer.MULTIPLEX_FIELD_NAME, true, Integer.class, false);
   
   /** The {@code __error-flag__} reserved (by P4GL) property. */
   public static final ReservedProperty _ERRORFLAG = new ReservedProperty(
         ID_ERROR_FLAG, TempRecord._ERROR_FLAG, Buffer.__ERROR_FLAG__, TempRecord._ERROR_FLAG, false, integer.class, true);
   
   /** The {@code __origin-rowid__} reserved (by P4GL) property. */
   public static final ReservedProperty _ORIGINROWID = new ReservedProperty(
         ID_ORIGIN_ROWID, TempRecord._ORIGIN_ROWID, Buffer.__ORIGIN_ROWID__, TempRecord._ORIGIN_ROWID, false, rowid.class, true);
   
   /** The {@code __datasource-rowid__} reserved (by P4GL) property. */
   public static final ReservedProperty _DATASOURCEROWID = new ReservedProperty(
         ID_DATASOURCE_ROWID, TempRecord._DATASOURCE_ROWID, Buffer.__DATA_SOURCE_ROWID__, TempRecord._DATASOURCE_ROWID, false, rowid.class, true);
   
   /** The {@code _errorString} reserved (by P4GL) property. */
   public static final ReservedProperty _ERRORSTRING = new ReservedProperty(
         ID_ERROR_STRING, TempRecord._ERROR_STRING, Buffer.__ERROR_STRING__, TempRecord._ERROR_STRING, false, character.class, true);
   
   /** The {@code __after-rowid__} reserved (by P4GL) property. */
   public static final ReservedProperty _PEERROWID = new ReservedProperty(
         ID_PEER_ROWID, TempRecord._PEER_ROWID, Buffer.__AFTER_ROWID__, TempRecord._PEER_ROWID, false, rowid.class, true);
   
   /** The {@code __row-state__} reserved (by P4GL) property. */
   public static final ReservedProperty _ROWSTATE = new ReservedProperty(
         ID_ROW_STATE, TempRecord._ROW_STATE, Buffer.__ROW_STATE__, TempRecord._ROW_STATE, false, integer.class, true);
   
   /** The {@code list__index} reserved (by FWD) property. */
   public static final ReservedProperty LIST__INDEX = new ReservedProperty(
         ID_LIST__INDEX, "list__index", "index", "list__index", true, Integer.class, false);  // or integer ?
   
   /** The {@code list__index} reserved (by FWD) property. */
   public static final ReservedProperty PARENT__ID = new ReservedProperty(
         ID_PARENT__ID, "parent__id", "fk_parent", "parent__id", true, Long.class, false);    // or int64 ?
   
   /** The map of reserved properties which exist in 4GL, but are hidden. */
   private static final Map<String, ReservedProperty> HIDDEN_PROPERTIES = new HashMap<>();
   
   static
   {
      HIDDEN_PROPERTIES.put(_ERRORFLAG.legacy, _ERRORFLAG);
      HIDDEN_PROPERTIES.put(_ORIGINROWID.legacy, _ORIGINROWID);
      HIDDEN_PROPERTIES.put(_DATASOURCEROWID.legacy, _DATASOURCEROWID);
      HIDDEN_PROPERTIES.put(_ERRORSTRING.legacy, _ERRORSTRING);
      HIDDEN_PROPERTIES.put(_PEERROWID.legacy, _PEERROWID);
      HIDDEN_PROPERTIES.put(_ROWSTATE.legacy, _ROWSTATE);
   }
   
   /**
    * Creates a new {@code ReservedProperty} object 
    *
    * @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.
    */
   private ReservedProperty(int id,
                           String name,
                           String legacy,
                           String column,
                           boolean mandatory,
                           Class<?> type,
                           boolean initialNull)
   {
      super(id, name, legacy, column, mandatory, type, initialNull, getMethod(id));
   }
   
   /**
    * Get the specified hidden property.
    * 
    * @param    name
    *           The name of the reserved property.
    *           
    * @return   The {@link ReservedProperty} instance for it.
    */
   public static ReservedProperty getByName(String name)
   {
      return HIDDEN_PROPERTIES.get(name.toLowerCase());
   }
   
   /**
    * Check whether the {@code name} argument is the name of a reserved property.
    * 
    * @param   name
    *          The name to test
    *
    * @return  {@code true} if {@code name} is the name of a reserved property.
    */
   public static boolean isReservedProperty(String name)
   {
      if (!name.startsWith("__"))
      {
         // the reserved property names always start with double underscore. If not, quick out.
         return false;
      }
      
      switch (name)
      {
         case Buffer.__AFTER_ROWID__:
         case Buffer.__DATA_SOURCE_ROWID__:
         case Buffer.__ERROR_FLAG__:
         case Buffer.__ERROR_STRING__:
         case Buffer.__ORIGIN_ROWID__:
         case Buffer.__ROW_STATE__:
            return true;
      }
      
      return false;
   }
   
   /**
    * Get the method for the specified reserved property.
    * 
    * @param    id
    *           The reserved property ID.
    *           
    * @return   The method, or <code>null</code> if this is not one of the {@link TempRecord} methods.
    * 
    * @throws   RuntimeException
    *           If the method can't be found, as this is a FWD error.
    */
   private static Method getMethod(int id)
   {
      try
      {
         switch (id)
         {
            case ID_ROW_STATE:
               return TempRecord.class.getDeclaredMethod("_rowState");
            case ID_PEER_ROWID:
               return TempRecord.class.getDeclaredMethod("_peerRowid");
            case ID_ERROR_STRING:
               return TempRecord.class.getDeclaredMethod("_errorString");
            case ID_ORIGIN_ROWID:
               return TempRecord.class.getDeclaredMethod("_originRowid");
            case ID_DATASOURCE_ROWID:
               return TempRecord.class.getDeclaredMethod("_datasourceRowid");
            case ID_ERROR_FLAG:
               return TempRecord.class.getDeclaredMethod("_errorFlags");
            default:
               return null;
         }
      }
      catch (NoSuchMethodException exc)
      {
         throw new RuntimeException(exc);
      }
   }
}