ClassDefinition.java
/*
** Module : ClassDefinition.java
** Abstract : defines the 4GL API for a class or interface definition
**
** Copyright (c) 2007-2025, Golden Code Development Corporation.
**
** -#- -I- --Date-- --JPRM-- ----------------------------------Description-----------------------------------
** 001 GES 20070911 @35134 First version, with support for resolving member variables,
** properties and methods. Honors the public/protected/private
** accessor. Manages the inheritance and interface hierarchy.
** 002 GES 20090518 @42405 Import change.
** 003 GES 20110822 Added support for static members. Added flag to differentiate 4GL
** classes from .NET classes.
** 004 GES 20140319 Store static and instance methods/vars in different maps since
** overloads can occur. Store the least restrictive method/var (from
** an access mode perspective) since overloads can occur.
** 005 GES 20160312 Added support for buffers, temp-tables, queries, datasets and
** data-sources as members of a class. Protected members can be
** accessed from child classes so the lookup processing was enhanced.
** Rewrite for common code instead of separate workers.
** 006 HC 20180312 Fixed lookup of STATIC flag of class data members and methods. The
** lookup didn't work correctly during class prescan mode.
** HC 20180319 Fixed an NPE when a referenced class is not defined (i.e. the class
** file not found). In this case compiler error must be issued.
** 007 GES 20181108 Fixed javadoc.
** CA 20181113 Fixed lookupHierarchy - in non-static mode, both static and runtime
** members must be checked.
** GES 20181122 Added lookupVariableWrapper().
** 008 GES 20181129 Associate the full filename with any new class or interface def.
** 20181216 Added method_def and method call tempIdx support.
** 20181227 Complete rewrite of method resolution to handle overloading and
** parameter wrapping.
** 009 GES 20190118 Changed "access" annotation to "access-mode" for methods. This is
** consistent with other resource types.
** CA 20190219 CONSTRUCTORs are managed same as methods (to be resolved in NEW
** statement).
** 010 CA 20190423 OO calls with POLY arguments are marked as 'dynamic', to emit them
** as if a DYNAMIC-INVOKE was used.
** Fixed tempidx collisions from pre-scan and normal parsing phases.
** Fixed 'var leak' during pre-scan, which prevented correct resolution
** of a var reference, if there was a DEF VAR in a method.
** 011 CA 20190520 Solved converted method names for override and overload cases.
** The 'provisional' members are required to be resolved once the
** initial pre-scan of a class has finished, by doing a full-parse on it
** and any super-classes or interfaces. This is required because at a
** time a class is referenced, it may not have been by FWD's normal
** processing, and we still need the provisional members solved.
** 012 CA 20190529 Fixed a bug in annotateMethodCall.
** 013 HC 20190714 Allowed handle to be passed to TABLE-HANDLE or DATASET-HANDLE
** parameters.
** CA 20190812 Mark a dynamic function call with 'dynamic-class' if is from a static
** context.
** 014 GES 20190620 Added multiple inheritance (only needed for interfaces). Added enum
** support.
** 20190622 Fixed calcFromParentGraph() which was broken in its first version.
** 20190624 Added full .NET dependency tracking including both direct and
** indirect cases.
** 20190625 Removed previous .NET dependency tracking.
** 20190710 Fixes to ensure that the dependency reports are complete. Efficiency
** improvements.
** 20190903 Added isJava() and isPolySignature() methods.
** CA 20190927 Added support for direct Java access from 4GL code.
** RFB 20191008 getExtent has to accommodate the extent that does not include any
** square brackets. We are treating this as a dynamic extent.
** 015 RFB 20200214 In addMethod, because the "qualified" annotation is being inserted into
** the method's return node (ret) during pre-scan, we want to prevent
** these additional annotation requests, since they aren't in ret.
** 016 CA 20200412 Added incremental conversion support.
** CA 20200313 Fixed an issue with method call's argument annotation in case of
** chained OO method calls (no need to emit 'new object').
** CA 20200323 Better fix for the previous fix (OO method call chaining in arg).
** CA 20200429 Legacy DEFINE ENUM conversion support.
** CA 20200503 A fix for a variable defined with the same name both in a method and
** as a member.
** GES 20200526 Finished the registration of the virtual enum methods.
** CA 20200529 Fixed an issue for incremental conversion, when the AST for a skeleton class
** (which was parsed in a previous conversion) does not exist.
** 017 CA 20200804 Emit bulk setter and getter for an extent property.
** CA 20210125 Progress.Lang.Class:Invoke and Progress.Reflect.Method:Invoke must be treated as
** having a POLY returned type.
** GES 20210121 Started rework of fuzzy matching to implement missing features. This first
** pass refactors the fuzzyMethodLookup() processing to make a list of all
** possible matches at the top of the method (by using the new candidates()
** method). This approach only returns candidates which match the access rights
** and number of parameters checks. It also returns all candidates from the
** entire inheritance hierarchy. This is how the 4GL does it, so that better
** matches in the inheritance hierarchy take precedence over local matches.
** The parameter mode processing was also switched. INPUT parameter types
** widen from caller to callee and OUTPUT parameter types narrow from caller
** to callee.
** CA 20210203 Fixed conversion issues with OUTPUT/INPUT-OUTPUT for extent or scalar parameters,
** involved in dynamic or static OO calls, functions or procedure calls. This
** includes mostly cases for OO properties/variables (static and non-static).
** CA 20210216 Fixed fuzzy method overload resolution when there are extent arguments - in this
** case, the match is 'exact' if the type matches, and both arg and parameter are
** extent.
** IAS 20210219 Made NameConverter instance public
** CA 20210221 Default to Progress.Lang.Object if an 'object' parameter is missing its qualified
** annotation.
** CA 20210305 For builtin OO method calls, save at the call the typelist for the target method
** parameters.
** CA 20210318 Fixed conversion of extent() function/statement with builtin OO properties.
** CA 20210609 Reworked INPUT/INPUT-OUTPUT parameters to a new approach, where they are explicitly
** initialized at the method's execution, and not at the caller's arguments.
** CA 20210616 Fixed incremental conversion for enums - do not mark them as static.
** GES 20210702 Allow parent hierarchy lookup for static methods. This is a 4GL quirk that is not
** present in Java. See #4978. Removed dead code and some debugging code. Improved
** support for .NET arrays and .NET generic type parameters.
** OM 20211122 Added hints-based support for 'unloading' schema.
** CA 20220128 Added a NPE protection for exactMethodLookup(), seen in incremental conversion,
** but with no local duplicate.
** CA 20220216 Abend if the root Progress.Lang.Object class can't be resolved.
** ME 20211028 Add support for 'virtual' POLY data type on methods return/parameters.
** CA 20220307 Legacy OO skeletons are updated to use POLY, removed the 'invoke' checks for
** poly methods, as these will be handled automatically.
** GES 20220329 Suppress duplicate fuzzy method lookup warnings.
** TJD 20220504 Java 11 compatibility minor changes
** CA 20220526 Fixed incremental conversion: buffers can be defined for permanent tables, too.
** CA 20220428 Annotate THIS-OBJECT and SUPER constructor call parameters.
** CA 20220513 Track the Java field name for the builtin enums, so they can emit the exact name.
** CA 20220516 Fixed tracking of extent data members (variables or properties).
** GES 20210311 Rewrote exact and fuzzy method matching to support all data types and
** overloading rules. Fixed many latent problems. Reworked how method names
** convert to resolve conflicts within the class hierarchy instead of project
** wide.
** CA 20220531 Class PROPERTY members must be loaded only once (when their javaname is computed).
** Resolve the skeleton methods' Java name from their FWD implementation. OVERRIDE
** is not mandatory at PROPERTY or METHOD, if overriding an interface property or
** method. DATASET and TEMP-TABLE are never marked provisional, as they can't be
** defined in a class method. Fixes for fuzzy matching logic for TABLE-HANDLE,
** TABLE, DATASET, DATASET-HANDLE - they are completely interchangable.
** CA 20220613 fuzzyMethodLookup must consider the 'TEMP-TABLE <' prefix, too.
** CA 20220727 OO variable references must get the refid javaname, before checking the javaname
** annotation.
** CA 20220727 'isInheritedFrom' must consider the directly implemented interfaces, too, not
** just the interfaces from the inherited parent classes.
** CA 20220727 Improved memory management for parsing; cleanup is done in two phases:
** 1. after each legacy class file has finished parsing, the SchemaDictionary will
** keep only protected temp-tables (all private tables are removed, as these
** can't be reached from sub-classes; all permanent tables are removed, as each
** class has its own reference to the permanent tables). Also, the class def
** instance will reduce its own used memory, which is not required when parsing
** sub-classes.
** 2. after parsing of the entire file set is finished, any SchemaDictionary or
** other ASTs referenced by the ClassDefinition are released.
** CA 20220818 An abstract property can define only a getter or a setter, while the implementation
** can define both. For this reason, only the overridden accessor must have the
** @Override annotation.
** CA 20230119 Fixed a bug in incremental conversion - already parsed classes must populate the
** legacyToJavaMethod map, and keep the javaname of the already converted methods.
** 018 GBB 20230512 Logging methods replaced by CentralLogger/ConversionStatus.
** 019 OM 20230115 Replaced absolutePath(), relativePath(), upPath() and downPath() with faster
** versions, based on node types rather on string paths.
** 020 CA 20230519 Overload matching requires to compute the distance between the argument's type
** and the parameter's type, such that an int argument will choose the int64
** parameter instead of a decimal parameter. Also, DATASET/TABLE-HANDLE must allow
** matches to continue, so the selection is done on the full signature.
** 021 CA 20230426 For method INPUT arguments, if the argument is from an expression and the arg's
** type is resolved as 'int64' for an 'integer' parameter, allow it; the runtime
** will validate the actual values.
** CA 20230501 In 'annotateCallSignature', an '?' literal for a 'object' parameter must emit as
** 'new object()'.
** 022 CA 20231004 Fixed conversion for class events with parameters of type DATASET, TABLE,
** DATASET-HANDE, TABLE-HANDLE and BUFFER.
** 023 CA 20230928 The openedge method resolution when the match is done via a poly argument
** (like h::f1), it will assume the return type of the last found overload - the
** runtime will still resolve the target based on the arguments, but the lookup must
** be done on the type resolved by the conversion, and not the runtime time of the
** lvalue on the chain call. For this reason, and to allow the chained call to fail
** properly at runtime, both the lookup type and the return type of the call are
** emitted at the 'invokePoly' API call.
** Refactored the fuzzy method resolution to be used by runtime, too.
** CA 20230929 Method chain calls starting with SUPER keyword can not be morphed to dynamic
** invoke, as the super calls require JVM to handle them.
** 024 CA 20231016 Register the virtual enum methods when loading a referenced enum AST.
** 025 CA 20240601 Added processParentGraph overload to allow processing on descent from parent to
** object or on ascent, from object class to direct parent class.
** 026 AOG 20241017 Changed extent parameter value when createMethod is called in registerVirtual.
** CA 20241026 The parent of .NET enums is System.Enum.
** CA 20241118 A property 'inherited' from an interface/super-class is allowed to define its
** own explicit getter or setter, even if the 'inherited' definition does not define
** it.
** CA 20241118 Annotate method calls with 'dotnet-cls', too, beside 'builtin-cls'.
** 027 CA 20250319 A method from a super-class can have the same name as current class, so calls to
** this kind of methods must not be resolved as a constructor for the current class.
** CA 20250319 A provisional class var found during pre-scan, which is referenced before the
** definition statement and is referenced via a chain, must be resolved even if is
** provisional - this is the only case allowed in 4GL, when a var can be defined
** after it is referenced.
** 028 AS 20250331 Fixed a typo in exactMethodLookup.
** 029 CA 20250513 Loading a previously parsed class must ensure all the schemas in 'db_references'
** are loaded in the SchemaDictionary associated with the ClassDefinition.
** 030 ICP 20250407 Corrected checkAccessRights, added the case when a public method matches a
** package private search
** ICP 20250423 Fixed loadBuiltinDefinition to correctly resolve setter and getter method names.
** 031 CA 20231215 Classes referenced via method parameters need to be loaded for incremental conversion.
** 032 AS 20250605 Resolve inherited properties naming conflicts.
*/
/*
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU Affero General Public License as
** published by the Free Software Foundation, either version 3 of the
** License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU Affero General Public License for more details.
**
** You may find a copy of the GNU Affero GPL version 3 at the following
** location: https://www.gnu.org/licenses/agpl-3.0.en.html
**
** Additional terms under GNU Affero GPL version 3 section 7:
**
** Under Section 7 of the GNU Affero GPL version 3, the following additional
** terms apply to the works covered under the License. These additional terms
** are non-permissive additional terms allowed under Section 7 of the GNU
** Affero GPL version 3 and may not be removed by you.
**
** 0. Attribution Requirement.
**
** You must preserve all legal notices or author attributions in the covered
** work or Appropriate Legal Notices displayed by works containing the covered
** work. You may not remove from the covered work any author or developer
** credit already included within the covered work.
**
** 1. No License To Use Trademarks.
**
** This license does not grant any license or rights to use the trademarks
** Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
** of Golden Code Development Corporation. You are not authorized to use the
** name Golden Code, FWD, or the names of any author or contributor, for
** publicity purposes without written authorization.
**
** 2. No Misrepresentation of Affiliation.
**
** You may not represent yourself as Golden Code Development Corporation or FWD.
**
** You may not represent yourself for publicity purposes as associated with
** Golden Code Development Corporation, FWD, or any author or contributor to
** the covered work, without written authorization.
**
** 3. No Misrepresentation of Source or Origin.
**
** You may not represent the covered work as solely your work. All modified
** versions of the covered work must be marked in a reasonable way to make it
** clear that the modified work is not originating from Golden Code Development
** Corporation or FWD. All modified versions must contain the notices of
** attribution required in this license.
*/
package com.goldencode.p2j.uast;
import java.lang.reflect.*;
import java.util.*;
import java.util.function.*;
import java.util.logging.*;
import java.util.stream.Collectors;
import com.goldencode.ast.*;
import com.goldencode.p2j.convert.*;
import com.goldencode.p2j.convert.db.*;
import com.goldencode.p2j.oo.lang.*;
import com.goldencode.p2j.schema.*;
import com.goldencode.p2j.util.*;
import com.goldencode.p2j.util.Utils; // conflicts with ANTLR so it needs to be explicitly imported
import antlr.*;
/**
* Contains a dictionary for each type of class resource and provides
* methods to maintain and lookup in the respective namespaces.
* <p>
* Provides support for the following namespaces:
* <ul>
* <li> member variables
* <li> properties
* <li> methods
* <li> temp-tables
* <li> buffers
* <li> queries
* <li> datasets
* <li> data-sources
* </ul>
*/
public class ClassDefinition
implements ProgressParserTokenTypes
{
/** Logger. */
private static final ConversionStatus LOG = ConversionStatus.get(ClassDefinition.class);
/** Data store identifier. */
protected static enum DataStoreType { VAR, METHOD, TABLE, QRY, DATASET, DATASRC };
/** The converter to use for generating Java names. */
public static final NameConverter nconvert = new NameConverter();
/** Instance data stores. */
private Map<DataStoreType, Map<String, MemberData>> istores = null;
/** Static data stores. */
private Map<DataStoreType, Map<String, MemberData>> sstores = null;
/** Instance methods mapped by name. */
private Map<String, Map<SignatureKey, MemberData>> imethods = null;
/** Static methods mapped by name. */
private Map<String, Map<SignatureKey, MemberData>> smethods = null;
/**
* A set of existing Java signatures to detect when there is a method overloading conflict (more than one
* method with the same java name and signature). Some overloaded methods can convert to identical Java
* signatures when they are different in the 4GL.
*/
private Set<String> javaMethodCollisions = new HashSet<>();
/** Lookup of the Java name (known to be unique) from the legacy signature (which also is be unique). */
private Map<String, String> legacyToJavaMethod = new HashMap<>();
/** Internal lock object. */
private Object lock = new Object();
/** Provides schema lookup for this class' scope. */
private SchemaDictionary schemaDict = null;
/** Fully qualified name of the class or interface. */
private final String name;
/** Super class definition. Only interfaces can have multiple parents. */
private final ClassDefinition[] parents;
/** Implemented interfaces. */
private final ClassDefinition[] interfaces;
/** List of implemented interfaces. */
private final Set<String> ifaces;
/** Flag marking if this is a built-in Progress class. */
private final boolean builtin;
/** Flag to differentiate between 4GL classes and .NET classes. */
private final boolean dotnet;
/** Flag marking the type of this OO 4GL compile unit. */
private final OOType otype;
/** The filename of the .cls that defined this class/interface. */
private final String filename;
/** Temporary ID number (before AST IDs are assigned). */
private int tempIdx = 0;
/** Classes for which we've already calculated .NET dependencies. */
private Set<ClassDefinition> dotnetProcessed = new HashSet<>();
/** .NET classes in the parent hierarchy. */
private Set<String> dotnetDerived = new HashSet<>();
/** Direct .NET references. */
private Set<String> directDotNet = new HashSet<>();
/** Indirect .NET references. */
private Set<String> indirectDotNet = new HashSet<>();
/** Flag indicating that this class was processed (fully parsed) after it was pre-scanned. */
private boolean processed = false;
/** Full Java package in dotted notation (no class name). */
private String javaPackage = null;
/** The Java field names for skeleton enums. */
private Map<String, String> javaEnumNames = new HashMap<>();
/** Simple Java class name, without any package prefix. */
private String javaClassName = null;
/** A set of already loaded properties - as a class is parsed during both prescan and full-scan. */
private Set<String> loadedProperties = new HashSet<>();
/** The fuzzy method lookup. */
private final FuzzyMethodCvt fuzzy = new FuzzyMethodCvt(this);
/**
* Construct an instance and load any superclass or interface hierarchy
* that is needed. For each superclass or interface, a separate class
* definition will be loaded. These definitions will be linked back by
* reference from this instance. This allows lookups to be done based on
* the object hierarchy.
*
* @param name
* Fully qualified name of the class or interface.
* @param parents
* List of parent classes, only interfaces can have more than 1. Pass
* <code>null</code> if there is no explicit super class.
* @param interfaces
* Collection of fully qualified interface names that are
* implemented by this class.
* @param builtin
* <code>true</code> if this definition is built-in to Progress.
* @param otype
* Flag marking the type of this OO 4GL compile unit.
* @param dotnet
* <code>true</code> if this is a .NET definition,
* <code>false</code> if this is a 4GL definition.
* @param filename
* The filename of the .cls that defined this class/interface.
*/
public ClassDefinition(String name,
ClassDefinition[] parents,
ClassDefinition[] interfaces,
boolean builtin,
OOType otype,
boolean dotnet,
String filename)
{
this.name = name;
this.parents = parents;
this.interfaces = interfaces;
this.ifaces = (interfaces != null) ? new HashSet<String>() : null;
this.builtin = builtin;
this.otype = otype;
this.dotnet = dotnet;
this.filename = filename;
if (interfaces != null)
{
for (ClassDefinition iface : interfaces)
{
ifaces.add(iface.getName());
}
}
if (builtin && !dotnet)
{
loadBuiltinDefinition();
}
if (otype == OOType.ENUM && !isJava() && !isDotNet())
{
registerVirtualEnumMethods(name, parents[0].getName().equalsIgnoreCase("Progress.Lang.FlagsEnum"));
}
}
/**
* Given a fully qualified legacy class name, load a new definition by looking into the AST for
* <code>oo-data-member</code> nodes and populate the members.
*
* @param sym
* The symbol resolver.
* @param qname
* The qualified legacy name for the class.
*
* @throws SchemaException
* If temp-tables from this definition couldn't be loaded.
*/
public ClassDefinition(SymbolResolver sym, String qname)
throws SchemaException
{
this.processed = true;
this.name = qname;
this.filename = ConversionData.findClassFilename(qname);
// load the root AST and load the following:
// parents (1 for class, 1-n for iface)
// interfaces (0-n only for class)
Aast root = AstManager.get().loadTree(filename + ".ast");
// get from the root
this.dotnet = Boolean.TRUE.equals(root.getAnnotation("dotnet-cls"));
this.builtin = Boolean.TRUE.equals(root.getAnnotation("builtin-cls")) && !dotnet;
this.otype = root.downPath(INTERFACE_DEF) ? OOType.INTERFACE
: root.downPath(ENUM_DEF) ? OOType.ENUM : OOType.CLASS;
if (builtin && !dotnet)
{
// this must be before we walk the AST
loadBuiltinDefinition();
}
if (!builtin)
{
this.schemaDict = new SchemaDictionary(null);
List<Object> dblist = root.getAnnotationList("db_references");
if (dblist != null)
{
for (Object odb : dblist)
{
String db = odb.toString();
if (!schemaDict.isDatabase(db))
{
schemaDict.loadSchema(db);
}
}
}
this.schemaDict.addScope(false);
SchemaWorker.loadFromPersistence(schemaDict, filename);
this.javaPackage = (String) root.getAnnotation("pkgname");
this.javaClassName = (String) root.getAnnotation("classname");
}
Set<String> ifaces = new HashSet<>();
List<ClassDefinition> interfaces = new ArrayList<>();
List<ClassDefinition> parents = new ArrayList<>();
boolean hadRootClass = false;
BiConsumer<List<ClassDefinition>, String> resolveParent = (list, pqname) ->
{
ClassDefinition clsdef = SymbolResolver.loadClassDefinition(sym, pqname);
if (clsdef == null)
{
if (sym == null)
{
// this code (and actually this ClassDefinition c'tor) should never be reached with a null
// SymbolResolver.
String file = ConversionData.findClassFilename(qname);
file = file + ".ast";
throw new RuntimeException("Can't load parent class " + pqname +
" - symbol resolver not available and " + file + " does not exist.");
}
String foundName = sym.loadClass(pqname);
clsdef = sym.lookupClass(foundName);
}
if (clsdef == null)
{
throw new RuntimeException("Could not load parent class: " + pqname);
}
list.add(clsdef);
};
boolean isIface = this.otype == OOType.INTERFACE;
Iterator<Aast> iter = root.iterator();
Aast ref = null;
String pqname;
while (iter.hasNext())
{
Aast node = iter.next();
int nodeType = node.getType();
switch (nodeType)
{
// parents and interfaces
case KW_IMPLEMTS:
ref = node.getImmediateChild(CLASS_NAME, null);
pqname = (String) ref.getAnnotation("qualified");
if (isIface)
{
resolveParent.accept(parents, pqname);
}
else
{
resolveParent.accept(interfaces, pqname);
ifaces.add(pqname);
}
break;
case KW_INHERITS:
ref = node.getImmediateChild(CLASS_NAME, null);
pqname = (String) ref.getAnnotation("qualified");
resolveParent.accept(parents, pqname);
hadRootClass = SymbolResolver.ROOT_OBJ_NAME.equalsIgnoreCase(pqname);
break;
}
// members
if (node.isAnnotation("oo-data-store"))
{
String storeName = (String) node.getAnnotation("oo-data-store");
DataStoreType storeType = DataStoreType.valueOf(storeName);
boolean isStatic = Boolean.TRUE.equals(node.getAnnotation("static"));
boolean isOverride = Boolean.TRUE.equals(node.getAnnotation("override"));
if (storeType == DataStoreType.METHOD)
{
String name = (String) node.getAnnotation("name");
ParameterKey[] sig = SymbolResolver.calculateDefinitionSignature(node);
MemberData mdat = createMethod(null,
name,
sig,
((Long) node.getAnnotation("access-mode")).intValue(),
isStatic,
((Long) node.getAnnotation("return_type")).intValue(),
(String) node.getAnnotation("qualified"),
(String) node.getAnnotation("javaname"),
SymbolResolver.readExtent(node),
-1,
node.getImmediateChild(KW_OVERRIDE, null) != null,
node.getImmediateChild(KW_ABSTRACT, null) != null);
for (int i = 0; i < mdat.signature.length; i++)
{
ParameterKey p = mdat.signature[i];
if (p.type.startsWith("object<"))
{
String psig = "object<? extends ";
int pidx = p.type.indexOf(psig);
if (pidx != -1)
{
String pcname = p.type.substring(pidx + psig.length(), p.type.length() - 1);
if (!pcname.equalsIgnoreCase(this.name))
{
SymbolResolver.loadClassDefinition(sym, pcname, false);
}
}
}
}
if (!builtin)
{
mdat.astId = node.getId();
mdat.tempIdx = ConversionData.getTempIdx(mdat.astId);
mdat.javaname = (String) node.getAnnotation("javaname");
registerMethod(mdat.javaname, mdat);
}
}
else
{
int tokenType = -1;
String varName = (String) node.getAnnotation("name");
Variable var;
int accessMode = node.isAnnotation("access-mode")
? ((Long) node.getAnnotation("access-mode")).intValue()
: ProgressParserTokenTypes.KW_PRIVATE;
switch (storeType)
{
case DATASET:
tokenType = DATA_SET;
var = SymbolResolver.createVariable(varName, tokenType, node, isStatic);
break;
case DATASRC:
tokenType = DATA_SOURCE;
var = SymbolResolver.createVariable(varName, tokenType, node, isStatic);
break;
case QRY:
var = null;
tokenType = QUERY;
break;
case TABLE:
var = null;
if (nodeType == DEFINE_BUFFER)
{
tokenType = BUFFER;
Aast symNode = node.getImmediateChild(SYMBOL, null);
String bufName = symNode.getText();
node = node.getImmediateChild(KW_FOR, null);
boolean skipGlobal = node.downPath(TEMP_TABLE);
if (node.downPath(TEMP_TABLE))
{
node = node.getImmediateChild(TEMP_TABLE, null);
}
else
{
node = node.getImmediateChild(TABLE, null);
}
varName = (String) node.getAnnotation("bufname");
String tableName = (String) node.getAnnotation("schemaname");
Object table = schemaDict.addTableEntry(null,
bufName,
BUFFER,
true,
symNode);
NameNode toTable = (NameNode) table;
NameNode fromTable = schemaDict.findTableFromNode(tableName, toTable, skipGlobal);
schemaDict.addFieldEntries(tableName, fromTable, toTable, skipGlobal);
}
else if (nodeType == KW_B4_TABLE)
{
Aast symNode = node.getImmediateChild(SYMBOL, null);
varName = symNode.getText();
}
else
{
varName = (String) node.getAnnotation("bufname");
if (node.downPath(KW_WORK_TAB))
{
tokenType = WORK_TABLE;
}
else
{
tokenType = TEMP_TABLE;
}
/*
Aast ttNode;
ttNode = node.getImmediateChild(KW_WORK_TAB, null);
ttNode = node.getImmediateChild(KW_TEMP_TAB, null);
NameNode table = schemaDict.addTableEntry(null,
varName,
tokenType,
true,
ttNode);
Iterator<Aast> fiter = ttNode.iterator();
while (iter.hasNext())
{
Aast field = fiter.next();
if (field.getType() != DEFINE_FIELD)
{
continue;
}
schemaDict.addFieldEntry(table, node, field, ftype, true);
sym.addField(table, symName, ftype, as_field_clause_AST);
// ?? sym.addFieldLike(table, symName, l_AST)
}
*/
}
break;
case VAR:
if (nodeType == DEFINE_VARIABLE || nodeType == DEFINE_PROPERTY)
{
tokenType = ((Long) node.getAnnotation("type")).intValue();
var = new Variable(varName,
tokenType,
(String) node.getAnnotation("qualified"));
var.setDotNetArray(Boolean.TRUE.equals(node.getAnnotation("dotnet-array")));
if (node.isAnnotation("generic-type-parameter"))
{
var.setGenericType((String) node.getAnnotation("generic-type-parameter"));
}
if (node.isAnnotation("generic-type-is-primitive"))
{
boolean prim = (boolean) node.getAnnotation("generic-type-is-primitive");
var.setGenericPrimitive(prim);
}
}
else
{
// enum or event
tokenType = nodeType == DEFINE_EVENT ? CLASS_EVENT : VAR_CLASS;
var = new Variable(varName,
tokenType,
(String) node.getAnnotation("qualified"));
tokenType = VAR_CLASS;
// TODO: for enums, do we need to lookup the javaname in javaEnumNames here or
// can we rely upon the AST node's "javaname" annotation?
}
var.setProperty(nodeType == DEFINE_PROPERTY);
var.setStatic(isStatic && !isEnum());
var.setAccess(accessMode);
var.setExtent(SymbolResolver.readExtent(node));
var.setOverride(isOverride);
// we need to know the variable's state at this point
SymbolResolver.trackPropertyOrEventMethodSignatures(nodeType, this, node, var, varName);
break;
default:
throw new RuntimeException("Unknown member for oo-data-store:\n" +
node.dumpTree(true));
}
MemberData mdat = addWorker(storeType,
varName,
null,
var,
tokenType,
accessMode,
isStatic,
(String) node.getAnnotation("qualified"),
-1);
mdat.javaname = (String) node.getAnnotation("javaname");
if (var != null)
{
var.setClassDefinition(this);
var.setBuiltin(this.builtin);
var.setOptions(node);
}
if (!builtin)
{
mdat.astId = node.getId();
mdat.tempIdx = ConversionData.getTempIdx(mdat.astId);
if (var != null)
{
var.setTempIndex(mdat.tempIdx);
}
}
}
};
}
// TODO: enum root class
// TODO: register GetEnum methods
if (!hadRootClass && !qname.equalsIgnoreCase(SymbolResolver.ROOT_OBJ_NAME))
{
ClassDefinition parent = SymbolResolver.loadClassDefinition(sym, SymbolResolver.ROOT_OBJ_NAME);
if (parent == null)
{
throw new RuntimeException("Could not load parent class definition " +
SymbolResolver.ROOT_OBJ_NAME + " for " + qname);
}
parents.add(parent);
}
this.ifaces = ifaces.isEmpty() ? null : Collections.unmodifiableSet(ifaces);
this.interfaces = interfaces.isEmpty() ? null : interfaces.toArray(new ClassDefinition[0]);
this.parents = parents.isEmpty() ? null : parents.toArray(new ClassDefinition[0]);
if (otype == OOType.ENUM && !isJava())
{
registerVirtualEnumMethods(name, this.parents[0].getName() == "Progress.Lang.FlagsEnum");
}
}
/**
* Detect if the signature contains a polymorphic type (is {@code BaseDataType}).
*
* @param parms
* Parameter array.
*
* @return {@code true} if a parameter is polymorphic.
*/
public static boolean isPolySignature(ParameterKey[] parms)
{
for (ParameterKey p : parms)
{
if ("BaseDataType".equals(p.type))
{
return true;
}
}
return false;
}
/**
* Lookup or calculate the Java method name for a property or event and store the data for method
* name disambiguation.
*
* @param node
* The AST for which the method is being registered.
* @param prefix
* The accessor prefix.
* @param lname
* The legacy property/event name.
* @param override
* {@code true} if the property or event is overridden.
* @param abstr
* {@code true} if the property or event is abstract.
* @param sig
* The parameter definition.
*
* @return The Java method name that is associated with the specific property accessor.
*/
public String registerPropertyMethod(Aast node,
String prefix,
String lname,
boolean override,
boolean abstr,
ParameterKey[] sig)
{
String accessor = prefix + "-" + lname;
String legacySig = SymbolResolver.calculateLegacySignature(accessor, sig);
String jparms = SymbolResolver.calculateJavaParameters(sig);
String key = prefix + "-javaname";
String javaname = null;
if (!isBuiltIn() && loadedProperties.contains(legacySig))
{
javaname = getConvertedMethodName(legacySig);
node.putAnnotation(key, javaname);
return javaname;
}
loadedProperties.add(legacySig);
if (node.isAnnotation(key))
{
// subsequent time in, read the stored name from the AST and update the in memory storage
javaname = (String) node.getAnnotation(key);
addJavaMethodName(javaname, jparms, legacySig);
}
else if (isBuiltIn())
{
javaname = getConvertedMethodName(legacySig);
if (javaname != null)
{
addJavaMethodName(javaname, jparms, legacySig);
node.putAnnotation(key, javaname);
}
else
{
LOG.log(Level.WARNING,
"Could not find legacy builtin method javaname for '" + name + ":" + legacySig + "'");
}
}
else if (override && (javaname = lookupOverrideName(legacySig, override, true)) != null)
{
// first time in (for an override)
addJavaMethodName(javaname, jparms, legacySig);
node.putAnnotation(key, javaname);
}
else
{
if (abstr)
{
// abstract properties/events that match an interface property/event should get the same name
javaname = lookupOverrideName(legacySig, false, abstr);
if (javaname != null)
{
addJavaMethodName(javaname, jparms, legacySig);
}
}
if (javaname == null)
{
// first time in (for a non-override), calculate the name and store it in memory and in the AST
javaname = calculateUniqueJavaMethodName(accessor, jparms, legacySig);
}
node.putAnnotation(key, javaname);
}
return javaname;
}
/**
* Compute the Java name for a legacy method which when combined with the Java parameter list will
* be a unique method name.
*
* @param name
* The legacy name.
* @param jparms
* The Java parameter list definition.
* @param legacySig
* The legacy signature that will be mapped to the Java name.
*
* @return The Java name.
*/
private String calculateUniqueJavaMethodName(String name, String jparms, String legacySig)
{
String jbase = nconvert.convert(name, MatchPhraseConstants.TYPE_METHOD);
String jname = jbase;
String javaSig = null;
// TODO: the suffix can be replaced with something more verbose, to indicate i.e. the
// collision reason
int suffix = 1;
while (true)
{
javaSig = jname + "(" + jparms + ")";
if (!hasCollision(javaSig))
{
addJavaMethodName(jname, jparms, legacySig);
break;
}
jname = jbase + "_" + suffix;
suffix++;
}
return jname;
}
/**
* Check the current class and all parent classes/interfaces for a Java signature collision.
*
* @param javaSig
* The Java method signature for which to check.
*
* @return {@code true} if there is a collision (an existing registered method with the same Java
* signature).
*/
private boolean hasCollision(String javaSig)
{
// quick out, fail if there is a local collision
if (javaMethodCollisions.contains(javaSig))
{
return true;
}
java.util.function.Function<ClassDefinition, Boolean> collide = (ClassDefinition cls) ->
{
return cls.javaMethodCollisions.contains(javaSig);
};
// no local collision, but now we need to check our parent hierarchy
return calcFromParentGraph(collide, (Boolean value) -> !value, Boolean.FALSE, true, true);
}
/**
* Update the internal maps to record a new Java name.
*
* @param javaname
* The Java method name to add.
* @param jparms
* The Java parameter list definition.
* @param legacySig
* The legacy signature that will be mapped to the Java name.
*/
private void addJavaMethodName(String javaname, String jparms, String legacySig)
{
String javaSig = javaname + "(" + jparms + ")";
if (!javaMethodCollisions.contains(javaSig))
{
javaMethodCollisions.add(javaSig);
}
else
{
// don't uncomment this except for debugging; built-in classes (and others?) will trigger this
// System.err.printf("ERROR: Java signature %s already exists in collisions list.\n", javaSig);
}
String old = legacyToJavaMethod.put(legacySig, javaname);
if (old != null && !old.equals(javaname))
{
// something is wrong... name must match!
LOG.log(Level.SEVERE,
"method " + legacySig +
" accessible from class " + getName() +
" has two Java names: " + javaname + " and " + old);
}
}
/**
* Register this method signature with a converted Java method name. The member data structure will
* have its javaname member set as a result of this call. 4 cases are handled (in this order):
* <p>
* <ol>
* <li> The caller has passed in a non-null javaname. This will force the name.
* <li> The method is a overridden method, lookup the method in the parent class and use that name.
* <li> The method signature is already registered, use the registered name.
* <li> Calculate a unique name and register it.
* </ol>
*
* @param javaname
* The javaname to forcibly assign to this legacy signature, or <code>null</code> to
* compute an unique one.
* @param mdat
* The method definition. Must NOT be {@code null}.
*/
private void registerMethod(String javaname, MemberData mdat)
{
String jparms = SymbolResolver.calculateJavaParameters(mdat.signature);
String legacySig = SymbolResolver.calculateLegacySignature(mdat.name, mdat.signature);
// check if any interface parents has a legacy signature for this method - as no OVERRIDE is required
// when implementing or re-defining as abstract these interface methods
if (!mdat.override)
{
mdat.override = lookupOverrideName(legacySig, true, true) != null;
}
if (mdat.var != null)
{
mdat.var.setOverride(mdat.override);
}
// case 1: the caller knows best, if javaname is passed then force it here
if (javaname != null)
{
mdat.javaname = javaname;
addJavaMethodName(mdat.javaname, jparms, legacySig);
}
// case 2: overrides use the name of the parent (inheritance hierarchy)
else if (mdat.override)
{
mdat.javaname = lookupOverrideName(legacySig, mdat.override, true);
addJavaMethodName(mdat.javaname, jparms, legacySig);
}
// case 3: the method is already registered with a unique name
else if (legacyToJavaMethod.containsKey(legacySig))
{
mdat.javaname = legacyToJavaMethod.get(legacySig);
}
else
{
if (mdat.abstr)
{
// case 4: abstract methods that match an interface method should get the same name
mdat.javaname = lookupOverrideName(legacySig, false, mdat.abstr);
if (mdat.javaname != null)
{
addJavaMethodName(mdat.javaname, jparms, legacySig);
}
}
if (mdat.javaname == null)
{
// case 5: not registered, calculate a unique name, avoiding signature conflicts
mdat.javaname = calculateUniqueJavaMethodName(mdat.name, jparms, legacySig);
}
}
}
/**
* Lookup the Java method name in the parent/interface hierarchy for the given legacy signature. The
* first match is returned.
*
* @param legacySig
* The legacy signature. This should uniquely identify a single method.
* @param override
* {@code true} is the method name is an override. This will force a search of parent
* (inheritance hierarchy).
* @param abstr
* {@code true} is the method name is abstract. This will force a search of any implemented
* interfaces.
*
* @return The first Java name found for this legacy signature or {@code null} if there is no match.
*/
private String lookupOverrideName(final String legacySig, boolean override, boolean abstr)
{
java.util.function.Function<ClassDefinition, String> oride = (ClassDefinition cls) ->
{
return cls.legacyToJavaMethod.get(legacySig);
};
return calcFromParentGraph(oride, (String value) -> value == null, null, override, abstr);
}
/**
* Load the FWD implementation for a builtin, non-.NET class. This maps legacy signatures to
* Java method names, so that they will be reused for method references or override cases.
*/
private void loadBuiltinDefinition()
{
Class<? extends _BaseObject_> cls = null;
String fullname = SymbolResolver.findConvertedLegacyClass(name);
if (fullname == null)
{
String clsPkg = (name.indexOf(".") < 0 ? "" : name.substring(0, name.lastIndexOf(".")));
String clsName = clsPkg.isEmpty() ? name : name.substring(clsPkg.length() + 1);
if (clsPkg.toLowerCase().startsWith("openedge") ||
clsPkg.toLowerCase().startsWith("progress"))
{
clsPkg = clsPkg.substring(9);
}
if (clsPkg.toLowerCase().startsWith("ccs"))
{
clsPkg = clsPkg.substring(4);
}
clsPkg = NameConverter.convertPackage(clsPkg);
clsName = nconvert.convert(clsName, MatchPhraseConstants.TYPE_CLASS);
String fname = clsPkg + (clsPkg.isEmpty() ? "" : ".") + clsName;
if ("core.string".equalsIgnoreCase(fname))
{
clsName = "LegacyString";
}
else if ("lang.object".equalsIgnoreCase(fname))
{
clsName = "BaseObject";
}
else if ("lang.class".equalsIgnoreCase(fname))
{
clsName = "LegacyClass";
}
else if ("lang.error".equalsIgnoreCase(fname))
{
clsName = "LegacyError";
}
javaPackage = "com.goldencode.p2j.oo." + clsPkg;
javaClassName = clsName;
fullname = javaPackage + "." + javaClassName;
try
{
cls = (Class<? extends _BaseObject_>) Class.forName(fullname);
}
catch (ClassNotFoundException e)
{
LOG.log(Level.SEVERE, "Builtin class " + fullname + " is not implemented in FWD!");
return;
}
}
else
{
try
{
cls = (Class<? extends _BaseObject_>) Class.forName(fullname);
int dotIdx = fullname.lastIndexOf('.');
javaPackage = dotIdx < 0 ? "" : fullname.substring(0, dotIdx);
javaClassName = dotIdx < 0 ? fullname : fullname.substring(dotIdx + 1);
}
catch (ClassNotFoundException e)
{
LOG.log(Level.SEVERE, "Builtin class " + fullname + " is not implemented in FWD!");
return;
}
}
final Class<?> clz = cls;
Consumer<Class<? extends _BaseObject_>> processMethods = (clazz) ->
{
Method[] mthds = clazz.getDeclaredMethods();
for (Method mthd : mthds)
{
LegacySignature lsig = mthd.getAnnotation(LegacySignature.class);
if (lsig == null || lsig.type() == InternalEntry.Type.DESTRUCTOR)
{
continue;
}
String mname = lsig.name();
switch (lsig.type())
{
// WARNING: if you track a constructor name here, the constructor name i.e. LogMessage
// can collide with a method name LogMessage. There is nothing to distinguish a c'tor
// from a method, at this time. In any case, a c'tor can't collide with a method name,
// as the c'tor Java name will be a method with a name following some specific rules.
// Only way for these to collide is for the user to explicitly define a 4GL method
// following FWD's c'tor convention for converted Java method name.
case GETTER:
if (lsig.extent() != SourceNameMapper.NO_EXTENT &&
lsig.parameters() != null &&
lsig.parameters().length == 0)
{
mname = "bulk-get-" + mname;
}
else
{
mname = "get-" + mname;
}
break;
case SETTER:
if (lsig.extent() != SourceNameMapper.NO_EXTENT && lsig.parameters() != null)
{
if (lsig.parameters().length == 1)
{
if (lsig.parameters()[0].extent() != SourceNameMapper.NO_EXTENT)
{
mname = "bulk-set-" + mname;
}
else
{
mname = "set-all-" + mname;
}
}
}
else
{
mname = "set-" + mname;
}
break;
case RESIZE:
case LENGTH:
case PUBLISH:
case SUBSCRIBE:
case UNSUBSCRIBE:
mname = lsig.type().toString() + "-" + mname;
break;
case CONSTRUCTOR:
if (clazz != clz)
{
// c'tors only from the current definition
continue;
}
String qname = getSimpleJavaName();
String qpkg = getJavaPackage().substring("com.goldencode.p2j.oo.".length());
qname = qpkg + "." + qname;
qname = qname.replace('.', '_');
mname = String.format("__%s_constructor_", qname);
if (Modifier.isStatic(mthd.getModifiers()))
{
mname = String.format("%sstatic__", mname);
}
break;
}
String legacySig = SymbolResolver.calculateLegacySignature(mname, lsig);
String javaname = mthd.getName();
addJavaMethodName(javaname, "", legacySig);
}
};
if (LegacyEnum.class.isAssignableFrom(cls))
{
Field[] fields = cls.getDeclaredFields();
for (int i = 0; i < fields.length; i++)
{
Field field = fields[i];
int mods = field.getModifiers();
if (!(Modifier.isPublic(mods) && Modifier.isStatic(mods)))
{
continue;
}
LegacySignature s = field.getAnnotation(LegacySignature.class);
if (s == null)
{
LOG.log(Level.SEVERE, "LegacySignature annotation is missing for enum field " + field + "!");
continue;
}
if (s.type() != InternalEntry.Type.VARIABLE)
{
LOG.log(Level.SEVERE,
"LegacySignature annotation at enum field " + field + " is not a variable!");
continue;
}
String legacy = s.name().toLowerCase();
String javaname = field.getName();
javaEnumNames.put(legacy, javaname);
}
}
Class<?> parent = cls;
while (parent != null)
{
processMethods.accept((Class<? extends _BaseObject_>) parent);
parent = parent.getSuperclass();
if (parent == Object.class || parent == BaseObject.class)
{
parent = null;
}
}
Set<Class<?>> ifaces = new HashSet<>();
Utils.collectInterfaces(cls, ifaces);
ifaces.forEach((iface) -> processMethods.accept((Class<? extends _BaseObject_>) iface));
}
/**
* Get the converted java method name for a method - this ensures the same Java name is used
* for methods which are overridden, across the entire converted legacy classes.
* <p>
* Legacy methods for which there are collisions with the converted Java signatures, the names
* are made unique, but still ensures that any overridden versions will reuse the same name.
*
* @param legacy
* The legacy name.
* @param mAst
* The method definition AST.
*
* @return The converted Java name to use for this legacy method.
*/
public String getConvertedMethodName(String legacy, Aast mAst)
{
ParameterKey[] signature = SymbolResolver.calculateDefinitionSignature(mAst);
return getConvertedMethodName(SymbolResolver.calculateLegacySignature(legacy, signature));
}
/**
* Get the Java method name for a legacy signature. This signature may be from a super-class.
*
* @param legacySig
* The legacy signature.
*
* @return The converted method name or {@code null} if no match is found.
*/
public String getConvertedMethodName(String legacySig)
{
if (legacyToJavaMethod.get(legacySig) != null)
{
return legacyToJavaMethod.get(legacySig);
}
if (parents != null)
{
for (ClassDefinition parent : parents)
{
String result = parent.getConvertedMethodName(legacySig);
if (result != null)
{
return result;
}
}
}
return null;
}
/**
* Set the fully parse state of this class.
*
* @param state
* The processed state.
*/
public void setProcessed(boolean state)
{
this.processed = state;
}
/**
* Check if this class definition was already fully parsed.
*
* @return The {@link #processed} flag.
*/
public boolean isProcessed()
{
return processed;
}
/**
* Check the parent hierarchy to determine if the 4GL class has a .NET ancestor. Store
* the result for future use. This just reports the indirect .NET dependency and does
* not report if this is itself a .NET class.
*/
public void derivedFromDotNet()
{
if (!dotnet && parents != null)
{
for (ClassDefinition parent : parents)
{
if (parent.dotnet)
{
dotnetDerived.add(parent.getName());
}
// recursively process up the inheritance hierarchy
parent.derivedFromDotNet();
// add those classes to the set if needed
if (parent.isDerivedFromDotNet())
{
dotnetDerived.addAll(parent.dotnetDerived);
}
}
}
}
/**
* Report if the parent hierarchy includes a .NET ancestor. This just reports the indirect
* .NET dependency and does not report if this is itself a .NET class.
*
* @return {@code true} if an ancestor is a .NET class.
*/
public boolean isDerivedFromDotNet()
{
return dotnetDerived.size() > 0;
}
/**
* Obtains the derived (parent hierarchy) .NET references, if any.
*
* @return The derived (parent hierarchy) .NET references.
*/
public Set<String> getDerivedDotNet()
{
return new HashSet<String>(dotnetDerived);
}
/**
* Calculate if the given reference is a direct or indirect .NET reference and update out
* counters.
*
* @param ref
* The class being referenced.
*/
public void markDotNetUsage(ClassDefinition ref)
{
// quick exit if we've already processed this class reference
if (dotnetProcessed.contains(ref))
{
return;
}
else
{
dotnetProcessed.add(ref);
}
// this is a direct .NET reference
if (ref.isDotNet())
{
directDotNet.add(ref.getName());
}
else
{
boolean indirect = false;
// this reference is not a direct .NET reference but the reference
// may have direct or indirect references, both of which are considered
// indirect references here
if (ref.isDerivedFromDotNet())
{
// add the list of parent .NET classes to our own list of indirect
indirectDotNet.addAll(ref.dotnetDerived);
indirect = true;
}
if (ref.hasDirectDotNet())
{
// add the list of direct references to our own list of indirect
indirectDotNet.addAll(ref.directDotNet);
indirect = true;
}
if (ref.hasIndirectDotNet())
{
// add the list of indirect references to our own list
indirectDotNet.addAll(ref.indirectDotNet);
indirect = true;
}
// were there any indirect references inside this referenced class?
if (indirect)
{
// then add this class name itself to the list of indirect references
indirectDotNet.add(ref.getName());
}
}
}
/**
* Reports if the class has any direct .NET references.
*
* @return {@code true} if the class has any direct .NET references.
*/
public boolean hasDirectDotNet()
{
return directDotNet.size() > 0;
}
/**
* Obtains the direct .NET references, if any.
*
* @return The direct .NET references.
*/
public Set<String> getDirectDotNet()
{
return new HashSet<String>(directDotNet);
}
/**
* Reports if the class has any indirect .NET references.
*
* @return {@code true} if the class has any indirect .NET references.
*/
public boolean hasIndirectDotNet()
{
return indirectDotNet.size() > 0;
}
/**
* Obtains the indirect .NET references, if any.
*
* @return The indirect .NET references.
*/
public Set<String> getIndirectDotNet()
{
return new HashSet<String>(indirectDotNet);
}
/**
* Reports if the a reference to this class should be considered an indirect .NET reference
* in the calling class.
*
* @return {@code true} if the class is NOT a .NET class AND it is derived from a .NET
* class or has its own direct or indirect references to a .NET class.
*/
public boolean isIndirectDotNetReference()
{
return !dotnet && (isDerivedFromDotNet() || hasDirectDotNet() || hasIndirectDotNet());
}
/**
* Get the AST IDs for all static or instance table definitions.
*
* @return A set of all static or instance table definitions, having protected access mode.
*/
public Set<Long> getTableIds()
{
// only protected tables are seen - there is no public access mode for them.
Map<String, MemberData> staticTables = getMemberStore(DataStoreType.TABLE, true);
Map<String, MemberData> instanceTables = getMemberStore(DataStoreType.TABLE, false);
Set<Long> res = new HashSet<>();
if (staticTables != null)
{
staticTables.values().forEach(mdat ->
{
if (!mdat.provisional && mdat.access == KW_PROTECTD)
{
res.add(mdat.astId);
}
});
}
if (instanceTables != null)
{
instanceTables.values().forEach(mdat ->
{
if (!mdat.provisional && mdat.access == KW_PROTECTD)
{
res.add(mdat.astId);
}
});
}
return res;
}
/**
* The parse for this entire file has finished, and the AST IDs have been computed.
* This is the time the capture the AST IDs or add other annotations, as is executed just
* before the AST is persisted.
*
* @param global
* Flag indicating that the parsing of all files has finished, and a global cleanup can be done
* for this instance.
*/
public void parseFinished(boolean global)
{
if (imethods != null)
{
imethods.keySet().forEach(k -> imethods.get(k).values().forEach(md -> md.parseFinished(global)));
}
if (smethods != null)
{
smethods.keySet().forEach(k -> smethods.get(k).values().forEach(md -> md.parseFinished(global)));
}
if (istores != null)
{
istores.keySet().forEach(k -> istores.get(k).values().forEach(md -> md.parseFinished(global)));
}
if (sstores != null)
{
sstores.keySet().forEach(k -> sstores.get(k).values().forEach(md -> md.parseFinished(global)));
}
if (global)
{
schemaDict = null;
}
else
{
cleanupObjectResources();
}
}
/**
* Obtains the name of this class.
*
* @return Fully qualified class name.
*/
public String getName()
{
return name;
}
/**
* Get the simple legacy name for this class.
*
* @return See above.
*/
public String getSimpleName()
{
if (name == null)
{
return null; // not yet computed
}
int dotIdx = name.lastIndexOf('.');
return dotIdx < 0 ? name : name.substring(dotIdx + 1);
}
/**
* Set the converted Java package and Java class name.
*
* @param pkg
* Full Java package in dotted notation (no class name).
* @param cls
* Simple Java class name, without any package prefix.
*/
public void setJavaClassName(String pkg, String cls)
{
this.javaPackage = pkg;
this.javaClassName = cls;
}
/**
* Get the converted fully qualified Java class name for this definition.
*
* @return The converted fully qualified Java class name or {@code null} if it has not yet been
* computed.
*/
public String getQualifiedJavaName()
{
String clsName = getSimpleJavaName();
if (clsName == null)
{
return null;
}
String pkg = getJavaPackage();
return (pkg == null || pkg.isEmpty()) ? clsName : pkg + "." + clsName;
}
/**
* Get the converted simple Java class name for this definition.
*
* @return The converted Java class name or {@code null} if it has not yet been computed.
*/
public String getSimpleJavaName()
{
return javaClassName;
}
/**
* Get the converted Java package for this definition.
*
* @return The converted Java package name or {@code null} if it has not yet been computed.
*/
public String getJavaPackage()
{
return javaPackage;
}
/**
* Obtains the filename of the .cls that defined this class/interface.
*
* @return Project relative .cls source file name.
*/
public String getFilename()
{
return filename;
}
/**
* Obtains the parent class definition for this class.
*
* @return Parent class definition or <code>null</code> if there is no
* explicit parent.
*/
public ClassDefinition[] getParents()
{
return parents;
}
/**
* Obtains the list of interfaces implemented by this class.
*
* @return Implemented interfaces.
*/
public Set<String> getInterfaces()
{
return ifaces == null ? null : new HashSet<String>(ifaces);
}
/**
* Access the flag that denotes if this is a built-in class definition.
*
* @return <code>true</code> if this is a built-in class.
*/
public boolean isBuiltIn()
{
return builtin;
}
/**
* Access the flag that denotes if this is a class definition.
*
* @return <code>true</code> if this is a class definition.
*/
public boolean isClass()
{
return otype == OOType.CLASS;
}
/**
* Access the flag that denotes if this is an interface definition.
*
* @return <code>true</code> if this is an interface definition.
*/
public boolean isInterface()
{
return otype == OOType.INTERFACE;
}
/**
* Access the flag that denotes if this is an enum definition.
*
* @return <code>true</code> if this is an enum definition.
*/
public boolean isEnum()
{
return otype == OOType.ENUM;
}
/**
* Access the flag that denotes if this is a .NET class definition.
*
* @return <code>true</code> if this is a .NET class.
*/
public boolean isDotNet()
{
return dotnet;
}
/**
* Access the flag that denotes if this is a Java class definition.
*
* @return <code>true</code> if this is a Java class.
*/
public boolean isJava()
{
return false;
}
/**
* The method indicates whether this inctance represents a "mock" class definition
* used to resolve symbols during class pre-scan mode in case the class file has not been
* parsed.
*
* @return <code>true</code> is this instance represents a "mock" class definition,
* <code>false</code> otherwise.
*/
public boolean isMock()
{
return false;
}
/**
* Get the next {@link #tempIdx} value.
*
* @return See above.
*/
public int nextTempIdx()
{
return tempIdx++;
}
/**
* Add the named method to this class. This is only called during parsing.
*
* @param name
* Method name.
* @param type
* Return type for the method.
* @param access
* Access mode (<code>KW_PUBLIC</code>, <code>KW_PROTECTD</code>
* or <code>KW_PRIVATE</code>).
* @param isStatic
* <code>true</code> if the method is static.
* @param node
* The AST to be annotated or <code>null</code> if no node needs annotations.
* @param ret
* The AST for the method's return type. This is used to copy OO annotations back to
* the method node.
*/
public void addMethod(String name, int type, int access, boolean isStatic, Aast node, Aast ret)
{
ParameterKey[] sig = SymbolResolver.calculateDefinitionSignature(node);
// methods provide overloading so we can't use the simple member store mapping approach
boolean isPreScan = SymbolResolver.isPreScan();
int extent = 0;
int mTempIdx = 0;
synchronized (lock)
{
Map<SignatureKey, MemberData> defs = getMethodStore(name, isStatic);
Map<String, Object> retType = new HashMap<>();
if (ret != null)
{
Iterator<String> iter = ret.annotationKeys();
while (iter.hasNext())
{
String akey = iter.next();
if ("original-token".equals(akey))
{
// this annotation has an AST as value, it just consumes memory if is kept.
continue;
}
retType.put(akey, ret.getAnnotation(akey));
}
}
String qname = (String) retType.get("qualified");
boolean override = node != null && node.getImmediateChild(KW_OVERRIDE, null) != null;
boolean abstr = node != null && node.getImmediateChild(KW_ABSTRACT, null) != null;
boolean register = !isPreScan || (builtin && !dotnet);
MemberData prevMdat = defs.get(new SignatureKey(sig));
extent = node == null ? 0 : SymbolResolver.readExtent(node);
MemberData mdat = createMethod(defs, name, sig, access, isStatic, type, qname,
null, extent, tempIdx, override, abstr, retType, register);
mdat.ast = node;
if (prevMdat != null)
{
if (isPreScan)
{
// this can happen if we are referencing this class via a USING from an USING defined
// in this file... in this case, inherit the tempidx, but show a warning.
// TODO: these show up for constructors and many other cases; we can't fill up the logs with
// useless warnings, so we will disable for now
// System.out.printf("\nERROR: duplicate method definition!\n");
// System.out.printf("EXISTING %s\n", prevMdat.toString());
// System.out.printf("DUPLICATE %s\n at %s", mdat.toString(), node.dumpTree());
}
// inherit the tempIdx computed at the pre-scan phase.
mdat.tempIdx = prevMdat.tempIdx;
}
else
{
if (!isPreScan)
{
// there must always be a pre-scan definition...
System.out.printf("\nERROR: no pre-scan method definition for \n");
System.out.printf("%s\n", mdat.toString());
return;
}
nextTempIdx();
}
mTempIdx = mdat.tempIdx;
}
// annotate the given node
if (node != null)
{
node.putAnnotation("name", name);
node.putAnnotation("return_type", Long.valueOf(type));
node.putAnnotation("signature", SymbolResolver.calculateLegacySignature(name, sig));
node.putAnnotation("access-mode", Long.valueOf(access));
node.putAnnotation("static", Boolean.valueOf(isStatic));
node.putAnnotation("oo-data-store", DataStoreType.METHOD.toString());
node.putAnnotation("tempidx", Long.valueOf(mTempIdx));
node.putAnnotation("tempidx-file", this.filename);
if (extent != 0)
{
node.putAnnotation("extent", Long.valueOf(extent));
}
if (ret != null && ret.isAnnotation("qualified"))
{
String qname = (String) ret.getAnnotation("qualified");
if (!(ret.isAnnotation("is-java") && (Boolean) ret.getAnnotation("is-java")))
{
qname = qname.toLowerCase();
}
node.putAnnotation("qualified", qname);
// Because the "qualified" annotation is being inserted into ret during pre-scan,
// we want to prevent these additional annotation requests, since they aren't
// in the method's return node (ret).
if (!isPreScan)
{
node.putAnnotation("source-file", (String) ret.getAnnotation("source-file"));
node.putAnnotation("builtin-cls", (Boolean) ret.getAnnotation("builtin-cls"));
node.putAnnotation("dotnet-cls", (Boolean) ret.getAnnotation("dotnet-cls"));
node.putAnnotation("indirect-dotnet", (Boolean) ret.getAnnotation("indirect-dotnet"));
node.putAnnotation("is-class", (Boolean) ret.getAnnotation("is-class"));
node.putAnnotation("is-interface", (Boolean) ret.getAnnotation("is-interface"));
node.putAnnotation("is-enum", (Boolean) ret.getAnnotation("is-enum"));
node.putAnnotation("is-java", (Boolean) ret.getAnnotation("is-java"));
if (ret.isAnnotation("generic-type-parameter"))
{
String gtype = (String) ret.getAnnotation("generic-type-parameter");
node.putAnnotation("generic-type-parameter", gtype);
}
if (ret.isAnnotation("generic-type-is-primitive"))
{
boolean prim = (boolean) ret.getAnnotation("generic-type-is-primitive");
node.putAnnotation("generic-type-is-primitive", prim);
}
}
}
// annotate the parameters
SymbolResolver.processDefinitionSignature(node, (Aast ast, ParameterKey pk, int idx) ->
{
ast.putAnnotation("jtype", pk.toJava());
});
}
}
/**
* Find the named method without any knowledge of the signature. This method will search up
* the parent hierarchy (recursively) if no match is found in the current class.
* <p>
* <b>If a positive value is returned, the caller can be assured that there is an accessible
* method with that name BUT the actual type of the return value cannot be known until the
* signature is known. This is due to the fact that the same method name can be present with
* differing return values and only the parameter signature matching can differentiate the
* exact method that will be called.</b>
* <p>
* <b>Do not call this from any location where the type must be known authoritatively.</b>
*
* @param name
* Resource name.
* @param access
* Access mode (<code>KW_PUBLIC</code>, <code>KW_PROTECTD</code> or
* <code>KW_PRIVATE</code>).
* @param isStatic
* If <code>true</code>, only static methods will be looked up.
* Otherwise any method can be returned.
* @param internal
* <code>true</code> if the lookup is internal to the current class definition.
*
* @return The type found or <code>-1</code> if no match exists.
*/
public int guessMethodType(String name, int access, boolean isStatic, boolean internal)
{
MemberData dat = guessMethod(name, access, isStatic, internal);
return (dat == null) ? -1 : dat.type;
}
/**
* Annotate a published class event, with the details of the parameter signature.
*
* @param evt
* The event node, annotated with its class definition.
* @param call
* The event publish node.
* @param isStatic
* <code>true</code> if the call is from a static context.
*/
public void annotateObjectEvent(Aast evt, Aast call, boolean isStatic)
{
String evtName = evt.getText();
int accessMode = (int) ((long) evt.getAnnotation("access-mode"));
Variable var = lookupVariableWrapper(evtName, accessMode, isStatic);
Aast evtSig = var.getDefinition().getImmediateChild(EVENT_SIGNATURE, null);
// fixup parents
((AnnotatedAst)call).fixupParent(null);
ParameterKey[] callsig = SymbolResolver.calculateCallSignature(call);
ParameterKey[] defsig = SymbolResolver.calculateDefinitionSignature(evtSig);
annotateCallSignature(call, callsig, defsig, null);
}
/**
* Annotate the given method invocation reference with details for the specified method
* in this class. This is a no-operation if the method doesn't exist or if the node is
* <code>null</code>.
*
* @param mname
* Method name, {@code null} for a c'tor invocation.
* @param signature
* Array of type values for the parameter list, in left to right order.
* @param access
* Access mode (<code>KW_PUBLIC</code>, <code>KW_PROTECTD</code>
* or <code>KW_PRIVATE</code>).
* @param isStatic
* <code>true</code> if the method is static.
* @param isSuper
* <code>true</code> if the rvalue refnode is the SUPER keyword.
* @param internal
* <code>true</code> if the lookup is internal to the current class definition.
* @param node
* The AST to be annotated.
*/
public void annotateMethodCall(String mname,
ParameterKey[] signature,
int access,
boolean isStatic,
boolean isSuper,
boolean internal,
Aast node)
{
// manufacture a c'tor name for the lookup, if needed
if (mname == null)
{
int idx = name.lastIndexOf('.');
mname = ((idx < 0) ? name : name.substring(idx + 1)).toLowerCase();
}
if (node != null)
{
// go ahead and annotate the chp_wrapper right now too
MethodSearchResult found = lookupMethodWorker(mname, signature, access, isStatic, internal, node);
MemberData mdat = (found == null) ? null : (MemberData) found.data;
if (mdat != null && (isSuper || !(found != null && found.isDynamic())))
{
// method chain calls starting with SUPER keyword can not be morphed to dynamic invoke, as the
// super calls require JVM to handle them.
// the type may have been set incorrectly based on guessing, since the parameter
// signature was not yet available; force it to the correct value here
if (node.getType() != FUNC_CLASS && node.getType() != KW_THIS_OBJ && node.getType() != KW_SUPER)
{
node.setType(mdat.type);
}
// the access mode won't necessarily be the same as that passed in
node.putAnnotation("access-mode", Long.valueOf(mdat.access));
node.putAnnotation("static", Boolean.valueOf(mdat.isStatic));
if (mdat.modes != null && !mdat.modes.isEmpty())
{
node.putAnnotation("param_modes", mdat.modes);
}
node.putAnnotation("tempidx", Long.valueOf(mdat.tempIdx));
node.putAnnotation("tempidx-file", mdat.container.filename);
node.putAnnotation("found-in-cls", mdat.container.name);
if (mdat.container.builtin)
{
node.putAnnotation("builtin-cls", true);
}
if (mdat.container.dotnet)
{
node.putAnnotation("dotnet-cls", true);
}
node.putAnnotation("found-in-source-file", mdat.container.filename);
if (mdat.extent != 0)
{
node.putAnnotation("extent", Long.valueOf(mdat.extent));
}
if (mdat.container.builtin && !mdat.container.dotnet)
{
String legacySignature = SymbolResolver.calculateLegacySignature(mdat.name, mdat.signature);
String javaname = mdat.container.getConvertedMethodName(legacySignature);
if (javaname != null)
{
node.putAnnotation("javaname", javaname);
}
}
else if (mdat.container.isEnum() && mdat.javaname != null)
{
node.putAnnotation("javaname", mdat.javaname);
}
// we cannot annotate the javaname at this point because we are currently in the non-pre-scan
// parse of a file and:
// - a method call can reference a method definition that appears later in the file so the
// parsing must rely upon the pre-scan method def instead of the regular parse method def
// where the javaname is calculated
// - can be a reference to a constructor which we currently don't calculate java names
//
// if (mdat.javaname != null)
// {
// node.putAnnotation("javaname", mdat.javaname);
// }
// else
// {
// // constructors don't have a javaname set
// if (!getSimpleName().equalsIgnoreCase(mname))
// {
// System.out.printf("WARNING: Missing javaname in %s\n\nfound %s\nfor location %s\n",
// getName(),
// mdat.toString(),
// node.dumpTree(true));
// }
// }
annotateCallSignature(node, signature, mdat.signature, found.getOverrides());
String legacySig = SymbolResolver.calculateLegacySignature(mdat.name, mdat.signature);
node.putAnnotation("signature", legacySig);
if (isBuiltIn())
{
// for builtin method calls, save the typelist too
for (int i = 0; i < mdat.signature.length; i++)
{
String type = mdat.signature[i].type;
int idx = type.indexOf('[');
if (idx >= 0)
{
type = type.substring(0, idx);
}
node.putAnnotation("typelist", type, i);
}
}
if (mdat.type == OO_METH_CLASS)
{
if (mdat.qname == null)
{
String err =
String.format("Missing class name for %s!", node.getDescriptiveTokenText());
throw new RuntimeException(err, new NoViableAltException((AnnotatedAst) node));
}
else
{
node.putAnnotation("qualified", mdat.qname.toLowerCase());
if (mdat.retType != null && mdat.retType.containsKey("generic-type-parameter"))
{
String gtype = (String) mdat.retType.get("generic-type-parameter");
node.putAnnotation("generic-type-parameter", gtype);
}
if (mdat.retType != null && mdat.retType.containsKey("generic-type-is-primitive"))
{
boolean prim = (boolean) mdat.retType.get("generic-type-is-primitive");
node.putAnnotation("generic-type-is-primitive", prim);
}
}
}
}
else
{
// dynamic cases will have a non-null result; this may be for table-handle parms, dataset-handle
// parms or POLY parms
if (found != null && found.isDynamic())
{
node.putAnnotation("dynamic", true);
if (mdat != null && mdat.isStatic)
{
// we need to hard-code both the return type and the static class used as target, for these
// dynamic cases.
node.putAnnotation("dynamic-classname", mdat.container.name);
}
else if (isStatic)
{
node.putAnnotation("dynamic-classname", this.name);
}
// constructors always return the expected type, so do not annotate them with 'dynamic-poly'
boolean isCtor = node.getType() == ProgressParserTokenTypes.FUNC_CLASS &&
((Long) node.getAnnotation("oldtype")) == ProgressParserTokenTypes.KW_NEW;
if (found.isPolyArgs() && !isCtor)
{
node.putAnnotation("dynamic-poly", true);
for (int i = 0; i < mdat.signature.length; i++)
{
ParameterKey pkey = mdat.signature[i];
String ptype = pkey.toJava();
node.putAnnotation("dynamic-poly-sig", ptype, -1);
}
if (mdat.type == OO_METH_CLASS)
{
if (mdat.qname == null)
{
String err =
String.format("Missing class name for %s!", node.getDescriptiveTokenText());
throw new RuntimeException(err, new NoViableAltException((AnnotatedAst) node));
}
else
{
node.putAnnotation("qualified", mdat.qname.toLowerCase());
if (mdat.retType != null && mdat.retType.containsKey("generic-type-parameter"))
{
String gtype = (String) mdat.retType.get("generic-type-parameter");
node.putAnnotation("generic-type-parameter", gtype);
}
if (mdat.retType != null && mdat.retType.containsKey("generic-type-is-primitive"))
{
boolean prim = (boolean) mdat.retType.get("generic-type-is-primitive");
node.putAnnotation("generic-type-is-primitive", prim);
}
}
}
}
}
else
{
// lookup failure, should not happen with syntactically correct code
System.out.printf("\nannotateMethodCall FAILURE: %s from class %s, access %s, " +
"static %b, at %s\n PARAMETERS: %d\n",
mname,
getName(),
ProgressParser.lookupTokenName(access),
isStatic,
node.dumpTree(),
signature.length);
for (int i = 0; i < signature.length; i++)
{
System.out.printf(" %s\n", signature[i]);
}
}
}
}
}
/**
* Add a data member (variable, property, enum or event) to this class.
*
* @param type
* The token type of the member being added. This will be DEFINE_VARIABLE, DEFINE_PROPERTY,
* DEFINE_EVENT or DEFINE_ENUM.
* @param name
* Variable or property name.
* @param var
* Variable instance.
* @param node
* The definition node for this member.
* @param access
* Access mode (<code>KW_PUBLIC</code>, <code>KW_PROTECTD</code>
* or <code>KW_PRIVATE</code>).
* @param isStatic
* <code>true</code> if the variable/property is static.
* @param qname
* Fully qualified class name where the resource represents an
* object instance, otherwise <code>null</code>.
* @param provisional
* {@code true} if this definition has not been finalized.
*/
public void addVariable(int type,
String name,
Variable var,
Aast node,
int access,
boolean isStatic,
String qname,
boolean provisional)
{
MemberData mdat = addWorker(DataStoreType.VAR,
name,
null,
var,
var.getTokenType(),
access,
isStatic,
qname,
-1);
mdat.setProvisional(provisional);
if (type == DEFINE_ENUM)
{
String ename = name.toLowerCase();
if (javaEnumNames.containsKey(ename))
{
mdat.javaname = javaEnumNames.get(ename);
}
}
if (mdat.javaname == null)
{
mdat.javaname = nconvert.convert(name, MatchPhraseConstants.TYPE_VARIABLE);
}
Consumer<ClassDefinition> consumer = (ClassDefinition cls) ->
{
resolvePossibleVariableConflicts(mdat, cls.istores);
resolvePossibleVariableConflicts(mdat, cls.sstores);
};
consumer.accept(this);
processParentGraph(consumer);
if (node != null)
{
node.putAnnotation("oo-data-store", DataStoreType.VAR.toString());
node.putAnnotation("javaname", mdat.javaname);
if (mdat.suffix > 1)
{
node.putAnnotation("definitive-javaname", mdat.javaname);
}
}
}
/**
* A method that resolves naming conflicts for properties by appending a suffix to the javaname.
*
* @param mdat
* Container to track data associated with the current class member.
* @param stores
* Data stores.
*/
private void resolvePossibleVariableConflicts(MemberData mdat,
Map<DataStoreType, Map<String, MemberData>> stores)
{
if (stores == null)
{
return;
}
for(DataStoreType dataStoreType: stores.keySet())
{
Map<String, MemberData> vars = stores.get(dataStoreType);
if (vars == null)
{
return;
}
for (MemberData attribute : vars.values())
{
// if the current javaname collides with any other javaname or
// a name of an attribute that is not suffixed, suffix the current javaname.
if (attribute != null &&
((attribute.name.equals(mdat.javaname) && attribute.suffix == 1) ||
(attribute.javaname != null && attribute.javaname.equals(mdat.javaname))) &&
!(attribute.name.equals(mdat.name) && dataStoreType == DataStoreType.VAR))
{
if (attribute.suffix > 1)
{
mdat.javaname = mdat.javaname.substring(0, mdat.javaname.length() - 2);
}
mdat.javaname = mdat.javaname.concat("_" + attribute.suffix);
mdat.suffix++;
return;
}
}
}
}
/**
* Obtain the token type of the data member (variable) or property given
* the name and access mode.
* <p>
* If the resource does not exist in this class definition the parent
* (if one exists) will be checked. This means a recursive call can
* occur here.
*
* @param name
* Variable or property name.
* @param access
* Access mode (<code>KW_PUBLIC</code>, <code>KW_PROTECTD</code>
* or <code>KW_PRIVATE</code>).
* @param isStatic
* If <code>true</code>, only static methods will be looked up.
* Otherwise any method can be returned.
*
* @return Return token type or -1 if no such variable exists.
*/
public int lookupVariable(String name, int access, boolean isStatic)
{
MemberData mdat = lookupHierarchy(DataStoreType.VAR, name, access, isStatic);
return (mdat == null) ? -1 : mdat.type;
}
/**
* Obtain the Java name of the data member the name and access mode.
* <p>
* If the resource does not exist in this class definition the parent (if one exists) will be checked.
* This means a recursive call can occur here.
*
* @param name
* Variable or property name.
* @param access
* Access mode ({@code KW_PUBLIC}, {@code KW_PROTECTD} or {@code KW_PRIVATE}).
* @param isStatic
* <code>true</code> if this is a static reference.
*
* @return The Java name of the data member or {@code null} if no such variable exists.
*/
public String lookupVariableJavaName(String name, int access, boolean isStatic)
{
MemberData mdat = lookupHierarchy(DataStoreType.VAR, name, access, isStatic);
return (mdat == null) ? null : mdat.javaname;
}
/**
* Obtain the extent of the data member the name and access mode.
* <p>
* If the resource does not exist in this class definition the parent (if one exists) will be checked.
* This means a recursive call can occur here.
*
* @param name
* Variable or property name.
* @param access
* Access mode ({@code KW_PUBLIC}, {@code KW_PROTECTD} or {@code KW_PRIVATE}).
* @param isStatic
* <code>true</code> if this is a static reference.
*
* @return The extent of the data member or 0 if the variable is not an extent var or if no such
* variable exists.
*/
public long lookupVariableExtent(String name, int access, boolean isStatic)
{
MemberData mdat = lookupHierarchy(DataStoreType.VAR, name, access, isStatic);
return (mdat == null || mdat.var.getExtent() == 0) ? 0L : (long) mdat.var.getExtent();
}
/**
* Obtain the fully qualified class name of the object instance
* represented by the data member (variable) or property given
* the name and access mode.
* <p>
* If the resource does not exist in this class definition the parent
* (if one exists) will be checked. This means a recursive call can
* occur here.
*
* @param name
* Variable or property name.
* @param access
* Access mode (<code>KW_PUBLIC</code>, <code>KW_PROTECTD</code>
* or <code>KW_PRIVATE</code>).
* @param isStatic
* <code>true</code> if this is a static reference.
*
* @return Fully qualified class name of the the object instance
* represented by this variable or data member, or
* <code>null</code> if no such variable exists.
*/
public String lookupVariableClassName(String name, int access, boolean isStatic)
{
MemberData mdat = lookupHierarchy(DataStoreType.VAR, name, access, isStatic);
return (mdat == null) ? null : mdat.qname;
}
/**
* Obtain the <code>Variable</code> wrapper for the data member (variable) or property given
* the name and access mode.
* <p>
* If the resource does not exist in this class definition the parent (if one exists) will
* be checked. This means a recursive call can occur here.
*
* @param name
* Variable or property name.
* @param access
* Access mode (<code>KW_PUBLIC</code>, <code>KW_PROTECTD</code>
* or <code>KW_PRIVATE</code>).
* @param isStatic
* <code>true</code> if this is a static reference.
*
* @return The associated wrapper or <code>null</code> if no such variable exists.
*/
public Variable lookupVariableWrapper(String name, int access, boolean isStatic)
{
return lookupVariableWrapper(name, access, isStatic, false);
}
/**
* Obtain the <code>Variable</code> wrapper for the data member (variable) or property given
* the name and access mode.
* <p>
* If the resource does not exist in this class definition the parent (if one exists) will
* be checked. This means a recursive call can occur here.
*
* @param name
* Variable or property name.
* @param access
* Access mode (<code>KW_PUBLIC</code>, <code>KW_PROTECTD</code>
* or <code>KW_PRIVATE</code>).
* @param isStatic
* <code>true</code> if this is a static reference.
* @param chainedReference
* Flag indicating that the reference being looked is from a ":" chain.
*
* @return The associated wrapper or <code>null</code> if no such variable exists.
*/
public Variable lookupVariableWrapper(String name, int access, boolean isStatic, boolean chainedReference)
{
MemberData mdat = lookupHierarchy(DataStoreType.VAR, name, access, isStatic, chainedReference);
return (mdat == null) ? null : mdat.var;
}
/**
* Cleanup any object resources which should not remain in memory. The only resources
* that will be used after this point are ones which can be looked up by other classes.
*/
public void cleanupObjectResources()
{
synchronized (lock)
{
if (schemaDict != null)
{
ArrayList<String> saveList = new ArrayList<String>();
defineExceptions(saveList, false);
defineExceptions(saveList, true);
if (saveList.isEmpty())
{
// nothing to save, just release the reference.
schemaDict = null;
}
else
{
String[] save = saveList.toArray(new String[0]);
schemaDict.removeAllExcept(save);
}
}
}
}
/**
* Obtain the backing schema dictionary, if it exists.
*
* @return The schema dictionary or <code>null</code> if it was never set.
*/
public SchemaDictionary getSchemaDict()
{
synchronized (lock)
{
return schemaDict;
}
}
/**
* Add the named temp-table or buffer to this class.
*
* @param node
* The definition AST.
* @param name
* Temp-table or buffer name.
* @param type
* The integer token type representing the type of table.
* @param access
* Access mode (<code>KW_PROTECTD</code> or <code>KW_PRIVATE</code>).
* @param isStatic
* <code>true</code> if the method is static.
* @param schemaDict
* The schema dictionary that manages the temp-tables and buffers for this
* class.
* @param inMethod
* Flag indicating we are in a method definition.
*/
public void addTable(Aast node,
String name,
int type,
int access,
boolean isStatic,
SchemaDictionary schemaDict,
boolean inMethod)
{
synchronized (lock)
{
if (this.schemaDict == null)
{
this.schemaDict = schemaDict;
}
}
MemberData mdat = addWorker(DataStoreType.TABLE, name, null, null, type, access, isStatic, null, -1);
// temp-tables are never provisional
mdat.setProvisional(type == BUFFER && (SymbolResolver.isPreScan() || inMethod));
if (!mdat.provisional)
{
mdat.ast = node;
if (node != null)
{
node.putAnnotation("access-mode", access == -1 ? (long) KW_PRIVATE : (long) access);
node.putAnnotation("oo-data-store", DataStoreType.TABLE.toString());
if (isStatic)
{
node.putAnnotation("static", isStatic);
}
}
}
}
/**
* Obtain the token type of the temp-table or buffer given the name and access mode.
* <p>
* If the resource does not exist in this class definition the parent
* (if one exists) will be checked. This means a recursive call can
* occur here.
*
* @param name
* Temp-table or buffer name.
* @param access
* Access mode (<code>KW_PUBLIC</code>, <code>KW_PROTECTD</code>
* or <code>KW_PRIVATE</code>).
* @param isStatic
* If <code>true</code>, only static methods will be looked up.
* Otherwise any method can be returned.
*
* @return Return token type or -1 if no such variable exists.
*/
public int lookupTable(String name, int access, boolean isStatic)
{
MemberData mdat = lookupHierarchy(DataStoreType.TABLE, name, access, isStatic);
return (mdat == null) ? -1 : mdat.type;
}
/**
* Add the named query to this class.
*
* @param name
* Query name.
* @param node
* The AST to be annotated or <code>null</code> if no node needs annotations.
* @param type
* The integer token type representing the type of table.
* @param access
* Access mode (<code>KW_PROTECTD</code> or <code>KW_PRIVATE</code>).
* @param isStatic
* <code>true</code> if the method is static.
*/
public void addQuery(String name,
Aast node,
int type,
int access,
boolean isStatic,
boolean inMethod)
{
MemberData mdat = addWorker(DataStoreType.QRY, name, null, null, type, access, isStatic, null, -1);
mdat.setProvisional(SymbolResolver.isPreScan() || inMethod);
if (!mdat.provisional)
{
mdat.ast = node;
if (node != null)
{
node.putAnnotation("access-mode", access == -1 ? (long) KW_PRIVATE : (long) access);
node.putAnnotation("oo-data-store", DataStoreType.QRY.toString());
if (isStatic)
{
node.putAnnotation("static", isStatic);
}
}
}
}
/**
* Obtain the token type of the query member given the name and access mode.
* <p>
* If the resource does not exist in this class definition the parent
* (if one exists) will be checked. This means a recursive call can
* occur here.
*
* @param name
* Query name.
* @param access
* Access mode (<code>KW_PUBLIC</code>, <code>KW_PROTECTD</code>
* or <code>KW_PRIVATE</code>).
* @param isStatic
* If <code>true</code>, only static methods will be looked up.
* Otherwise any method can be returned.
*
* @return Return token type or -1 if no such variable exists.
*/
public int lookupQuery(String name, int access, boolean isStatic)
{
MemberData mdat = lookupHierarchy(DataStoreType.QRY, name, access, isStatic);
return (mdat == null) ? -1 : mdat.type;
}
/**
* Add the named dataset to this class.
*
* @param var
* The associated variable for this data-set.
* @param node
* The definition AST.
* @param name
* Dataset name.
* @param type
* The integer token type representing the type of table.
* @param access
* Access mode (<code>KW_PROTECTD</code> or <code>KW_PRIVATE</code>).
* @param isStatic
* <code>true</code> if the method is static.
* @param inMethod
* Flag indicating we are in a method definition.
*/
public void addDataSet(Variable var,
Aast node,
String name,
int type,
int access,
boolean isStatic,
boolean inMethod)
{
MemberData mdat = addWorker(DataStoreType.DATASET, name, null, var, type, access, isStatic, null, -1);
// datasets can't be defined in a class method
mdat.setProvisional(false);
if (!mdat.provisional)
{
mdat.ast = node;
if (node != null)
{
node.putAnnotation("access-mode", access == -1 ? (long) KW_PRIVATE : (long) access);
node.putAnnotation("oo-data-store", DataStoreType.DATASET.toString());
if (isStatic)
{
node.putAnnotation("static", isStatic);
}
}
}
}
/**
* Obtain the token type of the dataset member given the name and access mode.
* <p>
* If the resource does not exist in this class definition the parent
* (if one exists) will be checked. This means a recursive call can
* occur here.
*
* @param name
* Dataset name.
* @param access
* Access mode (<code>KW_PUBLIC</code>, <code>KW_PROTECTD</code>
* or <code>KW_PRIVATE</code>).
* @param isStatic
* If <code>true</code>, only static methods will be looked up.
* Otherwise any method can be returned.
*
* @return Return token type or -1 if no such dataset exists.
*/
public int lookupDataSet(String name, int access, boolean isStatic)
{
MemberData mdat = lookupHierarchy(DataStoreType.DATASET, name, access, isStatic);
return (mdat == null) ? -1 : mdat.type;
}
/**
* Obtain the token type of the dataset member given the name and access mode.
* <p>
* If the resource does not exist in this class definition the parent
* (if one exists) will be checked. This means a recursive call can
* occur here.
*
* @param name
* Dataset name.
* @param access
* Access mode (<code>KW_PUBLIC</code>, <code>KW_PROTECTD</code>
* or <code>KW_PRIVATE</code>).
* @param isStatic
* If <code>true</code>, only static methods will be looked up.
* Otherwise any method can be returned.
*
* @return Return token type or -1 if no such dataset exists.
*/
public Variable lookupDataSetWrapper(String name, int access, boolean isStatic)
{
MemberData mdat = lookupHierarchy(DataStoreType.DATASET, name, access, isStatic);
return (mdat == null) ? null : mdat.var;
}
/**
* Add the named data source to this class.
*
* @param var
* The associated variable for this data-set.
* @param node
* The definition AST.
* @param name
* Data source name.
* @param type
* The integer token type representing the type of table.
* @param access
* Access mode (<code>KW_PROTECTD</code> or <code>KW_PRIVATE</code>).
* @param isStatic
* <code>true</code> if the method is static.
* @param inMethod
* Flag indicating we are in a method definition.
*/
public void addDataSource(Variable var,
Aast node,
String name,
int type,
int access,
boolean isStatic,
boolean inMethod)
{
MemberData mdat = addWorker(DataStoreType.DATASRC, name, null, var, type, access, isStatic, null, -1);
mdat.setProvisional(SymbolResolver.isPreScan() || inMethod);
if (!mdat.provisional)
{
mdat.ast = node;
if (node != null)
{
node.putAnnotation("access-mode", access == -1 ? (long) KW_PRIVATE : (long) access);
node.putAnnotation("oo-data-store", DataStoreType.DATASRC.toString());
if (isStatic)
{
node.putAnnotation("static", isStatic);
}
}
}
}
/**
* Obtain the token type of the data source member given the name and access mode.
* <p>
* If the resource does not exist in this class definition the parent
* (if one exists) will be checked. This means a recursive call can
* occur here.
*
* @param name
* Data source name.
* @param access
* Access mode (<code>KW_PUBLIC</code>, <code>KW_PROTECTD</code>
* or <code>KW_PRIVATE</code>).
* @param isStatic
* If <code>true</code>, only static methods will be looked up.
* Otherwise any method can be returned.
*
* @return Return token type or -1 if no such data source exists.
*/
public int lookupDataSource(String name, int access, boolean isStatic)
{
MemberData mdat = lookupHierarchy(DataStoreType.DATASRC, name, access, isStatic);
return (mdat == null) ? -1 : mdat.type;
}
/**
* Obtain the token type of the data source member given the name and access mode.
* <p>
* If the resource does not exist in this class definition the parent
* (if one exists) will be checked. This means a recursive call can
* occur here.
*
* @param name
* Data source name.
* @param access
* Access mode (<code>KW_PUBLIC</code>, <code>KW_PROTECTD</code>
* or <code>KW_PRIVATE</code>).
* @param isStatic
* If <code>true</code>, only static methods will be looked up.
* Otherwise any method can be returned.
*
* @return Return token type or -1 if no such data source exists.
*/
public Variable lookupDataSourceWrapper(String name, int access, boolean isStatic)
{
MemberData mdat = lookupHierarchy(DataStoreType.DATASRC, name, access, isStatic);
return (mdat == null) ? null : mdat.var;
}
/**
* Reports if the given data member is a static member of the given class.
*
* @param name
* Member name to lookup.
* @param access
* Access mode (<code>KW_PUBLIC</code>, <code>KW_PROTECTD</code>
* or <code>KW_PRIVATE</code>).
*
* @return <code>true</code> if the member is static. <code>false</code> otherwise
* including the case where the member does not exist at all.
*/
public boolean isStaticDataMember(String name, int access)
{
return lookupHierarchy(DataStoreType.VAR, name, access, true) != null;
}
/**
* Get all defined methods in this class.
*
* @return All defined method names.
*/
public Set<String> getDefinedMethods()
{
Set<String> res = new HashSet<>();
if (imethods != null)
{
imethods.values().forEach(mp -> mp.values().forEach(md -> res.add(md.name)));
}
if (smethods != null)
{
smethods.values().forEach(mp -> mp.values().forEach(md -> res.add(md.name)));
}
return res;
}
/**
* Recursively process the parent hierarchy to test if the given parent matches some
* condition. This searches the list of parents, checking UP before SIDEWAYS.
*
* @param check
* Delegated code to check the condition.
*
* @return {@code true} if the condition is matched anywhere in the parent graph.
*/
public boolean testParentGraph(Predicate<ClassDefinition> check)
{
boolean result = false;
if (parents != null)
{
for (ClassDefinition parent : parents)
{
result = check.test(parent);
if (result)
{
break;
}
else
{
// recurse
result = parent.testParentGraph(check);
if (result)
{
break;
}
}
}
}
return result;
}
/**
* Recursively process the parent hierarchy to calculate a value. This searches the list of
* parents, checking UP before SIDEWAYS.
*
* @param func
* Delegated code to calculate the value.
* @param continu
* A condition which if it returns <code>true</code>, then the next parent should be tried.
* On <code>false</code>, the current result is returned.
* @param defVal
* Default value to return if no parents exist.
* @param inherit
* {@code true} to search the inheritance hierarchy for a match.
* @param ifaces
* {@code true} to search interfaces (if any exist) in addition to direct parents.
*
* @return The calculated value or {@code null} if no value is calculated.
*/
public <R> R calcFromParentGraph(java.util.function.Function<ClassDefinition, R> func,
Predicate<R> continu,
R defVal,
boolean inherit,
boolean ifaces)
{
Set<String> processed = new HashSet<>();
return calcFromParentGraphWorker(func, continu, defVal, processed, inherit, ifaces);
}
/**
* Recursively process the parent hierarchy to execute a consumer. This searches the list of
* parents, checking UP before SIDEWAYS.
*
* @param consumer
* Delegated code to process the value.
*/
public void processParentGraph(Consumer<ClassDefinition> consumer)
{
processParentGraph(consumer, true);
}
/**
* Recursively process the parent hierarchy to execute a consumer. This searches the list of
* parents, checking UP before SIDEWAYS.
*
* @param consumer
* Delegated code to process the value.
* @param descent
* Flag indicating if the processing is done on descent (from nearest parent to object) or ascent
* (from object to nearest parent).
*/
public void processParentGraph(Consumer<ClassDefinition> consumer, boolean descent)
{
if (parents != null)
{
for (ClassDefinition parent : parents)
{
if (descent)
{
consumer.accept(parent);
}
parent.processParentGraph(consumer, descent);
if (!descent)
{
consumer.accept(parent);
}
}
}
}
/**
* Check if any parent class has the specified accessor. This is required to properly emit
* <code>Override</code> annotations only for getters/setters defined in the abstract property.
*
* @param ast
* The accessor AST.
*
* @return <code>true</code> if a parent defines this getter/setter.
*/
public boolean hasParentPropertyAccessor(Aast ast)
{
if (parents == null)
{
return false;
}
int type = ast.getType();
if (!(type == KW_GET || type == KW_SET))
{
return false;
}
String property = (String) ast.getParent().getAnnotation("name");
boolean getter = (type == KW_GET);
java.util.function.Function<ClassDefinition, Boolean> test = (ClassDefinition parent) ->
{
Variable var = parent.lookupVariableWrapper(property, KW_PROTECTD, false);
return var != null && (getter ? var.isWithGetter() : var.isWithSetter());
};
Boolean res = calcFromParentGraph(test, (b) -> !b, false, true, true);
return res != null && res;
}
/**
* Recursively process the parent hierarchy to calculate a value. This searches the list of
* parents, checking UP before SIDEWAYS.
*
* @param func
* Delegated code to calculate the value.
* @param continu
* A condition which if it returns <code>true</code>, then the next parent should be tried.
* On <code>false</code>, the current result is returned.
* @param defVal
* Default value to return if no parents exist.
* @param inherit
* {@code true} to search the inheritance hierarchy for a match.
* @param ifaces
* {@code true} to search interfaces (if any exist) in addition to direct parents.
*
* @return The calculated value or {@code null} if no value is calculated.
*/
private <R> R calcFromParentGraphWorker(java.util.function.Function<ClassDefinition, R> func,
Predicate<R> continu,
R defVal,
Set<String> processed,
boolean inherit,
boolean ifaces)
{
R result = defVal;
int start = 0;
int num = ((inherit && parents != null) ? parents.length : 0) +
((ifaces && interfaces != null) ? interfaces.length : 0);
ClassDefinition[] list = new ClassDefinition[num];
if (inherit && parents != null)
{
System.arraycopy(parents, 0, list, start, parents.length);
start = parents.length;
}
if (ifaces && interfaces != null)
{
System.arraycopy(interfaces, 0, list, start, interfaces.length);
}
for (ClassDefinition parent : list)
{
// avoid duplicate processing
if (processed.contains(parent.getName()))
{
continue;
}
processed.add(parent.getName());
result = func.apply(parent);
if (!continu.test(result))
{
break;
}
else
{
// recurse
result = parent.calcFromParentGraphWorker(func, continu, defVal, processed, inherit, ifaces);
if (!continu.test(result))
{
break;
}
}
}
return result;
}
/**
* Obtain the static or instance member map or create (and return) it if it doesn't
* already exist.
*
* @param type
* The data store type to lookup.
* @param isStatic
* <code>true</code> if the static map is to be returned, <code>false</code> for
* the instance version.
*
* @return The specified map. It will not be <code>null</code>.
*/
private Map<String, MemberData> getMemberStore(DataStoreType type, boolean isStatic)
{
Map<String, MemberData> data = null;
synchronized (lock)
{
if (isStatic && sstores == null)
{
sstores = new LinkedHashMap<DataStoreType, Map<String, MemberData>>();
}
if (!isStatic && istores == null)
{
istores = new LinkedHashMap<DataStoreType, Map<String, MemberData>>();
}
data = isStatic ? sstores.get(type) : istores.get(type);
if (data == null)
{
data = new LinkedHashMap<String, MemberData>();
if (isStatic)
{
sstores.put(type, data);
}
else
{
istores.put(type, data);
}
}
}
return data;
}
/**
* Get the correct map of method signatures to member data instances. This must only be called when the
* lock instance is owned.
*
* @param name
* Method name.
* @param isStatic
* <code>true</code> if the method is static.
*
* @return The method map.
*/
private Map<SignatureKey, MemberData> getMethodStore(String name, boolean isStatic)
{
if (imethods == null)
{
imethods = new LinkedHashMap<>();
smethods = new LinkedHashMap<>();
}
Map<String, Map<SignatureKey, MemberData>> store = isStatic ? smethods : imethods;
String key = name.toLowerCase();
Map<SignatureKey, MemberData> defs = store.get(key);
if (defs == null)
{
defs = new LinkedHashMap<>();
store.put(key, defs);
}
return defs;
}
/**
* Obtain the member given the name and access mode. If the member does not exist in
* this class definition the parent (if one exists) will be checked.
*
* @param type
* Type of data store to check.
* @param name
* Variable/data member name.
* @param access
* Access mode (<code>KW_PUBLIC</code>, <code>KW_PROTECTD</code>
* or <code>KW_PRIVATE</code>).
* @param isStatic
* If <code>true</code>, only static methods will be looked up.
* Otherwise any method can be returned.
*
* @return Member data or <code>null</code> if no such variable or data
* member exists.
*/
private MemberData lookupHierarchy(DataStoreType type,
String name,
int access,
boolean isStatic)
{
return lookupHierarchy(type, name, access, isStatic, false);
}
/**
* Obtain the member given the name and access mode. If the member does not exist in
* this class definition the parent (if one exists) will be checked.
*
* @param type
* Type of data store to check.
* @param name
* Variable/data member name.
* @param access
* Access mode (<code>KW_PUBLIC</code>, <code>KW_PROTECTD</code>
* or <code>KW_PRIVATE</code>).
* @param isStatic
* If <code>true</code>, only static methods will be looked up.
* Otherwise any method can be returned.
* @param chainedReference
* Flag indicating that the reference being looked is from a ":" chain.
*
* @return Member data or <code>null</code> if no such variable or data
* member exists.
*/
private MemberData lookupHierarchy(DataStoreType type,
String name,
int access,
boolean isStatic,
boolean chainedReference)
{
MemberData mdat = null;
synchronized (lock)
{
mdat = lookupWorker(getMemberStore(type, isStatic), name, access);
if (mdat == null && !isStatic)
{
// can't find an instance member, check for static
mdat = lookupWorker(getMemberStore(type, true), name, access);
}
if (mdat != null && mdat.provisional)
{
if (chainedReference)
{
// if a match is done on a provisional reference, while in a chain, then there must be a
// definition in current class
mdat.setProvisional(false);
}
else
{
// provisional members can't be found
mdat = null;
}
}
if (mdat == null && parents != null)
{
// change access mode since we aren't allowed to see private stuff
// in parent
if (access == KW_PRIVATE)
access = KW_PROTECTD;
for (ClassDefinition parent : parents)
{
// recurse up inheritence hierarchy
mdat = parent.lookupHierarchy(type, name, access, isStatic);
if (mdat != null)
break;
}
}
// if only static members will match
if (mdat != null && isStatic && !mdat.isStatic)
{
// clear our result
mdat = null;
}
if (mdat == null && type == DataStoreType.VAR && !isStatic && interfaces != null)
{
// lookup in interfaces, too
for (ClassDefinition ifaceDef : interfaces)
{
mdat = ifaceDef.lookupHierarchy(type, name, access, isStatic);
if (mdat != null)
{
break;
}
}
}
}
return mdat;
}
/**
* Process a method call to mark any "fuzzy" parameters with the wrapper type that must
* be used for the actual call.
*
* @param call
* The OO_METH_* node whose parameters will be annotated.
* @param callSig
* The array of parameter types that is the method definition signature.
* @param defSig
* The array of parameter types that is the method definition signature.
* @param overrides
* Type overrides found by any fuzzy matching for this method.
*/
private void annotateCallSignature(Aast call,
ParameterKey[] callSig,
ParameterKey[] defSig,
String[] overrides)
{
int idx = 0;
// iterate all parameters and extract their types
Aast parm = (Aast) call.getFirstChild();
if ((call.isAnnotation("oldtype") && (long) call.getAnnotation("oldtype") == KW_NEW) ||
call.getType() == KW_THIS_OBJ ||
call.getType() == KW_SUPER)
{
parm = (Aast) call.getImmediateChild(LPARENS, null);
parm = (Aast) parm.getFirstChild();
}
while (parm != null)
{
int type = parm.getType();
// avoid the optional mode or table parm modifiers
if (type != KW_PUBLISH &&
type != CLASS_EVENT &&
type != KW_INPUT &&
type != KW_IN_OUT &&
type != KW_OUTPUT &&
type != KW_APPEND &&
type != KW_BIND &&
type != KW_BY_REF &&
type != KW_BY_VALUE)
{
parm.putAnnotation("jtype", defSig[idx].toJava());
parm.putAnnotation("parmtype", defSig[idx].mode.longValue());
if (parm.getType() == PARAMETER)
{
Aast ref = parm.getChildAt(parm.getNumImmediateChildren() - 1);
ref.putAnnotation("parmtype", defSig[idx].mode.longValue());
}
boolean outputMode = defSig[idx].mode != null && defSig[idx].mode.intValue() != KW_INPUT;
String classname = defSig[idx].type;
String callType = callSig[idx].type;
String defType = defSig[idx].type;
int idx1 = defType.indexOf("[");
int idx2 = callType.indexOf("[");
// remove the [] from the classname/callType
if (idx1 > 0)
{
classname = defType.substring(0, idx1);
}
if (outputMode)
{
if (!callSig[idx].isExtent())
{
// this is not required to be set for extent arguments
parm.putAnnotation("assign_back", true);
if (defSig[idx].mode.intValue() == KW_IN_OUT)
{
parm.putAnnotation("needs_value", true);
}
if (parm.getType() == PARAMETER)
{
// copy these to the reference, too
Aast ref = parm.getChildAt(parm.getNumImmediateChildren() - 1);
ref.putAnnotation("assign_back", true);
if (defSig[idx].mode.intValue() == KW_IN_OUT)
{
ref.putAnnotation("needs_value", true);
}
}
}
}
if (!defType.equals(callType))
{
// force everything for now
boolean force = true;
if (classname.startsWith("BUFFER ") ||
classname.startsWith("TEMP-TABLE") ||
classname.equals("TABLE-HANDLE") ||
classname.startsWith("DATASET ") ||
classname.equals("DATASET-HANDLE") ||
(!"unknown".equals(callType) &&
(classname.startsWith("object<") ||
classname.startsWith("jobject<"))))
{
force = false;
}
else if (isExtentMatch(defType, callType))
{
// non-extents and extents are matched here
// both caller and def are extent
if (idx1 != -1 && idx2 != -1)
{
String prefix1 = defType.substring(0, idx1);
String prefix2 = callType.substring(0, idx2);
// compare data types
if (prefix1.equals(prefix2))
{
// extent differences alone don't need wrapping
force = false;
}
else
{
// TODO: conversion needs to be enhanced to support wrapping array parms
System.out.printf("WARNING: FUZZY TYPE EXTENT detected at parm %d " +
"(def = %s, call = %s), node = %s\n",
idx,
defSig[idx],
callSig[idx],
call.dumpTree());
}
}
// the caller is extent or the def is extent but not both
else if (idx1 != -1 || idx2 != -1)
{
// TODO: conversion needs to be enhanced to support wrapping array parms
System.out.printf("WARNING: FUZZY TYPE EXTENT detected at parm %d " +
"(def = %s, call = %s), node = %s\n",
idx,
defSig[idx],
callSig[idx],
call.dumpTree());
}
// neither caller nor def are extent
else if (parm.getType() == FUNC_CLASS ||
parm.getType() == VAR_CLASS ||
parm.getType() == FIELD_CLASS ||
parm.getType() == FUNC_CLASS ||
parm.getType() == ATTR_CLASS ||
parm.getType() == METH_CLASS ||
parm.getType() == OO_METH_CLASS)
{
force = false;
}
}
// do not wrap the last parameter for Progress.Lang.Class:setParameter
if (!(parm.getIndexPos() == 3 &&
parm.getParent().getType() == OO_METH_LOGICAL &&
parm.getParent().getText().equalsIgnoreCase("setParameter") &&
parm.getParent().getAnnotation("found-in-cls")
.toString()
.equalsIgnoreCase("Progress.Lang.ParameterList")))
{
if (force)
{
if (callType.indexOf('[') < 0)
{
if (parm.getType() == UNKNOWN_VAL ||
(parm.getType() == PARAMETER && parm.getFirstChild().getType() == UNKNOWN_VAL))
{
if (parm.getType() == PARAMETER)
{
parm.putAnnotation("wrap_parameter", true);
}
}
else if (!classname.equals("BaseDataType") &&
!classname.equals("Object"))
{
parm.putAnnotation("wrap_parameter", true);
}
}
if (idx2 > 0)
{
callType = callType.substring(0, idx2);
}
Aast ref = parm;
if (classname.indexOf('<') > 0)
{
classname = classname.substring(0, classname.indexOf('<'));
if (ref.downPath(UNKNOWN_VAL) || ref.downPath(EXPRESSION, UNKNOWN_VAL))
{
ref.removeAnnotation("wrap_parameter");
ref = (Aast) ref.getFirstChild();
}
}
ref.putAnnotation("classname", classname);
ref.putAnnotation("calltype", callType);
}
}
}
idx++;
}
parm = (Aast) parm.getNextSibling();
}
}
/**
* Find the named method without any knowledge of the signature. This method will search up
* the parent hierarchy (recursively) if no match is found in the current class.
* <p>
* <b>If a non-null value is returned, the caller can be assured that there is an accessible
* method with that name BUT the actual type/details of the method cannot be known until the
* signature is known. This is due to the fact that the same method name can be present with
* differing return values/parameter signature and only the parameter signature matching can
* differentiate the exact method that will be called.</b>
* <p>
* <b>Do not call this from any location where the method details must be known
* authoritatively.</b>
*
* @param name
* Resource name.
* @param access
* Access mode (<code>KW_PUBLIC</code>, <code>KW_PROTECTD</code> or
* <code>KW_PRIVATE</code>).
* @param isStatic
* If <code>true</code>, only static methods will be looked up.
* Otherwise any method can be returned.
* @param internal
* <code>true</code> if the lookup is internal to the current class definition.
*
* @return The method found or <code>null</code> if no match exists.
*/
private MemberData guessMethod(String name, int access, boolean isStatic, boolean internal)
{
MemberData mdat = null;
synchronized (lock)
{
Map<String, Map<SignatureKey, MemberData>> map = new LinkedHashMap<>();
if (isStatic)
{
if (smethods != null)
{
map.putAll(smethods);
}
else
{
map = null;
}
}
else
{
// here we can access both static and instance methods
if (smethods == null && imethods == null)
{
map = null;
}
if (smethods != null)
{
map.putAll(smethods);
}
if (imethods != null)
{
map.putAll(imethods);
}
}
if (map != null)
{
Map<SignatureKey, MemberData> defs = map.get(name.toLowerCase());
if (defs != null)
{
Iterator<MemberData> iter = defs.values().iterator();
while (iter.hasNext())
{
MemberData dat = checkAccessRights(iter.next(), access);
if (dat != null)
{
// if we are here, we have an access rights match, but we must ensure that
// there is a static/instance match
if ((!isStatic && (!dat.isStatic || internal)) ||
(isStatic && dat.isStatic))
{
// done!
return dat;
}
}
}
}
}
// there was no match in the current class, look up the parent hierarchy
if (parents != null)
{
// change access mode since we aren't allowed to see private stuff in parent
int _access = (access == KW_PRIVATE) ? KW_PROTECTD : access;
for (ClassDefinition parent : parents)
{
// recurse up inheritence hierarchy
mdat = parent.guessMethod(name, _access, isStatic, internal);
if (mdat != null)
break;
}
}
}
if (mdat == null && !isStatic && internal)
{
// fallback to static method check, when looking an instance method
mdat = guessMethod(name, access, true, internal);
}
// no match anywhere
return mdat;
}
/**
* Obtain the method's member given the name and access mode. If the member does not exist in
* this class definition the parent (if one exists) will be checked.
*
* @param name
* Method name.
* @param sig
* Array describing the signature of the method call.
* @param access
* Access mode (<code>KW_PUBLIC</code>, <code>KW_PROTECTD</code>
* or <code>KW_PRIVATE</code>).
* @param isStatic
* If <code>true</code>, only static methods will be looked up.
* Otherwise any method can be returned.
* @param internal
* <code>true</code> if the lookup is internal to the current class definition.
* @param node
* The AST referencing this method.
*
* @return Member data or <code>null</code> if no such variable or data
* member exists.
*/
private MethodSearchResult lookupMethodWorker(String name,
ParameterKey[] sig,
int access,
boolean isStatic,
boolean internal,
Aast node)
{
int type = node.getType();
boolean constructor = type == KW_SUPER ||
type == KW_THIS_OBJ ||
(type == FUNC_CLASS && (long) node.getAnnotation("oldtype") == KW_NEW);
MethodSearchResult result = null;
synchronized (lock)
{
result = exactMethodLookup(name, sig, access, isStatic, internal, constructor);
if (result == null && !isStatic && internal)
{
// can't find an instance member, check for static
result = exactMethodLookup(name, sig, access, true, internal, constructor);
}
if (result == null)
{
result = fuzzy.fuzzyMethodLookup(name, sig, access, isStatic, internal, node, constructor);
if (result == null && !isStatic && internal)
{
// can't find an instance member, check for static
result = fuzzy.fuzzyMethodLookup(name, sig, access, true, internal, node, constructor);
}
}
}
return result;
}
/**
* Find the named method based on an exact signature match. This method searches all methods in the
* parent hierarchy not just in the current class. This search the hierarchy approach is needed so
* that some special exceptions can be honored:
* <p>
* <ul>
* <li>If the caller does not specify a mode and there is an otherwise exact match, that method is
* selected. Buffers have no mode processing so this case is ignored for them.</li>
* <li>A caller's table-handle or dataset-handle parameter which has an exact match is disallowed
* if there are any alternatives with a table/dataset at that parameter AND that parameter type
* is what determines that the signatures are different.</li>
* </ul>
*
* @param name
* Resource name.
* @param caller
* Method call signature.
* @param access
* Access mode (<code>KW_PUBLIC</code>, <code>KW_PROTECTD</code> or
* <code>KW_PRIVATE</code>).
* @param isStatic
* If <code>true</code>, only static methods will be looked up.
* Otherwise any method can be returned.
* @param internal
* <code>true</code> if the lookup is internal to the current class definition.
* @param constructor
* Flag indicating this must resolve a constructor.
*
* @return Resource found or <code>null</code> if no match exists.
*/
private MethodSearchResult exactMethodLookup(final String name,
final ParameterKey[] caller,
final int access,
final boolean isStatic,
final boolean internal,
final boolean constructor)
{
List<MatchMetrics> list = candidates(name, caller.length, access, isStatic, internal, false, null,
constructor);
// quick out if there are no possible matches
if (list.isEmpty())
{
return null;
}
final boolean[] ignore = new boolean[caller.length];
Predicate<MatchMetrics> exactMatch = (mem) ->
{
for (int i = 0; i < caller.length; i++)
{
if (!ignore[i] && !caller[i].equals(mem.data.getSignature(i)))
{
return false;
}
}
return true;
};
List<MatchMetrics> exact = list.stream().filter(exactMatch).collect(Collectors.toList());
Predicate<MatchMetrics> flexModeMatch = (mem) ->
{
for (int i = 0; i < caller.length; i++)
{
if (!ignore[i])
{
if (caller[i].mode != null)
{
if (!caller[i].equals(mem.data.getSignature(i)))
{
return false;
}
}
else
{
if (!caller[i].typeEquivalent(mem.data.getSignature(i)))
{
return false;
}
}
}
}
return true;
};
MethodSearchResult result = null;
boolean flex = false;
if (exact.isEmpty())
{
List<MatchMetrics> flexMode = list.stream().filter(flexModeMatch).collect(Collectors.toList());
if (flexMode.size() == 1)
{
result = new MethodSearchResult(flexMode.get(0).data, null);
flex = true;
}
}
else
{
if (exact.size() == 1)
{
result = new MethodSearchResult(exact.get(0).data, null);
}
}
if (result != null)
{
// table-handle/dataset-handle matches are only valid when the only difference in signature is
// between a table/dataset and table-handle/dataset-handle
Iterator<MatchMetrics> iter = list.iterator();
// calculate which parameters are both table-handles/dataset-handles and have conflicting
// signature (another signature that has a table/dataset in the same parameter position)
while (iter.hasNext())
{
MatchMetrics m = iter.next();
for (int i = 0; i < caller.length; i++)
{
// quick iteration if we already know this parameter should be ignored
if (ignore[i])
continue;
String type = m.data.getSignature(i).type;
if ("TABLE-HANDLE".equals(caller[i].type))
{
if (type != null && type.startsWith("TEMP-TABLE"))
{
ignore[i] = true;
}
}
else if ("DATASET-HANDLE".equals(caller[i].type))
{
if (type != null && type.startsWith("DATASET"))
{
ignore[i] = true;
}
}
}
}
// ignoring the conflicting parameters, can we still calculate find a unique match?
List<MatchMetrics> ignoreTH = list.stream()
.filter(flex ? flexModeMatch : exactMatch)
.collect(Collectors.toList());
if (ignoreTH.size() != 1)
{
result = null;
}
}
return result;
}
/**
* Detect if any extent specification in the parameters are a fuzzy match or if neither have
* an extent specification.
* <p>
* It is assumed that any exact matches are handled elsewhere.
*
* @param defSig
* Definition parameter type spec.
* @param callSig
* Call parameter type spec.
*
* @return {@code true} if a fixed extent call parm is passed to an indeterminate extent def
* parm or if neither have an extent spec.
*/
private boolean isExtentMatch(String defSig, String callSig)
{
if (!defSig.endsWith("]") && !callSig.endsWith("]"))
{
// neither spec has extent
return true;
}
if (defSig.endsWith("[]") && callSig.endsWith("]"))
{
// this is a fixed (or indeterminate) extent passed to an indeterminate extent
return true;
}
return false;
}
/**
* Get the list of all possible methods that could match in this class AND its parent classes. The
* actual types are not checked but the method name, access mode, static/instance and number of
* parameters must all match.
*
* @param name
* Method name.
* @param num
* Number of parameters.
* @param access
* Access mode (<code>KW_PUBLIC</code>, <code>KW_PROTECTD</code> or <code>KW_PRIVATE</code>).
* @param isStatic
* If <code>true</code>, only static methods will be looked up. Otherwise any method can be
* returned.
* @param internal
* {@code true} if the lookup is internal to the current class definition.
* @param first
* {@code true} if the first match should be immediately returned as the result.
* @param exist
* The set of existing methods already represented in the list.
* @param constructor
* Flag indicating this must resolve a constructor.
*
* @return List of possible methods that match. This may be an empty list if no match exists.
*/
List<MatchMetrics> candidates(String name,
int num,
int access,
boolean isStatic,
boolean internal,
boolean first,
Set<SignatureKey> exist,
boolean constructor)
{
Set<SignatureKey> existing = exist == null ? new HashSet<>() : exist;
List<MatchMetrics> results = new LinkedList<>();
synchronized (lock)
{
Map<String, Map<SignatureKey, MemberData>> map = isStatic ? smethods : imethods;
// if the method name matches this class method name, then it can't be part of this class
// (4gl compile error)
if (map != null && (constructor || !name.equalsIgnoreCase(this.getSimpleName())))
{
Map<SignatureKey, MemberData> defs = map.get(name.toLowerCase());
if (defs != null)
{
if (first)
{
results.add(new MatchMetrics(defs.values().iterator().next()));
return results;
}
Iterator<MemberData> iter = defs.values().iterator();
while (iter.hasNext())
{
MemberData dat = checkAccessRights(iter.next(), access);
if (dat != null && dat.signature.length == num)
{
// special case: no parameters; this is Highlander (i.e. "there can be only one")
// and we don't have to search the parents, this is the match
if (num == 0)
{
results.add(new MatchMetrics(dat));
return results;
}
SignatureKey key = new SignatureKey(dat.signature);
if (!existing.contains(key))
{
results.add(new MatchMetrics(dat));
existing.add(key);
}
}
}
}
}
// fuzzy matches are processed up the the parent hierarchy even if there are possible matches in
// the current class
if (parents != null)
{
// change access mode since we aren't allowed to see private stuff in parent
int _access = (access == KW_PRIVATE) ? KW_PROTECTD : access;
for (ClassDefinition parent : parents)
{
// recurse up inheritance hierarchy
results.addAll(parent.candidates(name, num, _access, isStatic, internal, first, existing,
false));
}
}
}
return results;
}
/**
* Detect if the given class is an instance of an implemented interface of this class.
*
* @param cls
* The class to check.
*
* @return {@code true} if the given class is an implemented interface in this class.
*/
boolean isImplemented(ClassDefinition cls)
{
if (ifaces == null)
{
return false;
}
return ifaces.contains(cls.getName()) || ifaces.contains(cls.getName().toLowerCase());
}
/**
* Detect if the given class is an instance of a parent class or an implemented interface of a parent
* class.
*
* @param cls
* The class to check.
* @param ifaces
* {@code true} if implemented interfaces should be checked.
* @param incrementer
* Code to be executed to increment the inheritance level.
*
* @return {@code true} if the given class is a parent class or an implemented interface of a parent
* class.
*/
boolean isInheritedFrom(ClassDefinition cls, boolean ifaces, Runnable incrementer)
{
if (parents != null)
{
for (ClassDefinition parent : parents)
{
if (cls == parent || (ifaces && parent.isImplemented(cls)))
{
// track the current parent level
incrementer.run();
return true;
}
if (parent.isInheritedFrom(cls, ifaces, incrementer))
{
// track our level's contribution to the parent level
incrementer.run();
return true;
}
}
}
if (ifaces && interfaces != null)
{
for (ClassDefinition parent : interfaces)
{
if (cls == parent || parent.isImplemented(cls))
{
// track the current parent level
incrementer.run();
return true;
}
}
}
return false;
}
/**
* Confirm that the given member is accessible at the access level specified.
*
* @param mdat
* Member data, may be {@code null}.
* @param access
* Access mode (<code>KW_PUBLIC</code>, <code>KW_PROTECTD</code> or
* <code>KW_PRIVATE</code>).
*
* @return The given resource if accessible or <code>null</code> access is not allowed or
* if the member is {@code null} on input.
*/
private MemberData checkAccessRights(MemberData mdat, int access)
{
if (mdat != null)
{
// check for a match to the access mode:
// 1. searching for private methods match everything
// 2. a direct match is always OK
// 3. a public method matches a protected or package private search
boolean ok = (access == KW_PRIVATE) ||
(access == mdat.access) ||
((access == KW_PROTECTD || access == KW_PK_PRIV) && mdat.access == KW_PUBLIC);
// pretend no match if access modes are not right
if (!ok)
{
return null;
}
}
return mdat;
}
/**
* Add the resource to the given maps, if it is not already present OR if it is present
* under a more restrictive access type (we must store the least restrictive of the types
* to allow operator overloading to work properly).
*
* @param stype
* Type of data store to check.
* @param name
* Resource name.
* @param signature
* Parameter signature for methods or <code>null</code>.
* @param var
* Variable reference for var resources or <code>null</code>.
* @param type
* Token type.
* @param access
* Access mode (<code>KW_PUBLIC</code>, <code>KW_PROTECTD</code>
* or <code>KW_PRIVATE</code>).
* @param isStatic
* <code>true</code> if the variable/property is static.
* @param qname
* Fully qualified class name where the resource represents an
* object instance, otherwise <code>null</code>.
* @param tempIdx
* Temporary ID.
* @return The found {@link MemberData} instance.
*/
private MemberData addWorker(DataStoreType stype,
String name,
ParameterKey[] signature,
Variable var,
int type,
int access,
boolean isStatic,
String qname,
int tempIdx)
{
synchronized (lock)
{
Map<String, MemberData> map = getMemberStore(stype, isStatic);
String lower = name.toLowerCase();
MemberData odat = map.get(lower);
boolean insert = true;
if (odat != null)
{
// a record already exists, only replace it if we are making it more accessible
if ((odat.access == KW_PRIVATE && (access == KW_PUBLIC || access == KW_PROTECTD)) ||
(odat.access == KW_PROTECTD && access == KW_PUBLIC))
{
// leave insert as true
}
else
{
insert = false;
}
}
if (insert)
{
MemberData mdat = new MemberData(name,
var,
signature,
type,
access,
isStatic,
qname,
this,
tempIdx,
null);
// add/replace in the full member list
map.put(lower, mdat);
return mdat;
}
else
{
// replace the var with the new one
if (odat.var != null)
{
var.setTempIndex(odat.var.getTempIndex());
odat.var = var;
}
}
return odat;
}
}
/**
* Find the named resource in the given map.
*
* @param map
* Dictionary for all resources regardless of access mode.
* @param name
* Resource name.
* @param access
* Access mode (<code>KW_PUBLIC</code>, <code>KW_PROTECTD</code>
* or <code>KW_PRIVATE</code>).
*
* @return Resource found or <code>null</code> if no match exists.
*/
private MemberData lookupWorker(Map<String, MemberData> map, String name, int access)
{
MemberData mdat = null;
synchronized (lock)
{
if (map != null)
{
mdat = checkAccessRights(map.get(name.toLowerCase()), access);
}
}
return mdat;
}
/**
* Pull out our lists of temp-tables and buffers so they can be used for
* cleanup.
*
* @param save
* Set of temp-tables and buffers.
* @param isStatic
* <code>true</code> to process static resources, otherwise instance resources.
*/
private void defineExceptions(ArrayList<String> save, boolean isStatic)
{
Map<String, MemberData> map = getMemberStore(DataStoreType.TABLE, isStatic);
if (map != null && !map.isEmpty())
{
Iterator<String> iter = map.keySet().iterator();
while (iter.hasNext())
{
MemberData mdat = map.get(iter.next());
if (mdat != null)
{
if ((mdat.type == BUFFER || mdat.type == TEMP_TABLE) && mdat.access == KW_PROTECTD)
{
save.add(mdat.name);
}
}
}
}
}
/**
* Add the 'virtual' enum methods, so they can be resolved by the parser. This includes the static
* {@code GetEnum} method for all enums and the instance {@code SetFlag}, {@code ToggleFlag} and
* {@code UnsetFlag} for flags enums.
*
* @param name
* Fully qualified class name of this enum.
* @param flags
* {@code true} if this is a flags enum.
*/
private void registerVirtualEnumMethods(String name, boolean flags)
{
ParameterKey[] sig1 = new ParameterKey[] { new ParameterKey(KW_INPUT, "int64") };
registerVirtual(name, "GetEnum", "getEnum", true, OO_METH_CLASS, name, sig1);
ParameterKey[] sig2 = new ParameterKey[] { new ParameterKey(KW_INPUT, "character") };
registerVirtual(name, "GetEnum", "getEnum", true, OO_METH_CLASS, name, sig2);
if (flags)
{
ParameterKey[] sig3 = new ParameterKey[]
{
new ParameterKey(KW_INPUT, String.format("object <? extends %s>", name))
};
registerVirtual(name, "SetFlag", "setFlag", false, OO_METH_CLASS, name, sig3);
registerVirtual(name, "ToggleFlag", "toggleFlag", false, OO_METH_CLASS, name, sig3);
registerVirtual(name, "UnsetFlag", "unsetFlag", false, OO_METH_CLASS, name, sig3);
}
}
/**
* Helper method to register a public virtual method.
*
* @param name
* Fully qualified class name.
* @param ori
* Legacy method name.
* @param meth
* Converted method name.
* @param isStatic
* {@code true} if this is a static method, {@code false} for instance methods.
* @param rtype
* Method's return token type.
* @param qname
* Fully qualified OO name of the return type or {@code null} for non-object return types.
* @param parms
* The signature details.
*/
private void registerVirtual(String name,
String ori,
String meth,
boolean isStatic,
int rtype,
String qname,
ParameterKey[] parms)
{
// the method must be registered
createMethod(null, ori, parms, KW_PUBLIC, true, rtype, qname, meth, 0, nextTempIdx(), false, false,
null, true);
}
/**
* Create a new method definition and track it.
*
* @param name
* The legacy method name.
* @param sig
* The method signature.
* @param accessMode
* The access mode.
* @param isStatic
* Flag indicating if the method is static or not.
* @param retType
* The return type.
* @param qualified
* The qualified legacy class name, for methods returning object.
* @param javaname
* The converted java name.
* @param extent
* The extent (for the return type).
* @param tempIdx
* The tempIdx to use.
* @param override
* {@code true} if the method is an override of a parent class/interface method.
* @param abstr
* {@code true} if the method is abstract of a parent class/interface method.
*
* @return The created method.
*/
private MemberData createMethod(Map<SignatureKey, MemberData> defs,
String name,
ParameterKey[] sig,
int accessMode,
boolean isStatic,
int retType,
String qualified,
String javaname,
int extent,
int tempIdx,
boolean override,
boolean abstr)
{
return createMethod(defs, name, sig, accessMode, isStatic, retType, qualified,
javaname, extent, tempIdx, override, abstr, null, false);
}
/**
* Create a new method definition and track it.
*
* @param name
* The legacy method name.
* @param sig
* The method signature.
* @param accessMode
* The access mode.
* @param isStatic
* Flag indicating if the method is static or not.
* @param retType
* The return type.
* @param qualified
* The qualified legacy class name, for methods returning object.
* @param javaname
* The converted java name.
* @param extent
* The extent (for the return type).
* @param tempIdx
* The tempIdx to use.
* @param override
* {@code true} if the method is an override of a parent class/interface method.
* @param abstr
* {@code true} if the method is abstract of a parent class/interface method.
* @param register
* {@code true} if the name should be registered in the maps.
*
* @return The created method.
*/
private MemberData createMethod(Map<SignatureKey, MemberData> defs,
String name,
ParameterKey[] sig,
int accessMode,
boolean isStatic,
int retType,
String qualified,
String javaname,
int extent,
int tempIdx,
boolean override,
boolean abstr,
Map<String, Object> ret,
boolean register)
{
synchronized (lock)
{
if (defs == null)
{
defs = getMethodStore(name, isStatic);
}
MemberData mdat = new MemberData(name,
null,
sig,
retType,
accessMode,
isStatic,
qualified,
this,
tempIdx,
ret);
mdat.extent = extent;
mdat.override = override;
mdat.abstr = abstr;
defs.put(new SignatureKey(sig), mdat);
if (register)
{
registerMethod(javaname, mdat);
}
return mdat;
}
}
}