Variable.java
/*
** Module : Variable.java
** Abstract : contains the data needed to define a Progress variable
**
** Copyright (c) 2005-2023, Golden Code Development Corporation.
**
** -#- -I- --Date-- --JPRM-- ----------------------------------Description----------------------------------
** 001 GES 20050210 @19752 First version, simple bean-like approach.
** 002 GES 20050330 @20563 Added support for a large number of options
** and helper methods to copy options and write
** them as annotations.
** 003 GES 20050401 @20597 Added support for a temporary index to allow
** the linking of a variable reference to its
** original definition at parse time. This is
** needed because project-unique IDs can't be
** easily generated at parse time, but are only
** added after the entire tree is complete.
** 004 GES 20050411 @20674 Massive rewrite to use ASTs to store variable
** options and to exchange ASTs with the schema
** dictionary and fully handle field and
** variable options. Most of the simple getter/
** setter methods are removed since they are
** not generally needed.
** 005 GES 20050414 @20724 Cleaned up initializer type conversions and
** added a special booleanManhandle() method
** to post-process a boolean initializer that
** is a user-defined string and thus cannot be
** properly lexed or parsed "in-line".
** 006 GES 20050501 @20956 Added annotations of the name and token type
** for the original variable definitions which
** eases processing at conversion time since
** the children don't have to be walked to get
** critical data.
** 007 GES 20070329 @32654 Fixed problem with some cases where extent
** options not needed to be converted into a
** non-extent form before the options were
** returned.
** 008 GES 20070629 @34347 Minor interface change.
** 009 GES 20070919 @35140 Added support for the fully qualified name
** of the class represented by a VAR_CLASS.
** 010 GES 20080702 @39057 Number initializers can be strings.
** 011 ECF 20080704 @39147 Minor memory optimization. Use Boolean
** constants instead of instantiating new
** Boolean objects.
** 012 GES 20090424 @41958 Import change.
** 013 GES 20090429 @42068 Match package and class name changes.
** 014 GES 20110826 Avoid NPE when there is an unspecified extent.
** Added annotations for 10.2x features.
** 015 GES 20111202 Handle boolean initializers for numeric types.
** 016 CA 20130209 Added dynamic EXTENT and EXTENT parameter support.
** 017 GES 20130307 Added cleanup of literal text before being converted to a
** double. This is needed because the 4GL literal form can be
** incompatible with the Java string form of a double.
** 018 VIG 20150108 Changed the method overrideIfNotPresent signature to
** default static.
** 019 GES 20150127 Double tildes in format strings from database fields.
** 020 CA 20150310 Extracted the logical initialization from string to logical.fromVar.
** 021 CA 20150519 Added support for the FONT option at the DEFINE VAR stmt.
** 022 EVL 20160224 Javadoc fixes to make compatible with Oracle Java 8 for Solaris 10.
** 023 GES 20160309 Fixed booleanManhandle() to allow processing of single quoted
** strings.
** 024 GES 20171017 Added hexadecimal literal support.
** 025 GES 20181106 Added a builtin flag and record this as an annotation when set.
** CA 20181121 Un-indexed .NET-style array variable references must act as
** System.Array.
** 026 CA 20181217 Added clsDef - the defining class for this variable, if applied.
** GES 20181218 Added annotation of default access modes.
** GES 20181228 Added annotation of extent for references (not just the definition).
** 027 CA 20190424 Added isStatic.
** 028 CA 20190508 Do not set a tempIdx annotation for builtin variables.
** CA 20190520 Added isExtent. Track the legacy signatures for FWD-generated
** methods, to solve converted name collisions with 4GL methods.
** 029 CA 20190530 Added support for CONTEXT-HELP-ID widget option.
** 030 SVL 20190614 Fix for int64 annotations.
** 031 RFB 20191219 Updated annotateOptions to handle INIT creating a determinate EXTENT
** which happens when EXTENT has no parameters, but there is an INIT.
** 032 RFB 20200214 Protect annotateClassRef from being called with no class name and
** updated some output messages to be more helpful for class pre-scanning.
** 033 CA 20200412 Added incremental conversion support.
** 034 CA 20200429 Legacy DEFINE ENUM conversion support.
** 035 CA 20200804 Added getExtent().
** 036 CA 20210318 Fixed conversion of extent() function/statement with builtin OO properties.
** GES 20210618 Track the .NET generic type (if present) and propagate it to references.
** Added a custom mechanism for caller-defined annotations using the
** AnnotationHelper interface.
** CA 20211112 XML-NODE-NAME must be annotated at the table field.
** TJD 20220504 Java 11 compatibility related minor changes
** GES 20220521 Added dataset signature annotation processing. Cleaned up some warnings.
** Removed unnecessary property handling. Added override flag.
** CA 20220531 Add the 'refid' annotation immediately to OO skeleton method calls, as these are
** not included in the post_parse_fixups processing of tempidx.
** CA 20220727 OO variable references must get the refid javaname, before checking the javaname
** annotation.
** CA 20220727 Improved memory management for parsing; cleanup is done in two phases:
** 1. after each legacy class file has finished parsing, the SchemaDictionary will
** keep only protected temp-tables (all private tables are removed, as these
** can't be reached from sub-classes; all permanent tables are removed, as each
** class has its own reference to the permanent tables). Also, the class def
** instance will reduce its own used memory, which is not required when parsing
** sub-classes.
** 2. after parsing of the entire file set is finished, any SchemaDictionary or
** other ASTs referenced by the ClassDefinition are released.
** CA 20220728 Fixed CA/20220727 - allow the Variable instance to survive parsing, but release
** the 'def' AST when full parsing finishes.
** CA 20220818 An abstract property can define only a getter or a setter, while the implementation
** can define both. For this reason, only the overridden accessor must have the
** @Override annotation.
** 037 CA 20230221 Javadoc fixes.
** 038 CA 20230325 For a DEF VAR v2 LIKE v1 EXTENT, if 'v1' has an INIT clause, do not copy it.
** 039 GBB 20230512 Logging methods replaced by CentralLogger/ConversionStatus.
** 040 OM 20230115 Replaced absolutePath(), relativePath(), upPath() and downPath() with faster
** versions, based on node types rather on string paths.
** 041 CA 20231004 Added a getter for the 'def' AST node (to be used in read-only mode).
*/
/*
** 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 com.goldencode.ast.*;
import com.goldencode.util.*;
import com.goldencode.p2j.util.*;
import com.goldencode.p2j.convert.*;
import java.util.logging.*;
/**
* A simple wrapper class to contain all related data about a variable. It
* can also be used to store data related to more simple widgets and frames.
* All options are stored as an AST and helper methods are provided to set,
* get, merge and annotate using these option ASTs (which come directly
* from the source trees or from the schema dictionary).
* <p>
* <b>One common option is unusual:</b>
* <p>
* <ul>
* <li> VIEW-AS phrase (this is a complex subtree, not just a simple
* value so it is stored in the AST BUT is cannot be stored into
* an annotation at this time)
* </ul>
* <p>
* All options that can be specified in a <code>DEFINE VARIABLE</code>
* statement are supported except:
* <p>
* <ul>
* <li> BGCOLOR
* <li> CONTEXT-HELP-ID
* <li> DCOLOR
* <li> DROP-TARGET
* <li> FONT
* <li> FGCOLOR
* <li> MOUSE-POINTER
* <li> PFCOLOR
* </ul>
* <p>
* All options that can be specified in <code>DEFINE PARAMETER</code> are
* supported (this is simply the parameter type itself).
* <p>
* All options that can be specified in a <code>format phrase</code> are
* supported except:
* <p>
* <ul>
* <li> AUTO-RETURN
* <li> BGCOLOR
* <li> BLANK
* <li> COLON
* <li> CONTEXT-HELP-ID
* <li> DCOLOR
* <li> DEBLANK
* <li> DISABLE-AUTO-ZAP
* <li> DROP-TARGET
* <li> FONT
* <li> FGCOLOR
* <li> MOUSE-POINTER
* <li> NO-TAB-STOP
* <li> PFCOLOR
* <li> TO
* <li> VALIDATE
* </ul>
* <p>
* All options that can be specified in a <code>message</code> statement are
* supported except <code>AUTO-RETURN</code>.
*
* @author GES
*/
public class Variable
implements ProgressParserTokenTypes,
TokenDataWrapper
{
/** Logger. */
private static final ConversionStatus LOG = ConversionStatus.get(Variable.class);
/** Possible enclosing quote characters. */
private final static char[] quotes = { '\"', '\'' };
/** The full text name of the variable. */
private String name = null;
/**
* Parser defined integer constant matching the token type of the
* variable.
*/
private int tokenType = -1;
/**
* Temporary index to link variable references with the original AST that
* is the definition. This should be set by the creator of the instance
* before adding this to the dictionary.
*/
private int tempIdx = -1;
/** Stores the AST that roots all options associated with the variable. */
private Aast def = null;
/** Fully qualified class name of the object instance referenced by this var. */
private String cls = null;
/** <code>true</code> if the variable is a built-in global var of the 4GL. */
private boolean builtin = false;
/** Flag indicating if this is a .NET array OO definition. */
private boolean dotNetArray = false;
/** Optional custom annotation processor. */
private AnnotationHelper helper = null;
/** Flag indicating the .NET generic type parameter for this instance, if any ({@code null} means none. */
private String genericType = null;
/** Flag to differentiate if the .NET generic type parameter is a primitive type. */
private boolean genericPrim = false;
/** Flag to differentiate variables from class properties. */
private boolean isProp = false;
/** Flag to differentiate instance and static members. */
private boolean isStatic = false;
/** Flag for override option (properties and events only). */
private boolean isOverride = false;
/** Flag for abstract option (properties and events only). */
private boolean isAbstract = false;
/** Access mode if not defined in a method. */
private int access = -1;
/** Extent value (defaults to 0, which is a scalar var). */
private int extent = 0;
/** The defining class for this variable, if this is class member. */
private ClassDefinition clsDef = null;
/** Flag specifying if this {@link #isProp PROPERTY} has defined a getter accessor. */
private boolean withGetter = false;
/** Flag specifying if this {@link #isProp PROPERTY} has defined a setter accessor. */
private boolean withSetter = false;
/**
* Constructs an instance with the required data to be contained.
*
* @param name
* The full name of the variable.
* @param tokenType
* The token type (as defined by the parser) of the variable.
* @param cls
* Fully qualified class name of the object instance represented
* or <code>null</code> if this variable does not represent an
* object instance.
*/
public Variable(String name, int tokenType, String cls)
throws IllegalArgumentException
{
if (name == null || tokenType == -1)
throw new IllegalArgumentException("Invalid name or type.");
this.name = name;
this.tokenType = tokenType;
this.cls = cls;
}
/**
* Make a copy of the given options, possibly making modifications on the
* copy. Specifically, if <code>extent</code> is greater than 0 and the
* source field is an extent field, the copy will represent a single
* element of the original extent field. The effect of this is to remove
* the KW_EXTENT child node, and to use only the initializer (if any)
* which corresponds with the element at index <code>extent</code>.
*
* @param source
* Options AST to be copied.
* @param extent
* One-based index of the element (for an extent field) after
* which the new field entry is to be modelled. This is only
* relevant for the initializer, so that we copy the correct
* initial value in the case of an initializer list for an extent
* field. Will be <code>0</code> if the "like" field is not an
* extent field, or if the entire extent field is the target of
* the like phrase, rather than only a specific element.
*
* @return Copy of the AST corresponding with the given name, with the
* possible modifications described above.
*/
public static Aast copyOptions(Aast source, int extent)
{
Aast copy = source.duplicateFresh();
// Handle special case where like target is a single element of an
// extent field.
if (extent > 0)
{
// we are changing some values, make sure no conflicting annotations
// exist
copy.clearAnnotations();
// Remove KW_EXTENT child node.
Aast kwExtent = copy.getImmediateChild(KW_EXTENT, null);
if (kwExtent != null)
{
kwExtent.remove();
}
// Normalize index.
extent--;
// Remove all KW_INIT children which are not the target we need
// to retain.
Aast kwInit = copy.getImmediateChild(KW_INIT, null);
if (kwInit != null && kwInit.getNumImmediateChildren() > 1)
{
// Due to a bug in Progress 9.1c (and possibly other versions),
// a temp-table field or var like some specific element of any
// extent field/var always picks up the initial value of the
// FIRST element in the extent, rather than the correct one. We
// emulate this behavior by always setting extent to 0. The more
// correct approach is commented out below, in case we need to do
// it right for other versions.
Aast initTgt = kwInit.getChildAt(0);
/*
---- BEGIN BETTER APPROACH ----
Aast initTgt = kwInit.getChildAt(extent);
if (initTgt == null)
{
int size = kwInit.getNumImmediateChildren();
// We have more than one initializer value, but not enough
// to match the index we were given, so use the last
// available.
initTgt = kwInit.getChildAt(size - 1);
}
----- END BETTER APPROACH -----
*/
// Remove all initializers from the KW_INIT node except the
// intended target.
Aast child = kwInit.getChildAt(0);
while (child != null)
{
Aast next = (Aast) child.getNextSibling();
if (child != initTgt)
{
child.remove();
}
child = next;
}
}
}
return copy;
}
/**
* Set the {@link #withGetter} flag.
*
* @param withGetter
* <code>true</code> if this {@link #isProp PROPERTY} has a getter defined.
*/
public void setWithGetter(boolean withGetter)
{
this.withGetter = withGetter;
}
/**
* Set the {@link #withSetter} flag.
*
* @param withSetter
* <code>true</code> if this {@link #isProp PROPERTY} has a setter defined.
*/
public void setWithSetter(boolean withSetter)
{
this.withSetter = withSetter;
}
/**
* Get the {@link #withGetter} flag.
*
* @return <code>true</code> if this {@link #isProp PROPERTY} has a getter defined.
*/
public boolean isWithGetter()
{
return withGetter;
}
/**
* Get the {@link #withSetter} flag.
*
* @return <code>true</code> if this {@link #isProp PROPERTY} has a setter defined.
*/
public boolean isWithSetter()
{
return withSetter;
}
/**
* Check if this is an extent 4GL variable.
*
* @return See above.
*/
public boolean isExtent()
{
return extent != -2 && extent != 0;
}
/**
* Sets the optional custom annotation processor for this instance.
*
* @param helper
* The custom annotation processor.
*/
public void setAnnotationHelper(AnnotationHelper helper)
{
this.helper = helper;
}
/**
* Check if this variable is defined as a .NET array.
*
* @return The {@link #dotNetArray} state.
*/
public boolean isDotNetArray()
{
return dotNetArray;
}
/**
* Set the flag for .NET array.
*
* @param dotNetArray
* The new value of the flag.
*/
public void setDotNetArray(boolean dotNetArray)
{
this.dotNetArray = dotNetArray;
}
/**
* Get .NET generic type parameter, if it exists.
*
* @return The {@link #genericType} state.
*/
public String getGenericType()
{
return genericType;
}
/**
* Set the .NET generic type parameter.
*
* @param genericType
* The .NET generic type parameter.
*/
public void setGenericType(String genericType)
{
this.genericType = genericType;
}
/**
* Returns the state of the flag to mark the generic type parameter as a
* primitive.
*
* @return <code>true</code> if this .NET generic type is primitive.
*/
public boolean isGenericPrimitive()
{
return genericPrim;
}
/**
* Sets the flag to mark the generic type parameter as a primitive.
*
* @param prim
* <code>true</code> if this .NET generic type is primitive.
*/
public void setGenericPrimitive(boolean prim)
{
this.genericPrim = prim;
}
/**
* Returns the full name of the variable.
*
* @return The full name of the variable.
*/
public String getName()
{
return name;
}
/**
* Sets the full name of the variable.
*
* @param name
* The full name of the variable.
*/
public void setName(String name)
{
this.name = name;
}
/**
* Returns the parser-defined token type of the variable.
*
* @return The token type of the variable.
*/
public int getTokenType()
{
return tokenType;
}
/**
* Sets the parser-defined token type of the variable.
*
* @param tokenType
* The token type of the variable.
*/
public void setTokenType(int tokenType)
{
this.tokenType = tokenType;
}
/**
* Returns the state of the built-in global variable flag.
*
* @return <code>true</code> if this is a built-in global variable.
*/
public boolean isBuiltin()
{
return builtin;
}
/**
* Sets the state of the built-in global variable flag.
*
* @param builtin
* <code>true</code> if this is a built-in global variable.
*/
public void setBuiltin(boolean builtin)
{
this.builtin = builtin;
}
/**
* Returns the property flag.
*
* @return <code>true</code> if this is a property.
*/
public boolean isProp()
{
return isProp;
}
/**
* Returns the override flag.
*
* @return <code>true</code> if this is an overridden property or event.
*/
public boolean isOverride()
{
return isOverride;
}
/**
* Returns the abstract flag.
*
* @return <code>true</code> if this is an abstract property or event.
*/
public boolean isAbstract()
{
return isAbstract;
}
/**
* Returns the static flag.
*
* @return <code>true</code> if this is a static member.
*/
public boolean isStatic()
{
return isStatic;
}
/**
* Sets the state of the override flag.
*
* @param isOverride
* <code>true</code> if this is an overridden property or event.
*/
public void setOverride(boolean isOverride)
{
this.isOverride = isOverride;
}
/**
* Sets the state of the abstract flag.
*
* @param isAbstract
* <code>true</code> if this is an abstract property or event.
*/
public void setAbstract(boolean isAbstract)
{
this.isAbstract = isAbstract;
}
/**
* Sets the state of the property flag.
*
* @param isProp
* <code>true</code> if this is a property.
*/
public void setProperty(boolean isProp)
{
this.isProp = isProp;
}
/**
* Sets the state of the static flag.
*
* @param isStatic
* <code>true</code> if this is a static member.
*/
public void setStatic(boolean isStatic)
{
this.isStatic = isStatic;
}
/**
* Set the {@link #access} mode.
*
* @param access
* The access mode.
*/
public void setAccess(int access)
{
this.access = access;
}
/**
* Set the {@link #extent} value.
*
* @param extent
* The extent value.
*/
public void setExtent(int extent)
{
this.extent = extent;
}
/**
* Get the {@link #extent} value.
*
* @return See above.
*/
public int getExtent()
{
return extent;
}
/**
* Returns the temporary index that identifies the original AST node that
* defined the variable.
*
* @return The temporary index.
*/
public int getTempIndex()
{
return tempIdx;
}
/**
* Sets the temporary index that identifies the original AST node that
* defined the variable.
*
* @param tempIdx
* The temporary index.
*/
public void setTempIndex(int tempIdx)
{
this.tempIdx = tempIdx;
}
/**
* Obtains the fully qualified name of the class of the object instance
* represented by this variable.
*
* @return The fully qualified name of the class of the object instance
* represented by this variable or <code>null</code> if this
* variable does not represent an object instance.
*/
public String getClassName()
{
return cls;
}
/**
* Sets the fully qualified name of the class of the object instance
* represented by this variable.
*
* @param cls
* The fully qualified name of the class of the object instance
* represented by this variable or <code>null</code> if this
* variable does not represent an object instance.
*/
public void setClassName(String cls)
{
this.cls = cls;
}
/**
* Get the defining class of this variable, if this is a class member.
*
* @return The {@link #clsDef}.
*/
public ClassDefinition getClassDefinition()
{
return clsDef;
}
/**
* Set the defining class of this variable.
*
* @param clsDef
* The defining class.
*/
public void setClassDefinition(ClassDefinition clsDef)
{
this.clsDef = clsDef;
}
/**
* Copies all options based on another object of this same type. The name,
* token type and temporary index are NOT overridden.
*
* @param like
* The instance from which to copy the options.
*/
public void copyDefaultOptions(Aast like)
{
if (like != null)
{
def = like.duplicate(null);
}
}
/**
* Sets all options based on the children of the passed-in AST, overriding
* any options previously copied based on a <code>LIKE</code> clause. The
* name, token type and temporary index are NOT overridden. <b>This merge
* process may create new AST nodes where none existed in the original
* source tree!</b>
* <p>
* The following options are handled:
* <p>
* <ul>
* <li> FORMAT
* <li> INITIAL
* <li> LABEL
* <li> COLUMN-LABEL
* <li> DECIMALS
* <li> EXTENT
* <li> CASE-SENSITIVE
* <li> NOT CASE-SENSITIVE
* <li> VIEW-AS
* </ul>
*
* @param ast
* The AST whose children define the options to be set.
*/
public void setOptions(Aast ast)
{
// are there any previous options to consider?
if (def == null)
{
// simple case: no previous like clause
def = ast;
return;
}
// merge old options into AST
overrideIfNotPresent(def, ast, KW_FORMAT);
if (!(def.downPath(KW_INIT) && ast.downPath(KW_EXTENT) && !ast.downPath(KW_EXTENT, NUM_LITERAL)))
{
// do not copy INIT option if this is a DEF VAR ... LIKE ... EXTENT.
overrideIfNotPresent(def, ast, KW_INIT);
}
overrideIfNotPresent(def, ast, KW_LABEL);
overrideIfNotPresent(def, ast, KW_COL_LAB);
overrideIfNotPresent(def, ast, KW_DECIMALS);
overrideIfNotPresent(def, ast, KW_EXTENT);
overrideIfNotPresent(def, ast, KW_VIEW_AS);
overrideIfNotPresent(def, ast, KW_SERIALZN);
// special processing for case-sensitive (only copy if neither option
// exists in the target AST)
if (ast.getImmediateChild(KW_NOT, null) == null &&
ast.getImmediateChild(KW_CASE_SEN, null) == null)
{
// try copying the NOT CASE-SENSITIVE
if (overrideIfNotPresent(def, ast, KW_NOT) == false)
{
// try copying the CASE-SENSITIVE
overrideIfNotPresent(def, ast, KW_CASE_SEN);
}
}
// save off the merged AST
def = ast;
}
/**
* Gets the AST that defines all options for this variable. These options
* may have been copied based on a <code>LIKE</code> clause or set via a
* source code definition. The name, token type and temporary index are
* NOT accessible via the returned AST.
* <p>
* The returned options may have been modified. Specifically, if
* <code>extent</code> is greater than 0 and the source variable is an
* extent variable, the result will represent a copy of a single element
* of the original extent variable. The effect of this is to remove the
* KW_EXTENT child node, and to use only the initializer (if any) which
* corresponds with the element at index <code>extent</code>.
*
* @param extent
* One-based index of the element (for an extent field) after
* which the new field entry is to be modelled. This is only
* relevant for the initializer, so that we copy the correct
* initial value in the case of an initializer list for an extent
* field. Will be <code>0</code> if the "like" field is not an
* extent field, or if the entire extent field is the target of
* the like phrase, rather than only a specific element.
*
* @return The AST whose children define the options for this variable.
*/
public Aast getOptions(int extent)
{
int len = 0;
Aast possible = def.getImmediateChild(KW_EXTENT, null);
if (possible != null)
{
len = -1; // dynamic-extent
if (possible.downPath(NUM_LITERAL))
{
len = Integer.parseInt(possible.getFirstChild().getText());
}
else if (possible.downPath(HEX_LITERAL))
{
String hex = possible.getFirstChild().getText();
decimal d = decimal.fromUnsignedHexLiteral(hex);
len = d.intValue();
}
}
// WARNING: should we only return a copy here? It doesn't seem to
// cause any trouble, but it seems dangerous. We could avoid
// all of this work and just let copyOptions() do the job...
if (extent == 0 || possible == null || len == 0)
return def;
return copyOptions(def, extent);
}
/**
* Stores all non-standard options as annotations to the AST passed as a parameter if this is the
* original definition, otherwise it stores a subset of values, most important of which is the cross-
* reference index to allow the original definition to be easily identified and accessed from a
* reference. If an option has the default value, it is not saved as an annotation.
* <p>
* At the end of the processing, if a custom annotation helper has been defined for this instance, it
* will be invoked.
*
* @param ast
* The AST to annotate.
* @param original
* <code>true</code> if this is to annotate the AST of the original definition,
* <code>false</code> if the AST is only a reference.
* @param inMethod
* <code>true</code> if this is being done from within a method definition.
* @param sym
* The current symbol resolver.
*/
public void annotateOptions(Aast ast, boolean original, boolean inMethod, SymbolResolver sym)
{
if (ast == null)
{
// nothing to do
return;
}
try
{
boolean isEnum = ast.getType() == ENUM_VALUE;
if (clsDef != null &&
clsDef.isBuiltIn() &&
def != null &&
def.getId() != null &&
def.getType() != ENUM_VALUE)
{
ast.putAnnotation("refid", def.getId());
}
else if (tempIdx != -1)
{
ast.putAnnotation("tempidx", Long.valueOf(tempIdx));
if (clsDef != null)
{
ast.putAnnotation("tempidx-file", clsDef.getFilename());
}
}
if (tokenType == VAR_CLASS || tokenType == FIELD_CLASS)
{
if (cls == null)
{
String msg = String.format("WARNING: annotateOptions cannot annotate Class Ref (class==null), " +
"ast=\n%s",
ast.dumpTree(true));
System.out.println(msg);
}
else
{
sym.annotateClassRef(cls, ast);
}
if (dotNetArray)
{
ast.putAnnotation("dotnet-array", true);
}
else
{
ast.removeAnnotation("dotnet-array");
}
if (genericType != null)
{
ast.putAnnotation("generic-type-parameter", genericType);
ast.putAnnotation("generic-type-is-primitive", genericPrim);
}
}
if (builtin)
{
ast.putAnnotation("builtin", true);
}
if (!original && clsDef != null)
{
ast.putAnnotation("found-in-cls", clsDef.getName());
ast.putAnnotation("found-in-source-file", clsDef.getFilename());
}
if (isProp)
{
ast.putAnnotation("cls-property", true);
ast.putAnnotation("cls-container", clsDef.getFilename());
}
if (original &&
!inMethod &&
ast.getType() != DEFINE_PARAMETER &&
ast.getType() != PARAMETER &&
clsDef != null)
{
// set default access mode (it will be overridden below if there is an
// explicit mode specified
access = isProp || isEnum || ast.getType() == DEFINE_EVENT ? KW_PUBLIC : KW_PRIVATE;
ast.putAnnotation("access-mode", Long.valueOf(access));
}
if (!original && ast.getType() == DATA_SET)
{
Aast ds = def.getImmediateChild(SYMBOL, null);
if (ds == null)
{
throw new RuntimeException(String.format("Malformed dataset definition %s.", def.dumpTree()));
}
String sig = (String) ds.getAnnotation("db_signature");
if (sig == null)
{
throw new RuntimeException(String.format("Missing 'db_signature' annotation in dataset " +
"definition %s.",
def.dumpTree()));
}
ast.putAnnotation("db_signature", sig);
}
if (!original && access != -1)
{
// access mode matters on the original definition, propagate it here
ast.putAnnotation("access-mode", Long.valueOf(access));
}
if (isStatic || isEnum)
{
ast.putAnnotation("static", true);
}
if (isOverride)
{
ast.putAnnotation("override", true);
}
if (isAbstract)
{
ast.putAnnotation("abstract", true);
}
if (extent != 0)
{
ast.putAnnotation("extent", Long.valueOf(extent));
}
if (isEnum)
{
def = ast;
ast.putAnnotation("name", name);
ast.putAnnotation("type", Long.valueOf(tokenType));
return;
}
// the following annotations are only stored in the original defining
// AST rather than in references
if (!original || def == null)
{
// nothing else to do
return;
}
// storing the name and type in annotations makes variable definitions
// easier to generate at conversion time
if (name != null)
{
ast.putAnnotation("name", name);
}
if (tokenType != -1)
{
ast.putAnnotation("type", Long.valueOf(tokenType));
}
Aast child = (Aast) def.getFirstChild();
Aast grandchild;
// Keep track of number of initializers, in case we need to create an annotation for
// extent, which may not have a constant. In such a case, the number of initializers
// become the size of the fixed extent.
// We also moved the ext scope to this level, so we can make a proper decision of the
// extent value outside of the options processing while loop.
boolean containsExtent = false;
int initcount = -1;
int ext = -1; // an unspecified extent is -1 (dynamic-extent)
while (child != null)
{
int ctype = child.getType();
switch (ctype)
{
case KW_SHARED:
ast.putAnnotation("shared", Boolean.TRUE );
boolean glob = def.getImmediateChild(KW_GLOBAL, null) != null;
boolean newed = def.getImmediateChild(KW_NEW, null) != null;
ast.putAnnotation("global",
glob ? Boolean.TRUE : Boolean.FALSE);
ast.putAnnotation("newed",
newed ? Boolean.TRUE : Boolean.FALSE);
break;
case KW_INPUT:
case KW_IN_OUT:
case KW_OUTPUT:
ast.putAnnotation("parmtype", Long.valueOf(ctype));
break;
case KW_PUBLIC:
case KW_PROTECTD:
case KW_PRIVATE:
// remember the explicit type
access = ctype;
ast.putAnnotation("access-mode", Long.valueOf(ctype));
break;
case KW_STATIC:
ast.putAnnotation("static", true);
break;
case KW_ABSTRACT:
ast.putAnnotation("abstract", true);
break;
case KW_OVERRIDE:
ast.putAnnotation("override", true);
break;
case KW_FORMAT:
grandchild = (Aast) child.getFirstChild();
String fmt = grandchild.getFirstChild().getText();
int dtype = def.getType();
// format strings that come from fields need extra processing
if (dtype > BEGIN_FIELDTYPES || dtype < END_FIELDTYPES)
{
// although format strings with tildes in 4GL source code need to be coded
// as two tildes so that one remains; BUT for fields this is NOT the case!
// if the format string comes from a permanent database schema the 4GL seems
// to implicitly double the tildes, we simulate that here
// TODO: should we exclude temp-table fields from this case?
fmt = fmt.replaceAll("~", "~~");
}
ast.putAnnotation("format", fmt);
break;
case KW_HELP:
ast.putAnnotation("help", child.getFirstChild().getText());
break;
case KW_INIT:
String name = "initial";
int j = 0;
grandchild = (Aast) child.getFirstChild();
while (grandchild != null)
{
int gtype = grandchild.getType();
String gtxt = grandchild.getText();
boolean unknown = (gtype == UNKNOWN_VAL);
switch (tokenType)
{
case VAR_DEC:
// this will handle NUM_LITERAL and DEC_LITERAL which
// are both valid initializers in Progress
Double d = null;
if (!unknown)
{
if (gtype == NUM_LITERAL || gtype == DEC_LITERAL)
{
boolean isInt = (gtype == NUM_LITERAL);
// cleanup the text to make it a valid input in Java
gtxt = NumberType.parseDecimal(gtxt, '.', ',', isInt);
// save our changes
grandchild.setText(gtxt);
d = Double.valueOf(gtxt);
}
else if (gtype == HEX_LITERAL)
{
d = decimal.fromUnsignedHexLiteral(gtxt).doubleValue();
}
else if (gtype == STRING)
{
gtxt = character.progressToJavaString(gtxt);
if (gtxt.length() == 0)
{
d = 0.0d;
}
else
{
d = Double.valueOf(gtxt);
}
}
else if (gtype == BOOL_TRUE || gtype == BOOL_FALSE)
{
// Progress silently accepts this initializer, we
// assume it is the same as 0 (no init), but this
// is untested at this time.
d = 0.0d;
}
}
ast.putAnnotation(name, d, j);
break;
case VAR_INT:
case VAR_INT64:
Long l = null;
if (!unknown)
{
if (gtype == DEC_LITERAL)
{
// Progress allows decimal initializers for
// integers, in this case we must round to the
// nearest integer...
Double dd = Double.valueOf(gtxt);
l = Long.valueOf(Math.round(dd.doubleValue()));
}
else if (gtype == NUM_LITERAL)
{
l = Long.valueOf(gtxt);
}
else if (gtype == HEX_LITERAL)
{
l = decimal.fromUnsignedHexLiteral(gtxt).longValue();
}
else if (gtype == STRING)
{
gtxt = character.progressToJavaString(gtxt);
if (gtxt.length() == 0)
{
l = 0l;
}
else
{
l = Long.valueOf(gtxt);
}
}
else if (gtype == BOOL_TRUE ||
gtype == BOOL_FALSE)
{
// Progress silently accepts this initializer,
// we assume it is the same as 0 (no init), but
// this is untested at this time.
l = 0l;
}
}
ast.putAnnotation(name, l, j);
break;
case VAR_LOGICAL:
// fixup stupid Progress "feature"
if (gtype == STRING)
{
try
{
booleanManhandle(def, grandchild);
}
catch (AstException ae)
{
LOG.log(Level.SEVERE, "", ae);
}
}
Boolean b = null;
if (!unknown)
{
b = (grandchild.getType() == BOOL_TRUE ?
Boolean.TRUE : Boolean.FALSE);
}
ast.putAnnotation(name, b, j);
break;
case VAR_CHAR:
case VAR_DATE:
String s = unknown ?
null :
new String(grandchild.getText());
ast.putAnnotation(name, s, j);
break;
}
j++;
grandchild = (Aast) grandchild.getNextSibling();
}
// Grab the number of initializers in case extent needs to become fixed
initcount = j;
break;
case KW_FONT:
long font = 0;
if (child.downPath(EXPRESSION, NUM_LITERAL))
{
font = Long.parseLong(child.getFirstChild().getFirstChild().getText());
}
else if (child.downPath(EXPRESSION, HEX_LITERAL))
{
String gtxt = child.getFirstChild().getFirstChild().getText();
font = decimal.fromUnsignedHexLiteral(gtxt).longValue();
}
ast.putAnnotation("font", font);
break;
case KW_CTX_H_ID:
long ctxHelpID = 0;
if (child.downPath(EXPRESSION, NUM_LITERAL))
{
ctxHelpID = Long.parseLong(child.getFirstChild().getFirstChild().getText());
}
else if (child.downPath(EXPRESSION, HEX_LITERAL))
{
String gtxt = child.getFirstChild().getFirstChild().getText();
font = decimal.fromUnsignedHexLiteral(gtxt).longValue();
}
ast.putAnnotation("context-help-id", ctxHelpID);
break;
case KW_LABEL:
int i = 0;
grandchild = (Aast) child.getFirstChild();
while (grandchild != null)
{
ast.putAnnotation("label", grandchild.getText(), i);
i++;
grandchild = (Aast) grandchild.getNextSibling();
}
break;
case KW_COL_LAB:
ast.putAnnotation("columnlabel", child.getFirstChild().getText());
break;
case KW_DECIMALS:
int prec = ExpressionConversionWorker.literalToInt(child.getChildAt(0));
if (tokenType == VAR_DEC && prec != 10)
{
ast.putAnnotation("decimals", Long.valueOf(prec));
}
break;
case KW_EXTENT:
Aast extChild = child.getChildAt(0);
if (extChild != null)
{
ext = ExpressionConversionWorker.literalToInt(extChild);
}
// Indicate there was an EXTENT keyword
containsExtent = true;
break;
case KW_NOT:
if (child.getFirstChild().getType() == KW_CASE_SEN)
{
ast.putAnnotation("casesens", Boolean.FALSE);
}
break;
case KW_CASE_SEN:
ast.putAnnotation("casesens", Boolean.TRUE);
break;
case KW_NO_UNDO:
ast.putAnnotation("undoable", Boolean.FALSE);
break;
case KW_SERIALAB:
ast.putAnnotation("serializable", Boolean.TRUE);
break;
case KW_SERIALZH:
ast.putAnnotation("serializable", Boolean.FALSE);
break;
case KW_SERIALZN:
ast.putAnnotation("serializename",
StringHelper.stripEnclosing(child.getFirstChild().getText(), quotes));
break;
case KW_XML_NNAM:
ast.putAnnotation("xmlnodename",
StringHelper.stripEnclosing(child.getFirstChild().getText(), quotes));
break;
}
child = (Aast) child.getNextSibling();
}
// Process any extent options here, since we have to hold off until we've checked
// for initilizers on a non-specified extent.
if (containsExtent)
{
ext = ((ext == -1) && (initcount != -1)) ? initcount : ext;
if (ext != 0)
{
this.extent = ext;
ast.putAnnotation("extent", Long.valueOf(ext));
}
}
}
finally
{
if (helper != null)
{
helper.annotate(this, ast, original, inMethod, sym);
}
}
}
/**
* This method provides a fixup for a ridiculous Progress feature of allowing arbitrary
* user-defined initializer strings. This processing can't be done during the initial
* parsing because the format string can appear <b>after</b> the initializer!
* <p>
* If no format string is available, then the default is made to be'yes/no' and
* 'true/false'. Otherwise the specified format is used. The core conversion logic
* is implemented in <code>logical.fromString()</code>.
* <p>
* This method will rewrite input AST's token type to be the proper boolean literal
* token type.
*
* @param grandparent
* This is the anscestor 2 levels up from the node being
* processed (bogusString). It must be passed in because
* this processing occurs during parsing, before the parent
* linkages have been fixed up in the tree. For this reason,
* it is not possible to use {@link Aast#getAncestor} or other
* more natural approaches to obtain the grandparent.
* @param bogusString
* The AST node which is a <code>STRING</code> but should be a
* <code>BOOL_TRUE</code> or <code>BOOL_FALSE</code>.
*
* @throws AstException
* When no match is found.
*/
public void booleanManhandle(Aast grandparent, Aast bogusString)
throws AstException
{
Aast format = grandparent.getImmediateChild(KW_FORMAT, null);
String target = bogusString.getText().toLowerCase();
String fmt = null;
if (format != null)
{
fmt = format.getFirstChild().getFirstChild().getText().toLowerCase();
fmt = StringHelper.stripEnclosing(fmt, quotes);
}
target = StringHelper.stripEnclosing(target, quotes);
Boolean res = logical.fromString(target, fmt);
if (res != null)
{
bogusString.setType(res ? BOOL_TRUE : BOOL_FALSE);
}
else
{
// this should never happen!
throw new AstException("VAR_LOGICAL of unknown format (text " +
bogusString.getText() +
", format string = " + fmt + ") ");
}
}
/**
* Render the instance as text.
*
* @return The details of this instance as text.
*/
public String toString()
{
return String.format("VARIABLE name %s; token type %d; extent %d; classname %s; AST %s",
name,
tokenType,
extent,
cls,
def == null ? "null" : def.dumpTree());
}
/**
* Method called to notify that the parse of the file has finished. This allows the instance to reduce the
* used memory.
*
* @param global
* Flag indicating that the parsing of all files has finished, and a global cleanup can be done
* for this instance.
*/
public void parseFinished(boolean global)
{
if (def == null)
{
return;
}
if (global)
{
def = null;
}
else
{
def = def.duplicate();
}
}
/**
* Get the {@link #def} ASt.
*
* @return See above. WARNING: do not alter this AST.
*/
Aast getDefinition()
{
return def;
}
/**
* Helper to copy a child subtree of a specific type from the source to
* the target AST if the target AST does not already have a child of that
* type.
*
* @param source
* The AST to copy from.
* @param target
* The AST to copy to.
* @param type
* The token type of the child tree to copy if it exists in the
* source tree and does not already exist in the target tree.
*
* @return <code>true</code> if a copy occurred.
*/
static boolean overrideIfNotPresent(Aast source, Aast target, int type)
{
Aast child = target.getImmediateChild(type, null);
if (child == null)
{
child = source.getImmediateChild(type, null);
if (child != null)
{
try
{
child.graftCopyTo(target, (int[]) null, false);
}
catch (AstException ae)
{
// unexpected failure
return false;
}
return true;
}
}
return false;
}
/**
* Provides a mechanism to specify custom annotations logic for a given variable instance.
*/
@FunctionalInterface
public static interface AnnotationHelper
{
/**
* Applys custom annotation processing to the given node.
*
* @param var
* The variable instance being used as the source of the annotations.
* @param ast
* The AST to annotate.
* @param original
* <code>true</code> if this is to annotate the AST of the original definition,
* <code>false</code> if the AST is only a reference.
* @param inMethod
* <code>true</code> if this is being done from within a method definition.
* @param sym
* The current symbol resolver.
*/
public void annotate(Variable var, Aast ast, boolean original, boolean inMethod, SymbolResolver sym);
}
}