# UastHintsWorker.java

    /*
    ** Module   : UastHintsWorker.java
    ** Abstract : provides a pattern engine service to lookup generic UAST hints
    **
    ** Copyright (c) 2005-2023, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- --JPRM-- -----------------------------------Description-----------------------------------
    ** 001 GES 20050314   @20326 Created initial version that provides
    **                           support for reading string, long, double
    **                           and boolean values using a file-unique key.
    ** 002 GES 20050316   @20398 Converted this worker to use the visitAst()
    **                           interface in PatternWorker instead of using
    **                           a rule driven approach.
    ** 003 ECF 20050526   @21290 Changes to support new expression engine
    **                           implementation. Changed library registration
    **                           mechanism based on superclass' modifications
    **                           to support single library per pattern worker
    **                           limit.
    ** 004 ECF 20050713   @21700 Added getTableHints user function. Retrieves
    **                           hints, if any, for a specified table.
    ** 005 GES 20051129   @23548 Fixes to remove null pointer exceptions.
    ** 006 ECF 20061126   @31401 Added getExternalTableHints() method to
    **                           HintsReader. This method is used to read a
    **                           set of hints for an explicit, external AST.
    **                           Specifically, it allows temp/work-tables to
    **                           read table hints for permanent tables.
    ** 007 GES 20070718   @34595 Fix to remove null pointer exception.
    ** 008 GES 20090429   @42067 Match package and class name changes.
    ** 009 GES 20090514   @42234 Removed dead code.
    ** 010 ECF 20091006   @44186 Allow a .dict AST to load UAST hints associated
    **                           with the 4GL program file. This allows us to
    **                           maintain a single hints file per program, but to
    ** 011 LMR 20110106          Fixes to reflect renaming of load() method in Configuration class to
    **                           getSchemaConfig() and  removal of its 'key' parameter.
    ** 012 VMN 20140423          Added enhance schema name conversion support for hint "escape" attribute.
    ** 013 ECF 20160516          Do not try to load hints in runtime mode.
    ** 014 IAS 20160619          Added support for the "read-only" hint
    ** 015 OM  20200108          Added support for "dirty-read" hint/attribute.
    ** 016 IAS 20210219          Use UastHints factory
    **     CA  20211214          The AST manager plugin must be context-local, for runtime conversion, as the
    **                           InMemoryRegistryPlugin is not thread-safe.
    **     OM  20220330          Moved database conversion artifacts to ${cvtpath} folder.
    **     OM  20220407          Looking for hints in the original folder, not in the volatile $cvtpath folder.
    **     OM  20220415          Improved previous update. Support for reading schema hints from sbName.df.hints.
    ** 017 VVT 20230318          Unit test configuration support. See #6237.
    ** 018 GBB 20230512          Logging methods replaced by CentralLogger/ConversionStatus.
    ** 019 SVL 20230516          Support for dirty-intra-read annotation.
    */

    /*
    ** 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.uast;

    import java.io.*;
    import java.util.*;
    import java.util.logging.*;

    import com.goldencode.ast.*;
    import com.goldencode.p2j.cfg.*;
    import com.goldencode.p2j.convert.*;
    import com.goldencode.p2j.pattern.*;
    import com.goldencode.p2j.schema.*;

    /**
     * Provides a pattern engine service to read string, long, double and boolean values using a file-unique key.
     */
    public class UastHintsWorker
    extends AbstractPatternWorker
    {
       /** Logger. */
       private static final ConversionStatus LOG = ConversionStatus.get(UastHintsWorker.class);

       /** Stores the current AST's hints object. */
       private UastHints hints = null;

       /**
        * Default constructor which calls the super-class constructor, registers its libraries and initializes its
        * instance members.
        *
        * @throws  ConfigurationException
        *          If a configuration access error is encountered.
        * @throws  AstException
        *          If an error occurs while doing AST operations.
        */
       public UastHintsWorker()
       throws ConfigurationException,
              AstException
       {
          super();
          setLibrary(new HintsReader());
       }

       /**
        * Resets the current hints instance to <code>null</code> each time a new
        * AST is visited.  This enables the lazy loading of hints files only when
        * needed.
        *
        * @param   ast
        *          The root node of the source AST about to be processed by the
        *          pattern engine.
        */
       @Override
       public void visitAst(Aast ast)
       {
          hints = null;
       }

       /**
        * Provides a service for reading generic named hints associated with the
        * current AST.  This class is a <code>PatternEngine</code> worker.
        */
       public class HintsReader
       {
          /**
           * Retrieve the table hints, if any, for the specified table name.
           *
           * @param   table
           *          Name of the table for which hint information should be
           *          retrieved.
           *
           * @return  Table hints instance for the specified table, or
           *          <code>null</code> if none exists.
           */
          public TableHints getTableHints(String table)
          {
             if (!loadHints())
             {
                return null;
             }

             Map<String, TableHints> map = hints.getTableHints();
             if (map == null)
             {
                return null;
             }

             return map.get(table);
          }

          /**
           * Retrieve the table hints, if any, for the specified table name from a hints file not directly
           * associated with the currently loaded AST. Instead, the hints file is assumed to be a database-related
           * hints file identified by the specified, fully qualified table name.
           * <p>
           * Notes:  (1) unlike the other service methods of this worker, the hints loaded by this method are not
           *             cached, but are read each time this method is invoked.
           *         (2) this method only works for tables from permanent database. If the hints of a table from
           *             {@code @temp_db} database is requested, {@code null} is returned.
           *
           * @param   table
           *          Name of the table for which hint information should be retrieved.  This name is a schema
           *          name which must be qualified by the target database name for which to load hints.
           *
           * @return  Table hints instance for the specified table, or {@code null} if none exists.
           */
          public TableHints getExternalTableHints(String table)
          {
             // Load the external hints file, if any.
             EntityName ename = new EntityName(EntityName.TABLE, table);
             UastHints extHints = null;

             try
             {
                String database = ename.getDatabase();
                SchemaConfig config = Configuration.getSchemaConfig();
                File importFile = config.getImportFile(database);
                if (importFile == null)
                {
                   return null;
                }
                extHints = getUastHints(importFile.getAbsolutePath());
             }
             catch (Exception exc)
             {
                LOG.log(Level.SEVERE, "", exc);
                return null;
             }

             Map<String, TableHints> map = extHints.getTableHints();
             if (map == null)
             {
                return null;
             }

             return map.get(ename.getTable());
          }

          /**
           * Get a UAST hint using a uniquely named key, accessing a string or
           * <code>null</code> if no such hint exists.
           *
           * @param    key
           *           Unique name for the hint, which is used as a key.
           *
           * @return   The named string or <code>null</code>.
           */
          public String getUastString(String key)
          {
             if (!loadHints())
             {
                return null;
             }

             String result = null;

             String cls = hints.getUastType(key);

             if (cls != null && cls.equals("java.lang.String"))
             {
                result = hints.getUastString(key);
             }

             return result;
          }

          /**
           * Get a UAST hint using a uniquely named key, accessing a string array
           * or <code>null</code> if no such hint exists.
           *
           * @param    key
           *           Unique name for the hint, which is used as a key.
           * @param    index
           *           The 0-based index of the entry to access.
           *
           * @return   The indexed element in the named string array or
           *           <code>null</code> if the array or element doesn't exist.
           */
          public String getUastStringArray(String key, int index)
          {
             if (!loadHints())
             {
                return null;
             }

             String[] array  = null;
             String   result = null;
             String   cls    = hints.getUastType(key);

             if (cls != null && cls.equals("[Ljava.lang.String;"))
             {
                array = hints.getUastStringArray(key);
             }

             try
             {
                if (array != null)
                {
                   result = array[index];
                }
             }

             catch (IndexOutOfBoundsException ioe)
             {
                // nothing to do, just return null
             }

             return result;
          }

          /**
           * Get a UAST hint using a uniquely named key, accessing a long or
           * <code>null</code> if no such hint exists.
           *
           * @param    key
           *           Unique name for the hint, which is used as a key.
           *
           * @return   The named long or <code>null</code>.
           */
          public Long getUastLong(String key)
          {
             if (!loadHints())
             {
                return null;
             }

             Long   result = null;
             String cls    = hints.getUastType(key);

             if (cls != null && cls.equals("java.lang.Long"))
             {
                result = hints.getUastLong(key);
             }

             return result;
          }

          /**
           * Get a UAST hint using a uniquely named key, accessing a double or
           * <code>null</code> if no such hint exists.
           *
           * @param    key
           *           Unique name for the hint, which is used as a key.
           *
           * @return   The named double or <code>null</code>.
           */
          public Double getUastDouble(String key)
          {
             if (!loadHints())
             {
                return null;
             }

             Double result = null;
             String cls    = hints.getUastType(key);

             if (cls != null && cls.equals("java.lang.Double"))
             {
                result = hints.getUastDouble(key);
             }

             return result;
          }

          /**
           * Get a UAST hint using a uniquely named key, accessing a boolean or
           * <code>null</code> if no such hint exists.
           *
           * @param    key
           *           Unique name for the hint, which is used as a key.
           *
           * @return   The named boolean or <code>null</code>.
           */
          public Boolean getUastBoolean(String key)
          {
             if (!loadHints())
             {
                return null;
             }

             Boolean result = null;
             String  cls    = hints.getUastType(key);

             if (cls != null && cls.equals("java.lang.Boolean"))
             {
                result = hints.getUastBoolean(key);
             }

             return result;
          }

          /**
           * Accesses the class name (data type) of a UAST hint using a uniquely
           * named key.
           *
           * @param    key
           *           Unique name for the hint, which is used as a key.
           *
           * @return   The class name of the hint if it exists or
           *           <code>null</code> if the hint does not exist.
           */
          public String getUastHintType(String key)
          {
             if (!loadHints())
             {
                return null;
             }

             return hints.getUastType(key);
          }

          /**
           * Checks the existence of a UAST hint using a uniquely named key.
           *
           * @param    key
           *           Unique name for the hint, which is used as a key.
           *
           * @return   <code>true</code> if any hint exists by that key for this
           *           file, <code>false</code> if no such hint exists.
           */
          public boolean isUastHint(String key)
          {
             if (!loadHints())
             {
                return false;
             }

             return hints.isUastHint(key);
          }

          /**
           * Checks if hint is set for schema.
           *
           * @return   <code>true</code> if "escape" hint is set for schema and equals
           *           <code>true</code>, otherwise <code>false</code>.
           */
          public boolean isSchemaEscaped()
          {
             if (!loadHints())
             {
                return false;
             }

             return hints.isSchemaEscaped();
          }

          /**
           * Checks if field should be escaped.
           *
           * @param    table
           *           The legacy table name.
           * @param    field
           *           The legacy field name.
           *
           * @return   value of "escape" hint for field if hint is set, otherwise "escape" hint for
           *           table or schema.
           */
          public boolean isFieldEscaped(String table, String field)
          {
             if (!loadHints())
             {
                return false;
             }

             return hints.isFieldEscaped(table, field);
          }

          /**
           * Checks if table should be escaped.
           *
           * @param    table
           *           The legacy table name.
           *
           * @return   value of "escape" hint for table if hint is set, otherwise "escape" hint for
           *           schema.
           */
          public boolean isTableEscaped(String table)
          {
             if (!loadHints())
             {
                return false;
             }

             return hints.isTableEscaped(table);
          }

          /**
           * Checks if table is read-only.
           *
           * @param    table
           *           The legacy table name.
           *
           * @return   value of "read-only" hint for table if hint is set, otherwise {@code false}
           */
          public boolean isTableReadOnly(String table)
          {
             if (!loadHints())
             {
                return false;
             }
             return hints.isTableReadOnly(table);
          }

          /**
           * Get unit test legacy type. Valid values are "OEUnit" and "ABLUnit", no default.
           *
           * @return see above
           */
          public String getUnitTestType()
          {
             if (!loadHints())
             {
                return null;
             }

             return hints.getUnitTestType();
          }

          /**
           * Checks if table is dirty-read  (that is, it needs to be tracked by the dirty share
           * manager across sessions).
           *
           * @param   table
           *          The legacy table name.
           *
           * @return  value of "dirty-read" hint for table if hint is set, otherwise {@code false}.
           */
          public boolean isTableDirtyRead(String table)
          {
             if (!loadHints())
             {
                return false;
             }

             return hints.isTableDirtyRead(table);
          }

          /**
           * Checks if table is dirty-read  (that is, it needs to be tracked by the dirty share
           * manager within the same session).
           *
           * @param   table
           *          The legacy table name.
           *
           * @return  value of "dirty-intra-read" hint for table if hint is set, otherwise {@code false}.
           */
          public boolean isTableDirtyIntraRead(String table)
          {
             if (!loadHints())
             {
                return false;
             }

             return hints.isTableDirtyIntraRead(table);
          }

          /**
           * Loads the hints file for an AST if it exists and hasn't already been loaded.
           *
           * @return   {@code true} if there are hints for this file and the hints are loaded, {@code false} if
           *           no hints are available (server runtime mode).
           */
          private boolean loadHints()
          {
             if (Configuration.isRuntimeConfig())
             {
                return false;
             }

             if (hints == null)
             {
                try
                {
                   Aast src  = getResolver().getSourceAst();
                   // locate the original file (.df, .p, .w, etc)
                   String filename = (String) src.getAncestor(-1).getAnnotation("srcfile");
                   if (filename == null)
                   {
                      // if that fails (expected annotation not present), locate the intermediary/artifact file
                      filename = src.getFilename();
                   }

                   hints = getUastHints(Configuration.toFile(filename).getAbsolutePath());
                }
                catch (Exception exc)
                {
                   // any failure drops us out
                   return false;
                }
             }

             return true;
          }

          /**
           * Look for the hints file of a specific 4GL source file. Supported cases:
           * <ul>
           *    <li>.df, .p, .w code source file. The hints are looked in same directory, with the .hints suffix
           *          appended to the file's name</li>
           *    <li>namespace.schema.hints (supported but deprecated, use the above, instead)</li>
           * </ul>
           *
           * @param   sourcePath
           *          The path of the source file (source or data definition)
           *
           * @return  If a .hints file is detected for respective parameter file a {@code UastHints} is returned.
           *          Otherwise {@code null} is returned.
           */
          private UastHints getUastHints(String sourcePath)
          {
             if (new File(sourcePath + UastHints.HINTS_SUFFIX).exists())
             {
                // the preferred way: the .hints suffix is appended to source filename
                return new UastHints(sourcePath);
             }

             String renPath = sourcePath;
             if (sourcePath.endsWith(".df"))
             {
                // the deprecated way: replace [.df] with [.schema.hints] suffix
                renPath = sourcePath.substring(0, sourcePath.length() - 3) + SchemaWorker.SCHEMA_POSTFIX;
             }
             else if (sourcePath.endsWith(AstGenerator.DICT_POSTFIX))
             {
                // the deprecated way: replace [.dict] with [.schema.hints] suffix
                int len = AstGenerator.DICT_POSTFIX.length();
                renPath = sourcePath.substring(0, sourcePath.length() - len) + SchemaWorker.SCHEMA_POSTFIX;
             }

             return new UastHints(renPath);
          }
       }
    }