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