CommonAstSupport.java
/*
** Module : CommonAstSupport.java
** Abstract : Common symbol resolution and function support for AST processing
**
** Copyright (c) 2005-2024, Golden Code Development Corporation.
**
** -#- -I- --Date-- --JPRM-- -----------------------------------Description-----------------------------------
** 001 ECF 20050228 @20100 Created initial version. Exposes basic AST
** properties and provides AST-related actions,
** including editing and annotation processing.
** 002 GES 20050228 @20116 Added an eval() user function to evaluate
** a runtime-specified string as an expression.
** 003 ECF 20050301 @20126 Fixed defect in ancestor user function. Was
** testing current AST first instead of its
** parent.
** 004 ECF 20050303 @20157 Added matches and sibling methods to Basic
** support class. The former is for regex string
** matches, the latter is to test for sibling
** AST token type matches.
** 005 ECF 20050303 @20166 Call Aast.iterator method directly instead of
** calling through AstWalker.
** 006 ECF 20050304 @20171 Added sprintf, printf, and printfln methods
** to Action. These add C-style printf-like
** capability to the base user function support.
** 007 GES 20050308 @20262 Added new methods for relativePath, various
** sibling oriented ops, additional versions of
** matches and other basic operations. Cleaned
** up some of the naming conventions and
** added/modified persistence actions.
** 008 ECF 20050310 @20267 Modified user function backing methods which
** accepted token names to accept token types
** instead. Token names are now resolved to
** literals during expression parsing.
** 009 GES 20050314 @20315 Added a path user variable that provides
** the absolute path (as a string) from the
** source AST to the root.
** 010 ECF 20050314 @20327 Added user functions to assign data to user
** variables.
** 011 GES 20050316 @20349 Added support for addParentToView().
** 012 GES 20050316 @20402 Fix bug in descendant() which incorrectly
** incremented the maxlevel variable to descend
** 1 level too far.
** 013 GES 20050317 @20403 Added support for addAncestorToView().
** 014 GES 20050317 @20404 Added support for user-generated exceptions.
** 015 ECF 20050316 @20414 Change invocation of Aast.iterator method to
** support signature change in Aast interface.
** 016 GES 20050318 @20428 Added tree dump support for the current
** source node and for an arbitrary ancestor
** of that node.
** 017 GES 20050321 @20451 Added escape processing for the format
** string on sprintf() and friends. This is
** needed because normal escape processing
** is done by javac and thus the formatting
** code has no concept of whether the input
** string is a javac (already converted) or
** non-javac string. In our case, we always
** know that it is a string that needs
** conversion, so we manually convert it
** before calling the sprintf function.
** 018 GES 20050328 @20512 Added ancestor annotation support (get/put
** and remove).
** 019 ECF 20050328 @20517 Replace references to getTarget method with
** getCopy method. This reflects a change in the
** superclass' interface.
** 020 GES 20050401 @20604 Added isNote() and getNoteType() functions.
** 021 GES 20050401 @20612 Added getId() user variable for the node ID.
** 022 ECF 20050405 @20646 Added upper and lower methods. These methods
** respectively uppercase and lowercase their
** input text.
** 023 GES 20050405 @20648 Added support for adding an AST to the view
** based on its ID as long as it is in the
** copy AST.
** 024 GES 20050411 @20675 Added support for array annotations.
** 025 GES 20050411 @20680 Added downPath() method.
** 026 GES 20050414 @20716 Added annotation support for ASTs accessed
** via their unique ID ("refid" or reference
** ID). This allows one AST which is a
** reference to another, to access, create,
** edit and delete annotations on the referenced
** node.
** 027 GES 20050427 @20910 Added substring, index and last index string
** functions. Also added copies function.
** 028 ECF 20050428 @20930 Added parseLong and parseDouble user
** functions.
** 029 GES 20050430 @20945 Added stateful streams approach to writing
** file output, which also allows one to query
** the number of bytes written at any given
** point in time. This new approach is more
** efficient than the stateless fprintf and it
** also has the advantage of allowing rules to
** detect if anything has been written at
** known points and thus process off of that
** knowledge.
** 030 GES 20050501 @20976 Added getNumImmediateChildren().
** 031 ECF 20050502 @20986 Changed implementation of eval and expr user
** function implementations to let the symbol
** resolver do the compilation and evaluation.
** Added a version of persist which accepts a
** terse flag.
** 032 ECF 20050526 @21301 Changes to support new expression engine
** implementation. Combined 3 worker library
** class definitions into 1 to accommodate the
** 1 library per worker limit imposed by the
** requirement to name each registered library.
** Added accessors for "this" and "copy" which
** represent the current source and copy ASTs
** set in the resolver, respectively. As rule
** sets are migrated, we should be able to
** remove large portions of the inner Library
** class.
** 033 ECF 20050527 @21319 Added create() user function. Instantiates
** an arbitrary object using reflection and
** variable argument list support.
** 034 GES 20050601 @21348 Made evalLib/execLib and eval/exec methods
** for processing named or user-defined
** expressions respectively. The only
** difference between eval and exec is that
** eval casts the return to a Boolean and
** exec returns an Object. The ...Lib versions
** handle named expressions. To use a named
** expression as an action, use the execLib
** form. This replaces the old expr() with
** evalLib().
** 035 GES 20050601 @21360 Added user-defined parameter lists to named
** expressions and exposed these via the
** evalLib/execLib user functions.
** 036 ECF 20050602 @21395 Changes to parent and type properties.
** Changed the semantic of getParent() to return
** the parent reference, rather than its token
** type. This gives more flexibility in terms of
** querying the parent AST directly. Changed the
** return type of getType() from long to int.
** 037 ECF @0050603 @21420 Added convenience user functions to create
** and initialize lists, sets, and maps:
** - createList
** - createSet
** - createMap
** 038 GES 20050613 @21476 Added next child index support.
** 039 GES 20050623 @21547 Fixed getRightSiblings() which returned a
** value that was always 1 too large.
** 040 ECF 20050629 @21597 Added user methods wrapText and removeQuotes.
** The former wraps a text string into multiple
** lines of a maximum width; the latter strips
** enclosing double quotes, if any, from a
** string.
** 041 ECF 20050708 @21670 Added trimLeading and trimTrailing user
** functions.
** 042 ECF 20050721 @21773 Added variants of create and createList.
** Added user functions to support reflection
** use from expressions.
** 043 ECF 20050805 @22013 Added "now" property as getNow() in callback
** library. Returns System.currentTimeMillis().
** 044 SIY 20050819 @22168 Added accessors for the configuration
** variables.
** 045 ECF 20050819 @22174 Added endCurrentWalk() user function to end
** the current AST walk. No further nodes in the
** current AST are processed after this method
** is executed. Processing continues with the
** next AST loaded by the pattern engine, if
** any.
** 046 ECF 20050908 @22660 New variant of parseLong() user function.
** This variant accepts a radix as well as the
** string representation of a long value.
** 047 ECF 20051017 @23055 Added brainwash user function. Clears IDs of
** AST, reregisters AST under a new filename,
** fixes up parent-child relationships.
** 048 GES 20051113 @23319 Moved ancestor/descendant function to the
** Aast implementation classes.
** 049 ECF 20051122 @23534 Added variant of throwException that accepts
** an AST. Appends a formatted descriptor of AST
** to error message.
** 050 GES 20051129 @23547 Changed one of the createList() signatures
** to createListFromObject() to resolve function
** name resolution ambiguity.
** 051 GES 20060129 @24134 Added AST ID to the formatted descriptor of
** an AST during throwException.
** 052 GES 20060201 @24184 Create any needed parent dirs when opening
** streams.
** 053 ECF 20060215 @24608 Added getAncestor user functions. Find an
** ancestor with a given annotation, optionally
** checking annotation value and token type as
** well.
** 054 ECF 20060225 @24831 Added getAncestorOfType user function. Finds
** first ancestor whose type matches any of a
** list of token types.
** 055 GES 20060302 @24847 Added upPath() which is similar to
** relativePath() except the current node's
** type is not included in the path.
** 056 ECF 20060410 @25454 Disambiguate getAncestor() variants. Reorder
** parameters so that expression engine does not
** encounter ambiguity among variants.
** 057 ECF 20060522 @26444 Added getAnalog() method. Retrieves the AST
** node from the copy tree which is at the same
** position relative to its parent as the
** current source node. This may differ from the
** current copy, if it has been displaced.
** 058 ECF 20070430 @33373 Fixed getAncestor(). Was not properly
** matching annotation value in the event the
** match value was an Integer.
** 059 GES 20070531 @33947 Moved useful helper code into Aast and the
** AnnotatedAst classes so that that code could
** be used in a wider variety of situations.
** 060 GES 20070723 @34644 Fix for case-insensitive matches().
** 061 ECF 20071015 @35559 Added parseLongQuiet() and parseDoubleQuiet()
** methods. These parse numeric strings without
** throwing NumberFormatException.
** 062 GES 20080326 @37823 Force fprintf() output to be UTF-8 encoded.
** 063 GES 20090422 @41897 Converted to standard string formatting.
** 064 GES 20090424 @41932 Import change.
** 065 GES 20090429 @42047 Match package and class name changes.
** 066 GES 20090515 @42210 Moved to AstManager from AstRegistry/AstPersister.
** 067 GES 20090518 @42386 Import change.
** 068 GES 20100630 Fixed latent bug which would save at the wrong
** node if persist() was called except when the root
** node was "this".
** 069 CA 20101105 Allow null AST parameter in formatMessage.
** 070 EVL 20121102 Adding helper functions to double backslashes in
** string under Windows environment. Adding helper
** functions to substitute the filename in Windows
** environment if it contains the Linux file
** separator. The char '/' replaced with '\' if
** the current OS is Windows.
** 071 CA 20130226 removeQuotes will remove single quotes too, if enclosed.
** 072 SVL 20130624 Added some helper function for use in rules pipelines.
** 073 CA 20131023 Added helper functions for use with conditional rule-set processing.
** 074 ECF 20130806 Added parseTree and loadTree user functions.
** 075 CA 20131023 The filename must always be normalized.
** 076 OM 20131021 Altered loadTree function to return the loaded AST.
** 077 HC 20140518 Added several functions to support decimal numbers.
** 078 ECF 20150328 Added variant of getAst which loads tree of specified ID if it is not
** found within the currently loaded trees. Added registerTree and
** getStoredObject methods to support certain runtime conversion cases.
** 079 OM 20150617 Added compileError() method that throws a CompileException.
** 080 CA 20150804 Added switchQuotes() method to prepare a string to have its quote
** char switched.
** 081 ECF 20150715 Replace StringBuffer with StringBuilder.
** 082 OM 20160608 Added trimTrailingSpaces() method to exposed library interface.
** 083 ECF 20160827 Cache classes for classForName to avoid more expensive calls to
** Class.forName.
** 084 OM 20160914 Added compareNodes() utility. Added breakpoint() support.
** 085 OM 20170118 Fixed compatibility for Solaris compiler.
** 086 GES 20170306 Added isAst().
** 087 CA 20170825 Added a mechanism to inject new ASTs into the source list, generated
** by the TRPL rules.
** 088 CA 20180507 Fixed getAst - if the extension is not .ast, .dict or .schema, add
** the .ast extension.
** 089 GES 20181023 Added addAllToView().
** 090 CA 20181213 Added getBuiltInClasses().
** HC 20181212 Added method overload getAncestorNoteBoolean and getLegacyName.
** 091 HC 20190224 Added equality check to compareNodes.
** 092 CA 20190516 Added getClassDefinition() and charAt() APIs.
** 093 CA 20191023 Added getGapMarking() APIs.
** 094 CA 20200412 Added incremental conversion support.
** 095 HC 20200324 Improved OCX conversion support - added support for infinite COM
** property/method chaining, removed the requirement for parser keyword
** tokens, added support for handle type resolution, plus oher fixes.
** 096 CA 20200508 OO class references are qualified only if 4GL qualifies them.
** EVL 20200601 Fix for NPE in getGapMarking() with certain conditions.
** 097 CA 20200718 ConversionData must not be used in runtime conversion mode.
** 098 HC 20200903 Improved method resolution when wrapping arguments passed to OCX-
** converted methods.
** CA 20200910 Fixed the datetime(-tz) field's initial value at import - it uses ISO8601
** format or NOW function.
** OM 20200925 Added support for generating denormalized extent fields in dynamic temp-table
** conversion.
** GES 20210715 Added loadConvertedClassByQname().
** HC 20211001 Implementation of i18n support.
** CA 20211012 Fixed generated WSDL and reworked SOAP support to rely on namespace, when
** resolving an operation.
** CA 20211214 The AST manager plugin must be context-local, for runtime conversion, as the
** InMemoryRegistryPlugin is not thread-safe.
** All worker's state must be context-local, for runtime conversion to work in
** multi-context mode, as the pattern workers are singletons.
** HC 20211215 Added support for implicit OCX arguments.
** ME 20211025 Added AST node as input to loadConvertedClass method.
** CA 20221010 For the 'create' TRPL API, cache the resolved constructor, based on the arguments
** signature. Refs #6813
** TJD 20220504 Upgrade do Java 11 minor changes
** GES 20220427 Modified to match code refactoring for OO method matching. Rework to eliminate
** ConvertedClassName. Instead the ClassDefinition handles all converted name
** processing.
** CA 20221010 For the 'create' TRPL API, cache the resolved constructor, based on the arguments
** signature. Refs #6813
** SVL 20230108 Improved performance by replacing some "for-each" loops with indexed "for" loops.
** 099 GBB 20230512 Logging methods replaced by CentralLogger/ConversionStatus.
** 100 OM 20230115 Replaced absolutePath(), relativePath(), upPath() and downPath() with faster
** versions, based on node types.
** 101 GBB 20230517 println and printfln levels elevated to INFO for rules' logs to not be
** filtered out.
** 102 GBB 20230613 println and printfln levels to FINE for runtime conversions. A new 'log' method
** added to support all logging levels.
** 103 CA 20231004 Fixed print-related methods to use INFO for conversion mode.
** 104 CA 20240324 Performance improvement for runtime annotations: are stored using an integer
** instead of string.
** 105 OM 20240416 Extracted UserGeneratedException in its own source file.
*/
/*
** 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.pattern;
import java.io.*;
import java.lang.reflect.*;
import java.math.*;
import java.util.*;
import java.util.concurrent.atomic.*;
import java.util.logging.*;
import com.goldencode.ast.*;
import com.goldencode.expr.*;
import com.goldencode.p2j.convert.*;
import com.goldencode.p2j.convert.db.*;
import com.goldencode.p2j.schema.*;
import com.goldencode.p2j.security.*;
import com.goldencode.p2j.uast.SymbolResolver;
import com.goldencode.p2j.util.*;
import com.goldencode.p2j.util.logging.*;
import com.goldencode.util.*;
import com.goldencode.p2j.cfg.*;
import com.goldencode.p2j.io.*;
import com.goldencode.p2j.oo.lang.*;
import com.goldencode.p2j.report.*;
import com.goldencode.p2j.uast.*;
/**
* Provides common symbol resolution and function support for AST processing.
* An instance of this class is loaded by default by the pattern engine, since
* the functionality provided is required by nearly every ruleset.
* <p>
* The implementations of variables and user functions are contained within a
* number of public, inner classes. They must be public in order for their
* members to be accessible via reflection (without policy file changes). They
* are registered just after construction with the {@link AstSymbolResolver}
* provided by the {@link PatternEngine}. They are accessed by instances of
* the {@link Rule} class via logical expressions at runtime.
*/
final class CommonAstSupport
extends AbstractPatternWorker
{
/** Logger. */
private static final ConversionStatus LOG = ConversionStatus.get(CommonAstSupport.class);
/** Context local work area. */
private static final ContextLocal<WorkArea> context = new ContextLocal<WorkArea>()
{
protected WorkArea initialValue()
{
return new WorkArea();
}
};
/** The engine which created this worker. */
private final PatternEngine engine;
/**
* Default constructor which defines the symbol libraries to be registered.
*
* @param engine
* The engine which created this worker.
*/
CommonAstSupport(PatternEngine engine)
{
super();
setLibrary(new Library());
this.engine = engine;
}
/**
* Resolve the numeric key for the specified annotation.
*
* @param key
* The string annotation.
*
* @return The numeric key for this annotation.
*/
public static int resolveRuntimeAnnotationKey(String key)
{
switch (key.toLowerCase())
{
case "class":
return Aast.RUNTIME_CLASS;
case "method":
return Aast.RUNTIME_METHOD;
case "value":
return Aast.RUNTIME_VALUE;
case "constructor":
return Aast.RUNTIME_CONSTRUCTOR;
}
throw new UnsupportedOperationException("Runtime annotation " + key + " is not supported.");
}
/**
* Hook to provide termination processing for a pattern worker.
*/
@Override
public void finish()
{
super.finish();
ConversionData.disconnect();
}
/**
* Try to match a constructor from the list of candidates, based upon a
* parameter signature.
*
* @param candidates
* An array of constructors from which to select a match.
* @param signature
* The parameter signature to be matched among the candidate
* constructors.
* @param strict
* <code>true</code> to conduct a strict match of primitive
* numeric arguments, else <code>false</code>. In a strict match,
* a primitive, numeric parameter type (or its wrapper class
* type) will match only the same primitive, numeric parameter
* type (or its wrapper class type). In a non-strict match, any
* primitive, numeric parameter type (or its wrapper class type)
* will match any other primitive, numeric parameter type (or
* its wrapper class type). The assumption in the non-strict case
* is that with enough wrapping/unwrapping and casting, any
* numeric type can be converted to any other numeric type
* (though information may be lost in narrowing conversions).
*
* @return The matching <code>Constructor</code> instance (if any), or
* <code>null</code> if no match was found.
*
* @throws IllegalArgumentException
* if more than one suitable matching constructor was detected
* (indicating ambiguity).
*/
private Constructor matchConstructor(Constructor[] candidates,
Class[] signature,
boolean strict)
{
Constructor ctor = null;
int len = candidates.length;
for (int i = 0; i < len; i++)
{
Constructor next = candidates[i];
Class[] required = next.getParameterTypes();
if (Function.matchSignature(required, signature, -1, strict))
{
if (ctor != null)
{
throw new IllegalArgumentException(
"Multiple constructors found for '" +
ctor.getDeclaringClass().getName() +
"' which match the supplied argument list");
}
ctor = next;
}
}
return ctor;
}
/**
* Implementation of basic symbol resolution and comparison-type user
* function services specific to AST processing. By convention, these
* functions are typically used in the conditional expression of a rule,
* though they are not limited to this use.
* <p>
* Implementation of common, AST related actions, such as persistence, AST
* node addition and removal, string and basic output services, adding
* nodes to the results view, etc.
* <p>
* Implementation of services to put, get, and remove annotations to/from
* ASTs.
*/
public class Library
{
/**
* Get the gap marking for the specified Java class, implementing a legacy OE class.
* <p>
* The gap is read from the {@link LegacyResourceSupport} annotation.
*
* @param classname
* The fully-qualified Java class name.
*
* @return The conversion and runtime support.
*/
public int getGapMarking(String classname)
{
try
{
Class<?> cls = Class.forName(classname);
// special case for _BaseObject_
if (cls == _BaseObject_.class)
{
cls = BaseObject.class;
}
// look for the annotation
LegacyResourceSupport lrs = cls.getAnnotation(LegacyResourceSupport.class);
if (lrs == null)
{
// no annotation, default to partial for conversion and non for runtime
return ReportConstants.CVT_LVL_PARTIAL | ReportConstants.RT_LVL_NONE;
}
return lrs.supportLvl();
}
catch (ClassNotFoundException e)
{
// class does not exist in FWD (even as stubs)
return ReportConstants.CVT_LVL_NONE | ReportConstants.RT_LVL_NONE;
}
}
/**
* Get the gap marking for the specified method in the given Java class, implementing a
* legacy OE method.
* <p>
* The gap is read from the {@link LegacyResourceSupport} annotation.
*
* @param classname
* The fully-qualified Java class name.
* @param method
* The Java method name.
* @param signature
* The legacy signature for the OE method. This must match with the Java method's
* {@link LegacySignature}.
*
* @return The conversion and runtime support.
*/
public int getGapMarking(String classname, String method, String signature)
{
try
{
Class<?> cls = Class.forName(classname);
// special case for _BaseObject_
if (cls == _BaseObject_.class)
{
cls = BaseObject.class;
}
// build the list of classes where to look
ArrayList<Class<?>> classes = new ArrayList<>();
if (cls.isInterface())
{
// look in this interface and all super-interfaces
Set<Class<?>> ifaces = new LinkedHashSet<>();
Utils.collectInterfaces(cls, ifaces);
classes.addAll(ifaces);
}
else
{
boolean isAbstract = Modifier.isAbstract(cls.getModifiers());
// look in all super-classes
Class<?> parent = cls;
while (parent != Object.class)
{
classes.add(parent);
if (isAbstract)
{
// if the source class is abstract, interfaces need to be considered, too
// (as the implementation may not be in the class hierarchy)
Set<Class<?>> ifaces = new LinkedHashSet<>();
Utils.collectInterfaces(parent, ifaces);
classes.addAll(ifaces);
}
parent = parent.getSuperclass();
}
}
for (int i = 0; i < classes.size(); i++)
{
Class<?> acls = classes.get(i);
// look for a method
Method[] mthds = acls.getDeclaredMethods();
for (Method mthd : mthds)
{
if (mthd.getName().equals(method))
{
LegacySignature ls = mthd.getAnnotation(LegacySignature.class);
if (ls != null)
{
String lsig = SymbolResolver.calculateLegacySignature(ls.name(), ls);
if (lsig != null && lsig.equalsIgnoreCase(signature))
{
LegacyResourceSupport lrs = mthd.getAnnotation(LegacyResourceSupport.class);
if (lrs == null)
{
return ReportConstants.CVT_LVL_FULL | ReportConstants.RT_LVL_STUB;
}
else
{
return lrs.supportLvl();
}
}
}
}
}
}
return ReportConstants.CVT_LVL_NONE | ReportConstants.RT_LVL_NONE;
}
catch (ClassNotFoundException e)
{
// class does not exist in FWD (even as stubs)
return ReportConstants.CVT_LVL_NONE | ReportConstants.RT_LVL_NONE;
}
}
/**
* Load the specified class definition. This will either create a {@link ClassDefinition}
* structure for a new class (to be later populated with members by the parser), or will restore
* an instance from an already-converted class.
*
* @param filename
* The file name for the legacy OO class.
* @param isNew
* Flag indicating that this is a new class (now being parsed), or a previously
* converted class.
* @param ast
* The abstract syntax tree node.
*
* @return The class definition.
*
* @throws ClassNotFoundException
* If the class can't be resolved or loaded.
*/
public ClassDefinition loadConvertedClass(String filename, boolean isNew, Aast ast)
throws ClassNotFoundException
{
if (filename == null)
{
throw new ClassNotFoundException("Can not find class for accessing " + ast.toStringVerbose());
}
return SymbolResolver.loadConvertedClass(filename, isNew);
}
/**
* Load the specified class definition.
*
* @param qname
* The legacy OO class qualified name.
*
* @return The found converted class.
*
* @throws ClassNotFoundException
* If the class can't be resolved or loaded.
*/
public ClassDefinition loadConvertedClassByQname(String qname)
throws ClassNotFoundException
{
return SymbolResolver.loadConvertedClassByQname(qname);
}
/**
* Get the parser's {@link ClassDefinition} for a given qualified class name, in legacy form.
*
* @param clsName
* The qualified legacy class name.
*
* @return The {@link ClassDefinition} instance, or <code>null</code> if it does not
* exist.
*/
public ClassDefinition loadClassDefinition(String clsName)
{
return SymbolResolver.loadClassDefinition(clsName);
}
/**
* End the current AST walk. This can be called at any time within any
* rules section. No further nodes in the current AST will be visited;
* however, any remaining rules and/or actions for the current node
* will still be processed. The pattern engine will continue processing
* with the next AST root node in its list, or complete its run if no
* further AST roots exist.
*
* @see AstSymbolResolver#setEndWalk
*/
public void endCurrentWalk()
{
getResolver().setEndWalk(true);
}
/**
* Get the class with the given name. For primitives, return the class
* which represents that type via the wrapper's <code>TYPE</code>
* member.
*
* @param name
* Name of a class, or the type name of a primitive.
*
* @return Class associated with the given name.
*
* @throws ClassNotFoundException
* if no class is found for <code>name</code>.
*/
public Class classForName(String name)
throws ClassNotFoundException
{
return classForName(name, false);
}
/**
* Get the class with the given name. For primitives, return the class
* which represents that type via the wrapper's <code>TYPE</code>
* member.
*
* @param name
* Name of a class, or the type name of a primitive.
* @param noThrow
* Set to {@code true} to never throw any exception.
*
* @return Class associated with the given name or {@code null}.
*
* @throws ClassNotFoundException
* if no class is found for <code>name</code> and {@code noThrow} is false.
*/
public Class classForName(String name, boolean noThrow)
throws ClassNotFoundException
{
WorkArea wa = context.get();
Class<?> clazz = wa.classCache.get(name);
if (clazz != null)
{
return clazz;
}
switch (name)
{
case "int":
clazz = Integer.TYPE;
break;
case "long":
clazz = Long.TYPE;
break;
case "boolean":
clazz = Boolean.TYPE;
break;
case "double":
clazz = Double.TYPE;
break;
case "float":
clazz = Float.TYPE;
break;
case "char":
clazz = Character.TYPE;
break;
case "byte":
clazz = Byte.TYPE;
break;
default:
try
{
clazz = Class.forName(name);
}
catch (ClassNotFoundException e)
{
if (!noThrow)
{
throw e;
}
}
break;
}
if (clazz != null)
{
wa.classCache.putIfAbsent(name, clazz);
}
return clazz;
}
/**
* Resolve a reflective method, given a class, method name, and
* parameter signature.
*
* @param cls
* Method's class.
* @param name
* Name of the method.
* @param paramTypes
* Array of classes representing the types of the parameters
* expected by the method.
*
* @return The method if it is found.
*
* @throws NoSuchMethodException
* if the method as described does not exist.
*/
public Method getMethod(Class cls, String name, Object[] paramTypes)
throws NoSuchMethodException
{
int len = paramTypes.length;
Class[] classes = new Class[len];
for (int i = 0; i < len; i++)
{
classes[i] = (Class) paramTypes[i];
}
return cls.getMethod(name, classes);
}
/**
* Helper function to invoke an arbitrary method through reflection.
*
* @param method
* Method to invoke.
* @param target
* Object upon which the method will be invoked.
* @param args
* Parameters passed to the method.
*
* @throws IllegalAccessException
* if method object enforces Java language access control and
* the underlying method is inaccessible.
* @throws InvocationTargetException
* if the underlying method throws an exception.
*/
public Object invoke(Method method, Object target, Object[] args)
throws IllegalAccessException,
InvocationTargetException
{
return method.invoke(target, args);
}
/**
* Tests whether the specified class declares member of the supplied name.
*
* @param cls
* Class to test.
* @param name
* Target member name.
*
* @return {@code true} if the class declares the supplied member, {@code false} otherwise.
*/
public boolean declaresMember(Class cls, String name)
{
for (Field m : cls.getDeclaredFields())
{
if (m.getName().equals(name))
{
return true;
}
}
for (Method m : cls.getDeclaredMethods())
{
if (m.getName().equals(name))
{
return true;
}
}
for (Class m : cls.getDeclaredClasses())
{
if (m.getSimpleName().equals(name))
{
return true;
}
}
for (Constructor m : cls.getDeclaredConstructors())
{
if (m.getName().equals(name))
{
return true;
}
}
return false;
}
/**
* Tests whether the specified class has tge method of the supplied name. The comparison is performed
* case-insensitively and the method returns the java method name as declared.
*
* @param cls
* Class to test.
* @param insensitiveName
* Case insensitive target method name.
*
* @return Returns the matched method name or {@code null} if not found.
*/
public String getMethodNoCase(Class cls, String insensitiveName)
{
for (Method m : cls.getMethods())
{
String mname = m.getName();
if (mname.equalsIgnoreCase(insensitiveName))
{
return mname;
}
}
return null;
}
/**
* Returns method parameter type on the specified index.
*
* @param cls
* The target class.
* @param methodName
* The target case sensitive method name.
* @param requiredParamNum
* Only match methods with the specified number of parameters.
* @param paramIdx
* The index of the parameter type to return.
* @param expectedParamType
* Expected paramater type. This helps in finding the correct method overload.
*
* @return Java class or {@code null} if the target method is not found or the target method
* doesn't declare the requested parameter.
*/
public Class getMethodParamType(Class cls,
String methodName,
int requiredParamNum,
int paramIdx,
Class expectedParamType)
{
Method[] methods = cls.getMethods();
ArrayList<Method> matchedMethods = new ArrayList<>();
for (Method m : methods)
{
if (m.getParameterTypes().length != requiredParamNum)
{
continue;
}
if (!m.getName().equals(methodName))
{
continue;
}
matchedMethods.add(m);
}
if (matchedMethods.isEmpty())
{
return null;
}
Class[] params = null;
if (expectedParamType != null)
{
// find exact match
for (int i = 0; i < matchedMethods.size(); i++)
{
Method m = matchedMethods.get(i);
params = m.getParameterTypes();
if (params[paramIdx].isAssignableFrom(expectedParamType))
{
return expectedParamType;
}
}
}
// find BaseDataType
for (int i = 0; i < matchedMethods.size(); i++)
{
Method m = matchedMethods.get(i);
params = m.getParameterTypes();
if (BaseDataType.class.isAssignableFrom(params[paramIdx]))
{
return params[paramIdx];
}
}
// last resort
return params[paramIdx];
}
/**
* Returns the resource type declared by the supplied method. Method may declare a resource type (the
* type of the value held by the returned handle) when its return type is {@link handle} by declaring
* the annotation {@link ResourceType} with the
* resource type defined.
*
* @param clazz
* The class to check.
* @param method
* The method to check.
*
* @return See above.
*/
public Class getResourceType(Class clazz, String method)
{
for (Method m : clazz.getMethods())
{
if (m.getName().equals(method))
{
ResourceType resType = m.getAnnotation(ResourceType.class);
if (resType != null)
{
return resType.type();
}
else
{
Class result = m.getReturnType();
return result == Void.class ? null : result;
}
}
}
return null;
}
/**
* Returns the simple class name of the supplied (fully qualified) class name.
*
* @param className
* Simple or fully qualified class name.
*
* @return Returns the simple class name.
*/
public String getClassSimpleName(String className)
{
if (!className.contains("."))
{
return className;
}
return className.substring(className.lastIndexOf('.') + 1);
}
/**
* Returns {@code true} if the supplied method declares {@link handle} as its return type.
*
* @param clazz
* The class to check.
* @param method
* The method to check.
*
* @return See above.
*/
public boolean returnsHandle(Class clazz, String method)
{
for (Method m : clazz.getMethods())
{
if (m.getName().equals(method))
{
return m.getReturnType().equals(handle.class);
}
}
return false;
}
/**
* Returns the legacy name of the supplied method on the supplied class. The method searches the
* supplied class for a method of the specified name (case insensitively) and returns the name as
* defined in {@link LegacyMethod} or {@link LegacyAttribute} annotations.
*
* @param cls
* Class where to search for the supplied method.
* @param methodName
* The class method name.
*
* @return legacy name or null if no match is found.
*/
public String getLegacyName(Class<?> cls, String methodName)
{
for (Method m : cls.getMethods())
{
if (m.getName().equalsIgnoreCase(methodName))
{
LegacyMethod lMethod = m.getAnnotation(LegacyMethod.class);
if (lMethod != null)
{
return lMethod.name();
}
LegacyAttribute lAttr = m.getAnnotation(LegacyAttribute.class);
if (lAttr != null)
{
return lAttr.name();
}
}
}
return null;
}
/**
* Create a new instance of the specified class, passing the provided
* arguments to its constructor.
*
* @param className
* Fully qualified name of the class to be instantiated.
* @param args
* Constructor arguments. Currently, <code>null</code>
* arguments within this array are not supported.
*
* @return Instance of the specified class.
*
* @throws ClassNotFoundException
* if a class named <code>className</code> cannot be found.
* @throws NullPointerException
* if any argument in <code>args</code> is <code>null</code>.
* @throws NoSuchMethodException
* if the signature defined by <code>args</code> matches no
* constructor in the specified class.
* @throws IllegalAccessException
* if the <code>Constructor</code> used enforces Java language
* access control and the underlying constructor is
* inaccessible.
* @throws IllegalArgumentException
* if the number of actual and formal parameters required by
* the underlying constructor differ; if an unwrapping
* conversion for primitive arguments fails; or if, after
* possible unwrapping, a parameter value cannot be converted
* to the corresponding formal parameter type by a method
* invocation conversion; if more than one suitable matching
* constructor was detected (indicating ambiguity).
* @throws InstantiationException
* if the class that declares the underlying constructor
* represents an abstract class.
* @throws InvocationTargetException
* if the underlying constructor throws an exception.
*/
public Object create(String className, Object[] args)
throws ReflectiveOperationException
{
// look up class
Class<?> target = classForName(className);
return create(target, args);
}
/**
* Create a new instance of the specified class, passing the provided
* arguments to its constructor.
*
* @param target
* Class to be instantiated.
* @param args
* Constructor arguments. Currently, <code>null</code>
* arguments within this array are not supported.
*
* @return Instance of the specified class.
*
* @throws NullPointerException
* if any argument in <code>args</code> is <code>null</code>.
* @throws NoSuchMethodException
* if the signature defined by <code>args</code> matches no
* constructor in the specified class.
* @throws IllegalAccessException
* if the <code>Constructor</code> used enforces Java language
* access control and the underlying constructor is
* inaccessible.
* @throws IllegalArgumentException
* if the number of actual and formal parameters required by
* the underlying constructor differ; if an unwrapping
* conversion for primitive arguments fails; or if, after
* possible unwrapping, a parameter value cannot be converted
* to the corresponding formal parameter type by a method
* invocation conversion; if more than one suitable matching
* constructor was detected (indicating ambiguity).
* @throws InstantiationException
* if the class that declares the underlying constructor
* represents an abstract class.
* @throws InvocationTargetException
* if the underlying constructor throws an exception.
*/
public Object create(Class<?> target, Object[] args)
throws ReflectiveOperationException
{
// determine provided signature from var-args array
int len = args.length;
Class<?>[] sig = new Class[len];
StringBuilder strSig = new StringBuilder();
boolean cache = true;
for (int i = 0; i < len; i++)
{
Object next = args[i];
if (next == null)
{
cache = false;
sig[i] = null;
}
else
{
sig[i] = next.getClass();
if (cache)
{
strSig.append(sig[i].getName()).append(',');
}
}
}
WorkArea wa = null;
Constructor<?> ctor = null;
Map<String, Constructor<?>> sigCache = null;
if (cache)
{
wa = context.get();
sigCache = wa.ctorCache.get(target);
if (sigCache != null)
{
ctor = sigCache.get(strSig.toString());
if (ctor != null)
{
return ctor.newInstance(args);
}
}
}
// scan each constructor in the target class to test for null matches and assignment
// compatible parameters
Constructor<?>[] ctors = target.getConstructors();
// test for a strict match with all available candidate constructors
ctor = matchConstructor(ctors, sig, true);
// if no match was found with the strict test, try a more lenient match test
if (ctor == null)
{
ctor = matchConstructor(ctors, sig, false);
}
// if still no match, we're out of luck
if (ctor == null)
{
throw new IllegalArgumentException(
"No constructor found for '" + target.getName() +
"' which matches the supplied argument list");
}
if (cache)
{
if (sigCache == null)
{
sigCache = new HashMap<>();
wa.ctorCache.put(target, sigCache);
}
sigCache.put(strSig.toString(), ctor);
}
// instantiate it
return ctor.newInstance(args);
}
/**
* Create a new <code>List</code> which contains the specified
* elements as its members.
*
* @param elements
* Elements of the newly created list object.
*
* @return List object which contains <code>elements</code>.
*/
public List<?> createList(Object[] elements)
{
return Arrays.asList(elements);
}
/**
* Split the given value into a list of {@link String}'s, using the given delimiter.
*
* @param val
* The value to split.
* @param delim
* The delimiter.
*/
public List<?> splitToList(String val, String delim)
{
return Arrays.asList(val.split(delim));
}
/**
* Generate a list from an array of objects. This variant is used when
* an array of objects as returned from a method invocation to a user
* expression must be converted into a list. The object array is not
* declared as such, to prevent the expression engine's symbol resolver
* from treating this as a user function which expects a variable
* argument list.
*
* @param array
* An object that represents an array of objects.
*
* @return List object which contains all elements within
* <code>array</code>.
*/
public List<?> createListFromObject(Object array)
{
return Arrays.asList((Object[]) array);
}
/**
* Create a linked hash set containing the specified elements. Because
* it is a set, duplicates (as determined by the <code>equals</code>
* method of the elements) are discarded. Iteration over the resulting
* set is guaranteed to be in the same order as unique elements were
* found in the <code>elements</code> array.
*
* @param elements
* Array whose unique elements define the contents of the
* newly created set.
*
* @return Linked hash set based upon <code>elements</code>.
*/
public LinkedHashSet<?> createSet(Object[] elements)
{
return new LinkedHashSet<>(Arrays.asList(elements));
}
/**
* Create a linked hash map whose keys are defined by <code>keys</code>
* and whose values are defined by <code>values</code>. The map is
* populated by iterating over <code>keys</code> and associating the
* value at the corresponding position in the <code>values</code> list.
* Thus, it is important that the iteration order of <code>keys</code>
* is <b>well-defined, and corresponds identically</b> with that of
* <code>values</code>.
*
* @param keys
* Set which defines the keys of the new map.
* @param values
* List which defines the values of the new map.
*
* @return New instance of map.
*
* @throws IllegalArgumentException
* if the sizes of the specified data structures are not
* equal.
*/
public <K, V> LinkedHashMap<K, V> createMap(Set<K> keys, List<V> values)
throws IllegalArgumentException
{
if (keys.size() != values.size())
{
throw new IllegalArgumentException(
"Set of map keys must be the same size as list of map values");
}
LinkedHashMap<K, V> map = new LinkedHashMap<>();
Iterator<K> iter = keys.iterator();
for (int i = 0; iter.hasNext(); i++)
{
map.put(iter.next(), values.get(i));
}
return map;
}
/**
* Creates new {@linkplain UUID} instance from the supplied data.
*
* @param high
* The most significant bits of the UUID
* @param low
* The least significant bits of the UUID
*
* @return New {@linkplain UUID} instance.
*/
public UUID createUUID(long high, long low)
{
return new UUID(high, low);
}
/**
* Get the current system time in milliseconds since the epoch.
*
* @return <code>System.currentTimeMillis()</code>
*/
public long getNow()
{
return System.currentTimeMillis();
}
/**
* Get the instance of the source AST currently set in the resolver.
*
* @return Current source AST.
*/
public Aast getThis()
{
return getSource();
}
/**
* Get the instance of the copy AST currently set in the resolver.
*
* @return Current copy AST.
*/
public Aast getCopy()
{
return CommonAstSupport.this.getCopy();
}
/**
* Get the AST node which is located in the copy tree at the analogous
* index position as the current source AST, with respect to their
* respective parents. Only one level of ancestor is considered.
* <p>
* This is meant to enable ascent rules which change the copy tree at
* the current level, such that the copy map is no longer a reliable
* mechanism to retrieve the proper analog to the current source node.
*
* @return AST from the copy tree, or <code>null</code> if no such AST
* exists.
*/
public Aast getAnalog()
{
Aast source = getSource();
Aast parent = source.getParent();
if (parent == null)
{
return null;
}
Long id = parent.getId();
int index = source.getIndexPos();
return getResolver().getAst(id).getChildAt(index);
}
/**
* Load the AST stored in the given file and return it. Do not register it with the AST
* resolver
*
* @param filename
* Name of file in which AST is stored.
*
* @return Aast read from the file.
*
* @throws IllegalArgumentException
* if there is any error loading <code>filename</code>.
*/
public Aast parseTree(String filename)
{
return AstManager.get().loadTree(filename);
}
/**
* Load the AST stored in the given file. This will register the AST and all of its
* descendants with the current AST resolver.
*
* @param filename
* Name of file in which AST is stored.
*
* @return Aast read from the file.
*
* @throws IllegalArgumentException
* if there is any error loading <code>filename</code>.
*/
public Aast loadTree(String filename)
{
return AstSymbolResolver.loadTree(getResolver(), filename);
}
/**
* Register the given AST and all its descendants with the AST resolver.
*
* @param ast
* Tree to register.
*/
public void registerTree(Aast ast)
{
getResolver().registerTree(ast);
}
/**
* Returns an AST instance referenced by its ID if it is loaded (and
* registered) in the current resolver.
*
* @param id
* The project-unique ID that specifies the AST.
*
* @return The AST or <code>null</code> if the associated AST either
* doesn't exist or is not loaded (and registered in the
* resolver).
*/
public Aast getAst(Long id)
{
return getAst(id, true);
}
/**
* Returns an AST instance referenced by its ID. If it is not loaded and registered with
* the current resolver, optionally load and register its enclosing tree.
*
* @param id
* The project-unique ID that specifies the AST.
* @param loadTree
* if <code>true</code>, the tree enclosing the AST with the given ID will be
* loaded and registered with the current resolver. <strong>Use this option with
* caution.</strong> ASTs can be large and this can consume a lot of memory.
*
* @return The AST or <code>null</code> if the associated AST doesn't exist.
*/
public Aast getAst(Long id, boolean loadTree)
{
return AstSymbolResolver.getAst(getResolver(), id, loadTree);
}
/**
* Remove the entire tree enclosing the specified AST from the resolver.
*
* @param id
* The project-unique ID that specifies the AST.
*/
public void removeAst(Long id)
{
Aast ast = getAst(id, false);
if (ast == null)
{
return;
}
getResolver().deregisterTree(ast);
}
/**
* Determines if the given file is a valid AST.
*
* @param filename
* The filename to check. It does not need to have the AST extension.
*
* @return <code>true</code> if there is an AST for the given file.
*/
public boolean isAst(String filename)
{
if (filename == null || filename.trim().length() == 0)
{
return false;
}
if (!filename.endsWith(AstGenerator.AST_POSTFIX))
{
filename = filename + AstGenerator.AST_POSTFIX;
}
return AstManager.get().isExistingAst(filename);
}
/**
* Get the token type of the current, source AST.
* <p>
* Corresponds with runtime variable <code>type</code>.
*
* @return Token type.
*/
public int getType()
{
return getSource().getType();
}
/**
* Set the token type of the current, copy AST.
*
* @return Always <code>true</code>.
*/
public boolean setType(long type)
{
getCopy().setType((int)type);
return true;
}
/**
* Get the token name of the current, source AST. The token name is
* the symbolic text representation of the integer token type.
* <p>
* Corresponds with runtime variable <code>name</code>.
*
* @return Token name.
*/
public String getName()
{
return getSource().getSymbolicTokenType();
}
/**
* Get the current, source AST's parent, if any.
* <p>
* Corresponds with runtime variable <code>parent</code>.
*
* @return Parent AST, or <code>null</code> if no source or parent.
*/
public Aast getParent()
{
Aast src = getSource();
return (src != null ? src.getParent() : null);
}
/**
* Get the token text of the current source AST.
* <p>
* Corresponds with runtime variable <code>text</code>.
*
* @return Token text.
*/
public String getText()
{
return getSource().getText();
}
/**
* Set the token text of the current copy AST.
*
* @return Always <code>true</code>.
*/
public boolean setText(String text)
{
getCopy().setText(text);
return true;
}
/**
* Get the project-unique AST node ID of the current, source AST.
* <p>
* Corresponds with runtime variable <code>id</code>.
*
* @return The project-unique node ID.
*/
public Long getId()
{
return getSource().getId();
}
/**
* Get the name of the source code file associated with the current
* source AST. The filename will be relative to <code>P2J_HOME</code>.
* <p>
* Corresponds with runtime variable <code>file</code>.
*
* @return Source code filename.
*/
public String getFile()
{
return Configuration.normalizeFilename(getSource().getFilename());
}
/**
* Returns the project relative filename of the copy AST persistence
* file, using the default persistence naming convention.
* <p>
* Corresponds with runtime variable <code>persistenceFilename</code>.
*
* @return The filename or <code>null</code> if the filename cannot
* be obtained.
*/
public String getPersistenceFilename()
{
String name = getCopy().getFilename();
if (name == null)
return null;
StringBuilder buf = new StringBuilder(name);
buf.append(AstGenerator.AST_POSTFIX);
return buf.toString();
}
/**
* Get the line number in the source code file at which the current
* source AST was found.
* <p>
* Corresponds with runtime variable <code>line</code>.
*
* @return Source code line number.
*/
public long getLine()
{
return getSource().getLine();
}
/**
* Get the column number in the source code file at which the current
* source AST was found.
* <p>
* Corresponds with runtime variable <code>column</code>.
*
* @return Source code column number.
*/
public long getColumn()
{
return getSource().getColumn();
}
/**
* Get the number of generations away from the root AST node this AST
* is located (in the source AST). A depth of 0 corresponds with the
* root node itself; 1 with the root's immediate children; and so on.
* <p>
* Corresponds with runtime variable <code>depth</code>.
*
* @return AST depth from root.
*/
public long getDepth()
{
return getSource().getDepth();
}
/**
* Accesses the full path from the current source AST to the root.
*
* @return Full path from the current AST to the root node, formatted
* as in {@link Aast#getPath}.
*/
public String getPath()
{
// get our full path to the root
return getSource().getPath();
}
/**
* Accesses the text representation (human readable 'parser output') of
* the tree rooted at the current AST node.
*
* @return Text dump of the tree structure and contents.
*/
public String getTreeDump()
{
return getSource().dumpTree();
}
/**
* Accesses the text representation (human readable 'parser output') of
* the tree rooted at the specified ancestor AST node, if such a node
* exists.
*
* @param level
* Number of generations up the tree to walk to obtain the
* ancestor. Set to <code>-1</code> to obtain the tree root,
* <code>0</code> is the current node, <code>1</code> is the
* parent, <code>2</code> is the 'grandparent' and so forth.
*
* @return Text dump of the tree structure and contents or
* <code>null</code> if no such node exists (if the
* <code>level</code> specified is greater than depth of the
* tree).
*/
public String getAncestorTreeDump(long level)
{
Aast ancestor = getSource().getAncestor((int)level);
if (ancestor == null)
return null;
return ancestor.dumpTree();
}
/**
* Determine whether <code>text</code> matches the specified regular
* expression.
*
* @param text
* String to test.
* @param regex
* Regular expression to apply against <code>text</code>.
*
* @return <code>true</code> for a match, else <code>false</code>.
*/
public boolean matches(String text, String regex)
{
return text.matches(regex);
}
/**
* Determine whether <code>text</code> matches the specified regular
* expression, taking the caller's case-sensitivity into account. If
* the case-insensitive mode is chosen, both the input text and the
* regular expression will be lowercased before match processing occurs.
*
* @param text
* String to test.
* @param regex
* Regular expression to apply against <code>text</code>.
* @param sensitive
* <code>true</code> if normal case-sensitivity should be
* used, <code>false</code> for case-insensitive matching.
*
* @return <code>true</code> for a match, else <code>false</code>.
*/
public boolean matches(String text, String regex, boolean sensitive)
{
String source = text;
String expr = regex;
if (!sensitive)
{
source = text.toLowerCase();
expr = regex.toLowerCase();
}
return source.matches(expr);
}
/**
* Implementation of the <code>evalLib</code> user function. Accepts
* the name of a named expression and executes that expression. The
* expression to which this function refers is assumed to return a
* <code>Boolean</code> result. This is a convenience form of
* <code>execLib</code> which eliminates the cast when used as a
* condition.
*
* @param name
* Name of an expression to execute.
* @param args
* The array of <code>Object</code> instances of the correct
* data type and in the correct order to match our parameter
* list. Each element of the array will be assigned into the
* matching parameter in our parameter list before the
* expression is executed.
*
* @return Boolean result of executing the named expression.
*
* @throws ExpressionException
* if any error occurs compiling the named expression before
* its first use.
*/
public Boolean evalLib(String name, Object[] args)
{
return (Boolean) execLib(name, args);
}
/**
* Implementation of the <code>execLib</code> user function. Accepts
* the name of a named expression and executes that expression.
*
* @param name
* Name of an expression to execute.
* @param args
* The array of <code>Object</code> instances of the correct
* data type and in the correct order to match our parameter
* list. Each element of the array will be assigned into the
* matching parameter in our parameter list before the
* expression is executed.
*
* @return Boolean result of executing the named expression.
*
* @throws ExpressionException
* if any error occurs compiling the named expression before
* its first use.
*/
public Object execLib(String name, Object[] args)
{
return getResolver().execute(name, args);
}
/**
* Implementation of the <code>eval</code> user function. Accepts the
* an expression, then compiles (if necessary) and executes that
* expression returning a <code>Boolean</code> result. This is a
* convenience form of <code>exec</code> which eliminates the cast
* when used as a condition.
*
* @param expr
* An expression to execute.
*
* @return Result of executing the passed expression.
*
* @throws ExpressionException
* if any error occurs compiling the expression before it's
* first use.
*/
public Boolean eval(String expr)
{
return (Boolean) exec(expr);
}
/**
* Implementation of the <code>exec</code> user function. Accepts the
* an expression, then compiles (if necessary) and executes that
* expression.
*
* @param expr
* An expression to execute.
*
* @return Result of executing the passed expression.
*
* @throws ExpressionException
* if any error occurs compiling the expression before it's
* first use.
*/
public Object exec(String expr)
{
return getResolver().execute(expr);
}
/**
* Implementation of the <code>root</code> user function. Determines
* whether the current AST is the root of the source tree.
*
* @return <code>true</code> if AST has no parent, else
* <code>false</code>.
*/
public boolean root()
{
return (getSource().getParent() == null);
}
/**
* Implementation of the <code>leaf</code> user function. Determines
* whether the current AST is a leaf node of the source tree.
*
* @return <code>true</code> if AST has no children, else
* <code>false</code>.
*/
public boolean leaf()
{
return (getSource().getFirstChild() == null);
}
/**
* Implementation of the <code>numImmediateChildren</code> user
* variable.
*
* @return The number of immediate children for the current AST node.
*/
public long getNumImmediateChildren()
{
return getSource().getNumImmediateChildren();
}
/**
* Implementation of the <code>nextChildIndex</code> user
* variable which denotes which child is about to be processed when
* processing a 'next-child' rule. Since child indices are 0-based,
* the next child index will always be greater than 0 during the
* processing of a 'next-child' rule, since the first time a
* 'next-child' rule is called, the previous child is 0 and the next
* child is 1 (so the return from this function will be 1).
*
* @return The next child to be processed or -1 if a 'next-child'
* rule is not in progress.
*/
public int getNextChildIndex()
{
return getResolver().getNextChildIndex();
}
/**
* Implementation of the <code>imaginary</code> user function.
* Determines whether the token underlying the current AST was created
* artificially, or is based upon a source code element.
*
* @return <code>true</code> if AST has a line:column position of 0:0,
* else <code>false</code>.
*/
public boolean imaginary()
{
Aast ast = getSource();
return (ast.getLine() == 0 && ast.getColumn() == 0);
}
/**
* Compares the current source AST's path from the root with the
* specified path.
*
* @param path
* Root-to-current path, formatted as described in
* {@link Aast#getPath}.
*
* @return <code>true</code> if specified path matches current source
* AST's path exactly, else <code>false</code>.
*/
@Deprecated
public boolean absolutePath(String path)
{
return getSource().absolutePath(path);
}
/**
* Compares the current source AST's path from the root with the specified path.
* <p>NOTE:<br>
* This method honors the TRPL's "Automatic Null Checking" and returns {@code false} if the first
* parameter is {@code null}.
*
* @param node
* The node to be checked.
* @param path
* Root-to-current path, formatted as described in {@link Aast#getPath}.
*
* @return {@code true} if specified path matches current source AST's path exactly.
*/
public boolean absolutePath(Aast node, Object[] path)
{
if (node == null)
{
return false;
}
return node.absolutePath(path);
}
/**
* Determines if the current source AST's path from the root <b>ends
* with</b> the specified path, on a case-insensitive basis.
* <p>
* The current source AST's type IS included in this path, which is
* different from the {@link #upPath} which compares paths upward
* from the parent node.
*
* @param path
* Partial path from the current AST up to an arbitrary
* number of parent nodes, formatted as described in
* {@link Aast#getPath}.
*
* @return <code>true</code> if specified path matches the end of the
* current source AST's path exactly, else <code>false</code>.
*/
@Deprecated
public boolean relativePath(String path)
{
return getSource().relativePath(path);
}
/**
* Determines if the node's path from the root <b>ends with</b> the specified path.
* <p>
* The node's type IS included in this path, which is different from the {@link #upPath} which compares
* paths upward starting at the parent node.
* <p>NOTE:<br>
* This method honors the TRPL's "Automatic Null Checking" and returns {@code false} if the first
* parameter is {@code null}.
*
* @param node
* The node to be checked.
* @param path
* Partial path from the node up to an arbitrary number of parent nodes. Must contain only
* {@code int} / {@code Integer} values.
*
* @return {@code true} if specified path matches the end of the node's path exactly.
*/
public boolean relativePath(Aast node, Object[] path)
{
if (node == null)
{
return false;
}
return node.relativePath(path);
}
/**
* Determines if the node's path from the root <b>ends with</b> the specified path.
* <p>
* The node's type IS included in this path, which is different from the {@link #upPath} which compares
* paths upward starting at the parent node.
* <p>NOTE:<br>
* This method honors the TRPL's "Automatic Null Checking" and returns {@code false} if the first
* parameter is {@code null}.
*
* @param node
* The node to be checked.
* @param tok1
* The first token for this path match.
*
* @return {@code true} if specified path matches the end of the node's path exactly.
*/
public boolean relativePath(Aast node, int tok1)
{
if (node == null)
{
return false;
}
return node.relativePath(tok1);
}
/**
* Determines if the node's path from the root <b>ends with</b> the specified path.
* <p>
* The node's type IS included in this path, which is different from the {@link #upPath} which compares
* paths upward starting at the parent node.
* <p>NOTE:<br>
* This method honors the TRPL's "Automatic Null Checking" and returns {@code false} if the first
* parameter is {@code null}.
*
* @param node
* The node to be checked.
* @param tok1
* The first token for this path match.
* @param tok2
* The second token for this path match.
*
* @return {@code true} if specified path matches the end of the node's path exactly.
*/
public boolean relativePath(Aast node, int tok1, int tok2)
{
if (node == null)
{
return false;
}
return node.relativePath(tok1, tok2);
}
/**
* Determines if the node's path from the root <b>ends with</b> the specified path.
* <p>
* The node's type IS included in this path, which is different from the {@link #upPath} which compares
* paths upward starting at the parent node.
* <p>NOTE:<br>
* This method honors the TRPL's "Automatic Null Checking" and returns {@code false} if the first
* parameter is {@code null}.
*
* @param node
* The node to be checked.
* @param tok1
* The first token for this path match.
* @param tok2
* The second token for this path match.
* @param tok3
* The third token for this path match.
*
* @return {@code true} if specified path matches the end of the node's path exactly.
*/
public boolean relativePath(Aast node, int tok1, int tok2, int tok3)
{
if (node == null)
{
return false;
}
return node.relativePath(tok1, tok2, tok3);
}
/**
* Determines if the node's path from the root <b>ends with</b> the specified path.
* <p>
* The node's type IS included in this path, which is different from the {@link #upPath} which compares
* paths upward starting at the parent node.
* <p>NOTE:<br>
* This method honors the TRPL's "Automatic Null Checking" and returns {@code false} if the first
* parameter is {@code null}.
*
* @param node
* The node to be checked.
* @param tok1
* The first token for this path match.
* @param tok2
* The second token for this path match.
* @param tok3
* The third token for this path match.
* @param tok4
* The fourth token for this path match.
*
* @return {@code true} if specified path matches the end of the node's path exactly.
*/
public boolean relativePath(Aast node, int tok1, int tok2, int tok3, int tok4)
{
if (node == null)
{
return false;
}
return node.relativePath(tok1, tok2, tok3, tok4);
}
/**
* Determines if the current source AST's path upward <b>matches</b>
* the specified path, on a case-insensitive basis. The format of the
* 'segments' of the up path starts with the parent's type and each
* subsequent segment must be the type of the next parent up in the
* hierarchy.
* <p>
* The type of the current node is NOT included in the up path. This
* is the only difference with {@link #relativePath}.
*
* @param path
* Partial path from the current AST's parent upward to an
* arbitrary number of ancestor nodes, formatted as described
* in {@link Aast#getPath}.
*
* @return <code>true</code> if specified path exactly matches the
* upward path from the current source AST's parent, else
* <code>false</code>.
*/
@Deprecated
public boolean upPath(String path)
{
return getSource().upPath(path);
}
/**
* Determines if the node's path upward <b>matches</b> the specified path. The value of the 'segments' of
* the up path starts with the parent's type and each subsequent segment must be the type of the next
* parent up in the hierarchy.
* <p>
* The type of the node is NOT included in the up path. This is the only difference between this
* method and {@link #relativePath}.
* <p>NOTE:<br>
* This method honors the TRPL's "Automatic Null Checking" and returns {@code false} if the first
* parameter is {@code null}.
*
* @param node
* The node to be checked.
* @param path
* Partial path from the node's parent upward to an arbitrary number of ancestor nodes. Must
* contain only {@code int} / {@code Integer} values.
*
* @return {@code true} if specified path exactly matches the upward path from the node's parent.
*/
public boolean upPath(Aast node, Object[] path)
{
if (node == null)
{
return false;
}
return node.upPath(path);
}
/**
* Determines if the node's path upward <b>matches</b> the specified path. The value of the 'segments' of
* the up path starts with the parent's type and each subsequent segment must be the type of the next
* parent up in the hierarchy.
* <p>
* The type of the node is NOT included in the up path. This is the only difference between this
* method and {@link #relativePath}.
* <p>NOTE:<br>
* This method honors the TRPL's "Automatic Null Checking" and returns {@code false} if the first
* parameter is {@code null}.
*
* @param node
* The node to be checked.
* @param tok1
* The first token for this path match.
*
* @return {@code true} if specified path exactly matches the upward path from the node's parent.
*/
public boolean upPath(Aast node, int tok1)
{
if (node == null)
{
return false;
}
return node.upPath(tok1);
}
/**
* Determines if the node's path upward <b>matches</b> the specified path. The value of the 'segments' of
* the up path starts with the parent's type and each subsequent segment must be the type of the next
* parent up in the hierarchy.
* <p>
* The type of the node is NOT included in the up path. This is the only difference between this
* method and {@link #relativePath}.
* <p>NOTE:<br>
* This method honors the TRPL's "Automatic Null Checking" and returns {@code false} if the first
* parameter is {@code null}.
*
* @param node
* The node to be checked.
* @param tok1
* The first token for this path match.
* @param tok2
* The second token for this path match.
*
* @return {@code true} if specified path exactly matches the upward path from the node's parent.
*/
public boolean upPath(Aast node, int tok1, int tok2)
{
if (node == null)
{
return false;
}
return node.upPath(tok1, tok2);
}
/**
* Determines if the node's path upward <b>matches</b> the specified path. The value of the 'segments' of
* the up path starts with the parent's type and each subsequent segment must be the type of the next
* parent up in the hierarchy.
* <p>
* The type of the node is NOT included in the up path. This is the only difference between this
* method and {@link #relativePath}.
* <p>NOTE:<br>
* This method honors the TRPL's "Automatic Null Checking" and returns {@code false} if the first
* parameter is {@code null}.
*
* @param node
* The node to be checked.
* @param tok1
* The first token for this path match.
* @param tok2
* The second token for this path match.
* @param tok3
* The third token for this path match.
*
* @return {@code true} if specified path exactly matches the upward path from the node's parent.
*/
public boolean upPath(Aast node, int tok1, int tok2, int tok3)
{
if (node == null)
{
return false;
}
return node.upPath(tok1, tok2, tok3);
}
/**
* Determines if the node's path upward <b>matches</b> the specified path. The value of the 'segments' of
* the up path starts with the parent's type and each subsequent segment must be the type of the next
* parent up in the hierarchy.
* <p>
* The type of the node is NOT included in the up path. This is the only difference between this
* method and {@link #relativePath}.
* <p>NOTE:<br>
* This method honors the TRPL's "Automatic Null Checking" and returns {@code false} if the first
* parameter is {@code null}.
*
* @param node
* The node to be checked.
* @param tok1
* The first token for this path match.
* @param tok2
* The second token for this path match.
* @param tok3
* The third token for this path match.
* @param tok4
* The fourth token for this path match.
*
* @return {@code true} if specified path exactly matches the upward path from the node's parent.
*/
public boolean upPath(Aast node, int tok1, int tok2, int tok3, int tok4)
{
if (node == null)
{
return false;
}
return node.upPath(tok1, tok2, tok3, tok4);
}
/**
* Determines if the current source AST's path downward <b>matches</b>
* the specified path, on a case-insensitive basis. More than 1 down
* path may match this specification, if any such match exists then
* this function will return <code>true</code>. The format of the
* 'segments' of the down path starts with the immediate child's type
* and each subsequent segment must be the type of the next child
* down in the hierarchy. The type of the current node is NOT included
* in the down path.
*
* @param path
* Partial path from the current AST down to an arbitrary
* number of descendant nodes, formatted as described in
* {@link Aast#getPath}.
*
* @return <code>true</code> if specified path exactly matches the
* downward path from the current source AST, else
* <code>false</code>.
*/
@Deprecated
public boolean downPath(String path)
{
return getSource().downPath(path);
}
/**
* Determines if the node's path downward <b>matches</b> the specified path. More than 1 down path may
* match this specification since sibling nodes can have the same token type. If any such match exists
* then this function will return {@code true}.
* <p>
* The list of the 'segments' of the down path starts with the immediate child's type and each
* subsequent segment must be the type of the next child down in the hierarchy. The type of the current
* node is NOT included in the down path.
* <p>
* <p>NOTE:<br>
* This method honors the TRPL's "Automatic Null Checking" and returns {@code false} if the first
* parameter is {@code null}.
*
* @param node
* The node to be checked.
* @param path
* Partial path from the node down to an arbitrary number of descendant nodes. Must contain
* only {@code int} / {@code Integer} values.
*
* @return {@code true} if specified path exactly matches the downward path from the node.
*/
public boolean downPath(Aast node, Object[] path)
{
if (node == null)
{
return false;
}
return node.downPath(path);
}
/**
* Determines if the node's path downward <b>matches</b> the specified path. More than 1 down path may
* match this specification since sibling nodes can have the same token type. If any such match exists
* then this function will return {@code true}.
* <p>
* The list of the 'segments' of the down path starts with the immediate child's type and each
* subsequent segment must be the type of the next child down in the hierarchy. The type of the current
* node is NOT included in the down path.
* <p>
* <p>NOTE:<br>
* This method honors the TRPL's "Automatic Null Checking" and returns {@code false} if the first
* parameter is {@code null}.
*
* @param node
* The node to be checked.
* @param tok1
* The first token for this path match.
*
* @return {@code true} if specified path exactly matches the downward path from the node.
*/
public boolean downPath(Aast node, int tok1)
{
if (node == null)
{
return false;
}
return node.downPath(tok1);
}
/**
* Determines if the node's path downward <b>matches</b> the specified path. More than 1 down path may
* match this specification since sibling nodes can have the same token type. If any such match exists
* then this function will return {@code true}.
* <p>
* The list of the 'segments' of the down path starts with the immediate child's type and each
* subsequent segment must be the type of the next child down in the hierarchy. The type of the current
* node is NOT included in the down path.
* <p>
* <p>NOTE:<br>
* This method honors the TRPL's "Automatic Null Checking" and returns {@code false} if the first
* parameter is {@code null}.
*
* @param node
* The node to be checked.
* @param tok1
* The first token for this path match.
* @param tok2
* The second token for this path match.
*
* @return {@code true} if specified path exactly matches the downward path from the node.
*/
public boolean downPath(Aast node, int tok1, int tok2)
{
if (node == null)
{
return false;
}
return node.downPath(tok1, tok2);
}
/**
* Determines if the node's path downward <b>matches</b> the specified path. More than 1 down path may
* match this specification since sibling nodes can have the same token type. If any such match exists
* then this function will return {@code true}.
* <p>
* The list of the 'segments' of the down path starts with the immediate child's type and each
* subsequent segment must be the type of the next child down in the hierarchy. The type of the current
* node is NOT included in the down path.
* <p>
* <p>NOTE:<br>
* This method honors the TRPL's "Automatic Null Checking" and returns {@code false} if the first
* parameter is {@code null}.
*
* @param node
* The node to be checked.
* @param tok1
* The first token for this path match.
* @param tok2
* The second token for this path match.
* @param tok3
* The third token for this path match.
*
* @return {@code true} if specified path exactly matches the downward path from the node.
*/
public boolean downPath(Aast node, int tok1, int tok2, int tok3)
{
if (node == null)
{
return false;
}
return node.downPath(tok1, tok2, tok3);
}
/**
* Determines if the node's path downward <b>matches</b> the specified path. More than 1 down path may
* match this specification since sibling nodes can have the same token type. If any such match exists
* then this function will return {@code true}.
* <p>
* The list of the 'segments' of the down path starts with the immediate child's type and each
* subsequent segment must be the type of the next child down in the hierarchy. The type of the current
* node is NOT included in the down path.
* <p>
* <p>NOTE:<br>
* This method honors the TRPL's "Automatic Null Checking" and returns {@code false} if the first
* parameter is {@code null}.
*
* @param node
* The node to be checked.
* @param tok1
* The first token for this path match.
* @param tok2
* The second token for this path match.
* @param tok3
* The third token for this path match.
* @param tok4
* The fourth token for this path match.
*
* @return {@code true} if specified path exactly matches the downward path from the node.
*/
public boolean downPath(Aast node, int tok1, int tok2, int tok3, int tok4)
{
if (node == null)
{
return false;
}
return node.downPath(tok1, tok2, tok3, tok4);
}
/**
* Implementation of the <code>ancestor</code> user function. Indicates
* whether a token type exists within the ancestry of the current source
* AST.
*
* @param type
* Token type to be matched.
* @param maxLevels
* Maximum number of generations up the tree to search for an
* ancestor. Set to <code>-1</code> to search up to the tree
* root, <code>0</code> for the current node, <code>1</code>
* for the parent, <code>2</code> for the 'grandparent' and so
* forth.
*
* @return <code>true</code> if <code>token</code> was found as an
* ancestor's token type within <code>maxLevels</code>
* generations, else <code>false</code>.
*/
public boolean ancestor(long type, long maxLevels)
{
return getSource().ancestor((int) maxLevels, (int) type);
}
/**
* Implementation of the <code>descendant</code> user function.
* Searches the current source AST's descendants for a token type
* match.
*
* @param type
* Token type to be matched.
* @param maxLevels
* Number of generations to search before abandoning the test.
* Set to <code>0</code> to search exhaustively.
*
* @return <code>true</code> if <code>token</code> was found as an
* descendant's token type within <code>maxLevels</code>
* generations, else <code>false</code>.
*/
public boolean descendant(long type, long maxLevels)
{
return getSource().descendant((int) maxLevels, (int) type);
}
/**
* Compares the current source AST's immediate descendant (direct
* child) at a specific 0-based index for a match with a specific token
* type.
*
* @param type
* Token type to be matched.
* @param index
* 0-based index of the child to test.
*
* @return <code>true</code> if <code>token</code> was found in a
* specific child's token type, else <code>false</code> if
* no child exists at that index or if the child does not
* match the token type.
*/
public boolean childAt(long type, long index)
{
// start at the first child (index 0)
Aast child = (Aast) getSource().getFirstChild();
// find the specific child index requested
for (int i = 0; child != null && i < index; i++)
{
child = (Aast) child.getNextSibling();
}
// test if the child at that index exists and if the type matches
if (child != null && child.getType() == type)
{
return true;
}
return false;
}
/**
* Test whether any of the current AST's siblings matches the given
* token type.
*
* @param type
* Token type to match.
*
* @return <code>true</code> if any of the current AST's siblings
* match, else <code>false</code>.
*/
public boolean sibling(long type)
{
return getSource().sibling(type);
}
/**
* Returns the number of siblings to the current AST's left.
* <p>
* Corresponds with runtime variable <code>leftSiblings</code>.
*
* @return The number of left siblings or 0 if this AST is the first
* child or has no parent (i.e. it is the tree root).
*/
public long getLeftSiblings()
{
return getSource().getNumLeftSiblings();
}
/**
* Returns the number of siblings to the current AST's right.
* <p>
* Corresponds with runtime variable <code>rightSiblings</code>.
*
* @return The number of right siblings or 0 if this AST is the last
* child or has no parent (i.e. it is the tree root).
*/
public long getRightSiblings()
{
return getSource().getNumRightSiblings();
}
/**
* Returns the 0-based child index of the current AST.
* <p>
* Corresponds with runtime variable <code>childIndex</code>.
*
* @return The number of left siblings or 0 if this AST is the
* first child or has no parent (i.e. it is the tree root).
*/
public long getChildIndex()
{
return getLeftSiblings();
}
/**
* Add the current copy AST to the filtered view of ASTs which can
* be pipelined to the next ruleset.
*
* @return <code>true</code>
*/
public boolean addToView()
{
getResolver().addToView();
return true;
}
/**
* Add the current copy AST to the filtered view of ASTs which can
* be pipelined to the next ruleset.
*
* @param list
* The nodes to add to the view.
*
* @return <code>true</code>
*/
public boolean addAllToView(List<Aast> list)
{
getResolver().addAllToView(list);
return true;
}
/**
* Add the AST specified by the passed ID to the filtered view of ASTs
* which can be pipelined to the next ruleset. The AST will only be
* added if it exists in the current copy of the source tree.
*
* @return <code>true</code> if the AST is valid (and thus was added),
* <code>false</code> otherwise.
*/
public boolean addToView(Long id)
{
try
{
AstManager mgr = AstManager.get();
long treeId = mgr.getTreeId(getSource().getId());
long astId = mgr.getTreeId(id);
if (treeId != astId)
{
return false;
}
}
catch (AstException ae)
{
// some problem
return false;
}
return getResolver().addToView(id);
}
/**
* Add the parent of the current copy AST to the filtered view of
* ASTs which can be pipelined to the next ruleset, if the parent
* node is <b>not</b> <code>null</code>.
*
* @return <code>true</code>
*/
public boolean addParentToView()
{
getResolver().addParentToView();
return true;
}
/**
* Add a specified ancestor of the current copy AST to the filtered
* view of ASTs which can be pipelined to the next ruleset, if the
* all nodes in the ancestry chain (up to and including the specified
* level) are <b>not</b> <code>null</code>.
*
* @param level
* Identifies the ancestor by the number of generations 'up'
* the tree from the current node. 0 specifies the current
* node, 1 specifies the parent node, 2 specifies the
* 'grandparent' node and so forth.
*
* @return <code>true</code>
*/
public boolean addAncestorToView(long level)
{
getResolver().addAncestorToView((int) level);
return true;
}
/**
* Add the current copy AST, and all of its descendants through
* <code>maxLevels</code> generations, to the filtered view of ASTs
* which can be pipelined to the next ruleset.
*
* @param maxLevels
* Number of descendant generations to add. To add only the
* current AST, specify <code>1</code> (or better yet, use
* {@link #addToView}).
*
* @return <code>true</code>
*/
public boolean addTreeToView(long maxLevels)
{
getResolver().addTreeToView((int) maxLevels);
return true;
}
/**
* Add to the filtered view of ASTs all of the nodes which form the
* path from an ancestor node of type <code>type</code> to the current
* copy AST. The nodes are added in root to leaf order. If an
* ancestor node of type <code>type</code> does not exist, no nodes
* are added (not even the current one).
*
* @param type
* Ancestor token type which marks the beginning of the path.
*
* @return <code>true</code> if an ancestor of type <code>type</code>
* was found and the path of nodes was successfully added to
* the filtered view, else <code>false</code>.
*/
public boolean addPathToViewFrom(long type)
{
return getResolver().addPathToViewFrom(type);
}
/**
* Prints a string representation of the current source AST to stdout,
* indented appropriately to indicate its depth in the AST structure.
*
* @return <code>true</code>
*/
public boolean printAst()
{
getResolver().printAst();
return true;
}
/**
* Removes the current AST node from the <em>copy</em> AST.
*
* @return <code>true</code>
*/
public boolean remove()
{
getCopy().remove();
return true;
}
/**
* Get the character at the specified index, as a string.
*
* @param text
* The source text.
* @param index
* The character's index.
*
* @return See above.
*/
public String charAt(String text, int index)
{
return "" + text.charAt(index);
}
/**
* Uppercase and return the given string.
*
* @param text
* The string to uppercase.
*
* @return <code>text</code> string in all upper case.
*/
public String upper(String text)
{
return text.toUpperCase();
}
/**
* Lowercase and return the given string.
*
* @param text
* The string to lowercase.
*
* @return <code>text</code> string in all lower case.
*/
public String lower(String text)
{
return text.toLowerCase();
}
/**
* Convert a numeric value to its default string representation.
*
* @param value
* Number to convert.
*
* @return Default string representation.
*/
public String string(long value)
{
return String.valueOf(value);
}
/**
* Convert a numeric value to its default string representation.
*
* @param value
* Number to convert.
*
* @return Default string representation.
*/
public String string(double value)
{
return String.valueOf(value);
}
/**
* Convert a boolean value to its default string representation.
*
* @param value
* Number to convert.
*
* @return Default string representation.
*/
public String string(boolean value)
{
return String.valueOf(value);
}
/**
* Returns the portion of the input string from the 0-based index
* to the end.
*
* @param text
* The input string.
* @param index
* The character at which to start the substring.
*
* @return The portion of the string specified.
*/
public String substring(String text, long index)
{
return text.substring((int) index);
}
/**
* Returns the portion of the input string from the 0-based index
* to the end.
*
* @param text
* The input string.
* @param begin
* The character at which to start the substring.
* @param end
* The character before which to end the substring.
*
* @return The portion of the string specified.
*/
public String substring(String text, long begin, long end)
{
return text.substring((int) begin, (int) end);
}
/**
* Returns the 0-based index of the first character that matches
* the find string in the input string or -1 if no match was found.
*
* @param text
* The input string.
* @param find
* The string to search for.
*
* @return The index of the beginning of the match.
*/
public long indexOf(String text, String find)
{
return (long) text.indexOf(find);
}
/**
* Returns the 0-based index of the first character that matches
* the find string after the starting point in the input string or -1
* if no match was found.
*
* @param text
* The input string.
* @param find
* The string to search for.
* @param start
* The starting index from which to search.
*
* @return The index of the beginning of the match.
*/
public long indexOf(String text, String find, long start)
{
return text.indexOf(find, (int) start);
}
/**
* Returns the 0-based index of the first character of the last match
* to the find string in the input string or -1 if no match was found.
* This match algorithm works backward.
*
* @param text
* The input string.
* @param find
* The string to search for.
*
* @return The index of the beginning of the match.
*/
public long lastIndexOf(String text, String find)
{
return text.lastIndexOf(find);
}
/**
* Returns the 0-based index of the first character of the last match
* of the find string before the starting point in the input string or
* -1 if no match was found. This match algorithm works backward.
*
* @param text
* The input string.
* @param find
* The string to search for.
* @param start
* The starting index from which to search backward.
*
* @return The index of the beginning of the match.
*/
public long lastIndexOf(String text, String find, long start)
{
return text.lastIndexOf(find, (int) start);
}
/**
* Returns the length in characters of the given string.
*
* @param text
* The input string.
*
* @return The length of the string in characters.
*/
public long stringLength(String text)
{
return text.length();
}
/**
* Returns a string that is comprised of the input string concatenated
* <code>num</code> times without intervening spaces.
*
* @param text
* The input string.
* @param num
* The number of times to repeat the input string in the
* output string.
*
* @return The concatenated string.
*/
public String copies(String text, int num)
{
StringBuilder sb = new StringBuilder();
for (int i = 0; i < num; i++)
sb.append(text);
return sb.toString();
}
/**
* Wrap a text string to fit into one or more lines of the specified
* width. Expects the space character to delimit the words in the
* input string. Returns a list of as many strings as are necessary
* to meet the per-line width requirement. Single words that are wider
* than <code>width</code> are <em>not</em> split, but will appear in
* their own line. All lines are left-aligned within the alloted width.
*
* @param text
* Text to be split.
* @param width
* Per-line width in characters.
*
* @return List of strings representing <code>text</code> split into
* lines of the specified width.
*/
public List wrapText(String text, int width)
{
return Arrays.asList(StringHelper.wrapText(text, width));
}
/**
* Strip enclosing double or single quotes, if any, plus any options, from the given string.
*
* @param text
* String optionally enclosed in quotes.
*
* @return A string based on <code>text</code>, but with quotes
* removed.
*/
public String removeQuotes(String text)
{
// are we starting with ' or "
char lead = text.charAt(0);
// if not a quote, return original
if (lead != '"' && lead != '\'')
return text;
String contents = null;
try
{
// this will always work as long as the input string is valid
contents = text.substring(1, text.lastIndexOf(lead));
}
catch (IndexOutOfBoundsException iob)
{
throw new IllegalArgumentException("Missing closing quote (" +
lead + ").");
}
return contents;
}
/**
* Prepare the given text so that its quotes can be switched from the previous quote to the
* new quotes.
* <p>
* This includes un-escaping and un-doubling the previous quote char and escaping the new
* quote char.
*
* @param text
* The text to have its quotes switched.
* @param prevQuoteChar
* The previous quote.
* @param newQuoteChar
* The new quote.
*
* @return The prepared string.
*/
public String switchQuotes(String text, String prevQuoteChar, String newQuoteChar)
{
StringBuilder res = new StringBuilder();
char pq = prevQuoteChar.charAt(0);
char nq = newQuoteChar.charAt(0);
// the text has already had its quotes removed
// ensure the previous quote is un-escaped or un-doubled in the text
int i = 0;
while (i < text.length())
{
char ch = text.charAt(i);
if (ch == '~' || ch == '\\' || ch == pq)
{
i = i + 1;
char chNext = text.charAt(i);
if (chNext != pq)
{
// append only if prev quote is not escaped or doubled
res.append(ch);
}
res.append(chNext);
}
else if (ch == nq)
{
// the new quote, if is not already escaped, needs to be escaped
res.append('~');
res.append(nq);
}
else
{
res.append(ch);
}
i = i + 1;
}
return res.toString();
}
/**
* Trim leading whitespace from <code>text</code>, without triggering
* a <code>NullPointerException</code> if <code>text</code> is
* <code>null</code>.
*
* @param text
* Text string to be trimmed.
*
* @return <code>text</code> with leading whitespace removed, or
* <code>null</code> if <code>text</code> is <code>null</code>.
*/
public String trimLeading(String text)
{
return StringHelper.safeTrimLeading(text);
}
/**
* Trim trailing whitespace (space, tab, CR and LF) from {@code text}, without triggering a
* {@code NullPointerException} if {@code text} is {@code null}.
*
* @param text
* Text string to be trimmed.
*
* @return {@code text} with trailing whitespace removed, or {@code null} if {@code text}
* is {@code null}.
*/
public String trimTrailing(String text)
{
return StringHelper.safeTrimTrailing(text);
}
/**
* Trim only trailing spaces from {@code text}, without triggering a
* {@code NullPointerException} if {@code text} is {@code null}.
*
* @param text
* Text string to be trimmed.
*
* @return {@code text} with trailing whitespace removed, or {@code null} if {@code text}
* is {@code null}.
*/
public String trimTrailingSpaces(String text)
{
return StringHelper.safeTrimTrailingSpaces(text);
}
/**
* Parse a <code>Long</code> from <code>text</code> and return it.
*
* @param text
* String representing a base-10 <code>long</code> value.
*
* @return <code>text</code> as a <code>Long</code>.
*
* @throws NumberFormatException
* if <code>text</code> cannot be parsed into a long.
*/
public Long parseLong(String text)
throws NumberFormatException
{
return (Long.valueOf(text));
}
/**
* Parse a <code>Long</code> from <code>text</code> using the given
* radix, and return it.
*
* @param text
* String representing a <code>long</code> value with the
* specified radix.
* @param radix
* Radix to be used interpreting <code>text</code>.
*
* @return <code>text</code> as a <code>Long</code>.
*
* @throws NumberFormatException
* if <code>text</code> cannot be parsed into a long.
*/
public Long parseLong(String text, int radix)
throws NumberFormatException
{
return Long.valueOf(text, radix);
}
/**
* Parse a <code>Long</code> from <code>text</code> and return it. Do
* not raise an exception if <code>text</code> cannot be parsed.
*
* @param text
* String representing a base-10 <code>long</code> value.
*
* @return <code>text</code> as a <code>Long</code>, or
* <code>null</code> if <code>text</code> cannot be parsed into
* a <code>Long</code>.
*/
public Long parseLongQuiet(String text)
{
return ExpressionConversionWorker.parseLongQuiet(text);
}
/**
* Parse a <code>Long</code> from <code>text</code> using the given
* radix, and return it. Do not raise an exception if <code>text</code>
* cannot be parsed.
*
* @param text
* String representing a <code>long</code> value with the
* specified radix.
* @param radix
* Radix to be used interpreting <code>text</code>.
*
* @return <code>text</code> as a <code>Long</code>, or
* <code>null</code> if <code>text</code> cannot be parsed into
* a <code>Long</code>.
*/
public Long parseLongQuiet(String text, int radix)
{
return ExpressionConversionWorker.parseLongQuiet(text, radix);
}
/**
* Parse a <code>Double</code> from <code>text</code> and return it.
*
* @param text
* String representing a <code>double</code> value.
*
* @return <code>text</code> as a <code>Double</code>.
*
* @throws NumberFormatException
* if <code>text</code> cannot be parsed into a double.
*/
public Double parseDouble(String text)
throws NumberFormatException
{
return (Double.valueOf(text));
}
/**
* Parse a <code>Double</code> from <code>text</code> and return it.
* Do not raise an exception if <code>text</code> cannot be parsed.
*
* @param text
* String representing a <code>double</code> value.
*
* @return <code>text</code> as a <code>Double</code> or
* <code>null</code> if <code>text</code> cannot be parsed.
*/
public Double parseDoubleQuiet(String text)
{
try
{
return (Double.valueOf(text));
}
catch (NumberFormatException exc)
{
return null;
}
}
/**
* Parse a <code>BigDecimal</code> from <code>text</code> and return it.
*
* @param text
* String representing a decimal value.
*
* @return <code>text</code> as a <code>BigDecimal</code>.
*
* @throws NumberFormatException
* if <code>text</code> cannot be parsed into a decimal.
*/
public BigDecimal parseBigDecimal(String text)
{
return new BigDecimal(text);
}
/**
* Get the NOW value. Used by import to resolve the NOW function at the field's inital value.
*
* @return See above.
*/
public datetimetz now()
{
return datetimetz.now();
}
/**
* Get the TODAY value. Used by import to resolve the TODAY function at the field's inital value.
*
* @return See above.
*/
public date today()
{
return date.today();
}
/**
* Parse the ISO8601 text as a datetimetz literal. Used by import for the field's initial value.
*
* @param text
* The datetimetz literal in ISO8601 format.
*
* @return See above.
*/
public datetimetz parseDatetimetzLiteral(String text)
{
return datetimetz.fromLiteral(text);
}
/**
* Parse the ISO8601 text as a datetime literal. Used by import for the field's initial value.
*
* @param text
* The datetime literal in ISO8601 format.
*
* @return See above.
*/
public datetime parseDatetimeLiteral(String text)
{
return datetime.fromLiteral(text);
}
/**
* Convert a <code>double</code> value to a <code>BigDecimal</code>.
*
* @param num
* A <code>Double</code> instance to be converted.
*
* @return An exact representation of the input value.
*/
public BigDecimal toBigDecimal(Double num)
{
return new BigDecimal(num);
}
/**
* Debug user function implementation to print text to stdout.
*
* @param text
* Text to be printed.
*
* @return <code>true</code>.
*/
public boolean print(String text)
{
LOG.log(isRuntimeConfig() ? Level.FINE : Level.INFO, text);
return true;
}
/**
* Debug user function implementation to print text to stdout, followed
* by a line break.
*
* @param text
* Text to be printed.
*
* @return <code>true</code>.
*/
public boolean println(String text)
{
LOG.log(isRuntimeConfig() ? Level.FINE : Level.INFO, text);
return true;
}
/**
* Logs a message on the requested level allowing formatting.
*
* @param levelString
* The string representation of a java.util.logging.Level.
* @param format
* A format string using C-like <code>sprintf</code> syntax
* for its substitution placeholders.
* @param args
* An array of objects used as formatting parameters.
*
* @return <code>true</code>.
*/
public boolean log(String levelString, String format, Object... args)
{
Level level = LoggingUtil.convertToLevel(levelString).orElse(Level.FINE);
String fmt = StringHelper.processEscapes(format);
String msg = (args == null || args.length == 0) ? fmt : String.format(fmt, args);
LOG.log(level, msg);
return true;
}
/**
* Persist the copy AST to file, using the default persistence naming
* convention. This will overwrite any existing file of the same name
* without complaint or warning.
*
* @return <code>true</code>.
*
* @throws ConfigurationException
* if <code>P2J_HOME</code> has not been defined for this
* process.
* @throws AstException
* if any error occurs persisting the AST.
*/
public boolean persist()
throws ConfigurationException,
AstException
{
Aast root = getCopy().getAncestor(-1);
StringBuilder buf = new StringBuilder(root.getFilename());
buf.append(AstGenerator.AST_POSTFIX);
String fname = Configuration.normalizeFilename(buf.toString());
AstManager.get().saveTree(root, fname, false);
return true;
}
/**
* Persist the copy AST to file, using the default persistence naming
* convention. This will overwrite any existing file of the same name
* without complaint or warning.
*
* @param filename
* The file to persist.
* @param projectRelative
* <code>true</code> if the filename is relative to the
* project home directory, <code>false</code> if the filename
* is absolute or relative to the current directory.
*
* @return <code>true</code>.
*
* @throws ConfigurationException
* if <code>P2J_HOME</code> has not been defined for this
* process.
* @throws AstException
* if any error occurs persisting the AST.
*/
public boolean persist(String filename, boolean projectRelative)
throws ConfigurationException,
AstException
{
return persist(filename, projectRelative, false);
}
/**
* Persist the copy AST to file, using the default persistence naming
* convention. This will overwrite any existing file of the same name
* without complaint or warning.
*
* @param filename
* The file to persist.
* @param projectRelative
* <code>true</code> if the filename is relative to the
* project home directory, <code>false</code> if the filename
* is absolute or relative to the current directory.
* @param terse
* <code>true</code> to persist the AST in terse mode,
* otherwise <code>false</code>.
*
* @return <code>true</code>.
*
* @throws ConfigurationException
* if <code>P2J_HOME</code> has not been defined for this
* process.
* @throws AstException
* if any error occurs persisting the AST.
*/
public boolean persist(String filename,
boolean projectRelative,
boolean terse)
throws ConfigurationException,
AstException
{
Aast root = getCopy().getAncestor(-1);
return persist(root, filename, projectRelative, terse);
}
/**
* Persist the specified AST to file, using the default persistence naming
* convention. This will overwrite any existing file of the same name
* without complaint or warning.
*
* @param root
* The root AST.
* @param filename
* The file to persist.
* @param projectRelative
* <code>true</code> if the filename is relative to the
* project home directory, <code>false</code> if the filename
* is absolute or relative to the current directory.
* @param terse
* <code>true</code> to persist the AST in terse mode,
* otherwise <code>false</code>.
*
* @return <code>true</code>.
*
* @throws ConfigurationException
* if <code>P2J_HOME</code> has not been defined for this
* process.
* @throws AstException
* if any error occurs persisting the AST.
*/
public boolean persist(Aast root,
String filename,
boolean projectRelative,
boolean terse)
throws ConfigurationException,
AstException
{
if (projectRelative)
{
filename = Configuration.normalizeFilename(filename);
}
AstManager.get().saveTree(root, filename, terse);
return true;
}
/**
* Add the given AST as a new program file, by registering its filename.
*
* @param root
* The AST root.
*/
public void addNewProgramFile(Aast root)
{
engine.addNewProgramFile(root.getFilename());
}
/**
* Clear the list of newly, generated, {@link PatternEngine#newProgramFiles program file
* names}.
*/
public void clearNewProgramFiles()
{
engine.clearNewProgramFiles();
}
/**
* Debug method to print a stack trace dump to <code>stderr</code>.
*
* @return <code>true</code>.
*/
public boolean dumpStack()
{
Thread.dumpStack();
return true;
}
/**
* Throws a {@link CompileException} with a user-defined message. This will halt pattern
* engine processing.
* <p>
* Data associated with the currently processing AST node will NOT be included in the
* formatted message.
* <p>
* Both {@code id} and {@code message} should match one of the Progress' set of compile
* errors, so it would be properly recognized by the dynamic conversion module.
*
* @param id
* The identifier of the error message.
* @param message
* Error message to include in the exception's text.
* @param ast
* The AST that caused the error.
*
* @throws CompileException
* Always, with the provided information.
*/
public void compileError(int id, String message, Aast ast)
throws CompileException
{
throw new CompileException(id, message, ast);
}
/**
* Throws a {@link CompileException} with a user-defined message. This will halt pattern
* engine processing.
* <p>
* Data associated with the currently processing AST node will NOT be included in the
* formatted message.
* <p>
* Both {@code id} and {@code message} should match one of the Progress' set of compile
* errors, so it would be properly recognized by the dynamic conversion module.
*
* @param id
* The identifier of the error message.
* @param message
* Error message to include in the exception's text.
*
* @throws CompileException
* Always, with the provided information.
*/
public void compileError(int id, String message)
throws CompileException
{
compileError(id, message, getSource());
}
/**
* Throws a {@link UserGeneratedException} with a user-defined message. This will halt pattern engine
* processing.
* <p>
* Data associated with the currently processing AST node will be
* included in the formatted message.
* <p>
* Use this method if the error you encounter is a P2J specific issue. If it is an error
* detected in code, use the {@link #compileError(int, String)} instead, with the
* parameters compatible to P4GL compiler error set.
*
* @param message
* Error message to include in the exception's text.
*/
public void throwException(String message)
throws UserGeneratedException
{
throwException(message, getSource());
}
/**
* Throws a {@link UserGeneratedException} with a user-defined message. This will halt pattern engine
* processing.
*
* @param message
* Error message.
* @param ast
* AST at which failure occurred.
*/
public void throwException(String message, Aast ast)
throws UserGeneratedException
{
throw new UserGeneratedException(message, ast);
}
/**
* Given a format string using C-like <code>printf</code> syntax, and
* an array of runtime replacement arguments, perform formatted variable
* substitution and return the resulting string.
*
* @param format
* A format string using C-like <code>sprintf</code> syntax
* for its substitution placeholders.
* @param args
* An array of objects which must contain only the following
* types:
* <ul>
* <li>java.lang.String
* <li>java.lang.Long
* <li>java.lang.Double
* </ul>
* These are the values to be substituted into the format
* string at placeholders of the appropriate, matching type.
*
* @return A string which is based upon the format string, with values
* from <code>args</code> substituted in for the appropriate,
* respective placeholder variables.
*/
public String sprintf(String format, Object[] args)
{
String fmt = StringHelper.processEscapes(format);
return String.format(fmt, args);
}
/**
* Given a format string using C-like <code>printf</code> syntax, and
* an array of runtime replacement arguments, perform formatted variable
* substitution and APPEND the resulting string to a named file. The
* file will be opened and closed in this method, such that no state
* is maintained between calls.
*
* @param filename
* The file to which the resulting string will be appended.
* If the file doesn't exist it will be created. The filename
* MUST be specified in a format that is relative to the
* project root.
* @param format
* A format string using C-like <code>sprintf</code> syntax
* for its substitution placeholders.
* @param args
* An array of objects which must contain only the following
* types:
* <ul>
* <li>java.lang.String
* <li>java.lang.Long
* <li>java.lang.Double
* </ul>
* These are the values to be substituted into the format
* string at placeholders of the appropriate, matching type.
*
* @return <code>true</code>.
*
* @throws ConfigurationException
* if <code>P2J_HOME</code> has not been defined for this
* process.
* @throws IllegalArgumentException
* if a stream for the specified filename cannot be created.
*/
public boolean fprintf(String filename, String format, Object[] args)
throws ConfigurationException,
IllegalArgumentException,
IOException
{
String fmt = StringHelper.processEscapes(format);
String line = String.format(fmt, args);
File file = Configuration.toFile(filename);
PrintStream out;
// if this filename exists as a directory, we can't save results
if (file.isDirectory())
{
throw new IllegalArgumentException(filename + " is a directory!");
}
try
{
out = new PrintStream( new FileOutputStream(file, true) );
}
catch (Exception exc)
{
throw new IllegalArgumentException("Cannot create stream for " +
filename + "(" +
exc.toString() + ")!");
}
out.print(line);
out.close();
return true;
}
/**
* Opens a new stream for writing and associates a unique ID with that
* stream. Any path to the target file is created if needed before the
* stream for that file is opened.
*
* @param filename
* The name of the file to be opened.
* @param projectRelative
* <code>true</code> if the filename is relative to the
* project home directory, <code>false</code> if the filename
* is absolute or relative to the current directory.
*
* @return The unique ID associated with this stream.
*
* @throws ConfigurationException
* if <code>P2J_HOME</code> has not been defined for this
* process.
* @throws IllegalArgumentException
* if a stream for the specified filename cannot be created.
*/
public Long openStream(String filename, boolean projectRelative)
throws ConfigurationException,
IllegalArgumentException,
IOException
{
File file = null;
if (projectRelative)
{
file = Configuration.toFile(filename);
}
else
{
file = new File(filename);
}
ByteCountOutputStream out;
// if this filename exists as a directory, we can't save results
if (file.isDirectory())
{
throw new IllegalArgumentException(filename + " is a directory!");
}
try
{
// this is a file, get the containing directory
File parent = file.getParentFile();
if (!parent.exists())
{
// the directory doesn't exist, so create it (including all
// intermediate directories needed
parent.mkdirs();
}
// create the file
FileOutputStream fos = new FileOutputStream(file, true);
// init our counting stream wrapper
out = new ByteCountOutputStream(fos);
}
catch (Exception exc)
{
throw new IllegalArgumentException("Cannot create stream for " +
filename + "(" +
exc.toString() + ")!");
}
WorkArea wa = context.get();
Long id = wa.nextId;
wa.streams.put(id, out);
wa.nextId++;
return id;
}
/**
* Closes a stream that was previously opened and which was associated
* with a specific ID.
*
* @param id
* The ID of a stream which muct have previously been opened.
*
* @return Always <code>true</code>.
*/
public boolean closeStream(Long id)
throws IOException
{
WorkArea wa = context.get();
ByteCountOutputStream out = wa.streams.remove(id);
if (out != null)
out.close();
return true;
}
/**
* Returns the number of bytes written to this stream since it was
* opened.
*
* @param id
* The ID of a stream which muct have previously been opened.
*
* @return The number of bytes written since opening the stream or
* -1 if the stream doesn't exist.
*/
public long getNumBytesWritten(Long id)
{
WorkArea wa = context.get();
ByteCountOutputStream out = wa.streams.get(id);
long result = -1;
if (out != null)
result = out.getBytesWritten();
return result;
}
/**
* Given a format string using C-like <code>printf</code> syntax, and
* an array of runtime replacement arguments, perform formatted variable
* substitution and APPEND the resulting string to previously opened
* stream identified by a numeric ID. This is a stateful version of
* the {@link #fprintf(String,String,Object[])}. Use the methods
* {@link #openStream} and {@link #closeStream} to open and close the
* stream.
* <p>
* The output from this method will be UTF-8 encoded.
*
* @param id
* The ID of a stream which muct have previously been opened.
* @param format
* A format string using C-like <code>sprintf</code> syntax
* for its substitution placeholders.
* @param args
* An array of objects which must contain only the following
* types:
* <ul>
* <li>java.lang.String
* <li>java.lang.Long
* <li>java.lang.Double
* </ul>
* These are the values to be substituted into the format
* string at placeholders of the appropriate, matching type.
*
* @return Always <code>true</code>.
*
* @throws IllegalArgumentException
* if the specified stream does not exist.
*/
public boolean fprintf(Long id, String format, Object[] args)
throws IllegalArgumentException,
IOException
{
WorkArea wa = context.get();
ByteCountOutputStream out = wa.streams.get(id);
if (out == null)
throw new IllegalArgumentException("Invalid stream ID " + id);
String fmt = StringHelper.processEscapes(format);
String line = String.format(fmt, args);
out.write(line.getBytes("UTF-8"));
return true;
}
/**
* Prints a string formatted by {@link #sprintf} to <code>stdout</code>
* with no trailing line break.
*
* @param format
* A format string using C-like <code>sprintf</code> syntax
* for its substitution placeholders.
* @param args
* An array of objects which must contain only the following
* types:
* <ul>
* <li>java.lang.String
* <li>java.lang.Long
* <li>java.lang.Double
* </ul>
* These are the values to be substituted into the format
* string at placeholders of the appropriate, matching type.
*
* @return <code>true</code>.
*/
public boolean printf(String format, Object[] args)
{
String fmt = StringHelper.processEscapes(format);
LOG.log(isRuntimeConfig() ? Level.FINE : Level.INFO, String.format(fmt, args));
return true;
}
/**
* Logs a list of {@link Object}s in {@code ERR} console with a (usually short) description.
* Optionally, the processing can be suspended if a debugger is attached to process and
* inspect the logged entities. The advantage of this method is the direct access to
* objects passed in as parameters, instead of looking them up in the TRPL runtime
* structures and over the {@link #printf} is the lack of formatting parameter (the
* {@link Object#toString()}) used here is powerful enough to display lists, integer and
* boolean values usually encountered in TRPL code. It also can display any datatypes
* instead of only 3 supported by {@code printf}.
* <p>
* Notes:
* <ul>
* <li>this method is only used for debugging. It is NOT recommended to use it in
* production code;</li>
* <li>the method uses the stderr console so the output may not be synchronized with
* normal stdout console.</li>
* </ul>
*
* @param location
* A short message that will displayed in front of the list of other objects.
* @param suspend
* Activate a special breakpoint location after the values were logged. If this is
* {@code false} or no breakpoint was set in debugger, this method behaves like
* {@code printfln} to err console that displays the values in a column.
* @param args
* The list of entities to be displayed. Depending on their type, the output for
* each one is different, the emphasis being put on {@link Aast} objects. The items
* in this list will be TAB-indented.
*
* @return always {@code true}.
*/
public boolean breakpoint(String location, boolean suspend, Object... args)
{
LOG.log(Level.FINEST, "#B# " + location);
for (Object arg : args)
{
if (arg instanceof Aast)
{
Aast aast = (Aast) arg;
if (aast.getType() == ProgressParserTokenTypes.EXPRESSION)
{
LOG.log(Level.FINEST, "\t" + aast);
aast = (Aast) aast.getFirstChild();
}
LOG.log(Level.FINEST, "\t" + aast + " " + aast.getText() + " @" + aast.getLine() +
":" + aast.getColumn());
}
else
{
LOG.log(Level.FINEST, "\t" + arg);
}
}
if (suspend)
{
suspend = false; // put the breakpoint on this line
}
return true;
}
/**
* Prints a string formatted by {@link #sprintf} to <code>stdout</code>
* with a trailing line break.
*
* @param format
* A format string using C-like <code>sprintf</code> syntax
* for its substitution placeholders.
* @param args
* An array of objects which must contain only the following
* types:
* <ul>
* <li>java.lang.String
* <li>java.lang.Long
* <li>java.lang.Double
* </ul>
* These are the values to be substituted into the format
* string at placeholders of the appropriate, matching type.
*
* @return <code>true</code>.
*/
public boolean printfln(String format, Object[] args)
{
String fmt = StringHelper.processEscapes(format);
LOG.log(isRuntimeConfig() ? Level.FINE : Level.INFO, String.format(fmt, args));
return true;
}
/**
* Return the first ancestor node which matches one of the specified
* token types.
*
* @param types
* Token types to match. May not be <code>null</code> or
* empty.
*
* @return First ancestor to match one of the given types, or
* <code>null</code> if no such node was found.
*/
public Aast getAncestorOfType(Object[] types)
{
if (types == null || types.length == 0)
{
throw new IllegalArgumentException("No token types were specified");
}
for (Aast next = getCopy().getParent();
next != null;
next = next.getParent())
{
if (matchTokenType(next, types))
{
return next;
}
}
return null;
}
/**
* Return the first ancestor node with the specified annotation,
* optionally refining the match by annotation value and/or token type.
*
* @param key
* Annotation key for which to search; may not be
* <code>null</code>.
* @param value
* Optional annotation value to match. If <code>null</code>,
* a match on the annotation key (and optionally on token
* type) is sufficient to constitute success.
* @param types
* Optional array of <code>Integer</code> values representing
* valid token types. If empty, token type is not considered
* as a match criterion.
*
* @return First ancestor node to match the given criteria, or
* <code>null</code> if no match was found.
*
* @throws ClassCastException
* if <code>types</code> contains any object type other than
* <code>Integer</code>.
*/
public Aast getAncestor(String key, Object value, Object[] types)
{
return getAncestor(null, key, value, types);
}
/**
* Return the first ancestor node with the specified annotation,
* optionally refining the match by annotation value and/or token type.
*
* @param index
* Index of annotation to compare when a node is found with
* the specfied annotation <code>key</code>. Only valid for
* list type annotations; must be <code>null</code> for
* simple annotations.
* @param key
* Annotation key for which to search; may not be
* <code>null</code>.
* @param value
* Optional annotation value to match. If <code>null</code>,
* a match on the annotation key (and optionally on token
* type) is sufficient to constitute success.
* @param types
* Optional array of <code>Integer</code> values representing
* valid token types. If empty, token type is not considered
* as a match criterion.
*
* @return First ancestor node to match the given criteria, or
* <code>null</code> if no match was found.
*
* @throws ClassCastException
* if <code>types</code> contains any object type other than
* <code>Integer</code>.
*/
public Aast getAncestor(Integer index,
String key,
Object value,
Object[] types)
{
boolean tokMatch = (types != null && types.length > 0);
if (value instanceof Integer)
{
// When we retrieve the associated annotation, it will be a Long.
// value must be of the same type for equals() to work correctly.
value = Long.valueOf(((Integer) value).intValue());
}
for (Aast next = getCopy().getParent();
next != null;
next = next.getParent())
{
if (!next.isAnnotation(key))
{
continue;
}
if (value == null)
{
// In this case, we are only checking for the presence of the
// annotation (and possibly for a token type match), not for a
// match on the annotation's value.
if (!tokMatch)
{
return next;
}
if (matchTokenType(next, types))
{
return next;
}
continue;
}
Object note = (index == null
? next.getAnnotation(key)
: next.getAnnotation(key, index.intValue()));
if (value.equals(note))
{
if (tokMatch)
{
if (matchTokenType(next, types))
{
return next;
}
}
else
{
// In this case, we are only checking for an annotation
// match and we don't care what token type we found.
return next;
}
}
}
return null;
}
/**
* Determines if an annotation exists of any <code>String</code>,
* <code>Double</code>, <code>Long</code>, or <code>Boolean</code>
* value, indexed by an arbitrary key.
*
* @param key
* Key under which the annotation is stored.
*
* @return <code>true</code> if an annotation exists mapped by that
* key.
*/
public boolean isNote(String key)
{
return getCopy().isAnnotation(key);
}
/**
* Determines the type of an annotation (if it exists) as indexed by an
* arbitrary key. The full class name will be returned, e.g.
* <code>java.lang.String</code>.
*
* @param key
* Key under which the annotation is stored.
*
* @return Class name of the annotation or <code>null</code> if the
* annotation doesn't exist.
*/
public String getNoteType(String key)
{
Object a = getCopy().getAnnotation(key);
if (a == null)
return null;
return a.getClass().getName();
}
/**
* Returns the size of an array annotation as indexed by an arbitrary
* key.
*
* @param key
* Key under which the annotation is stored.
*
* @return The number of elements in an array annotation that exists
* mapped by that key or 0 if no such named array annotation
* exists. A scalar annotation using that same name will also
* result in a return of 0.
*/
public long getNoteSize(String key)
{
return (long) getCopy().annotationSize(key);
}
/**
* Gets a <code>String</code> annotation from the current,
* <em>copy</em> AST.
*
* @param key
* Case-insensitive annotation key.
*
* @return The desired annotation value, or <code>null</code> if it
* does not exist in the current, copy AST.
*/
public String getNoteString(String key)
{
Object a = getCopy().getAnnotation(key);
return (a instanceof String ? (String) a : null);
}
/**
* Gets a <code>String</code> annotation at a specific index from an
* array annotation in the current <em>copy</em> AST.
*
* @param key
* Case-insensitive annotation key.
* @param index
* The index of -1 indicates the last element of the array.
* Otherwise this is the explicit zero-based index at which to
* find the annotation.
*
* @return The annotation object stored under the specified key and at
* the specified index, or <code>null</code> if the annotation
* doesn't exist, if the annotation is not an array or if no
* element exists at the given index.
*/
public String getNoteString(String key, long index)
{
Object a = getCopy().getAnnotation(key, (int)index);
return (a instanceof String ? (String) a : null);
}
/**
* Gets a <code>Long</code> annotation from the current,
* <em>copy</em> AST.
*
* @param key
* Case-insensitive annotation key.
*
* @return The desired annotation value, or <code>null</code> if it
* does not exist in the current, copy AST.
*/
public Long getNoteLong(String key)
{
Object a = getCopy().getAnnotation(key);
return (a instanceof Long ? (Long) a : null);
}
/**
* Gets a <code>Long</code> annotation at a specific index from an
* array annotation in the current <em>copy</em> AST.
*
* @param key
* Case-insensitive annotation key.
* @param index
* The index of -1 indicates the last element of the array.
* Otherwise this is the explicit zero-based index at which to
* find the annotation.
*
* @return The annotation object stored under the specified key and at
* the specified index, or <code>null</code> if the annotation
* doesn't exist, if the annotation is not an array or if no
* element exists at the given index.
*/
public Long getNoteLong(String key, long index)
{
Object a = getCopy().getAnnotation(key, (int)index);
return (a instanceof Long ? (Long) a : null);
}
/**
* Gets a <code>Double</code> annotation from the current,
* <em>copy</em> AST.
*
* @param key
* Case-insensitive annotation key.
*
* @return The desired annotation value, or <code>null</code> if it
* does not exist in the current, copy AST.
*/
public Double getNoteDouble(String key)
{
Object a = getCopy().getAnnotation(key);
return (a instanceof Double ? (Double) a : null);
}
/**
* Gets a <code>Double</code> annotation at a specific index from an
* array annotation in the current <em>copy</em> AST.
*
* @param key
* Case-insensitive annotation key.
* @param index
* The index of -1 indicates the last element of the array.
* Otherwise this is the explicit zero-based index at which to
* find the annotation.
*
* @return The annotation object stored under the specified key and at
* the specified index, or <code>null</code> if the annotation
* doesn't exist, if the annotation is not an array or if no
* element exists at the given index.
*/
public Double getNoteDouble(String key, long index)
{
Object a = getCopy().getAnnotation(key, (int)index);
return (a instanceof Double ? (Double) a : null);
}
/**
* Gets a <code>Boolean</code> annotation from the current,
* <em>copy</em> AST.
*
* @param key
* Case-insensitive annotation key.
*
* @return The desired annotation value, or <code>null</code> if it
* does not exist in the current, copy AST.
*/
public Boolean getNoteBoolean(String key)
{
Object a = getCopy().getAnnotation(key);
return (a instanceof Boolean ? (Boolean) a : null);
}
/**
* Gets a <code>Boolean</code> annotation at a specific index from an
* array annotation in the current <em>copy</em> AST.
*
* @param key
* Case-insensitive annotation key.
* @param index
* The index of -1 indicates the last element of the array.
* Otherwise this is the explicit zero-based index at which to
* find the annotation.
*
* @return The annotation object stored under the specified key and at
* the specified index, or <code>null</code> if the annotation
* doesn't exist, if the annotation is not an array or if no
* element exists at the given index.
*/
public Boolean getNoteBoolean(String key, long index)
{
Object a = getCopy().getAnnotation(key, (int)index);
return (a instanceof Boolean ? (Boolean) a : null);
}
/**
* Gets a <code>String</code> annotation from the specified ancestor
* in the <em>copy</em> AST.
*
* @param level
* Number of generations up the tree to walk to obtain the
* ancestor. Set to <code>-1</code> to obtain the tree root,
* <code>0</code> is the current node, <code>1</code> is the
* parent, <code>2</code> is the 'grandparent' and so forth.
* @param key
* Case-insensitive annotation key.
*
* @return The desired annotation value, or <code>null</code> if
* either the annotation or the specified ancestor does not
* exist.
*/
public String getAncestorNoteString(long level, String key)
{
Aast ans = getCopy().getAncestor((int)level);
if (ans == null)
return null;
Object a = ans.getAnnotation(key);
return (a instanceof String ? (String) a : null);
}
/**
* Gets a <code>String</code> annotation at a specific index from an
* array annotation in the specified ancestor of the current
* <em>copy</em> AST.
*
* @param level
* Number of generations up the tree to walk to obtain the
* ancestor. Set to <code>-1</code> to obtain the tree root,
* <code>0</code> is the current node, <code>1</code> is the
* parent, <code>2</code> is the 'grandparent' and so forth.
* @param key
* Case-insensitive annotation key.
* @param index
* The index of -1 indicates the last element of the array.
* Otherwise this is the explicit zero-based index at which to
* find the annotation.
*
* @return The annotation object stored under the specified key and at
* the specified index, or <code>null</code> if the annotation
* doesn't exist, if the annotation is not an array or if no
* element exists at the given index.
*/
public String getAncestorNoteString(long level, String key, long index)
{
Aast ans = getCopy().getAncestor((int)level);
if (ans == null)
return null;
Object a = ans.getAnnotation(key, (int)index);
return (a instanceof String ? (String) a : null);
}
/**
* Gets a <code>Long</code> annotation from the specified ancestor
* in the <em>copy</em> AST.
*
* @param level
* Number of generations up the tree to walk to obtain the
* ancestor. Set to <code>-1</code> to obtain the tree root,
* <code>0</code> is the current node, <code>1</code> is the
* parent, <code>2</code> is the 'grandparent' and so forth.
* @param key
* Case-insensitive annotation key.
*
* @return The desired annotation value, or <code>null</code> if
* either the annotation or the specified ancestor does not
* exist.
*/
public Long getAncestorNoteLong(long level, String key)
{
Aast ans = getCopy().getAncestor((int)level);
if (ans == null)
return null;
Object a = ans.getAnnotation(key);
return (a instanceof Long ? (Long) a : null);
}
/**
* Gets a <code>Long</code> annotation at a specific index from an
* array annotation in the specified ancestor of the current
* <em>copy</em> AST.
*
* @param level
* Number of generations up the tree to walk to obtain the
* ancestor. Set to <code>-1</code> to obtain the tree root,
* <code>0</code> is the current node, <code>1</code> is the
* parent, <code>2</code> is the 'grandparent' and so forth.
* @param key
* Case-insensitive annotation key.
* @param index
* The index of -1 indicates the last element of the array.
* Otherwise this is the explicit zero-based index at which to
* find the annotation.
*
* @return The annotation object stored under the specified key and at
* the specified index, or <code>null</code> if the annotation
* doesn't exist, if the annotation is not an array or if no
* element exists at the given index.
*/
public Long getAncestorNoteLong(long level, String key, long index)
{
Aast ans = getCopy().getAncestor((int)level);
if (ans == null)
return null;
Object a = ans.getAnnotation(key, (int)index);
return (a instanceof Long ? (Long) a : null);
}
/**
* Gets a <code>Double</code> annotation from the specified ancestor
* in the <em>copy</em> AST.
*
* @param level
* Number of generations up the tree to walk to obtain the
* ancestor. Set to <code>-1</code> to obtain the tree root,
* <code>0</code> is the current node, <code>1</code> is the
* parent, <code>2</code> is the 'grandparent' and so forth.
* @param key
* Case-insensitive annotation key.
*
* @return The desired annotation value, or <code>null</code> if
* either the annotation or the specified ancestor does not
* exist.
*/
public Double getAncestorNoteDouble(long level, String key)
{
Aast ans = getCopy().getAncestor((int)level);
if (ans == null)
return null;
Object a = ans.getAnnotation(key);
return (a instanceof Double ? (Double) a : null);
}
/**
* Gets a <code>Double</code> annotation at a specific index from an
* array annotation in the specified ancestor of the current
* <em>copy</em> AST.
*
* @param level
* Number of generations up the tree to walk to obtain the
* ancestor. Set to <code>-1</code> to obtain the tree root,
* <code>0</code> is the current node, <code>1</code> is the
* parent, <code>2</code> is the 'grandparent' and so forth.
* @param key
* Case-insensitive annotation key.
* @param index
* The index of -1 indicates the last element of the array.
* Otherwise this is the explicit zero-based index at which to
* find the annotation.
*
* @return The annotation object stored under the specified key and at
* the specified index, or <code>null</code> if the annotation
* doesn't exist, if the annotation is not an array or if no
* element exists at the given index.
*/
public Double getAncestorNoteDouble(long level, String key, long index)
{
Aast ans = getCopy().getAncestor((int)level);
if (ans == null)
return null;
Object a = ans.getAnnotation(key, (int)index);
return (a instanceof Double ? (Double) a : null);
}
/**
* Gets a <code>Boolean</code> annotation from the specified ancestor
* in the <em>copy</em> AST.
*
* @param level
* Number of generations up the tree to walk to obtain the
* ancestor. Set to <code>-1</code> to obtain the tree root,
* <code>0</code> is the current node, <code>1</code> is the
* parent, <code>2</code> is the 'grandparent' and so forth.
* @param key
* Case-insensitive annotation key.
*
* @return The desired annotation value, or <code>null</code> if
* either the annotation or the specified ancestor does not
* exist.
*/
public Boolean getAncestorNoteBoolean(long level, String key)
{
Aast ans = getCopy().getAncestor((int)level);
if (ans == null)
return null;
Object a = ans.getAnnotation(key);
return (a instanceof Boolean ? (Boolean) a : null);
}
/**
* Gets a <code>Boolean</code> annotation from the specified ancestor
* in the <em>copy</em> AST.
*
* @param level
* Number of generations up the tree to walk to obtain the
* ancestor. Set to <code>-1</code> to obtain the tree root,
* <code>0</code> is the current node, <code>1</code> is the
* parent, <code>2</code> is the 'grandparent' and so forth.
* @param key
* Case-insensitive annotation key.
* @param defaultValue
* When the annotation is not found, this is the value returned from the method.
*
* @return The desired annotation value, or <code>null</code> if
* either the annotation or the specified ancestor does not
* exist.
*/
public Boolean getAncestorNoteBoolean(long level, String key, Boolean defaultValue)
{
Boolean result = getAncestorNoteBoolean(level, key);
return result != null ? result : defaultValue;
}
/**
* Gets a <code>Boolean</code> annotation at a specific index from an
* array annotation in the specified ancestor of the current
* <em>copy</em> AST.
*
* @param level
* Number of generations up the tree to walk to obtain the
* ancestor. Set to <code>-1</code> to obtain the tree root,
* <code>0</code> is the current node, <code>1</code> is the
* parent, <code>2</code> is the 'grandparent' and so forth.
* @param key
* Case-insensitive annotation key.
* @param index
* The index of -1 indicates the last element of the array.
* Otherwise this is the explicit zero-based index at which to
* find the annotation.
*
* @return The annotation object stored under the specified key and at
* the specified index, or <code>null</code> if the annotation
* doesn't exist, if the annotation is not an array or if no
* element exists at the given index.
*/
public Boolean getAncestorNoteBoolean(long level, String key, long index)
{
Aast ans = getCopy().getAncestor((int) level);
if (ans == null)
return null;
Object a = ans.getAnnotation(key, (int)index);
return (a instanceof Boolean ? (Boolean) a : null);
}
/**
* Gets a <code>String</code> annotation from the referenced node
* in the <em>copy</em> AST.
*
* @param id
* The unique ID that references a specific node in the tree.
* @param key
* Case-insensitive annotation key.
*
* @return The desired annotation value, or <code>null</code> if
* either the annotation or the referenced node does not
* exist.
*/
public String getReferenceNoteString(long id, String key)
{
Aast ans = getResolver().getAst(Long.valueOf(id));
if (ans == null)
return null;
Object a = ans.getAnnotation(key);
return (a instanceof String ? (String) a : null);
}
/**
* Gets a <code>String</code> annotation at a specific index from an
* array annotation in the referenced node of the current
* <em>copy</em> AST.
*
* @param id
* The unique ID that references a specific node in the tree.
* @param key
* Case-insensitive annotation key.
* @param index
* The index of -1 indicates the last element of the array.
* Otherwise this is the explicit zero-based index at which to
* find the annotation.
*
* @return The annotation object stored under the specified key and at
* the specified index, or <code>null</code> if the annotation
* doesn't exist, if the annotation is not an array, if no
* element exists at the given index or the referenced node
* does not exist.
*/
public String getReferenceNoteString(long id, String key, long index)
{
Aast ans = getResolver().getAst(Long.valueOf(id));
if (ans == null)
return null;
Object a = ans.getAnnotation(key, (int)index);
return (a instanceof String ? (String) a : null);
}
/**
* Gets a <code>Long</code> annotation from the referenced node
* in the <em>copy</em> AST.
*
* @param id
* The unique ID that references a specific node in the tree.
* @param key
* Case-insensitive annotation key.
*
* @return The desired annotation value, or <code>null</code> if
* either the annotation or the referenced node does not
* exist.
*/
public Long getReferenceNoteLong(long id, String key)
{
Aast ans = getResolver().getAst(Long.valueOf(id));
if (ans == null)
return null;
Object a = ans.getAnnotation(key);
return (a instanceof Long ? (Long) a : null);
}
/**
* Gets a <code>Long</code> annotation at a specific index from an
* array annotation in the referenced node of the current
* <em>copy</em> AST.
*
* @param id
* The unique ID that references a specific node in the tree.
* @param key
* Case-insensitive annotation key.
* @param index
* The index of -1 indicates the last element of the array.
* Otherwise this is the explicit zero-based index at which to
* find the annotation.
*
* @return The annotation object stored under the specified key and at
* the specified index, or <code>null</code> if the annotation
* doesn't exist, if the annotation is not an array, if no
* element exists at the given index or the referenced node
* does not exist.
*/
public Long getReferenceNoteLong(long id, String key, long index)
{
Aast ans = getResolver().getAst(Long.valueOf(id));
if (ans == null)
return null;
Object a = ans.getAnnotation(key, (int)index);
return (a instanceof Long ? (Long) a : null);
}
/**
* Gets a <code>Double</code> annotation from the referenced node
* in the <em>copy</em> AST.
*
* @param id
* The unique ID that references a specific node in the tree.
* @param key
* Case-insensitive annotation key.
*
* @return The desired annotation value, or <code>null</code> if
* either the annotation or the referenced node does not
* exist.
*/
public Double getReferenceNoteDouble(long id, String key)
{
Aast ans = getResolver().getAst(Long.valueOf(id));
if (ans == null)
return null;
Object a = ans.getAnnotation(key);
return (a instanceof Double ? (Double) a : null);
}
/**
* Gets a <code>Double</code> annotation at a specific index from an
* array annotation in the referenced node of the current
* <em>copy</em> AST.
*
* @param id
* The unique ID that references a specific node in the tree.
* @param key
* Case-insensitive annotation key.
* @param index
* The index of -1 indicates the last element of the array.
* Otherwise this is the explicit zero-based index at which to
* find the annotation.
*
* @return The annotation object stored under the specified key and at
* the specified index, or <code>null</code> if the annotation
* doesn't exist, if the annotation is not an array, if no
* element exists at the given index or the referenced node
* does not exist.
*/
public Double getReferenceNoteDouble(long id, String key, long index)
{
Aast ans = getResolver().getAst(Long.valueOf(id));
if (ans == null)
return null;
Object a = ans.getAnnotation(key, (int)index);
return (a instanceof Double ? (Double) a : null);
}
/**
* Gets a <code>Boolean</code> annotation from the referenced node
* in the <em>copy</em> AST.
*
* @param id
* The unique ID that references a specific node in the tree.
* @param key
* Case-insensitive annotation key.
*
* @return The desired annotation value, or <code>null</code> if
* either the annotation or the referenced node does not
* exist.
*/
public Boolean getReferenceNoteBoolean(long id, String key)
{
Aast ans = getResolver().getAst(Long.valueOf(id));
if (ans == null)
return null;
Object a = ans.getAnnotation(key);
return (a instanceof Boolean ? (Boolean) a : null);
}
/**
* Gets a <code>Boolean</code> annotation at a specific index from an
* array annotation in the referenced node of the current
* <em>copy</em> AST.
*
* @param id
* The unique ID that references a specific node in the tree.
* @param key
* Case-insensitive annotation key.
* @param index
* The index of -1 indicates the last element of the array.
* Otherwise this is the explicit zero-based index at which to
* find the annotation.
*
* @return The annotation object stored under the specified key and at
* the specified index, or <code>null</code> if the annotation
* doesn't exist, if the annotation is not an array, if no
* element exists at the given index or the referenced node
* does not exist.
*/
public Boolean getReferenceNoteBoolean(long id, String key, long index)
{
Aast ans = getResolver().getAst(Long.valueOf(id));
if (ans == null)
return null;
Object a = ans.getAnnotation(key, (int)index);
return (a instanceof Boolean ? (Boolean) a : null);
}
/**
* Store an annotation of type <code>String</code> in the current
* <em>copy</em> AST. If an annotation exists with the identical key,
* it is replaced.
*
* @param key
* Case-insensitive annotation key.
* @param value
* Annotation data.
*/
public boolean putNote(String key, String value)
{
getCopy().putAnnotation(key, value);
return true;
}
/**
* Store an annotation of type <code>String</code> at the specified
* index in an array annotation in the current <em>copy</em> AST.
*
* @param key
* Case-insensitive annotation key.
* @param value
* Annotation data.
* @param index
* The index of -1 indicates the last element of the array.
* Otherwise this is the explicit zero-based index at which to
* place the annotation.
*/
public boolean putNote(String key, String value, long index)
{
getCopy().putAnnotation(key, value, (int)index);
return true;
}
/**
* Store an annotation of type <code>Long</code> in the current
* <em>copy</em> AST. If an annotation exists with the identical key,
* it is replaced.
*
* @param key
* Case-insensitive annotation key.
* @param value
* Annotation data.
*/
public boolean putNote(String key, Long value)
{
getCopy().putAnnotation(key, value);
return true;
}
/**
* Store an annotation of type <code>Long</code> at the specified
* index in an array annotation in the current <em>copy</em> AST.
*
* @param key
* Case-insensitive annotation key.
* @param value
* Annotation data.
* @param index
* The index of -1 indicates the last element of the array.
* Otherwise this is the explicit zero-based index at which to
* place the annotation.
*/
public boolean putNote(String key, Long value, long index)
{
getCopy().putAnnotation(key, value, (int)index);
return true;
}
/**
* Store an annotation of type <code>Double</code> in the current
* <em>copy</em> AST. If an annotation exists with the identical key,
* it is replaced.
*
* @param key
* Case-insensitive annotation key.
* @param value
* Annotation data.
*/
public boolean putNote(String key, Double value)
{
getCopy().putAnnotation(key, value);
return true;
}
/**
* Store an annotation of type <code>Double</code> at the specified
* index in an array annotation in the current <em>copy</em> AST.
*
* @param key
* Case-insensitive annotation key.
* @param value
* Annotation data.
* @param index
* The index of -1 indicates the last element of the array.
* Otherwise this is the explicit zero-based index at which to
* place the annotation.
*/
public boolean putNote(String key, Double value, long index)
{
getCopy().putAnnotation(key, value, (int)index);
return true;
}
/**
* Store an annotation of type <code>Boolean</code> in the current
* <em>copy</em> AST. If an annotation exists with the identical key,
* it is replaced.
*
* @param key
* Case-insensitive annotation key.
* @param value
* Annotation data.
*/
public boolean putNote(String key, Boolean value)
{
getCopy().putAnnotation(key, value);
return true;
}
/**
* Store an annotation of type <code>Boolean</code> at the specified
* index in an array annotation in the current <em>copy</em> AST.
*
* @param key
* Case-insensitive annotation key.
* @param value
* Annotation data.
* @param index
* The index of -1 indicates the last element of the array.
* Otherwise this is the explicit zero-based index at which to
* place the annotation.
*/
public boolean putNote(String key, Boolean value, long index)
{
getCopy().putAnnotation(key, value, (int)index);
return true;
}
/**
* Remove an annotation from the current <em>copy</em> AST.
*
* @param key
* Case-insensitive annotation key.
*
* @return <code>true</code> if an entry for <code>key</code> was
* found and removed; <code>false</code> if none was found.
*/
public boolean removeNote(String key)
{
return (getCopy().removeAnnotation(key) != null);
}
/**
* Remove an element (at the specified index) of an array annotation
* from an annotation specified by the key in the current <em>copy</em>
* AST. The special index of -1 indicates the removal of the last
* element of the array.
*
* @param key
* Case-insensitive annotation key.
* @param index
* The index of -1 indicates the last element of the array.
* Otherwise this is the explicit zero-based index at which to
* remove the annotation.
*
* @return <code>true</code> if an entry for <code>key</code> was
* found and removed; <code>false</code> if none was found,
* if the annotation is not an array or if no element exists
* at the given index.
*/
public boolean removeNote(String key, long index)
{
return (getCopy().removeAnnotation(key, (int)index) != null);
}
/**
* Store an annotation of type <code>String</code> in the specified
* ancestor in the <em>copy</em> AST. If an annotation exists with
* the identical key, it is replaced.
*
* @param level
* Number of generations up the tree to walk to obtain the
* ancestor. Set to <code>-1</code> to obtain the tree root,
* <code>0</code> is the current node, <code>1</code> is the
* parent, <code>2</code> is the 'grandparent' and so forth.
* @param key
* Case-insensitive annotation key.
* @param value
* Annotation data.
*
* @return <code>true</code> if the annotation was added,
* <code>false</code> if no such ancestor was found.
*/
public boolean putAncestorNote(long level, String key, String value)
{
Aast ans = getCopy().getAncestor((int)level);
if (ans == null)
return false;
ans.putAnnotation(key, value);
return true;
}
/**
* Store an element in an array annotation of type <code>String</code>
* at the specified index in the specified ancestor in the <em>copy</em>
* AST. The special index of -1 indicates the add as the last element
* of the array.
*
* @param level
* Number of generations up the tree to walk to obtain the
* ancestor. Set to <code>-1</code> to obtain the tree root,
* <code>0</code> is the current node, <code>1</code> is the
* parent, <code>2</code> is the 'grandparent' and so forth.
* @param key
* Case-insensitive annotation key.
* @param value
* Annotation data.
* @param index
* The index of -1 indicates the last element of the array.
* Otherwise this is the explicit zero-based index at which to
* insert the annotation.
*
* @return <code>true</code> if the annotation was added,
* <code>false</code> if no such ancestor was found.
*/
public boolean putAncestorNote(long level, String key, String value, long index)
{
Aast ans = getCopy().getAncestor((int)level);
if (ans == null)
return false;
ans.putAnnotation(key, value, (int)index);
return true;
}
/**
* Store an annotation of type <code>Long</code> in the specified
* ancestor in the <em>copy</em> AST. If an annotation exists with
* the identical key, it is replaced.
*
* @param level
* Number of generations up the tree to walk to obtain the
* ancestor. Set to <code>-1</code> to obtain the tree root,
* <code>0</code> is the current node, <code>1</code> is the
* parent, <code>2</code> is the 'grandparent' and so forth.
* @param key
* Case-insensitive annotation key.
* @param value
* Annotation data.
*
* @return <code>true</code> if the annotation was added,
* <code>false</code> if no such ancestor was found.
*/
public boolean putAncestorNote(long level, String key, Long value)
{
Aast ans = getCopy().getAncestor((int)level);
if (ans == null)
return false;
ans.putAnnotation(key, value);
return true;
}
/**
* Store an element in an array annotation of type <code>Long</code>
* at the specified index in the specified ancestor in the <em>copy</em>
* AST. The special index of -1 indicates the add as the last element
* of the array.
*
* @param level
* Number of generations up the tree to walk to obtain the
* ancestor. Set to <code>-1</code> to obtain the tree root,
* <code>0</code> is the current node, <code>1</code> is the
* parent, <code>2</code> is the 'grandparent' and so forth.
* @param key
* Case-insensitive annotation key.
* @param value
* Annotation data.
* @param index
* The index of -1 indicates the last element of the array.
* Otherwise this is the explicit zero-based index at which to
* insert the annotation.
*
* @return <code>true</code> if the annotation was added,
* <code>false</code> if no such ancestor was found.
*/
public boolean putAncestorNote(long level, String key, Long value, long index)
{
Aast ans = getCopy().getAncestor((int)level);
if (ans == null)
return false;
ans.putAnnotation(key, value, (int)index);
return true;
}
/**
* Store an annotation of type <code>Double</code> in the specified
* ancestor in the <em>copy</em> AST. If an annotation exists with
* the identical key, it is replaced.
*
* @param level
* Number of generations up the tree to walk to obtain the
* ancestor. Set to <code>-1</code> to obtain the tree root,
* <code>0</code> is the current node, <code>1</code> is the
* parent, <code>2</code> is the 'grandparent' and so forth.
* @param key
* Case-insensitive annotation key.
* @param value
* Annotation data.
*
* @return <code>true</code> if the annotation was added,
* <code>false</code> if no such ancestor was found.
*/
public boolean putAncestorNote(long level, String key, Double value)
{
Aast ans = getCopy().getAncestor((int)level);
if (ans == null)
return false;
ans.putAnnotation(key, value);
return true;
}
/**
* Store an element in an array annotation of type <code>Double</code>
* at the specified index in the specified ancestor in the <em>copy</em>
* AST. The special index of -1 indicates the add as the last element
* of the array.
*
* @param level
* Number of generations up the tree to walk to obtain the
* ancestor. Set to <code>-1</code> to obtain the tree root,
* <code>0</code> is the current node, <code>1</code> is the
* parent, <code>2</code> is the 'grandparent' and so forth.
* @param key
* Case-insensitive annotation key.
* @param value
* Annotation data.
* @param index
* The index of -1 indicates the last element of the array.
* Otherwise this is the explicit zero-based index at which to
* insert the annotation.
*
* @return <code>true</code> if the annotation was added,
* <code>false</code> if no such ancestor was found.
*/
public boolean putAncestorNote(long level, String key, Double value, long index)
{
Aast ans = getCopy().getAncestor((int)level);
if (ans == null)
return false;
ans.putAnnotation(key, value, (int)index);
return true;
}
/**
* Store an annotation of type <code>Boolean</code> in the specified
* ancestor in the <em>copy</em> AST. If an annotation exists with
* the identical key, it is replaced.
*
* @param level
* Number of generations up the tree to walk to obtain the
* ancestor. Set to <code>-1</code> to obtain the tree root,
* <code>0</code> is the current node, <code>1</code> is the
* parent, <code>2</code> is the 'grandparent' and so forth.
* @param key
* Case-insensitive annotation key.
* @param value
* Annotation data.
*
* @return <code>true</code> if the annotation was added,
* <code>false</code> if no such ancestor was found.
*/
public boolean putAncestorNote(long level, String key, Boolean value)
{
Aast ans = getCopy().getAncestor((int)level);
if (ans == null)
return false;
ans.putAnnotation(key, value);
return true;
}
/**
* Store an element in an array annotation of type <code>Boolean</code>
* at the specified index in the specified ancestor in the <em>copy</em>
* AST. The special index of -1 indicates the add as the last element
* of the array.
*
* @param level
* Number of generations up the tree to walk to obtain the
* ancestor. Set to <code>-1</code> to obtain the tree root,
* <code>0</code> is the current node, <code>1</code> is the
* parent, <code>2</code> is the 'grandparent' and so forth.
* @param key
* Case-insensitive annotation key.
* @param value
* Annotation data.
* @param index
* The index of -1 indicates the last element of the array.
* Otherwise this is the explicit zero-based index at which to
* insert the annotation.
*
* @return <code>true</code> if the annotation was added,
* <code>false</code> if no such ancestor was found.
*/
public boolean putAncestorNote(long level,
String key,
Boolean value,
long index)
{
Aast ans = getCopy().getAncestor((int)level);
if (ans == null)
return false;
ans.putAnnotation(key, value, (int)index);
return true;
}
/**
* Remove an annotation from the specified ancestor in the
* <em>copy</em> AST.
*
* @param level
* Number of generations up the tree to walk to obtain the
* ancestor. Set to <code>-1</code> to obtain the tree root,
* <code>0</code> is the current node, <code>1</code> is the
* parent, <code>2</code> is the 'grandparent' and so forth.
* @param key
* Case-insensitive annotation key.
*
* @return <code>true</code> if an entry for <code>key</code> was
* found and removed; <code>false</code> if no such annotation
* or no such ancestor was found.
*/
public boolean removeAncestorNote(long level, String key)
{
Aast ans = getCopy().getAncestor((int)level);
if (ans == null)
return false;
return (ans.removeAnnotation(key) != null);
}
/**
* Remove an element (at the specified index) of an array annotation
* from an annotation specified by the key in the specified ancestor of
* the current <em>copy</em> AST. The special index of -1 indicates the
* removal of the last element of the array.
*
* @param level
* Number of generations up the tree to walk to obtain the
* ancestor. Set to <code>-1</code> to obtain the tree root,
* <code>0</code> is the current node, <code>1</code> is the
* parent, <code>2</code> is the 'grandparent' and so forth.
* @param key
* Case-insensitive annotation key.
* @param index
* The index of -1 indicates the last element of the array.
* Otherwise this is the explicit zero-based index at which to
* remove the annotation.
*
* @return <code>true</code> if an entry for <code>key</code> was
* found and removed; <code>false</code> if none was found,
* if the annotation is not an array or if no element exists
* at the given index or if the specified ancestor does not
* exist.
*/
public boolean removeAncestorNote(long level, String key, long index)
{
Aast ans = getCopy().getAncestor((int)level);
if (ans == null)
return false;
return (ans.removeAnnotation(key, (int)index) != null);
}
/**
* Store an annotation of type <code>String</code> in the referenced
* node in the <em>copy</em> AST. If an annotation exists with
* the identical key, it is replaced.
*
* @param id
* The unique ID that references a specific node in the tree.
* @param key
* Case-insensitive annotation key.
* @param value
* Annotation data.
*
* @return <code>true</code> if the annotation was added,
* <code>false</code> if no such node was found.
*/
public boolean putReferenceNote(long id, String key, String value)
{
Aast ans = getResolver().getAst(Long.valueOf(id));
if (ans == null)
return false;
ans.putAnnotation(key, value);
return true;
}
/**
* Store an element in an array annotation of type <code>String</code>
* at the specified index in the referenced node in the <em>copy</em>
* AST. The special index of -1 indicates the add as the last element
* of the array.
*
* @param id
* The unique ID that references a specific node in the tree.
* @param key
* Case-insensitive annotation key.
* @param value
* Annotation data.
* @param index
* The index of -1 indicates the last element of the array.
* Otherwise this is the explicit zero-based index at which to
* insert the annotation.
*
* @return <code>true</code> if the annotation was added,
* <code>false</code> if no such node was found.
*/
public boolean putReferenceNote(long id,
String key,
String value,
long index)
{
Aast ans = getResolver().getAst(Long.valueOf(id));
if (ans == null)
return false;
ans.putAnnotation(key, value, (int)index);
return true;
}
/**
* Store an annotation of type <code>Long</code> in the referenced
* node in the <em>copy</em> AST. If an annotation exists with
* the identical key, it is replaced.
*
* @param id
* The unique ID that references a specific node in the tree.
* @param key
* Case-insensitive annotation key.
* @param value
* Annotation data.
*
* @return <code>true</code> if the annotation was added,
* <code>false</code> if no such node was found.
*/
public boolean putReferenceNote(long id, String key, Long value)
{
Aast ans = getResolver().getAst(Long.valueOf(id));
if (ans == null)
return false;
ans.putAnnotation(key, value);
return true;
}
/**
* Store an element in an array annotation of type <code>Long</code>
* at the specified index in the referenced node in the <em>copy</em>
* AST. The special index of -1 indicates the add as the last element
* of the array.
*
* @param id
* The unique ID that references a specific node in the tree.
* @param key
* Case-insensitive annotation key.
* @param value
* Annotation data.
* @param index
* The index of -1 indicates the last element of the array.
* Otherwise this is the explicit zero-based index at which to
* insert the annotation.
*
* @return <code>true</code> if the annotation was added,
* <code>false</code> if no such node was found.
*/
public boolean putReferenceNote(long id,
String key,
Long value,
long index)
{
Aast ans = getResolver().getAst(Long.valueOf(id));
if (ans == null)
return false;
ans.putAnnotation(key, value, (int)index);
return true;
}
/**
* Store an annotation of type <code>Double</code> in the referenced
* node in the <em>copy</em> AST. If an annotation exists with
* the identical key, it is replaced.
*
* @param id
* The unique ID that references a specific node in the tree.
* @param key
* Case-insensitive annotation key.
* @param value
* Annotation data.
*
* @return <code>true</code> if the annotation was added,
* <code>false</code> if no such node was found.
*/
public boolean putReferenceNote(long id, String key, Double value)
{
Aast ans = getResolver().getAst(Long.valueOf(id));
if (ans == null)
return false;
ans.putAnnotation(key, value);
return true;
}
/**
* Store an element in an array annotation of type <code>Double</code>
* at the specified index in the referenced node in the <em>copy</em>
* AST. The special index of -1 indicates the add as the last element
* of the array.
*
* @param id
* The unique ID that references a specific node in the tree.
* @param key
* Case-insensitive annotation key.
* @param value
* Annotation data.
* @param index
* The index of -1 indicates the last element of the array.
* Otherwise this is the explicit zero-based index at which to
* insert the annotation.
*
* @return <code>true</code> if the annotation was added,
* <code>false</code> if no such node was found.
*/
public boolean putReferenceNote(long id,
String key,
Double value,
long index)
{
Aast ans = getResolver().getAst(Long.valueOf(id));
if (ans == null)
return false;
ans.putAnnotation(key, value, (int)index);
return true;
}
/**
* Store an annotation of type <code>Boolean</code> in the referenced
* node in the <em>copy</em> AST. If an annotation exists with
* the identical key, it is replaced.
*
* @param id
* The unique ID that references a specific node in the tree.
* @param key
* Case-insensitive annotation key.
* @param value
* Annotation data.
*
* @return <code>true</code> if the annotation was added,
* <code>false</code> if no such node was found.
*/
public boolean putReferenceNote(long id, String key, Boolean value)
{
Aast ans = getResolver().getAst(Long.valueOf(id));
if (ans == null)
return false;
ans.putAnnotation(key, value);
return true;
}
/**
* Store an element in an array annotation of type <code>Boolean</code>
* at the specified index in the referenced node in the <em>copy</em>
* AST. The special index of -1 indicates the add as the last element
* of the array.
*
* @param id
* The unique ID that references a specific node in the tree.
* @param key
* Case-insensitive annotation key.
* @param value
* Annotation data.
* @param index
* The index of -1 indicates the last element of the array.
* Otherwise this is the explicit zero-based index at which to
* insert the annotation.
*
* @return <code>true</code> if the annotation was added,
* <code>false</code> if no such node was found.
*/
public boolean putReferenceNote(long id,
String key,
Boolean value,
long index)
{
Aast ans = getResolver().getAst(Long.valueOf(id));
if (ans == null)
return false;
ans.putAnnotation(key, value, (int)index);
return true;
}
/**
* Remove an annotation from the referenced node in the
* <em>copy</em> AST.
*
* @param id
* The unique ID that references a specific node in the tree.
* @param key
* Case-insensitive annotation key.
*
* @return <code>true</code> if an entry for <code>key</code> was
* found and removed; <code>false</code> if no such annotation
* or no such node was found.
*/
public boolean removeReferenceNote(long id, String key)
{
Aast ans = getResolver().getAst(Long.valueOf(id));
if (ans == null)
return false;
return (ans.removeAnnotation(key) != null);
}
/**
* Remove an element (at the specified index) of an array annotation
* from an annotation referenced by the key in the referenced node of
* the current <em>copy</em> AST. The special index of -1 indicates the
* removal of the last element of the array.
*
* @param id
* The unique ID that references a specific node in the tree.
* @param key
* Case-insensitive annotation key.
* @param index
* The index of -1 indicates the last element of the array.
* Otherwise this is the explicit zero-based index at which to
* remove the annotation.
*
* @return <code>true</code> if an entry for <code>key</code> was
* found and removed; <code>false</code> if none was found,
* if the annotation is not an array or if no element exists
* at the given index or if the referenced node does not
* exist.
*/
public boolean removeReferenceNote(long id, String key, long index)
{
Aast ans = getResolver().getAst(Long.valueOf(id));
if (ans == null)
return false;
return (ans.removeAnnotation(key, (int)index) != null);
}
/**
* Retrieve value of specified configuration variable.
*
* @param id
* Configuration variable ID.
*
* @return configuration parameter value.
*/
public String getConfigParameter(String id)
{
return Configuration.getParameter(id);
}
/**
* Save all the 4GL classes (builtin or business logic), .NET or Java classes encountered
* during parsing.
*/
public void saveClasses()
{
SymbolResolver.saveClasses();
}
/**
* Retrieve value of specified configuration variable.
*
* @param id
* Configuration variable ID.
* @param def
* Default value for the variable.
*
* @return configuration parameter value.
*/
public String getConfigParameter(String id, String def)
{
return Configuration.getParameter(id, def);
}
/**
* Retrieve value of specified configuration variable.
*
* @param id
* Configuration variable ID.
* @param def
* Default value for the variable.
*
* @return configuration parameter value.
*/
public Boolean getConfigParameter(String id, boolean def)
{
return Configuration.getParameter(id, def);
}
/**
* Determines whether the {@link Configuration} is in conversion mode (configuration is read
* from the configuration directory) or server run time mode.
*
* @return <code>true</code> if it is a server run time mode (when configuration is NOT read
* from the configuration directory).
*/
public boolean isRuntimeConfig()
{
return Configuration.isRuntimeConfig();
}
/**
* Check whether the extent properties in dynamic temp-tables should be denormalized. When this is
* enabled , the extent fields will NOT use secondary tables for SQL storage. <p>
*
* @return {@code true} if the extent properties in dynamic temp-tables should be denormalized.
*/
public boolean isDenormalizeDynamicTempTables()
{
return Configuration.isRuntimeConfig() && TableHints.isDenormalizeDynamicTempTables();
}
/**
* If conditional rule-set is enabled, check if the given flag is activated.
*
* @param flag
* The flag to check.
*
* @return See above.
*
* @see PatternEngine#isFlagActivated(String)
*/
public boolean isFlagActivated(String flag)
{
return CommonAstSupport.this.isFlagActivated(flag);
}
/**
* Check if conditional rule-sets is activated.
*
* @return See above.
*
* @see PatternEngine#isConditionalRuleSets
*/
public boolean isConditionalRuleSets()
{
return CommonAstSupport.this.isConditionalRuleSets();
}
/**
* Check if the current mode is for runtime-conversion of query/tables.
*
* @return See above.
*/
public boolean isRuntimeQueryMode()
{
return CommonAstSupport.this.isRuntimeQueryMode();
}
/**
* Determine whether the token type of the specified node matches any
* of the given types.
*
* @param node
* An AST.
* @param types
* Array of <code>Integer</code>s representing valid token
* types. May be empty, must not be <code>null</code>.
*
* @return <code>true</code> if a match is detected, else
* <code>false</code>.
*
* @throws ClassCastException
* if <code>types</code> contains any object type other than
* <code>Integer</code>.
*/
private boolean matchTokenType(Aast node, Object[] types)
{
int len = types.length;
int type = node.getType();
for (int i = 0; i < len; i++)
{
Number ttype = (Number) types[i];
if (ttype.intValue() == type)
{
return true;
}
}
return false;
}
/**
* Fixing the regular expression string for special Windows requirement.
* Now it solves the backslash issue in Windows Java API call involving
* regular expression string. Does nothing if OS other than Windows.
*
* @param text
* Regular expression string to be adjusted acccording to
* OS special requirements.
*
* @return Specially modified regular expression string. Now doubles
* the backslash character containing in the input string.
*/
public String fixupRegex(String text)
{
return StringHelper.fixupRegex(text);
}
/**
* Fixing the file name for special Windows requirement. Now it
* substitutes the possible '/' character with backslash '\' to
* accept to Windows filename requirements. Does nothing if OS other
* than Windows.
*
* @param text
* The string representing the filename with path separator.
*
* @return Specially modified file name string. The Linux hardcoded
* '/' character replaced with '\' if current OS is Windows.
*/
public String fixupFileName(String text)
{
return StringHelper.fixupFileName(text);
}
/**
* Calling the java.util.regex.Matcher.quoteReplacement().
*
* @param text
* String to replace special characters. An examples: \ or $.
*
* @return The new string with added characters according to respecive
* Java API call documentation.
*/
public String quoteReplacement(String text)
{
return java.util.regex.Matcher.quoteReplacement(text);
}
/**
* Store the given object under the given id. Can be retrieved later using
* {@link PatternEngine#getStoredObject(String)}.
*
* @param id
* Arbitrary object id.
* @param object
* Object to store.
*/
public void storeObject(String id, Object object)
{
getResolver().getPatternEngine().storeObject(id, object);
}
/**
* Store the given object under the given category and id. Can be retrieved later using
* {@link PatternEngine#getStoredObject(String, String)}.
*
* @param category
* Arbitrary object category.
* @param id
* Arbitrary object id.
* @param object
* Object to store.
*/
public void storeObject(String category, String id, Object object)
{
getResolver().getPatternEngine().storeObject(category, id, object);
}
/**
* Get the object stored by the pattern engine under the given id.
*
* @param id
* Object's identifier.
*
* @return Stored object, or <code>null</code> if none.
*/
public Object getStoredObject(String id)
{
return getResolver().getPatternEngine().getStoredObject(id);
}
/**
* Get the object stored by the pattern engine under the given category and id.
*
* @param id
* Object's identifier.
*
* @return Stored object, or <code>null</code> if none.
*/
public Object getStoredObject(String category, String id)
{
return getResolver().getPatternEngine().getStoredObject(category, id);
}
/**
* Opens a new in-memory stream for writing, associates an unique ID with that stream and
* stores it (can be retrieved later using {@link PatternEngine#getStoredObject(String)}).
* Note that {@link ByteArrayOutputStream} is stored under the given name, then this stream
* is wrapped with {@link ByteCountOutputStream} and the ID of {@link ByteCountOutputStream}
* is returned for using in rules pipeline.
*
* @param name
* Unique name of this stream under which it will be stored. Doesn't match returned
* ID.
*
* @return the unique ID associated with this stream.
*/
public Long openStoredStream(String name)
{
ByteArrayOutputStream bos = new ByteArrayOutputStream();
// init our counting stream wrapper
ByteCountOutputStream out = new ByteCountOutputStream(bos);
WorkArea wa = context.get();
Long id = wa.nextId;
wa.streams.put(id, out);
wa.nextId++;
storeObject(name, bos);
return id;
}
/**
* Check if two nodes are equivalent (if they represent the same expression so they will
* probably generate the same code. The nodes and their subnodes are walked recursively DFS
* and stop at first difference, if any.
* <p>
* This feature was introduced because of the lack of support for recursive call in TRPL.
* At this moment only the type/text of the nodes and their subnodes are checked. Depending
* on necessities, the compare operation may be extended to annotations, too.
*
* @param node1
* An {@link Aast} node to be check.
* @param node2
* Another {@link Aast} node to be check.
*
* @return {@code true} if the nodes are equivalent
*/
public boolean compareNodes(Aast node1, Aast node2)
{
// before doing equivalence checks, test for equality
if (node1 == node2)
{
// TODO: confirm this assertion when both nodes are null
return true;
}
if (node1 == null || node2 == null)
{
// one is null, the other isn't
return false;
}
// check primary properties
if (node1.getType() != node2.getType() ||
!node1.getText().equals(node2.getText()) ||
node1.getNumImmediateChildren() != node2.getNumImmediateChildren())
{
return false;
}
// if the nodes represent a denormalized extent field then the idx is no more a subnode,
// we need to check the annotations
if (node1.isAnnotation("extent"))
{
// the up-path are the same, so if the nodes represent the same node, the "extent"
// annotation should also a match
if (node1.getAnnotation("extent_elem_idx") != node2.getAnnotation("extent_elem_idx"))
{
// same EXTENT field, but different index, the nodes are different
return false;
}
}
// recursively check children
Aast subnode1 = (Aast) node1.getFirstChild();
Aast subnode2 = (Aast) node2.getFirstChild();
while (subnode1 != null)
{
if (!compareNodes(subnode1, subnode2))
{
return false; // there was a difference down the tree
}
subnode1 = (Aast) subnode1.getNextSibling();
subnode2 = (Aast) subnode2.getNextSibling();
}
// if no differences found:
return true;
}
/**
* Mark this file 'with_services'.
*
* @param filename
* The file name.
*/
public void withServicesFile(String filename)
{
if (isRuntimeConfig())
{
return;
}
ConversionData.withServicesFile(filename);
}
/**
* Create a db-backed map, with a {@link String} key and an {@link Integer} value.
* <p>
* For runtime conversion, a simple {@link HashMap} will be returned.
*
* @return See above.
*
* @see ConversionData#createMap
*/
public Map<?, ?> createString2IntMap(String name)
{
return isRuntimeConfig()
? new HashMap<>()
: ConversionData.createMap(String.class, Integer.class, name);
}
/**
* Create a db-backed map, with a {@link String} key and a {@link Long} value.
* <p>
* For runtime conversion, a simple {@link HashMap} will be returned.
*
* @return See above.
*
* @see ConversionData#createMap
*/
public Map<?, ?> createString2LongMap(String name)
{
return isRuntimeConfig()
? new HashMap<>()
: ConversionData.createMap(String.class, Long.class, name);
}
/**
* Create a db-backed map, with a {@link String} key and a {@link String} value.
* <p>
* For runtime conversion, a simple {@link HashMap} will be returned.
*
* @return See above.
*
* @see ConversionData#createMap
*/
public Map<?, ?> createString2StringMap(String name)
{
return isRuntimeConfig()
? new HashMap<>()
: ConversionData.createMap(String.class, String.class, name);
}
/**
* Create a db-backed set, with a {@link String} key.
* <p>
* For runtime conversion, a simple {@link TreeSet} will be returned.
*
* @return See above.
*
* @see ConversionData#createSet
*/
public Set<?> createStringSet(String name)
{
return isRuntimeConfig()
? new TreeSet<>()
: ConversionData.createSet(String.class, name);
}
/**
* Create a db-backed map, with a {@link String} key and as values another map, having
* {@link String} for both key and value.
* <p>
* For runtime conversion, a simple {@link HashMap} will be returned.
*
* @return See above.
*
* @see ConversionData#createMapToMap
*/
public Map<?, ?> createStringMapToStringMap(String name)
{
return isRuntimeConfig()
? new HashMap<>()
: ConversionData.createMapToMap(String.class, String.class, String.class, name);
}
/**
* Create a db-backed map, with a {@link String} key and as values another set, having
* {@link String} for key.
* <p>
* For runtime conversion, a simple {@link HashMap} will be returned.
*
* @return See above.
*
* @see ConversionData#createMapToSet
*/
public Map<?, ?> createStringMapToStringSet(String name)
{
return isRuntimeConfig()
? new HashMap<>()
: ConversionData.createMapToSet(String.class, String.class, name);
}
/**
* Create a db-backed map, with a {@link String} key and as values another set, having
* {@link Long} for key.
* <p>
* For runtime conversion, a simple {@link HashMap} will be returned.
*
* @return See above.
*
* @see ConversionData#createMapToSet
*/
public Map<?, ?> createStringMapToLongSet(String name)
{
return isRuntimeConfig()
? new HashMap<>()
: ConversionData.createMapToSet(String.class, Long.class, name);
}
/**
* Create a db-backed map, with a {@link AstKey} key and {@link String} values.
* <p>
* For runtime conversion, a simple {@link HashMap} will be returned.
*
* @return See above.
*
* @see ConversionData#createMap
*/
public Map<?, ?> createMapAstKeyToString(String name)
{
return isRuntimeConfig()
? new HashMap<>()
: ConversionData.createMap(AstKey.class, String.class, name);
}
/**
* Create a db-backed map, with a {@link AstKey} key and {@link Integer} values.
* <p>
* For runtime conversion, a simple {@link HashMap} will be returned.
*
* @return See above.
*
* @see ConversionData#createMap
*/
public Map<?, ?> createMapAstKeyToInt(String name)
{
return isRuntimeConfig()
? new HashMap<>()
: ConversionData.createMap(AstKey.class, Integer.class, name);
}
/**
* Create a db-backed map, with a {@link FrameAstKey} key and {@link String} values.
* <p>
* For runtime conversion, a simple {@link HashMap} will be returned.
*
* @return See above.
*
* @see ConversionData#createMap
*/
public Map<?, ?> createMapFrameAstKeyToString(String name)
{
return isRuntimeConfig()
? new HashMap<>()
: ConversionData.createMap(FrameAstKey.class, String.class, name);
}
/**
* Create a db-backed map, with a {@link AstKey} key and {@link Aast} values.
* <p>
* For runtime conversion, a simple {@link HashMap} will be returned.
*
* @return See above.
*
* @see ConversionData#createMap
*/
public Map<?, ?> createMapAstKeyToAst(String name)
{
return isRuntimeConfig()
? new HashMap<>()
: ConversionData.createMap(AstKey.class, Aast.class, name);
}
/**
* For a given legacy class, set its converted Java name and package.
* .
* @param filename
* The associated file name.
* @param legacyName
* The legacy fully qualified name.
* @param pkgname
* The converted package name.
* @param classname
* The simple Java class name.
*/
public void setConvertedClassName(String filename,
String legacyName,
String pkgname,
String classname)
{
ClassDefinition cls = SymbolResolver.getClassDefinition(legacyName);
cls.setJavaClassName(pkgname, classname);
ConversionData.setConvertedClassName(filename, pkgname, classname);
}
/**
* For the given db-backed map (if not in runtime conversion mode), force a logged change
* with the given key and value, for the currently processed AST. This is required to keep
* a history of changes, when (for a certain key) the value is semantically the same, but
* not the same object (for example, a temp-table AST node is saved in p2o.xml for each
* authoritative definition, but we need to keep track of all temp-table ASTs, in case the
* authoritative program gets re-converted).
*
* @param map
* The db-backed map.
* @param key
* The key for which the value needs to be logged.
* @param value
* The value which needs to be logged.
*/
public void forceMapValue(Map<?, ?> map, Object key, Object value)
{
if (map instanceof H2Map)
{
((H2Map) map).forceChange(key, value);
}
}
/**
* Get the next value for the given sequence.
* <p>
* For runtime, it will use the {@code sequences} map to store them.
*
* @param name
* The sequence name.
*
* @return The next sequence value.
*/
public int nextSequenceValue(String name)
{
if (isRuntimeConfig())
{
WorkArea wa = context.get();
AtomicInteger seq = wa.sequences.get(name);
if (seq == null)
{
wa.sequences.put(name, seq = new AtomicInteger(0));
}
return seq.incrementAndGet();
}
return ConversionData.nextSequenceValue(name);
}
/**
* Get the simple java class names for all classes in this package (builtin or converted code).
*
* @param pkg
* The package name.
*
* @return The set of simple java class names.
*/
public Set<String> getAllClassesInPackage(String pkg)
{
if (pkg == null)
{
return Collections.emptySet();
}
Set<String> res = null;
if (pkg.endsWith(".*"))
{
String pack = pkg.substring(0, pkg.length() - ".*".length());
res = ConversionData.getClassesInPackage(pack);
}
else
{
res = new HashSet<>();
res.add(pkg);
}
return res;
}
/**
* Get the converted simple class name for the specified fully-qualified 4GL class name.
*
* @param qname
* The qualified class name.
*
* @return The simple Java class name or <code>null</code> if it was not found.
*/
public String getConvertedSimpleClassName(String qname)
{
return ConversionData.getConvertedSimpleClassName(qname);
}
/**
* Set the runtime annotation into the AST, using a numeric key.
* <p>
* The annotation is resolved via {@link CommonAstSupport#resolveRuntimeAnnotationKey}.
*
* @param node
* The ast node.
* @param key
* The string annotation.
* @param value
* The annotation value.
*/
public void setRuntimeAnnotation(Aast node, String key, Object value)
{
int runtimeKey = resolveRuntimeAnnotationKey(key);
node.setRuntimeAnnotation(runtimeKey, value);
}
/**
* Get the runtime annotation from the AST, using a numeric key.
*
* The annotation is resolved via {@link CommonAstSupport#resolveRuntimeAnnotationKey}.
*
* @param node
* The ast node.
* @param key
* The string annotation.
*
* @return The annotation value, <code>null</code> if is not yet set.
*/
public Object getRuntimeAnnotation(Aast node, String key)
{
int runtimeKey = resolveRuntimeAnnotationKey(key);
return node.getRuntimeAnnotation(runtimeKey);
}
}
/**
* Context local work area.
*/
private static class WorkArea
{
/** A map of in-memory sequences. */
private final Map<String, AtomicInteger> sequences = new HashMap<>();
/** Cache of found classes for object instantiation */
private final Map<String, Class<?>> classCache = new HashMap<>();
/** Cache of resolved constructors by their class and signature. */
private final Map<Class<?>, Map<String, Constructor<?>>> ctorCache = new HashMap<>();
/** Stores all open streams. */
private Map<Long, ByteCountOutputStream> streams = new HashMap<>();
/** Stores the next available ID for streams. */
private long nextId = 0;
}
}