UastHints.java
/*
** Module : UastHints.java
** Abstract : reads a UAST hints file and cleanly exposes the contents
**
** Copyright (c) 2005-2023, Golden Code Development Corporation.
**
** -#- -I- --Date-- --JPRM-- -----------------------------------Description-----------------------------------
** 001 GES 20050204 @19572 Created initial version. This supports
** reading an XML file that "lives" in the same
** location (and basename) as the associated
** Progress 4GL source file to which it refers.
** The hints file is the source file's basename
** + ".hints". At this time the only hint
** supported is a database hint (to load a list
** of databases into the schema dictionary
** before parsing starts).
** 002 GES 20050207 @19622 Added runtime preprocessor arguments
** support.
** 003 GES 20050210 @19751 Added alias support and the capability to
** preload the variable/widget dictionaries.
** 004 GES 20050221 @19907 Moved token type to symbolic name lookup
** helper to the ProgressParser which is a
** more natural place for it.
** 005 GES 20050314 @20325 Added support for generic UAST hints that
** must be uniquely named (within a given file)
** and the value can be one of the following
** data types: boolean, long, double, string,
** string array. Each uniquely named value
** can be obtained using the name as a key.
** 006 GES 20050316 @20401 Added a method to get a generic UAST hint's
** class name (data type) and a mechanism to
** detect if a specific generic hint exists
** or not.
** 007 GES 20050331 @20562 Minor modification to match change in
** Variable constructor.
** 008 ECF 20050421 @20810 Import p2j.xml package to use XmlHelper.
** 009 ECF 20050713 @21699 Added schema hints. Schema hints are used to
** override name conversions at the table and
** field level, and to define new associations
** between tables, or to provide supplemental
** data for existing associations.
** 010 SIY 20050823 @22197 Fixed loading of multiple UAST hints.
** 011 GES 20070313 @32398 Added support for various file-level propath
** customizations.
** 012 GES 20070321 @32501 Protect from a null ptr when the source file
** has no pathing.
** 013 GES 20070322 @32549 Added support for directory level hints.
** 014 GES 20070403 @32716 Added support for ignoring backslash as an
** escape sequence in the preprocessor.
** 015 GES 20070423 @33207 Added support for overriding the marker
** character in the preprocessor.
** 016 GES 20070717 @34581 Honor ${SYMBOL} style substitutions in the
** propath. This allows these entries to act
** like those in the project level config file.
** 017 GES 20070816 @34891 Added support for overriding the BATCH-MODE,
** OPSYS and WINDOW-SYSTEM built-in variables
** of the preprocessor. The other preprocessor
** flags now support a global override in the
** project configuration.
** 018 GES 20070904 @34951 Added E4GL preprocessing hints support.
** 019 GES 20070919 @35139 Added class name support to variable hints.
** 020 GES 20080324 @37667 Changed preprocessor arguments to encode
** each one as a separate XML element instead
** of the more cumbersome encoded string.
** 021 ECF 20080704 @39146 Minor memory optimization. Use Boolean
** constants instead of instantiating new
** Boolean objects.
** 022 GES 20081008 @40357 Directory hints are honored at any level of
** parent directory from the most specific
** directory and being loaded in turn from each
** successive enclosing parent until reaching
** the top-level base path.
** 023 GES 20090429 @42066 Match package and class name changes.
** 024 GES 20090515 @42232 Import change.
** 025 GES 20110623 Cleanups.
** 026 GES 20111014 Documentation fix.
** 027 CA 20140424 Allow empty-array string hints.
** 028 VMN 20140427 Added support for literal replacements.
** 029 IAS 20160404 Configuration.substituteParms() is not static now.
** 030 ECF 20160407 Added processing related to call graph hints.
** 031 IAS 20160619 Added support for the "read-only" hint
** 032 GES 20170303 Added preprocessor debug option.
** 033 HC 20170708 Added source-charset uast preprocessor hint.
** 032 CA 20180321 Added support for PROCESS-ARCHITECTURE preprocessor var.
** 033 GES 20190227 Ensure that the sourceCharset is not null.
** 034 OM 20200108 Added support for "dirty-read" hint/attribute.
** 035 GES 20200626 Try to detect the current JVM's pointer size to be the default process
** architecture.
** 036 RFB 20200929 Use the Configuration default charset, not the JVM default charset. Ref #4859.
** IAS 20210219 Added factory method and cache.
** OM 20211122 Added hints-based support for 'unloading' schema.
** TJD 20220504 Java 11 compatibility related minor changes
** 039 VVT 20230318 Unit test configuration support. See #6237.
** 040 GBB 20230512 Logging methods replaced by CentralLogger/ConversionStatus.
** 041 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.nio.charset.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.logging.*;
import com.goldencode.p2j.convert.*;
import org.w3c.dom.*;
import com.goldencode.ast.*;
import com.goldencode.util.*;
import com.goldencode.p2j.cfg.*;
import com.goldencode.p2j.schema.*;
/**
* Loads hints information from a UAST hints XML file into memory and
* exposes the data in an easily accessible format. The purpose of this class
* is to separate the hints persistence mechanism (i.e., XML) from the user
* of these hints.
* <p>
* There are 2 levels of hints files:
* <p>
* <ol>
* <li> Directory - hints that apply to all source files loaded from that
* specific path or from any paths which are enclosed.
* <li> File - hints that apply only to the exact source file being loaded.
* </ol>
* <p>
* The directory hints are loaded first and then file-specific hints are
* loaded. This allows generic (directory level) processing to be overridden
* by more specific values at the file level.
* <p>
* Directory hints are loaded in the order found. The search starts in the
* base path (top-level source directory) and then walks downward to each
* more deeply nested directory until the directory containing the file is
* searched. This means that all directory hints files that exist in that
* path will be loaded (the search does not stop when the first directory
* hints file is found). Also note that any value in a more specific
* directory may override a more enclosing directory hint.
* <p>
* Note that the format of both type of hints files are the same and there is
* no provision for removing a hint that is specified in a previously loaded
* hints file. Each specific type of hint handles this differently. Sometimes
* the hint will work as an addition, but in other cases the hint acts as a
* replacement. Here is the guide:
* <p>
* <pre>
* Hint Type Edit Type
* ------------------------- -------------------------
* database adds to end of the list
* alias adds to end of the list
* variable adds to end of the list
* preprocessor args replacement
* preprocessor unix escapes replacement
* preprocessor marker replacement
* preprocessor batch mode replacement
* preprocessor opsys replacement
* preprocessor window system replacement
* preprocessor debug replacement
* propath replacement
* schema hints replacement
* callgraph rootlist replacement
* callgraph include-spec replacement
* embedded 4GL preprocessor replacement
* generic uast replacement
* </pre>
* <p>
* Once constructed, the hints file has been loaded into memory and the
* contents are available via simple 'getter' method calls such as
* {@link #getDatabases}. This class hides the storage mechanism and the
* file format from the callers. It also handles any conversion from the
* string oriented XML format to the proper data type of the value in
* question. Generally, if the hints file doesn't exist or if the value
* being queried doesn't exist in that hints file, the result from a
* lookup will be <code>null</code>.
* <p>
* There is support for generic UAST hints that must be uniquely named
* (within a given file) and the value can be one of the following data
* types: boolean, long, double, string, string array. Each uniquely named
* value can be obtained using the name as a key.
* <p>
* The following is the structure of the XML file:
* <p>
* <ul>
* <li> hints (root element)
* <ul>
* <li> database (element, can be 0 or more)
* <ul>
* <li> name (attribute, the name of the database to load into the
* schema dictionary before parsing starts)
* </ul>
* <li> alias (element, can be 0 or more)
* <ul>
* <li> name (attribute, the name of the alias to create in the
* schema dictionary before parsing starts)
* <li> database (attribute, the name of the database to associated
* with the alias in the schema dictionary before parsing
* starts)
* </ul>
* <li> variable (element, can be 0 or more)
* <ul>
* <li> name (attribute, the name of the variable or widget to add
* to the variable/widget dictionary before parsing starts)
* <li> type (attribute, the non-negative integer token type of
* the variable/widget)
* <li> classname (optional attribute, the fully qualified name of
* the class of an variable that represents an object instance)
* </ul>
* <li> preprocessor (element, can be 0 or 1)
* <ul>
* <li> argument (element, can be 0 or 1)
* <ul>
* <li> name (optional attribute, the name of the argument)
* <li> pos (optional attribute, the positional number of the
* argument, which will be used to create a name for the
* argument)
* <li> one of either the name or pos attributes must be present to
* allow this value to be added to the arguments map
* <li> value (attribute, the exact string encoding of the
* specific argument to be passed to the preprocessor)
* </ul>
* <li> unix-escapes (element, can be 0 or 1)
* <ul>
* <li> value (attribute, the string "true" or "false") - honors
* a system-wide default
* </ul>
* <li> marker (element, can be 0 or 1)
* <ul>
* <li> value (attribute, a base-10 integer between 0 and 255
* inclusive) - honors a system-wide default
* </ul>
* <li> batch (element, can be 0 or 1)
* <ul>
* <li> value (attribute, the string "true" or "false") - honors
* a system-wide default
* </ul>
* <li> opsys (element, can be 0 or 1)
* <ul>
* <li> value (attribute, the exact string encoding the operating
* system to be returned by the preprocessor's OPSYS
* built-in) - honors a system-wide default
* </ul>
* <li> winsys (element, can be 0 or 1)
* <ul>
* <li> value (attribute, the exact string encoding the window
* system to be returned by the preprocessor's WINDOW-SYSTEM
* built-in) - honors a system-wide default
* </ul>
* <li> debug (element, can be 0 or 1)
* <ul>
* <li> value (attribute, the string "true" or "false") - honors
* a system-wide default
* </ul>
* </ul>
* <li> propath (element, can be 0 or 1)
* <ul>
* <li> type (required attribute), must be one of the following
* text strings
* <ul>
* <li> replace
* <li> prepend
* <li> append
* <li> dynamic-prepend
* <li> dynamic-append
* </ul>
* <li> value (required attribute for types replace, prepend and
* append, ignored otherwise; encodes the exact string to
* be used as the replacement propath, to be prepended to the
* propath or to be appended to the propath), parameters from
* the project configuration can be referenced as substitutions
* to allow the path values to be made project-relative (use
* something like "${P2J_HOME}/my/src/path:")
* </ul>
* <li> schema (element, can be 0 or 1)
* <ul>
* <li> table (element, can be 1 or more)
* <ul>
* <li> name (required attribute, name of the database table)
* <li> field (element, can be 0 or more)
* <ul>
* <li> name (required attribute, name of the database table)
* <li> phrase (element, can be 0 or more)
* <ul>
* <li> match (required attribute, text to match within the
* field name being converted)
* <li> replace (required attribute, text to substitute
* for matched text within the field name being
* converted)
* </ul>
* </ul>
* <li> phrase (element, can be 0 or more)
* <ul>
* <li> match (required attribute, text to match within the
* table/field name being converted)
* <li> replace (required attribute, text to substitute for
* matched text within the field/table name being
* converted)
* <li> no-inherit (optional attribute, true to apply this
* match phrase to only the table name, but not the field
* children; if false or missing, the phrase is inherited
* by all fields of the table)
* </ul>
* <li> association (element, can be 0 or more)
* <ul>
* <li> type (required attribute, type of association between
* this table and another: one-to-one, many-to-one, or
* one-to-many)
* <li> references (required attribute, name of table
* referenced by this association)
* <li> foreign (optional attribute, true if this table
* represents the foreign key end of a one-to-one
* association)
* <li> field (element, can be 0 or more)
* <ul>
* <li> name (required attribute, name of field in <i>this
* table</i> which is used to join the tables in this
* association)
* <li> references (required attribute, name of field in
* <i>the referenced table</i> which is used to join
* the tables in this association)
* </ul>
* </ul>
* </ul>
* </ul>
* <li> callgraph (element, can be 0 or 1)
* <ul>
* <li> rootlist (element, can be 0 or 1)
* <ul>
* <li> value (attribute, the path to a rootlist XML file)
* </ul>
* <li> include-spec (element, can be 0 or 1)
* <ul>
* <li> value (attribute, a specification for included files)
* </ul>
* </ul>
* <li> e4gl (embedded 4GL preprocessor element, can be 0 or 1)
* <ul>
* <li> wsoptions (element, can be 0 or 1)
* <ul>
* <li> value (attribute, the exact string encoding all webspeed
* options to be passed to the preprocessor for this file)
* - honors a system-wide default
* </ul>
* <li> mode (element, can be 0 or 1)
* <ul>
* <li> value (attribute, the string name of the preprocessor
* compatibility mode) - honors a system-wide default
* </ul>
* <li> output (element, can be 0 or 1)
* <ul>
* <li> value (attribute, file name of the 4GL source file that
* will be generated from the HTML input file)
* </ul>
* </ul>
* <li> uast
* <ul>
* <li> name (attribute, the unique name of the hint which will be
* used as a key)
* <li> datatype (attribute, the name of the value's data type), can
* be one of the following (case is ignored):
* <ul>
* <li> string
* <li> string[]
* <li> long
* <li> double
* <li> boolean
* </ul>
* <li> value (attribute, the exact string encoding the value of the
* hint, except when the datatype is string array)
* <li> array-val (element, 1 per array entry when the type is
* string array, otherwise it is absent)
* <ul>
* <li> value (attribute, the exact string encoding the value
* of the entry in the array)
* </ul>
* </ul>
* </ul>
* </ul>
*/
public class UastHints
implements HintsConstants
{
/** Logger */
private static final ConversionStatus LOG = ConversionStatus.get(UastHints.class);
/** UastHints cache by file name */
private static final ConcurrentMap<String, UastHints> HINTS = new ConcurrentHashMap<>();
/** No PROPATH modification is specified for this file. */
public static final int PATH_MOD_NONE = 0;
/** Replace the PROPATH with the associated string. */
public static final int PATH_MOD_REPLACE = 1;
/** Prepend the PROPATH with the associated string. */
public static final int PATH_MOD_PREPEND = 2;
/** Append the PROPATH with the associated string. */
public static final int PATH_MOD_APPEND = 3;
/** Prepend the file's containing directory to the PROPATH. */
public static final int PATH_MOD_DYN_PREPEND = 4;
/** Append the file's containing directory to the PROPATH. */
public static final int PATH_MOD_DYN_APPEND = 5;
/**
* The standard suffix appended to a source code filename to create a
* unique filename for storing hints.
*/
public static final String HINTS_SUFFIX = ".hints";
/** The standard name for the directory level hints file. */
public static final String DIR_HINTS = "directory" + HINTS_SUFFIX;
/** Best efforts attempt to detect the JVM word/pointer size, may be different from the system value. */
private static final int DEFAULT_ARCH;
/** The list of databases that must be connected before parsing. */
private Set<String> databases = null;
/**
* The list of databases that must be hidden before parsing. This list is set up for source files which
* might encounter naming conflicts when these databases are active. If a database occurs in both
* {@code databases} and {@code bannedDatabases}, the <b>latter</b> takes precedence.
*/
private Set<String> bannedDatabases = null;
/** Preprocessor arguments (positional or named) in name + value pairs. */
private Map<String, String> arguments = null;
static
{
String arch = System.getProperty("os.arch");
DEFAULT_ARCH = (arch != null && arch.contains("64")) ? 64 : 32;
}
/**
* The preprocessor should honor the backslash character as an escape
* sequence (this corresponds to how the preprocessor works on UNIX).
* This value defaults to <code>true</code>.
*/
private boolean unixEscapes = defaultUnixEscapes();
/** Sets the internal character used for preprocessor synchronization. */
private char marker = defaultMarker();
/** BATCH-MODE preprocessor built-in variable value. */
private boolean batch = defaultBatch();
/** OPSYS preprocessor built-in variable value. */
private String opsys = defaultOpsys();
/** WINDOW-SYSTEM preprocessor built-in variable value. */
private String winsys = defaultWinsys();
/** PROCESS-ARCHITECTURE preprocessor built-in variable value. */
private int arch = defaultArch();
/** Preprocessor debug flag. */
private boolean preprocDebug = defaultPreprocDebug();
/** WSOPTIONS E4GL preprocessor value. */
private String wsoptions = Configuration.getParameter(ELEM_WSOPTIONS);
/** E4GL preprocessor compatibility mode. */
private String mode = Configuration.getParameter(ELEM_MODE);
/** E4GL preprocessor output filename. */
private String output = null;
/**
* The set of all aliases and the corresponding database they reference.
* Used to create aliases in the schema dictionary before parsing starts.
*/
private ArrayList<String[]> aliases = null;
/**
* The set of all variables/widgets to be added to the global variable
* dictionary or widget dictionaries before parsing begins.
*/
private Variable[] variables = null;
/** Schema escape attribute, null means that attribute is not set. */
private Boolean escaped = null;
/** Map of schema table hints, keyed by table name */
private Map<String, TableHints> tableHints = null;
/** Path to a call graph root list XML file */
private String rootList = null;
/** Call graph included file spec */
private String includeSpec = null;
/** Stores the mapping of a unique key to a generic UAST hint. */
private final Map<String, Object> uast = new HashMap<>();
/** Encodes any PROPATH customization specified for this file. */
private int pathModType = PATH_MOD_NONE;
/** Encodes the text to use for PROPATH customization. */
private String pathModTxt = null;
/** Path to the source directory. */
private String sourceDir = null;
/** Path to the top-level source directory. */
private String basePath = null;
/** The source charset. */
private Charset sourceCharset = Charset.forName(Configuration.getParameter(SOURCE_CHARSET));
/** Unit test legacy type: valid values are "OEUnit" and "ABLUnit", default is "ABLUnit" */
private String unitTestType = defaultUnitTestType();
/**
* Get a UastHints instance for the file.
* @param sourceName
* The Progress 4GL source program's filename with which this
* hints file is associated.
* @return UastHints instance for the file.
*/
public static UastHints uastHints(String sourceName)
{
return HINTS.computeIfAbsent(sourceName, UastHints::new);
}
/**
* Constructs an instance, loads the associated hints file if it exists
* and parses the contents.
*
* @param sourceName
* The Progress 4GL source program's filename with which this
* hints file is associated.
*/
public UastHints(String sourceName)
{
try
{
File hints = new File(sourceName + HINTS_SUFFIX);
initPaths(hints);
Iterator<String> iter = walkDirectoryTree();
// load each directory hints file in turn from less nested to more nested
while (iter.hasNext())
{
loadHints(new File(iter.next()));
}
// second pass to parse the file-specific hints and overwrite or set config values
loadHints(hints);
}
catch (AstException excpt)
{
LOG.log(Level.WARNING,"", excpt);
}
}
/**
* Provides a standard way to get the default value for the unix-escapes value.
*
* @return <code>true</code> if UNIX escapes should be honored.
*/
public static boolean defaultUnixEscapes()
{
return Boolean.parseBoolean(Configuration.getParameter(ELEM_ESC, "true"));
}
/**
* Provides a standard way to get the default value for the marker value.
*
* @return The default character to use as a stream-synchronization character.
*/
public static char defaultMarker()
{
return (char) Integer.parseInt(Configuration.getParameter(ELEM_MARKER, "1"));
}
/**
* Provides a standard way to get the default value for the batch value.
*
* @return <code>true</code> if batch mode is active in the preprocessor.
*/
public static boolean defaultBatch()
{
return Boolean.parseBoolean(Configuration.getParameter(ELEM_BATCH, "false"));
}
/**
* Provides a standard way to get the default value for the preprocessor debug value.
*
* @return <code>true</code> if debug mode is active in the preprocessor.
*/
public static boolean defaultPreprocDebug()
{
return Boolean.parseBoolean(Configuration.getParameter("preproc-debug", "false"));
}
/**
* Provides a standard way to get the default value for the opsys value.
*
* @return The default operating system descriptor.
*/
public static String defaultOpsys()
{
return Configuration.getParameter(ELEM_OPSYS);
}
/**
* Provides a standard way to get the default value for the winsys value.
*
* @return The default windowing system descriptor.
*/
public static String defaultWinsys()
{
return Configuration.getParameter(ELEM_WINSYS);
}
/**
* Access the default value for the process architecture (word/pointer size in bits) for the current
* JVM. This may be different from the system architecture. For example, if the JVM is 32-bit then
* 32 will be returned, even if running on a 64-bit system.
*
* @return The default process architecture for this JVM.
*/
public static int defaultArch()
{
String arch = Configuration.getParameter(ELEM_ARCH, null);
return (arch != null) ? Integer.parseInt(arch) : DEFAULT_ARCH;
}
/**
* Provides a standard way to get the default value for the unit test
* legacy type: valid values are "OEUnit" and "ABLUnit", no default.
*
* @return The default unit test legacy type, may be {@code null}
*/
public static String defaultUnitTestType()
{
return Configuration.getParameter(ELEM_UNITTESTTYPE, "ABLUnit");
}
/**
* Get a list of all database names that must be connected before the ProgressParser can execute.
*
* @return Array of database names or {@code null} if only the default databases are required.
*/
public Set<String> getDatabases()
{
return databases;
}
/**
* Get a set of banned database names which must be 'unloaded' before processing the current source file.
*
* @return A set of database names or {@code null} if only the default databases are required.
*/
public Set<String> getBannedDatabases()
{
return bannedDatabases;
}
/**
* Get the list of aliases (and associated database names) that must be
* created in the schema dictionary.
*
* @return List of String[] or <code>null</code> if no aliases are
* required.
*/
public List<String[]> getAliases()
{
return aliases;
}
/**
* Get a list of all variables and widgets that must be added to the
* variable and widget dictionaries before the ProgressParser can execute.
*
* @return Array of variables or <code>null</code> if no variables need
* to be added.
*/
public Variable[] getVariables()
{
return variables;
}
/**
* Get the type code that specifies the file-specific customization that
* should be honored for the <code>PROPATH</code>. One of the well known
* (public) constants of this class will be returned.
*
* @return Type code.
*/
public int getPathModType()
{
return pathModType;
}
/**
* Get the text to use for <code>PROPATH</code> customization.
*
* @return Text string to replace, prepend or append to the
* <code>PROPATH</code> or if no replacement text was explicitly
* specified, then the parent directory of the associated
* source file will be returned.
*/
public String getPathModTxt()
{
return pathModTxt;
}
/**
* Get the parent directory of the source file.
*
* @return The parent directory of the associated source file.
*/
public String getSourceDir()
{
return sourceDir;
}
/**
* Get the runtime arguments that must be passed to the preprocessor or
* <code>null</code> if no runtime arguments exist.
*
* @return Preprocessor arguments or <code>null</code> if no arguments
* are required.
*/
public Map<String, String> getArguments()
{
return arguments;
}
/**
* Gets the preprocessor's escape sequence setting.
*
* @return <code>true</code> if the preprocessor should honor UNIX
* escape sequences (backslash).
*/
public boolean isUnixEscapes()
{
return unixEscapes;
}
/**
* Gets the character to use for the preprocessor's internal stream/state
* synchronization mechanism.
*
* @return The marker character to use or <code>'\001'</code> if no
* marker has been specified.
*/
public char getMarker()
{
return marker;
}
/**
* Gets the preprocessor's BATCH-MODE setting.
*
* @return <code>true</code> if the preprocessor should be in batch mode.
*/
public boolean isBatchMode()
{
return batch;
}
/**
* Get any override for the preprocessor's OPSYS setting.
*
* @return OPSYS override or <code>null</code> if not specified.
*/
public String getOpsys()
{
return opsys;
}
/**
* Get any override for the preprocessor's WINDOW-SYSTEM setting.
*
* @return WINDOW-SYSTEM override or <code>null</code> if not specified.
*/
public String getWindowSystem()
{
return winsys;
}
/**
* Get any override for the preprocessor's PROCESS-ARCHITECTURE setting.
*
* @return PROCESS-ARCHITECTURE override or <code>null</code> if not specified.
*/
public int getProcessArchitecture()
{
return arch;
}
/**
* Gets the preprocessor's debug flag.
*
* @return <code>true</code> if the preprocessor should be in debug mode.
*/
public boolean isPreprocDebug()
{
return preprocDebug;
}
/**
* Get any override for the E4GL preprocessor's WSOPTIONS setting.
*
* @return WSOPTIONS override or <code>null</code> if not specified.
*/
public String getWSOptions()
{
return wsoptions;
}
/**
* Get any override for the E4GL preprocessor's compatibility mode.
*
* @return Compatibility mode name or <code>null</code> if not specified.
*/
public String getE4GLMode()
{
return mode;
}
/**
* Get any override for the E4GL preprocessor's output file name.
*
* @return Output file name or <code>null</code> if not specified.
*/
public String getE4GLOutputFile()
{
return output;
}
/**
* Get unit test legacy type. Valid values are "OEUnit" and "ABLUnit", no default.
*
* @return see above
*/
public String getUnitTestType()
{
return unitTestType;
}
/**
* Get an unmodifiable map of schema table hints, keyed by table name.
*
* @return The map of table hints, or <code>null</code> if none have
* been defined in the current hints file.
*/
public Map<String, TableHints> getTableHints()
{
if (tableHints == null)
{
return null;
}
return Collections.unmodifiableMap(tableHints);
}
/**
* Get an unmodifiable map of schema table hints, keyed by table name.
*
* @return The map of table hints, or emptyMap if none have
* been defined in the current hints file.
*/
public Map<String, TableHints> getTableHintsOrEmpty()
{
return tableHints == null ? Collections.emptyMap():
Collections.unmodifiableMap(tableHints);
}
/**
* Get the path to a call-graph root list XML file.
*
* @return Path to a root list file or <code>null</code> if not defined in hints file.
*/
public String getRootList()
{
return rootList;
}
/**
* Get a file specification of included call-graph files.
*
* @return Include spec or <code>null</code> if not defined in hints file.
*/
public String getIncludeSpec()
{
return includeSpec;
}
/**
* 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)
{
return (String) uast.get(key);
}
/**
* 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.
*
* @return The named string array or <code>null</code>.
*/
public String[] getUastStringArray(String key)
{
return (String[]) uast.get(key);
}
/**
* 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)
{
return (Long) uast.get(key);
}
/**
* 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)
{
return (Double) uast.get(key);
}
/**
* 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)
{
return (Boolean) uast.get(key);
}
/**
* Get all the hint keys which have the specified prefix.
*
* @param prefix
* The key prefix to match.
*
* @return An array with the hint keys.
*/
public String[] getUastHints(String prefix)
{
List<String> hintKeys = new ArrayList<>();
for (String key : uast.keySet())
{
if (key.equals(prefix) || key.startsWith(prefix + "_"))
{
hintKeys.add(key);
}
}
return hintKeys.toArray(new String[0]);
}
/**
* Get the Java class name for a UAST hint using a uniquely named key,
* or <code>null</code> if no such hint exists.
*
* @param key
* Unique name for the hint, which is used as a key.
*
* @return The class name or <code>null</code>.
*/
public String getUastType(String key)
{
Object o = uast.get(key);
if (o == null)
return null;
return o.getClass().getName();
}
/**
* 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 it is set, otherwise "escape" hint for
* table or schema.
*/
public boolean isFieldEscaped(String table, String field)
{
if (tableHints != null && tableHints.containsKey(table))
{
TableHints tableHint = tableHints.get(table);
Boolean fieldEscaped = tableHint.isFieldEscaped(field);
if (fieldEscaped != null)
{
return fieldEscaped;
}
}
return isSchemaEscaped();
}
/**
* 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 (tableHints != null && tableHints.containsKey(table))
{
TableHints tableHint = tableHints.get(table);
Boolean tableEscaped = tableHint.isEscaped();
if (tableEscaped != null)
{
return tableEscaped;
}
}
return isSchemaEscaped();
}
/**
* 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 (tableHints != null )
{
TableHints tableHint = tableHints.get(table);
return tableHint != null && tableHint.isReadOnly();
}
return false;
}
/**
* 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 (tableHints != null )
{
TableHints tableHint = tableHints.get(table);
return tableHint != null && tableHint.isDirtyRead();
}
return false;
}
/**
* Checks if table is dirty-intra-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 (tableHints != null )
{
TableHints tableHint = tableHints.get(table);
return tableHint != null && tableHint.isDirtyIntraRead();
}
return false;
}
/**
* Report whether the database schema name for this schema should be escaped.
*
* @return <code>true</code> if schema name should be escaped, <code>false</code> if schema
* name should not be escaped, <code>null</code> if attribute "escape" is
* set not set for this schema.
*/
public boolean isSchemaEscaped()
{
return escaped != null && escaped;
}
/**
* Checks if a UAST hint exists under a uniquely named key.
*
* @param key
* Unique name for the hint, which is used as a key.
*
* @return <code>true</code> if a hint exists under that name.
*/
public boolean isUastHint(String key)
{
return (uast.get(key) != null);
}
/**
* Returns the source charset hint value. The returned value is <code>null</code> when
* no charset is explicitly defined in the hint file.
*
* @return see above.
*/
public Charset getSourceCharset()
{
return sourceCharset;
}
/**
* Returns the containing directory for the given file or "." if the
* file doesn't exist AND ALSO has no pathing included in the original
* name. If the calculated parent directory does not exist, then "."
* will be returned.
*
* @param file
* The file object to inspect.
*
* @return The containing directory or "." on failure.
*/
private static String containingDirectory(File file)
{
String result = ".";
File parent = file.getParentFile();
// parent will not be null except if file is both non-existent AND was
// created without any pathing info
if (parent != null && parent.exists())
{
try
{
// store the full path
result = parent.getCanonicalPath();
}
catch (IOException ioe)
{
// should not happen (famous last words)
}
}
return result;
}
/**
* Read XML hints file and create a list of all databases to be connected and one with databases to be
* ignored.
*
* @param root
* The root element of the XML document.
*/
private void readDatabases(Element root)
throws Exception
{
// create the list of databases
NodeList dbNodes = root.getElementsByTagName(ELEM_DATABASE);
int len = dbNodes.getLength();
for (int i = 0; i < len; i++)
{
Element elemDb = (Element) dbNodes.item(i);
String load = elemDb.getAttribute(ATTR_LOAD);
if (load != null && (load.equalsIgnoreCase("no") || load.equalsIgnoreCase("false")))
{
// only if LOAD is expressly set to false/no
if (bannedDatabases == null)
{
bannedDatabases = new HashSet<>();
}
bannedDatabases.add(elemDb.getAttribute(ATTR_NAME));
}
else
{
// the default action if not set or failed to parse as negative
if (databases == null)
{
databases = new HashSet<>();
}
databases.add(elemDb.getAttribute(ATTR_NAME));
}
}
}
/**
* Read XML hints file and create a list of string arrays (each with
* 2 elements) representing the set of all alias names and the associated
* database name for each one.
*
* @param root
* The root element of the XML document.
*/
private void readAliases(Element root)
throws Exception
{
// enable multiple passes
ArrayList<String[]> als = (aliases == null) ? new ArrayList<>() : aliases;
// create the list of aliases
NodeList aliasNodes = root.getElementsByTagName(ELEM_ALIAS);
int len = aliasNodes.getLength();
for (int i = 0; i < len; i++)
{
Element elemAlias = (Element) aliasNodes.item(i);
String name = elemAlias.getAttribute(ATTR_NAME);
String db = elemAlias.getAttribute(ATTR_DB);
// we always add to the global scope
als.add(new String[] {name, db});
}
// store our list of aliases if this is the first time
if (aliases == null && len > 0)
{
aliases = als;
}
}
/**
* Read XML hints file and create an array of <code>Variable</code>
* objects representing the set of all variables/widgets to add to the
* associated dictionaries. Note that all variables are always flagged
* to be loaded in the global namespace.
*
* @param root
* The root element of the XML document.
*/
private void readVariables(Element root)
throws Exception
{
ArrayList<Variable> vars = new ArrayList<>();
// enable multiple passes
if (variables != null)
{
vars.addAll(Arrays.asList(variables));
}
// create the list of variables
NodeList varNodes = root.getElementsByTagName(ELEM_VAR);
int len = varNodes.getLength();
for (int i = 0; i < len; i++)
{
Element elemVar = (Element) varNodes.item(i);
String name = elemVar.getAttribute(ATTR_NAME);
String s = elemVar.getAttribute(ATTR_TYPE);
String cls = elemVar.getAttribute(ATTR_CLASSNAME); // OK if null
int type = ProgressParser.lookupTokenType(s);
vars.add(new Variable(name, type, cls));
}
// store our list of variables
if (len > 0)
{
variables = vars.toArray(new Variable[0]);
}
}
/**
* Read XML hints file and initialize the path modification members based
* on the first <code>propath</code> node found.
*
* @param root
* The root element of the XML document.
*/
private void readPathMod(Element root)
throws Exception
{
// create the list of variables
NodeList ppNodes = root.getElementsByTagName(ELEM_PROPATH);
if (ppNodes.getLength() > 0)
{
Element elemVar = (Element) ppNodes.item(0);
String type = elemVar.getAttribute(ATTR_TYPE);
if ("replace".equalsIgnoreCase(type))
{
pathModType = PATH_MOD_REPLACE;
}
else if ("prepend".equalsIgnoreCase(type))
{
pathModType = PATH_MOD_PREPEND;
}
else if ("append".equalsIgnoreCase(type))
{
pathModType = PATH_MOD_APPEND;
}
else if ("dynamic-prepend".equalsIgnoreCase(type))
{
pathModType = PATH_MOD_DYN_PREPEND;
}
else if ("dynamic-append".equalsIgnoreCase(type))
{
pathModType = PATH_MOD_DYN_APPEND;
}
if (pathModType == PATH_MOD_REPLACE ||
pathModType == PATH_MOD_PREPEND ||
pathModType == PATH_MOD_APPEND)
{
// set hard coded/explicit override
pathModTxt = elemVar.getAttribute(ATTR_VAL);
// handle any substitutions like ${P2J_HOME}
pathModTxt = Configuration.getInstance().substituteParms(pathModTxt);
}
if (pathModType == PATH_MOD_DYN_PREPEND ||
pathModType == PATH_MOD_DYN_APPEND)
{
// set default propath customization text
pathModTxt = sourceDir;
}
}
}
/**
* Read XML hints file and create a string representing the encoded
* runtime preprocessor arguments and other preprocessor hints.
*
* @param root
* The root element of the XML document.
*/
private void readPreproc(Element root)
throws Exception
{
NodeList ppNodes = root.getElementsByTagName(ELEM_PREPROC);
if (ppNodes.getLength() == 1)
{
Element preproc = (Element) ppNodes.item(0);
NodeList argNodes = preproc.getElementsByTagName(ELEM_ARG);
NodeList escNodes = preproc.getElementsByTagName(ELEM_ESC);
NodeList markNodes = preproc.getElementsByTagName(ELEM_MARKER);
NodeList batchNodes = preproc.getElementsByTagName(ELEM_BATCH);
NodeList opsysNodes = preproc.getElementsByTagName(ELEM_OPSYS);
NodeList winsysNodes = preproc.getElementsByTagName(ELEM_WINSYS);
NodeList archNodes = preproc.getElementsByTagName(ELEM_ARCH);
NodeList debugNodes = preproc.getElementsByTagName(ELEM_DEBUG);
NodeList charsetNodes = preproc.getElementsByTagName(SOURCE_CHARSET);
int alen = argNodes.getLength();
if (alen > 0)
{
arguments = new LinkedHashMap<>();
for (int j = 0; j < alen; j++)
{
Element args = (Element) argNodes.item(j);
String val = args.getAttribute(ATTR_VAL);
if (val != null)
{
String name = null;
String pos = args.getAttribute(ATTR_POS);
if (pos != null)
{
// positional arg
// confirm this is a valid number (parsing issues will
// be propagated to the caller)
Integer.parseInt(pos);
// it is valid, just concatenate it into a valid name
name = "{" + pos;
}
else
{
// named arg
name = args.getAttribute(ATTR_NAME);
}
if (name != null)
{
arguments.put(name, val);
}
}
}
}
if (escNodes.getLength() == 1)
{
Element esc = (Element) escNodes.item(0);
unixEscapes = "true".equals(esc.getAttribute(ATTR_VAL));
}
if (markNodes.getLength() == 1)
{
Element mark = (Element) markNodes.item(0);
marker = (char) Integer.parseInt(mark.getAttribute(ATTR_VAL));
}
if (batchNodes.getLength() == 1)
{
Element bnode = (Element) batchNodes.item(0);
batch = Boolean.parseBoolean(bnode.getAttribute(ATTR_VAL));
}
if (opsysNodes.getLength() == 1)
{
Element onode = (Element) opsysNodes.item(0);
opsys = onode.getAttribute(ATTR_VAL);
}
if (winsysNodes.getLength() == 1)
{
Element wnode = (Element) winsysNodes.item(0);
winsys = wnode.getAttribute(ATTR_VAL);
}
if (archNodes.getLength() == 1)
{
Element wnode = (Element) archNodes.item(0);
arch = Integer.parseInt(wnode.getAttribute(ATTR_VAL));
}
if (debugNodes.getLength() == 1)
{
Element dnode = (Element) debugNodes.item(0);
preprocDebug = Boolean.parseBoolean(dnode.getAttribute(ATTR_VAL));
}
if (charsetNodes.getLength() == 1)
{
Element node = (Element) charsetNodes.item(0);
sourceCharset = Charset.forName(node.getAttribute(ATTR_VAL));
}
}
}
/**
* Process sub-hierarchy which contains schema-related hints, if any.
* An instance of {@link com.goldencode.p2j.schema.TableHints TableHints}
* is created for each <code>table</code> element parsed within the
* <code>schema</code> section of the document. These are mapped for
* later lookup by table name.
*
* @param root
* The root element of the XML document.
*/
private void readSchemaTables(Element root)
{
// get schema hints sub-hierarchy, if any
NodeList list = root.getElementsByTagName(ELEM_SCHEMA);
if (list.getLength() == 0)
{
return;
}
// there should not be more than one schema element
Element schema = (Element) list.item(0);
// read schema escape attribute
if (schema.hasAttribute(ATTR_ESCAPE))
{
escaped = "true".equalsIgnoreCase(schema.getAttribute(ATTR_ESCAPE));
}
// enable multiple passes
if (tableHints == null)
{
tableHints = new HashMap<>();
}
// create a TableHints instance for each table element found
list = schema.getElementsByTagName(ELEM_TABLE);
int len = list.getLength();
for (int i = 0; i < len; i++)
{
TableHints next = new TableHints((Element) list.item(i));
tableHints.put(next.getName(), next);
}
}
/**
* Read XML hints file and create a string representing the encoded
* runtime preprocessor arguments and other preprocessor hints.
*
* @param root
* The root element of the XML document.
*/
private void readCallGraph(Element root)
throws Exception
{
NodeList cgNodes = root.getElementsByTagName(ELEM_CALLGRAPH);
if (cgNodes.getLength() == 1)
{
Element callgraph = (Element) cgNodes.item(0);
NodeList rlNodes = callgraph.getElementsByTagName(ELEM_ROOTLIST);
NodeList isNodes = callgraph.getElementsByTagName(ELEM_INCLUDE_SPEC);
if (rlNodes.getLength() == 1)
{
Element elem = (Element) rlNodes.item(0);
rootList = elem.getAttribute(ATTR_VAL);
}
if (isNodes.getLength() == 1)
{
Element elem = (Element) isNodes.item(0);
includeSpec = elem.getAttribute(ATTR_VAL);
}
}
}
/**
* Read XML hints file and create a string representing the encoded
* E4GL preprocessor hints.
*
* @param root
* The root element of the XML document.
*/
private void readE4GL(Element root)
throws Exception
{
NodeList e4glNodes = root.getElementsByTagName(ELEM_E4GL);
if (e4glNodes.getLength() == 1)
{
Element e4gl = (Element) e4glNodes.item(0);
NodeList optNodes = e4gl.getElementsByTagName(ELEM_WSOPTIONS);
NodeList modeNodes = e4gl.getElementsByTagName(ELEM_MODE);
NodeList outNodes = e4gl.getElementsByTagName(ELEM_OUTPUT);
if (optNodes.getLength() == 1)
{
Element opt = (Element) optNodes.item(0);
String wsoptions = opt.getAttribute(ATTR_VAL);
if (wsoptions != null)
{
this.wsoptions = wsoptions;
}
}
if (modeNodes.getLength() == 1)
{
Element mod = (Element) modeNodes.item(0);
String mode = mod.getAttribute(ATTR_VAL);
if (mode != null)
{
this.mode = mode;
}
}
if (outNodes.getLength() == 1)
{
Element out = (Element) outNodes.item(0);
String output = out.getAttribute(ATTR_VAL);
if (output != null)
{
this.output = output;
}
}
}
}
/**
* Read XML hints file and create a map of objects representing the set of
* all generic UAST hints, each element accessed by a unique name.
*
* @param root
* The root element of the XML document.
*/
private void readUastGeneric(Element root)
throws Exception
{
// create the list of generic UAST hints
NodeList nodes = root.getElementsByTagName(ELEM_UAST);
int len = nodes.getLength();
for (int i = 0; i < len; i++)
{
Element elem = (Element) nodes.item(i);
String key = elem.getAttribute(ATTR_NAME);
String cls = elem.getAttribute(ATTR_DTYPE);
// process an array of strings if we have one
if ("String[]".equalsIgnoreCase(cls))
{
NodeList vals = elem.getElementsByTagName(ELEM_ARRAY);
int vlen = vals.getLength();
ArrayList<String> strings = new ArrayList<>();
for (int j = 0; j < vlen; j++)
{
Element e = (Element) vals.item(j);
strings.add(e.getAttribute(ATTR_VAL));
}
// allow zero-length arrays.
uast.put(key, strings.toArray(new String[0]));
}
else
{
String val = elem.getAttribute(ATTR_VAL);
// instantiate the Object wrapper for the annotation based on the
// stored class name, then store the annotation in the AST
if ("String".equalsIgnoreCase(cls))
{
uast.put(key, val);
}
else if ("Long".equalsIgnoreCase(cls))
{
uast.put(key, Long.valueOf(val));
}
else if ("Boolean".equalsIgnoreCase(cls))
{
uast.put(key, Boolean.valueOf(val));
}
else if ("Double".equalsIgnoreCase(cls))
{
uast.put(key, Double.valueOf(val));
}
else
{
throw new AstException("Invalid UAST hint data type " + cls);
}
}
}
}
/**
* Load UAST hints from an XML document associated with the specified
* filename.
*
* @param hintsFile
* The hints file to read.
*
* @throws AstException
* An unknown or unexpected failure.
*/
private void loadHints(File hintsFile)
throws AstException
{
// is this a valid file?
if (!hintsFile.exists() || !hintsFile.isFile())
{
return;
}
try
{
Document dom = XmlHelper.parse(hintsFile);
Element root = dom.getDocumentElement();
readUnitTestType(root);
readDatabases(root);
readAliases(root);
readVariables(root);
readPreproc(root);
readPathMod(root);
readSchemaTables(root);
readCallGraph(root);
readE4GL(root);
readUastGeneric(root);
}
catch (Exception exc)
{
throw new AstException("Error loading hints file: " + hintsFile.getAbsolutePath(), exc);
}
}
/**
* Unit test type from XML hints file
*
* @param root
* The root element of the XML document.
*/
private void readUnitTestType(Element root)
throws Exception
{
final NodeList ppNodes = root.getElementsByTagName(ELEM_UNITTESTTYPE);
switch (ppNodes.getLength())
{
case 1:
{
final String attVal = ((Element) ppNodes.item(0)).getAttribute("type");
switch(attVal)
{
case "OEUnit":
case "ABLUnit":
unitTestType = attVal;
return;
default:
throw new AstException("Invalid value for " + ELEM_UNITTESTTYPE
+ ": only \"OEUnit\" and \"ABLUnit\" allowed.");
}
}
case 0:
return;
default:
throw new AstException("More than one element " + ELEM_UNITTESTTYPE);
}
}
/**
* Initialize the base path and source path for this file.
*
* @param hints
* The file-level hints.
*/
private void initPaths(File hints)
{
// find the top level source directory
basePath = Configuration.getParameter(ELEM_BASEPATH);
if (basePath != null)
{
basePath = containingDirectory(new File(basePath));
}
// find the most deeply nested containing directory
sourceDir = containingDirectory(hints);
}
/**
* Provides an iterator that allows one to walk downward through each
* containing path of the current file, from less nested to more nested.
* The first directory in the list will be the configured base path of
* the project and the last directory will be that which contains the
* current file.
*
* @return An iterator to the list of directories.
*/
private Iterator<String> walkDirectoryTree()
{
LinkedList<String> list = new LinkedList<>();
String srcDir = sourceDir;
// walk up the directory hierarchy adding each directory in turn
// (from most deeply nested to least deeply nested) into the list
while (srcDir != null && srcDir.length() > 0)
{
// calc the directory level hints filename and store it in the list
list.add(srcDir + File.separatorChar + DIR_HINTS);
// find the next most enclosing directory
int idx = srcDir.lastIndexOf(File.separatorChar);
if (idx < 0)
{
break;
}
srcDir = srcDir.substring(0, idx);
// end at the top-level directory
if (basePath != null && basePath.equals(srcDir))
{
break;
}
}
// the dirs are listed in most specific to least specific order so
// we return an iterator that walks backward
return list.descendingIterator();
}
}