AstSymbolResolver.java
/*
** Module : AstSymbolResolver.java
** Abstract : Symbol resolver which is aware of AST-related issues
**
** Copyright (c) 2005-2023, Golden Code Development Corporation.
**
** -#- -I- --Date-- --JPRM-- ----------------------------Description-----------------------------
** 001 ECF 20050301 @20135 Created initial version. Implementation of a
** symbol resolver which is aware of AST-related
** issues, such as hierarchy structure, named
** expression libraries, programming language
** tokens, etc.
** 002 ECF 20050310 @20273 Added support for literal resolution. This is
** a new requirement of the expression parser.
** 003 ECF 20050314 @20324 Added user variable support. Implementations
** of the various resolveToXxxx methods are now
** provided, which first check the user variable
** pool before reverting to the parent class'
** implementation.
** 004 GES 20050316 @20350 Added support for addParentToView() and
** addAncestorToView().
** 005 ECF 20050316 @20415 Removed obsolete call to AstWalker.iterator
** method.
** 006 ECF 20050318 @20441 Added methods to get/set text of condition
** currently being evaluated.
** 007 ECF 20050328 @20516 Added functionality to support target (i.e.,
** converted code) ASTs. Changed getTargetAst
** method to getCopyAst, to more accurately
** reflect the nature of the AST being accessed.
** Added methods to set and get the root target
** AST and to access ASTs by their IDs.
** 008 GES 20050405 @20649 Added support for adding an arbitrary AST to
** the view based on its ID as long as it is in
** the ID map.
** 009 ECF 20050422 @20850 Changed resolveToLiteral method signature. It
** now accepts an optional namespace parameter.
** This enables us to prevent collisions between
** pattern workers when resolving constants, if
** multiple workers can resolve a constant to
** potentially different literals.
** 010 GES 20050425 @20864 Target AST nodes are being removed from the
** registry between rule-sets, but this is a
** "bad thing" as it would require persisting
** and reloading at the rule-set level rather
** than at the pipeline as is more natural.
** As a quick solution, the setAsts() method
** that rebuilds the registry (with the source
** ASTs) will now add the target AST nodes to
** the registry too, if the target root is not
** null. This is not optimal since it would be
** better to only clear these out at the end
** of the pipeline but that will require a
** different overall approach.
** 011 GES 20050428 @20929 Converted setAsts() to register the copy
** AST rather than the source AST so that any
** workers or rule-sets that lookup by ID will
** get the copy rather than the supposedly
** read-only source AST.
** 012 ECF 20040430 @20967 Changes related to namespace support for user
** functions and variables. Moved all pattern
** worker management from the PatternEngine.
** Constant resolution is now also handled by
** the resolver.
** 013 ECF 20050523 @21298 Adapted to use new expression engine. Removed
** all variable pool and expression library
** processing. Removed obsolete methods and
** added scope-relevant methods.
** 014 GES 20050601 @21359 Added support for named expressions that take
** a parameter list.
** 015 GES 20050602 @21378 Remove hard-coded check for named
** expressions using the engine and instead
** allow the new nested structure of the
** rule containers resolve this walking up
** the scopes until the engine is encountered.
** 016 GES 20050602 @21387 Converted NamedExpression to NamedFunction.
** 017 ECF 20050604 @21414 Added static createExpression method. Creates
** new Expression instances and sets runtime
** flags consistent with current debug level.
** 018 ECF 20050607 @21434 Change required by stack overflow fix to
** RuleContainer. Pass a null 'omit' argument to
** RuleContainer.getFunctionContainer method.
** 019 ECF 20050608 @21456 Fix to resolveConstant method. Was allowing
** resolution within the wrong namespace for a
** qualified constant.
** 020 GES 20050613 @21475 Added next child index support. Also fixed
** minor issue with execute(text, args).
** 021 ECF 20050615 @21502 Allow create method to be called more than
** once per process. On second and subsequent
** invocations, the existing singleton instance
** is returned. This relaxation was necessary
** for call graph processing.
** 022 ECF 20050720 @21745 Fixed defect which corrupted function scope.
** When invoking a function from within another
** function, return of the inner function nulled
** out the function scope (and resolving
** container), which corrupted scope for the
** enclosing function. Reimplemented function
** scope and resolving container variables as
** stacks.
** 023 ECF 20050728 @21972 Support caller scope change to NamedFunction.
** Rule container currently in scope is passed
** NamedFunction.execute as the current scope.
** 024 ECF 20050819 @22175 Added support for premature termination of an
** AST walk. Added methods isEndWalk() and
** setEndWalk() for this purpose.
** 025 GES 20060129 @24138 Simple change that allows multiple different
** pattern engines to sequentially run.
** 026 ECF 20080730 @39264 Fixed possible memory leak. Replaced hard
** reference to PatternEngine with weak
** reference. Also replaced Stack with Deque for
** certain instance variables.
** 027 GES 20090518 @42384 Import change.
** 028 GES 20130221 Removed the capability to have unqualified constants.
** 029 SVL 20130624 Use context-local instances instead of singleton.
** 030 ECF 20140403 Added static set() method; implemented generics.
** 031 ECF 20150219 Put popping of funcScopes and resolvingContainers in a finally block
** to preserve integrity of these deques when executing a function.
** 032 ECF 20171228 Improve performance by reducing context-local lookups.
** 033 CA 20180507 Added deregisterTree.
** 034 GES 20181023 Added addAllToView().
** 035 CA 20200412 Added incremental conversion support.
** 036 CA 20201015 Replaced java.util.Stack with ArrayDeque (as synchronization is not required).
** 037 GBB 20230512 Logging methods replaced by CentralLogger/ConversionStatus.
*/
/*
** 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.lang.ref.*;
import java.util.*;
import java.util.logging.*;
import com.goldencode.ast.*;
import com.goldencode.expr.*;
import com.goldencode.p2j.convert.*;
import com.goldencode.p2j.security.*;
/**
* Provides symbol resolution of fundamental AST properties, user functions,
* and actions. This class lets its superclass do most of the heavy lifting
* in terms of registering libraries and resolving variables and user
* functions. On top of these capabilities, it adds an awareness of an AST
* hierarchy.
* <p>
* The pattern engine creates an instance of this class for each run of a
* ruleset pipeline. The pipeline uses this object to associate the following
* constructs with one another at a specific point in time, for a user-defined
* purpose:
* <ul>
* <li>an AST node (actually, two nodes: a read-only source representation
* and a mirror, copy representation, which is writable);
* <li>a target AST hierarchy, which typically represents the output of code
* conversion work done by pattern workers;
* <li>a language token resolution capability based on the keywords, language
* statements, data types, and other constructs of a specific programming
* language;
* <li>a collection in which to store AST references, in order to optionally
* filter a set of results, based upon a set of user-defined rules.
* </ul>
*
* The user defines a set of rules to apply against an AST hierarchy. For
* each ruleset, the pattern engine defines the state of this symbol resolver
* by setting the constructs listed above in this object. An AST walker
* iterates over a collection of AST nodes, setting this object's source and
* copy AST nodes at each point along the iteration, and applies the
* current ruleset against each node in the source AST hierarchy. The rules
* in the ruleset contain compiled expressions which use this resolver
* to substitute for variables and user functions, based upon the current
* internal state of this resolver. Rules may optionally add copy AST
* nodes to the results collection, which provides a filtered view of ASTs
* for subsequent rulesets to iterate, within a user-defined ruleset
* pipeline.
* <p>
* A map of ASTs is maintained, where each AST is indexed by its unique ID.
* This allows client code to create AST cross-references by storing one or
* more external AST IDs as a node's annotations. The cross-references are
* resolved by looking up an AST by its ID, using the the {@link #getAst}
* method. This requires that every AST which may need to be looked up using
* this mechanism first be registered with this resolver using the {@link
* #registerAst} method. The map is cleared each time the resolver's {@link
* #cleanUp} method is invoked. This method is called by the pattern engine
* after each AST is fully processed against a ruleset. The map is populated
* (at least for the source AST tree) when the source, root AST is first
* set with the {@link #setAsts} method. Pattern workers which create and
* manipulate target (i.e., converted code) ASTs are responsible for
* registering their AST nodes separately.
* <p>
* All pattern worker implementations loaded by a ruleset or by the pipeline
* itself are managed by the resolver. Pattern workers are registered by
* class name, and only one instance of a pattern worker implementation is
* allowed at a time. Once an instance is registered, no other instance of
* the same class is accepted. Rule containers register their pattern workers
* here, based upon instructions received from the {@link ConfigLoader}. Each
* rule container is responsible for mapping namespace aliases to the pattern
* worker objects about which it cares. As user expressions are parsed and
* compiled, the resolver will use the namespace qualifier to request the
* associated pattern worker object from a rule container.
* <p>
* <b>Important Note:</b> this class is not thread-safe. It is assumed that
* the pattern engine using this resolver object is responsible for correctly
* synchronizing the state of a resolver with the rules that access its data.
*
* @see PatternEngine
* @see RuleSet
*/
public class AstSymbolResolver
extends SymbolResolver
{
/** Variable name for current AST's token type */
private static final String TYPE = "type";
/** Variable name for current AST's parent's token type */
private static final String PARENT = "parent";
/** Context-local instance of this class */
private final static ContextLocal<AstSymbolResolver> context = new ContextLocal<>();
/** Pattern workers indexed by fully qualified class name */
private final Map<String, PatternWorker> workers = new LinkedHashMap<>();
/** Pattern processing engine */
private WeakReference<PatternEngine> engine = null;
/** Stack of rule containers currently in use for resolving namespaces */
private Deque<RuleContainer> resolvingContainers = new ArrayDeque<>();
/** Map of ids to ASTs */
private Map<Long, Aast> astMap = new HashMap<>();
/** Set of filtered results which define a view on the original AST */
private Set<Aast> view = null;
/** Rule container which is currently in scope */
private RuleContainer ruleScope = null;
/** Named function stack; top function is currently being processed */
private Deque<Scope> funcScopes = new ArrayDeque<>();
/** Read-only source AST currently being processed */
private Aast sourceAst = null;
/** Copy of source AST which may be modified by actions */
private Aast copyAst = null;
/** Root node of target (converted code) AST hierarchy */
private Aast targetRootAst = null;
/** Text of conditional expression currently under test */
private String currentCondition = null;
/** Tracks the next child index during walking. */
private int nextChildIndex = -1;
/** Flag to indicate walking should cease after the current AST node */
private boolean endWalk = false;
/**
* Create an instance of this class which is associated with the given
* pattern engine object.
*
* @param engine
* Pattern processing engine which uses this object to perform
* expression symbol resolution.
*/
private AstSymbolResolver(PatternEngine engine)
{
super();
this.engine = new WeakReference<PatternEngine>(engine);
setRuleScope(engine);
}
/**
* Retrieve the context-local instance of this class, if it has been created.
*
* @return AST symbol resolver instance.
*
* @throws IllegalStateException
* if this method is invoked before the symbol resolver has
* been created.
*/
public static AstSymbolResolver getResolver()
{
AstSymbolResolver instance = context.get();
if (instance == null)
{
throw new IllegalStateException("AST symbol resolver has not yet been created");
}
return instance;
}
/**
* Load the AST stored in the given file. This will register the AST and all of its
* descendants with the current AST resolver.
*
* @param resolver
* The {@link AstSymbolResolver} instance. If <code>null</code>, use the singleton
* from {@link #getResolver()}.
* @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 static Aast loadTree(AstSymbolResolver resolver, String filename)
{
if (resolver == null)
{
resolver = getResolver();
}
final Aast ast = AstManager.get().loadTree(filename);
resolver.registerTree(ast);
return ast;
}
/**
* 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 resolver
* The {@link AstSymbolResolver} instance. If <code>null</code>, use the singleton
* from {@link #getResolver()}.
* @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 static Aast getAst(AstSymbolResolver resolver, Long id, boolean loadTree)
{
if (id == null)
{
return null;
}
if (resolver == null)
{
resolver = getResolver();
}
Aast ast = resolver.getAst(id);
if (ast != null || !loadTree)
{
return ast;
}
// the AST was not found among those already loaded and registered; load the enclosing
// tree and try again
String filename = AstManager.get().getTreeName(id);
if (!(filename.endsWith(".ast") ||
filename.endsWith(".dict") ||
filename.endsWith(".jast") ||
filename.endsWith(".schema") ||
filename.endsWith(".p2o")))
{
filename = String.format("%s.ast", filename);
}
loadTree(resolver, filename);
ast = getAst(resolver, id, false);
return ast;
}
/**
* Indicate whether the current walk should end after the current node
* is processed.
*
* @return <code>true</code> if the walk will end after the current node;
* <code>false</code> if it will continue.
*/
public boolean isEndWalk()
{
return endWalk;
}
/**
* Set a flag indicating whether the current walk should end after the
* current node is processed. Typically, this method is invoked with
* <code>endWalk</code> set to <code>true</code> to cancel an existing
* walk in progress. It is invoked with <code>endWalk</code> set to
* <code>false</code> by the pattern engine after a walk has been ended
* to reset this flag for the next AST to be processed.
* <p>
* Note: setting this flag to <code>true</code> will end the walk
* <i>after</i> all rules and actions have been processed for the current
* node, but it will not prevent remaining rules and actions from being
* applied to the current node. The walk termination is not disruptive,
* in that it does not abruptly halt pattern engine processing, it just
* causes remaining nodes in the current AST to go unprocessed.
* <p>
* To forcibly terminate pattern engine processing from a ruleset
* (to report an unrecoverable error, for instance) use {@link
* CommonAstSupport.Library#throwException} instead.
*
* @param endWalk
* <code>true</code> to end the walk after the current node;
* <code>false</code> to allow the walk to continue.
*/
public void setEndWalk(boolean endWalk)
{
this.endWalk = endWalk;
}
/**
* Create and return the context-local instance of this class. If an instance
* of this class already exists in this process, it is returned, unless
* <code>engine</code> refers to a different object instance than the
* one stored in the existing resolver. In the latter case, an exception
* is thrown.
*
* @param engine
* Pattern processing engine.
*
* @return AST symbol resolver instance.
*
* @throws IllegalStateException
* if an instance of the symbol resolver has already been
* created, based upon a different pattern engine instance.
*/
static AstSymbolResolver create(PatternEngine engine)
{
AstSymbolResolver instance = context.get();
if (instance == null)
{
instance = new AstSymbolResolver(engine);
context.set(instance);
}
else if (instance.getPatternEngine() != engine)
{
throw new IllegalStateException(
"A symbol resolver using a different pattern engine already exists");
}
return instance;
}
/**
* Clears the context-local instance of this class (sets the reference to
* <code>null</code>). This will allow subsequent calls to {@link #create}
* to complete successfully. This must NEVER be called while a pattern
* engine is still processing. Use at your own risk!
*/
static void reset()
{
context.set(null);
}
/**
* Set the context-local instance of the resolver to the given instance.
*
* @param resolver
* Symbol resolver to set as the context-local resolver.
*/
static void setLocal(AstSymbolResolver resolver)
{
context.set(resolver);
}
/**
* Create an expression object associated with the context-local instance of
* the resolver, based upon the specified infix expression phrase. The
* appropriate flags for runtime diagnostics are set into the new
* expression, based upon the current debug level set for this process.
* For consistency, this method should be used whenever creating new
* expression instances for use with the pattern engine.
*
* @param infix
* Infix notation expression which forms the basis of the new
* expression object.
*
* @return Configured instance of an expression, ready to be executed.
*/
static Expression createExpression(String infix)
{
// Create expression object with proper runtime flags.
Expression expr = new Expression(getResolver(), infix);
switch (RuleContainer.getDebugLevel())
{
case RuleContainer.MSG_DEBUG:
expr.setRuntimeFlags(Expression.RT_ERROR);
break;
case RuleContainer.MSG_TRACE:
expr.setRuntimeFlags(Expression.RT_DEBUG);
break;
default:
break;
}
return expr;
}
/**
* Get the pattern engine which created and uses this symbol resolver.
*
* @return Pattern engine.
*/
public PatternEngine getPatternEngine()
{
return engine.get();
}
/**
* Get the rule container which defines the current scope defined for
* the resolver. This method is used by the expression engine to resolve
* variable, library, and constant references when parsing and compiling
* user expressions.
*
* @return Rule container or NamedFunction object which defines the
* currently active scope. If a <code>NamedFunction</code>
* is being processed, this function will be returned.
* Otherwise this will be a ruleset or the pattern engine
* itself.
*/
protected Scope getCurrentScope()
{
return (funcScopes.isEmpty() ? ruleScope : (Scope) funcScopes.peek());
}
/**
* Resolve the specified constant into a numeric, boolean, or string
* literal value, if it is recognized. If <code>qualifier</code> is not
* <code>null</code>, it is used to identify a pattern worker from which to
* request resolution of <code>constant</code> to a literal. If the
* qualifier is omitted (is passed as <code>null</code>), this method is a
* NOP. The reason for this is that in the past, this parameter was optional
* but unqualified constant names are no longer supported. However, to
* ensure that there is no breakage, a <code>null</code> is still allowed
* to be passed. It just will never return a value in that case.
* <p>
* Return an object of the following type (or <code>null</code>):
* <ul>
* <li>Long
* <li>Double
* <li>Boolean
* <li>String
* </ul>
*
* @param qualifier
* An namespace qualifier to distinguish between pattern
* workers used to resolve the constant. It may be
* <code>null</code>, however when this is <code>null</code>
* this method will be a NOP.
* This value is case-insensitive.
* @param constant
* A string representing a constant to be resolved.
*
* @return A numeric, boolean, or string literal, or <code>null</code>
* if <code>constant</code> was not recognized as a placeholder
* for a literal.
*/
public Object resolveConstant(String qualifier, String constant)
{
Object result = null;
PatternWorker worker = null;
// Try to resolve the constant using the pattern worker indicated
// by the namespace.
if (qualifier != null)
{
worker = getWorker(qualifier);
if (worker != null)
{
result = worker.resolveConstant(constant);
}
}
return result;
}
/**
* Register an AST, so that it is accessible by its unique ID with the
* {@link #getAst} method.
*
* @param ast
* AST to be registered.
*
* @throws NullPointerException
* if <code>ast</code> has not yet been assigned a unique ID.
*/
public void registerAst(Aast ast)
{
Long id = ast.getId();
if (id == null)
{
throw new NullPointerException(ast.getSymbolicTokenType() +
" (" + ast.getText() + ") has no ID");
}
astMap.put(id, ast);
}
/**
* Get the AST with the specified ID. The resulting AST may be a node in
* either the source or target tree.
*
* @param id
* ID which uniquely identifies the desired AST.
*
* @return The AST associated with <code>id</code>, or <code>null</code>
* if no node is found for <code>id</code>.
*/
public Aast getAst(Long id)
{
return astMap.get(id);
}
/**
* Get the AST currently under inspection within the source tree. During
* init- and post-rule processing of a ruleset, this will always be the
* root node of the source AST. During tree/view walking, this will be the
* AST node currently being iterated in the source tree.
* <p>
* The contract for {@link PatternWorker} implementations, though this is
* not explicitly enforced, is that this node should never be edited, and
* must be considered read-only. Otherwise, the logic which controls the
* iteration may be compromised. Any edits required by an action defined
* by a pattern worker must be made to the <em>copy</em> of this node.
*
* @return AST node from the source tree as described above.
*
* @see #getCopyAst
*/
public Aast getSourceAst()
{
return sourceAst;
}
/**
* Get the AST currently under inspection within the copy tree. During
* init- and post-rule processing of a ruleset, this will always be the
* root node of the copy AST. During tree/view walking, this will be the
* <em>copy</em> of the AST node currently being iterated in the source
* tree.
* <p>
* This node is fair game for editing during a tree/view walk, since it
* is not used for iteration logic.
*
* @return AST node from the source tree as described above.
*
* @see #getSourceAst
*/
public Aast getCopyAst()
{
return copyAst;
}
/**
* Get the AST which is the root of the target AST hierarchy. The target
* AST hierarchy typically represents converted code.
*
* @return Root node of target AST, or <code>null</code> if it has not
* been set.
*/
public Aast getTargetRootAst()
{
return targetRootAst;
}
/**
* Set the root node of the target AST hierarchy. The target AST hierarchy
* typically represents converted code.
*
* @param targetRootAst
* Root node of target AST.
*/
public void setTargetRootAst(Aast targetRootAst)
{
this.targetRootAst = targetRootAst;
}
/**
* Execute a dynamically provided user expression.
*
* @param text
* The text of the infix expression to be executed.
*
* @return The result of executing the expression.
*
* @throws ExpressionException
* if the expression cannot be compiled.
*/
public Object execute(String text)
{
Expression expr = createExpression(text);
Object result = expr.execute();
return result;
}
/**
* Execute a named user expression passing user provided named parameters
* to be referenced in the expression.
*
* @param text
* This parameter represents the alias which identifies the named
* expression to look up.
* @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 The result of executing the expression.
*
* @throws ExpressionException
* if a required lookup fails or if the expression cannot be
* compiled.
*/
public Object execute(String text, Object[] args)
{
// lookup the named function
RuleContainer container = ruleScope.getFunctionContainer(text, null);
if (container == null)
{
throw new ExpressionException("Cannot resolve named function: " + text);
}
// get existing scope; this represents the scope of the function
// caller
Scope callerScope = getCurrentScope();
// obtain the current NamedFunction and cache it as the current
// function scope
resolvingContainers.push(container);
NamedFunction func = container.getFunction(text);
funcScopes.push(func);
Object result = null;
try
{
// execute the expression, passing the args to be replaced
result = func.execute(this, callerScope, args);
}
finally
{
// reset our expression scope since we are not processing the expression now
funcScopes.pop();
// reset the container so downstream processing is not hosed
resolvingContainers.pop();
}
return result;
}
/**
* Add the <em>copy</em> of the current source AST node to the filtered
* view of AST nodes associated with the current ruleset run. This view
* is made available to subsequent rulesets in the pipeline.
*/
public void addToView()
{
view.add(copyAst);
}
/**
* Add all ASTs in the list to the filtered view of AST nodes associated with the current
* ruleset run. This view is made available to subsequent rulesets in the pipeline.
*
* @param list
* The ordered list of AST nodes.
*/
public void addAllToView(List<Aast> list)
{
view.addAll(list);
}
/**
* Add an arbitrary AST node to the filtered view of AST nodes associated
* with the current ruleset run. This view is made available to subsequent
* rulesets in the pipeline. Please note that the AST MUST exist in one
* of the currently loaded ASTs (copy or target) and it must be registered
* in the ID map.
*
* @return <code>true</code> if the AST is valid (and thus was added),
* <code>false</code> otherwise.
*/
public boolean addToView(Long id)
{
Aast ast = getAst(id);
if (ast == null)
{
return false;
}
view.add(ast);
return true;
}
/**
* Add the parent of the <em>copy</em> of the current source AST node
* to the filtered view of AST nodes associated with the current ruleset
* run. This view is made available to subsequent rulesets in the
* pipeline.
* <p>
* The parent node is <b>not</b> added if it is <code>null</code>.
*/
public void addParentToView()
{
addAncestorToView(1);
}
/**
* Add a specified ancestor of the <em>copy</em> of the current source
* AST node to the filtered view of AST nodes associated with the current
* ruleset run. This view is made available to subsequent rulesets in
* the pipeline.
* <p>
* The node is <b>not</b> added if a parent in the chain of levels or at
* the specified level is <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.
*/
public void addAncestorToView(int level)
{
Aast next = copyAst;
for (int i = 0; i < level && next != null; i++)
{
next = next.getParent();
}
if (next != null)
{
view.add(next);
}
}
/**
* Add the <em>copy</em> of the current source AST node and its
* descendants to the set of results, descending at most
* <code>maxLevels</code> generations deep.
*
* @param maxLevels
* Number of generations to be added to the result set,
* beginning with the current AST. To add only the current
* AST, specify <code>1</code> (or better yet, use {@link
* #addToView}).
*/
public void addTreeToView(int maxLevels)
{
Iterator<Aast> iter = copyAst.iterator(maxLevels, null);
while (iter.hasNext())
{
view.add(iter.next());
}
}
/**
* Add to the filtered view of ASTs all of the <em>copy</em> 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)
{
Aast next = copyAst;
Deque<Aast> stack = new ArrayDeque<>();
while (next != null)
{
stack.push(next);
if (next.getType() == type)
{
break;
}
next = next.getParent();
}
// If we have walked all the way back to the root AST but have not
// found an ancestor which matches our boundary condition, do not
// add anything to the results set.
if (next == null)
{
return false;
}
// Add each result in order from root to leaf.
while (!stack.isEmpty())
{
view.add(stack.pop());
}
return true;
}
/**
* Debug method to print a representation of the current copy AST to
* <code>stdout</code>. The text includes the AST's text, token name,
* line number, and column number. It is indented according to its
* nesting depth within the containing AST hierarchy.
*/
public void printAst()
{
StringBuilder buf = new StringBuilder();
int depth = copyAst.getDepth();
for (int i = 0; i < depth; i++)
{
buf.append(" ");
}
int line = copyAst.getLine();
int col = copyAst.getColumn();
String text = copyAst.getText();
buf.append(text)
.append(" [")
.append(copyAst)
.append("] @")
.append(line)
.append(":")
.append(col);
ConversionStatus.get(AstSymbolResolver.class)
.log(Level.FINEST, buf.toString());
}
/**
* Get the infix expression test, exactly as provided by the user or the
* pattern profile, of the rule condition currently under test. This is
* provided to allow workers to access this information, e.g., for
* reporting purposes. If no rule is currently being applied, there will
* be no such text.
*
* @return Text of the current condition under test, or <code>null</code>
* if no rule currently is being applied.
*/
public String getCurrentCondition()
{
return currentCondition;
}
/**
* Return the pattern worker instance associated with the given name.
*
* @param name
* Namespace identifier.
*
* @return Pattern worker instance associated with <code>name</code>
* within the scope of the current rule container.
*/
private PatternWorker getWorker(String name)
{
PatternWorker worker = null;
if (!resolvingContainers.isEmpty())
{
RuleContainer container = (RuleContainer) resolvingContainers.peek();
worker = container.getWorker(name);
}
else
{
worker = ruleScope.getWorker(name);
}
PatternEngine e = engine.get();
if (worker == null && ruleScope != e)
{
worker = e.getWorker(name);
}
return worker;
}
/**
* Set the current condition text. This is the infix expression of the
* condition associated with the rule currently being applied.
*
* @param currentCondition
* Expression text in infix notation of the current rule's
* condition.
*/
void setCurrentCondition(String currentCondition)
{
this.currentCondition = currentCondition;
}
/**
* Get the index of the child being processed during a next child
* rule.
*
* @return The index of the next child about to be processed or -1 if not
* processing a next child rule.
*/
int getNextChildIndex()
{
return nextChildIndex;
}
/**
* Set the index of the child being processed during a next child
* rule.
*
* @param index
* The index of the next child or -1 if not processing a next
* child rule.
*/
void setNextChildIndex(int index)
{
nextChildIndex = index;
}
/**
* Returns an iterator on all registered pattern workers. Iteration is
* in the order of registration.
* <p>
* We cannot simply return an iterator of the map's value collection
* because the order of iteration is important. The order is driven
* by the key set, so we return a custom iterator which uses the
* key set to iterate, but returns the value at each key. Also, we
* don't want to allow removals, so we disallow this with our iterator's
* implementation of remove().
*
* @return Pattern worker object iterator.
*/
Iterator<PatternWorker> workers()
{
return new Iterator<PatternWorker>()
{
private Iterator<String> iter = workers.keySet().iterator();
public boolean hasNext()
{
return iter.hasNext();
}
public PatternWorker next()
{
if (!iter.hasNext())
{
throw new NoSuchElementException();
}
return workers.get(iter.next());
}
public void remove()
{
throw new UnsupportedOperationException();
}
};
}
/**
* Register the given pattern worker under its fully qualified class
* name. Once a worker is registered, it remains in scope for the
* lifetime of the AST symbol resolver. The currently registered worker
* is returned by this method. Only one instance of a concrete worker
* implementation may be registered. If another is passed to this method,
* it is ignored and the originally registered instance is returned
* instead.
*
* @param worker
* The worker instance to be registered.
*
* @return The argument <code>worker</code>, or if another instance of
* <code>worker</code>'s class was already registered, that
* instance is returned instead.
*/
PatternWorker registerWorker(PatternWorker worker)
{
// If a worker of this type is registered already, do not register another.
String className = worker.getClass().getName();
PatternWorker registered = workers.get(className);
if (registered != null)
{
return registered;
}
// Initialize worker and add it to the roll call.
worker.initialize();
workers.put(className, worker);
return worker;
}
/**
* Get the {@link PatternWorker} of the specified type, if any is
* already registered.
*
* @param className
* <code>PatternWorker</code> implementation class name.
*
* @return Registered pattern worker instance or <code>null</code> if
* no worker of the specified type has yet been registered.
*/
PatternWorker getRegisteredWorker(String className)
{
return workers.get(className);
}
/**
* Reset the internal state of this symbol resolver object. This method is
* called by the pattern engine before each ruleset in a pipeline is
* applied to an AST. This replaces the filtered view created for the last
* ruleset in the pipeline with an empty collection, and sets the rule
* scope of the resolver to the rule container specified.
* <p>
* <b>Implementation Note:</b> it is safe to iterate over a previous view
* returned by {@link #view}, even after this method is called. The
* <code>Iterator</code> returned by the {@link #view} method is associated
* with a previous instance of the underlying collection. That instance is
* replaced in this method, not cleared and re-used. Thus the iterator may
* be used safely.
*
* @param ruleScope
* The rule container which defines the scope of namespaces and
* expression libraries currently available to the resolver. This
* is needed in order to properly apply rules.
*/
void reset(RuleContainer ruleScope)
{
setRuleScope(ruleScope);
this.view = new LinkedHashSet<>();
}
/**
* Sets the symbol resolver's current scope to be the specified rule
* container.
*
* @param ruleScope
* Object which defines scope context for user variables and
* worker libraries.
*/
void setRuleScope(RuleContainer ruleScope)
{
this.ruleScope = ruleScope;
}
/**
* Gets the symbol resolver's current container scope.
*
* @return Object which defines scope context for user variables and
* worker libraries.
*/
RuleContainer getRuleScope()
{
return ruleScope;
}
/**
* Set the current source and copy ASTs which will form the basis of all
* AST-related symbol resolution done by this resolver. This method is
* called to reset the source and copy ASTs being inspected during a
* tree/view walk of an AST hierarchy. Any rules which are defined using
* this resolver will be applied using these ASTs.
* <p>
* If the copy AST specified here is the root of its tree, and the
* internal map of AST IDs to AST objects is empty, this method will
* walk once through the copy tree to register each AST, in preparation
* for ruleset processing against that AST.
* <p>
* If there is a target root AST assigned and the internal map of AST IDs
* to AST objects is empty (upon entry to this method), this method will
* walk once through the target tree to register each AST.
*
* @param sourceAst
* The AST currently being inspected. It is read-only by contract,
* though this is not explicitly enforced.
* @param copyAst
* A copy of the source AST currently being inspected. It exists
* in a duplicate hierarchy (at least until actions are invoked
* which may edit that hierarchy) to that of the source AST. It
* shares all of the internal state of the source AST from which
* it was copied, except that any object references to other ASTs
* (i.e., parent and/or children) are actually references to
* similar duplicates, and are self-contained within this AST's
* duplicate hierarchy. It is to this node that any edits required
* by pattern workers or rule actions should be made.
*/
void setAsts(Aast sourceAst, Aast copyAst)
{
this.sourceAst = sourceAst;
this.copyAst = copyAst;
if (astMap.isEmpty())
{
// Populate the AST map if necessary.
if (copyAst != null && copyAst.isRoot())
{
registerTree(copyAst);
}
// The target nodes need to be "brought along" too.
if (targetRootAst != null)
{
registerTree(targetRootAst);
}
}
}
/**
* Helper to add an entire subtree (rooted at the specified AST node) to
* the AST registry.
*
* @param root
* Root node of the tree to be added.
*/
public void registerTree(Aast root)
{
Iterator<Aast> iter = root.iterator();
while (iter.hasNext())
{
registerAst(iter.next());
}
}
/**
* Helper to remove an entire subtree (rooted at the specified AST node) from the AST registry.
*
* @param root
* Root node of the tree to be removed.
*/
public void deregisterTree(Aast root)
{
Iterator<Aast> iter = root.iterator();
while (iter.hasNext())
{
Aast ast = iter.next();
Long id = ast.getId();
if (id != null)
{
astMap.remove(id);
}
}
}
/**
* Clean up internal resources.
*/
void cleanUp()
{
astMap = new HashMap<>();
setRuleScope(engine.get());
}
/**
* Return an iterator on the filtered view of ASTs currently associated
* with this resolver. Actions may add copy AST nodes to this view.
* This method provides the mechanism which allows one ruleset in a
* pipeline to iterate over the filtered results of the previous ruleset's
* application to the AST hierarchy.
* <p>
* The collection underlying this view is replaced with an empty
* collection before each ruleset in the pipeline is processed. However,
* it is safe to iterate over the view returned by this method after this
* occurs, because a new collection is used for each pass, and the
* <code>Iterator</code> instance returned by this method continues to be
* associated with the results collection which was in use at the time
* it was called.
*
* @return An iterator over the filtered view which results from the
* application of a ruleset against an AST.
*
* @see #reset
*/
Iterator<Aast> view()
{
return view.iterator();
}
}