JavaClassDefinition.java
/*
** Module : JavaClassDefinition.java
** Abstract : defines the 4GL API for a Java class or interface definition
**
** Copyright (c) 2019-2025, Golden Code Development Corporation.
**
** -#- -I- --Date-- --------------------------------------Description---------------------------------------
** 001 GES 20190903 First version which contains a Java class instance and uses reflection
** to answer any queries.
** 002 CA 20191211 Convert unknowns to Java null, when invoking Java code from 4GL code.
** 003 CA 20200412 Added incremental conversion support.
** 004 GES 20210708 Removed dead code.
** TJD 20220504 Java 11 compatibility minor changes
** 20220428 Set class and package name methods as overrides.
** 005 CA 20230712 Allow Java array and 'object' parameters at direct Java calls.
** 006 CA 20230718 Fixed a regression in 'typeToJavaClass', arrays need to be handled separately.
** 007 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.
** 008 CA 20241002 Candidate public methods must be explicitly searched in the type and all super-classes and
** super-interfaces: synthetic methods can't be excluded, as for some reason in some cases
** the bytecode contains a synthetic method for the super-class override, and in some other
** cases an inherited method is copied to the sub-class (see StringBuilder.append and length).
** Optimization in 'fuzzyMethodLookup' - a single-arg match to a java.lang.Object parameter
** will not consider java.lang.Object if there are other matches.
** Fixed non-primitive array type calculation in 'nonArrayType'.
** 009 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.
*/
/*
** 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 com.goldencode.ast.*;
import com.goldencode.p2j.convert.*;
import com.goldencode.p2j.oo.lang.*;
import com.goldencode.p2j.schema.*;
import com.goldencode.p2j.util.*;
import com.goldencode.p2j.util.Utils;
import antlr.*;
/**
* Contains a Java class instance and uses reflection to answer any queries.
*/
public class JavaClassDefinition
extends ClassDefinition
{
/** Contained Java class instance for which we are the wrapper. */
private Class cls = null;
/**
* Construct an instance.
*
* @param cls
* Java class instance to wrap. Must NOT be {@code null}.
*/
public JavaClassDefinition(Class<?> cls)
{
super(cls.getName(), null, null, false, decodeOOType(cls), false, cls.getName());
this.cls = cls;
}
/**
* Report if the given modifiers have the static bit set.
*
* @param mods
* Modifiers bitset to check.
*
* @return {@code true} if the static bit is set.
*/
public static boolean isStaticModifier(int mods)
{
return Modifier.isStatic(mods);
}
/**
* Convert the Java Modifier access mode into the Progress token type for an access mode.
*
* @param access
* Access mode ({@code Modifier.PRIVATE}, {@code Modifier.PROTECTED},
* {@code Modifier.PUBLIC}).
*
* @return {@code KW_PUBLIC}, {@code KW_PROTECTD} or {@code KW_PRIVATE} or -1 if there
* is an error.
*/
public static int javaModToTokenType(int access)
{
int mod = -1;
switch (access)
{
case Modifier.PUBLIC:
mod = KW_PUBLIC;
break;
case Modifier.PROTECTED:
mod = KW_PROTECTD;
break;
case Modifier.PRIVATE:
mod = KW_PRIVATE;
break;
}
return mod;
}
/**
* Check the given modifiers to see which of the Java private, protected or public access
* level bits is set.
*
* @param modifiers
* Modifiers bitset to check.
*
* @return {@code Modifier.PRIVATE}, {@code Modifier.PROTECTED}, {@code Modifier.PUBLIC}
* or 0 if there is an error.
*/
public static int javaAccessLevel(int modifiers)
{
if ((modifiers & Modifier.PRIVATE) == Modifier.PRIVATE)
{
return Modifier.PRIVATE;
}
else if ((modifiers & Modifier.PROTECTED) == Modifier.PROTECTED)
{
return Modifier.PROTECTED;
}
else if ((modifiers & Modifier.PUBLIC) == Modifier.PUBLIC)
{
return Modifier.PUBLIC;
}
return 0;
}
/**
* Get the simple Java class name for this definition.
*
* @return See above.
*/
@Override
public String getSimpleJavaName()
{
return cls.getSimpleName();
}
/**
* Get the Java package for this definition.
*
* @return See above.
*/
@Override
public String getJavaPackage()
{
return cls.getPackage().getName();
}
/**
* 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.
*/
@Override
public String getConvertedMethodName(String legacySig)
{
throw new UnsupportedOperationException("This should not be called!");
}
/**
* Check if this class definition was already fully parsed.
*
* @return The {@link #processed} flag.
*/
@Override
public boolean isProcessed()
{
return true;
}
/**
* 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.
*/
@Override
public void derivedFromDotNet()
{
}
/**
* Report if the parent hierarchy includes a .NET ancestor which cannot ever be {@code true}.
*
* @return Always {@code false}.
*/
@Override
public boolean isDerivedFromDotNet()
{
return false;
}
/**
* Obtains the empty set of derived (parent hierarchy) .NET references.
*
* @return An empty set.
*/
@Override
public Set<String> getDerivedDotNet()
{
return new HashSet<String>();
}
/**
* Calculate if the given reference is a direct or indirect .NET reference and update out
* counters.
*
* @param ref
* The class being referenced.
*/
@Override
public void markDotNetUsage(ClassDefinition ref)
{
}
/**
* Reports if the class has any direct .NET references, which cannot ever be {@code true}.
*
* @return Always {@code false}.
*/
@Override
public boolean hasDirectDotNet()
{
return false;
}
/**
* Obtains the direct .NET references.
*
* @return Always an empty set.
*/
@Override
public Set<String> getDirectDotNet()
{
return new HashSet<String>();
}
/**
* Reports if the class has any indirect .NET referenceswhich cannot ever be {@code true}.
*
* @return Always {@code false}.
*/
@Override
public boolean hasIndirectDotNet()
{
return false;
}
/**
* Obtains the indirect .NET references, an empty set.
*
* @return Always an empty set.
*/
@Override
public Set<String> getIndirectDotNet()
{
return new HashSet<String>();
}
/**
* Reports if the a reference to this class should be considered an indirect .NET reference
* in the calling class, which cannot ever be {@code true}.
*
* @return Always {@code false}.
*/
@Override
public boolean isIndirectDotNetReference()
{
return false;
}
/**
* Obtains the parent class definition for this class. This is only used for processing
* 4GL class definitions and/or their possible usage of 4GL resources in the inheritance
* hierarchy. Java classes don't participate in this because they (by definition) are not
* 4GL class definitions AND they don't support 4GL resources (datasets, buffers...).
*
* @return Always <code>null</code>.
*/
@Override
public ClassDefinition[] getParents()
{
return null;
}
/**
* Obtains the list of interfaces implemented by this class. This is only used for
* recursively parsing all implemented interfaces. Java classes don't require parsing
* so they don't participate in that process.
*
* @return Implemented interfaces.
*/
@Override
public Set<String> getInterfaces()
{
return null;
}
/**
* Access the flag that denotes if this is a built-in class definition.
*
* @return Always {@code false}.
*/
@Override
public boolean isBuiltIn()
{
return false;
}
/**
* Access the flag that denotes if this is a .NET class definition.
*
* @return Always {@code false}.
*/
@Override
public boolean isDotNet()
{
return false;
}
/**
* Access the flag that denotes if this is a Java class definition.
*
* @return Always {@code true}.
*/
@Override
public boolean isJava()
{
return true;
}
/**
* 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 Always {@code false}.
*/
@Override
public boolean isMock()
{
return false;
}
/**
* 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
* Ignored.
*
* @return The type found or <code>-1</code> if no match exists.
*/
@Override
public int guessMethodType(String name, int access, boolean isStatic, boolean internal)
{
Method m = guessMethod(name, access, isStatic);
return (m == null) ? -1 : decodeReturnType(m.getReturnType());
}
/**
* 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.
*/
@Override
public void annotateMethodCall(String mname,
ParameterKey[] signature,
int access,
boolean isStatic,
boolean isSuper,
boolean internal,
Aast node)
{
if (node != null)
{
MethodMatch match = lookupMethodWorker(mname, signature, access, isStatic, node);
if (match != null)
{
Executable m = match.method;
if (m instanceof Method)
{
Class<?> ret = ((Method) m).getReturnType();
int type = decodeReturnType(ret);
// 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.setType(type);
}
String rname = ret.getName();
if (rname.charAt(0) == '[')
{
// all Java arrays are indeterminate (no fixed size at compile time)
node.putAnnotation("extent", Long.valueOf(-1));
}
if (type == OO_METH_CLASS)
{
// we deliberately do not lowercase here; that means that any usage of a
// user-defined OO 4GL class won't work downstream
node.putAnnotation("qualified", nonArrayType(rname));
}
}
int mods = m.getModifiers();
int jaccess = javaModToTokenType(javaAccessLevel(mods));
// the access mode won't necessarily be the same as that passed in
node.putAnnotation("access-mode", Long.valueOf(jaccess));
node.putAnnotation("static", Boolean.valueOf(isStaticModifier(mods)));
node.putAnnotation("found-in-cls", cls.getName());
node.putAnnotation("found-in-source-file", cls.getName());
node.putAnnotation("javaname", m.getName());
node.putAnnotation("is-java", true);
annotateCallSignature(node, signature, match);
}
}
}
/**
* 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.
*/
@Override
public int lookupVariable(String name, int access, boolean isStatic)
{
Field[] flds = getCandidateFields(name, access, isStatic);
return (flds.length == 0) ? -1 : decodeVarType(flds[0].getType());
}
/**
* 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.
*/
@Override
public String lookupVariableClassName(String name, int access, boolean isStatic)
{
Field[] flds = getCandidateFields(name, access, isStatic);
return (flds.length == 0) ? null : flds[0].getType().getName();
}
/**
* 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.
*/
@Override
public Variable lookupVariableWrapper(String name, int access, boolean isStatic, boolean chainedReference)
{
Field[] flds = getCandidateFields(name, access, isStatic);
JavaFieldDefinition jfd = null;
if (flds.length != 0)
{
int type = decodeVarType(flds[0].getType());
Class<?> fieldCls = flds[0].getType();
String ftype = null;
if (!fieldCls.isPrimitive())
{
ftype = fieldCls.getName();
}
jfd = new JavaFieldDefinition(flds[0], name, type, ftype);
}
return jfd;
}
/**
* 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.
*/
@Override
public boolean isStaticDataMember(String name, int access)
{
Field[] flds = getCandidateFields(name, access, true);
return flds.length > 0;
}
/**
* Get all defined methods in this class.
*
* @return Always {@code null}.
*/
@Override
public Set<String> getDefinedMethods()
{
// not needed for Java classes, but only for built-in OO 4GL
return null;
}
/**
* 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 match
* The details of the match found.
*/
private void annotateCallSignature(Aast call, ParameterKey[] callSig, MethodMatch match)
{
int idx = 0;
Class<?>[] cvt = match.cvt;
// iterate all parameters and extract their types
Aast parm = (Aast) call.getFirstChild();
if (call.isAnnotation("oldtype") && (long) call.getAnnotation("oldtype") == KW_NEW)
{
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_INPUT &&
type != KW_IN_OUT &&
type != KW_OUTPUT &&
type != KW_APPEND &&
type != KW_BIND &&
type != KW_BY_REF &&
type != KW_BY_VALUE)
{
if (cvt != null && cvt[idx] != null)
{
boolean primitive = cvt[idx].isPrimitive() || cvt[idx].equals(String.class);
if (primitive)
{
parm.putAnnotation("primitive", primitive);
}
parm.putAnnotation("classname", cvt[idx].getName());
}
else
{
parm.putAnnotation("classname", callSig[idx].type);
}
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
* Method 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 The method found or <code>null</code> if no match exists.
*/
private Method guessMethod(String name, int access, boolean isStatic)
{
// get those methods which match on name, access mode and instance/static
Executable[] meths = getCandidateMethods(name, -1, access, isStatic);
return (meths.length == 0) ? null : (Method) meths[0];
}
/**
* Obtain the method instance given the name, signature and access mode.
*
* @param name
* Method name, {@code null} for a c'tor invocation.
* @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 node
* The AST referencing this method.
*
* @return Method found or <code>null</code> if no such method exists.
*/
private MethodMatch lookupMethodWorker(String name,
ParameterKey[] sig,
int access,
boolean isStatic,
Aast node)
{
for (int i = 0; i < sig.length; i++)
{
if (sig[i].mode != null && sig[i].mode.intValue() != KW_INPUT)
{
String spec = "Java method %s parameter %d mode must be unspecified or INPUT.";
String mname = (name == null) ? "<init>" : name;
throw new IllegalArgumentException(String.format(spec, mname, i));
}
}
MethodMatch m = exactMethodLookup(name, sig, access, isStatic);
if (m == null)
{
m = fuzzyMethodLookup(name, sig, access, isStatic, node);
}
if (m == null)
{
noMethodMatchError(name, sig, access, isStatic, node);
}
return m;
}
/**
* Find the named method based on an exact signature match. This method will search up the
* parent hierarchy (recursively) if no exact match is found in the current class.
*
* @param name
* Method name, {@code null} for a c'tor invocation.
* @param sig
* 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.
*
* @return Method found or <code>null</code> if no match exists.
*/
private MethodMatch exactMethodLookup(String name,
ParameterKey[] sig,
int access,
boolean isStatic)
{
Executable m = null;
try
{
Class<?>[] parms = createJavaParameters(sig);
if (name == null)
{
m = cls.getConstructor(parms);
}
else
{
m = cls.getMethod(name, parms);
}
}
catch (NoSuchMethodException nsme)
{
// doesn't exist
}
return (m != null && checkModifiers(m, access, isStatic)) ? new MethodMatch(m) : null;
}
/**
* Check if the given member matches the access level and instance/static requirement.
*
* @param m
* Member to check.
* @param access
* Access mode (<code>KW_PUBLIC</code>, <code>KW_PROTECTD</code> or
* <code>KW_PRIVATE</code>).
* @param isStatic
* If <code>true</code>, the method must be static.
*
* @return {@code true} if the method matches the access and instance/static requirements.
*/
private boolean checkModifiers(Member m, int access, boolean isStatic)
{
int mods = m.getModifiers();
// make sure the access mode requirements is met
if (checkAccessRights(mods, access))
{
boolean staticMethod = isStaticModifier(mods);
// make sure the static/instance requirements are met
if ((!isStatic && !staticMethod) || (isStatic && staticMethod))
{
return true;
}
}
return false;
}
/**
* Find the named method based on a fuzzy signature match. This method will search up the
* parent hierarchy (recursively) if no fuzzy match is found in the current class.
*
* @param name
* Method name, {@code null} for a c'tor invocation.
* @param sig
* 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 node
* The AST referencing this method.
*
* @return Resource found or <code>null</code> if no match exists.
*/
private FuzzyMatch fuzzyMethodLookup(String name,
ParameterKey[] sig,
int access,
boolean isStatic,
Aast node)
{
FuzzyMatch match = null;
TreeSet<FuzzyMatch> set = new TreeSet<>();
// get those methods which match on name, number of parms, access mode and instance/static
Executable[] meths = getCandidateMethods(name, sig.length, access, isStatic);
Class<?>[][] exact = getExactMatchCriteria(sig);
Set<Class<?>>[] near = getNearMatchCriteria(exact);
// iterate through the array and find all possible matches based on the parameters
for (Executable cand : meths)
{
FuzzyMatch f = testFuzzyMatch(cand, exact, near);
// add each possible match to the sorted set
if (f != null)
{
set.add(f);
}
}
// if the set is empty there is no match
if (!set.isEmpty())
{
// the first element in the sorted set is the best match
match = set.first();
if (set.size() > 1 && sig.length == 1 && match.method.getParameterTypes()[0] == Object.class)
{
// this is an optimization for single-arg overloads with 'java.lang.Object' - if we match on this,
// and there are other matches, remove it.
set.remove(match);
match = set.first();
}
}
return match;
}
/**
* Display a detailed error message to report a method that was not found.
*
* @param name
* Method name, {@code null} for a c'tor invocation.
* @param sig
* 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 node
* The AST referencing this method.
*/
private void noMethodMatchError(String name,
ParameterKey[] sig,
int access,
boolean isStatic,
Aast node)
{
System.out.printf("WARNING: no matching Java method definition for %s.%s()\n",
cls.getName(),
(name == null) ? "<init>" : name);
System.out.printf("\n- access %s, static %b\n PARAMETERS: %d\n",
ProgressParser.lookupTokenName(access),
isStatic,
sig.length);
for (int i = 0; i < sig.length; i++)
{
System.out.printf(" %s\n", sig[i]);
}
System.out.printf("\ncalling location: %s", node.dumpTree());
}
/**
* Determine if the given parameter match criteria can match the given method.
*
* @param m
* The method candidate to test.
* @param exact
* The types of exact matches.
* @param near
* The types of the near matches.
*
* @return A match instance if a match s possible or {@code null} if a match does not exist.
*/
private FuzzyMatch testFuzzyMatch(Executable m, Class<?>[][] exact, Set<Class<?>>[] near)
{
Class<?>[] parms = m.getParameterTypes();
Class<?>[] cvt = new Class<?>[parms.length];
int ex = 0;
int unwrap = 0;
int nr = 0;
int parent = 0;
for (int i = 0; i < parms.length; i++)
{
if (parms[i] == exact[i][0] || exact[i][0] == unknown.class)
{
ex++;
}
else if (parms[i] == exact[i][1])
{
// instead of marking this for conversion, should we mark it for unwrapping?
unwrap++;
cvt[i] = parms[i];
}
else if (near[i].contains(parms[i]))
{
nr++;
cvt[i] = parms[i];
}
else if (parms[i].isAssignableFrom(exact[i][0]))
{
parent++;
}
else if (exact[i][1] != null && parms[i].isAssignableFrom(exact[i][1]))
{
// instead of marking this for conversion, should we mark it for unwrapping?
parent++;
cvt[i] = parms[i];
}
else
{
// no match
return null;
}
}
// all parms matched
return new FuzzyMatch(m, ex, unwrap, nr, parent, cvt);
}
/**
* Return the array of method instances that match by name, number of parameters, access
* mode and static/instance.
*
* @param name
* Method name.
* @param parms
* Number of parameters or -1 if the number of parameters is not known.
* @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 Array of methods or c'tors found, possibly an empty array.
*/
private Executable[] getCandidateMethods(String name, int parms, int access, boolean isStatic)
{
Executable[] meths = resolvePublicExecutables(name).toArray(new Executable[0]);
ArrayList<Executable> list = new ArrayList<>();
for (Executable m : meths)
{
// subset by name, number of parameters, access mode, isStatic
if (matchesParmNum(parms, m) && checkModifiers(m, access, isStatic))
{
list.add(m);
}
}
meths = list.toArray(new Executable[0]);
list.clear();
// eliminate overridden methods
for (int i = 0; i < meths.length; i++)
{
Executable m1 = meths[i];
if (m1 == null)
{
continue;
}
for (int j = 0; j < meths.length; j++)
{
Executable m2 = meths[j];
if (m2 == null || i == j)
{
continue;
}
if (m2.getDeclaringClass() != m1.getDeclaringClass() &&
m2.getDeclaringClass().isAssignableFrom(m1.getDeclaringClass()) &&
Arrays.equals(m1.getParameterTypes(), m2.getParameterTypes()))
{
// if m1 is an override of m2, then eliminate m2
meths[j] = null;
}
}
}
for (int i = 0; i < meths.length; i++)
{
if (meths[i] != null)
{
list.add(meths[i]);
}
}
return list.toArray(new Executable[0]);
}
/**
* Resolve the public executables from this class and all super-classes or super-interfaces. Interfaces
* will be searched only if the name is specified, as when null, a constructor is assumed.
*
* @param name
* Method name, null for constructor.
*
* @return The list of public methods/constructors.
*/
private List<Executable> resolvePublicExecutables(String name)
{
Set<Class<?>> ifaces = new HashSet<>();
Utils.collectInterfaces(cls, ifaces);
List<Executable> res = new ArrayList<>();
if (name != null)
{
for (Class<?> iface : ifaces)
{
Executable[] execs = iface.getDeclaredMethods();
for (int i = 0; i < execs.length; i++)
{
Executable e = execs[i];
if (e.isSynthetic())
{
continue;
}
if (Modifier.isPublic(e.getModifiers()) && name.equals(e.getName()))
{
res.add(e);
}
}
}
}
Class<?> type = this.cls;
while (type != null)
{
Executable[] execs = name == null ? type.getDeclaredConstructors() : type.getDeclaredMethods();
for (int i = 0; i < execs.length; i++)
{
Executable e = execs[i];
if (e.isSynthetic())
{
continue;
}
if (Modifier.isPublic(e.getModifiers()) && (name == null || name.equals(e.getName())))
{
res.add(e);
}
}
type = type.getSuperclass();
}
return res;
}
/**
* Check if the number of given parameters matches the number of parms in the
* candidate member.
*
* @param parms
* Number of parameters or -1 if the number of parameters is not known.
* @param e
* The method or c'tor to check.
*
* @return {@code true} if the number of parameters matches or if it is not known.
*/
private boolean matchesParmNum(int parms, Executable e)
{
return (parms == -1 || e.getParameterCount() == parms);
}
/**
* Return the array of field instances that match by name, access mode and static/instance.
*
* @param name
* Field 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 Array of fields found, possibly an empty array.
*/
private Field[] getCandidateFields(String name, int access, boolean isStatic)
{
Field[] flds = cls.getFields();
ArrayList<Field> list = new ArrayList<>();
for (Field f : flds)
{
// subset by name, access mode, isStatic
if (name.equals(f.getName()) &&
checkModifiers(f, access, isStatic))
{
list.add(f);
}
}
return list.toArray(new Field[0]);
}
/**
* Convert a 4GL parameter signature into a Java class parameter list.
*
* @param sig
* List of 4GL parameters.
*
* @return The Java parameter list.
*/
private static Class<?>[] createJavaParameters(ParameterKey[] sig)
{
Class<?>[] parms = new Class<?>[sig.length];
for (int i = 0; i < sig.length; i++)
{
parms[i] = parameterTypeToJavaClass(sig[i].type);
}
return parms;
}
/**
* Convert a 4GL parameter type into a Java class, handling arrays as needed.
*
* @param type
* 4GL parameter.
*
* @return The Java class.
*/
private static Class<?> parameterTypeToJavaClass(String type)
{
// this is a 4GL type name as returned from ECW.expressionType() and possibly appended
// with [] (for indeterminate extent) or [%d] (for fixed extent); object references will
// have a classname in object<? extends __> format but non-object references will have
// the simple BDT basename (e.g. character or comhandle)
int idx = type.indexOf('[');
String jname = (idx != -1) ? type.substring(0, idx) : type;
Class<?> cls = typeToJavaClass(jname, "com.goldencode.p2j.util.", (idx != -1));
if (cls == null)
{
cls = typeToJavaClass(jname, null, (idx != -1));
}
return cls;
}
/**
* Unwrap the 4GL object parameter type into a referenced Java class, handling arrays as
* needed.
*
* @param type
* 4GL parameter.
*
* @return The Java class or {@code null} if this is not an object reference.
*/
private static Class<?> unwrapObjectTypeToJavaClass(String type)
{
// this is a 4GL type name as returned from ECW.expressionType() and possibly appended
// with [] (for indeterminate extent) or [%d] (for fixed extent); object references will
// have a classname in object<? extends __> format but non-object references will have
// the simple BDT basename (e.g. character or comhandle)
// this should find the last char before the object reference type, if it is object
int ex = type.lastIndexOf(' ');
if (ex == -1)
{
return null;
}
int idx = type.indexOf('[');
String jname = (idx != -1) ? type.substring(0, idx) : type;
String oname = jname.substring(ex + 1, jname.length() - 1);
return typeToJavaClass(oname, null, (idx != -1));
}
/**
* Convert a type into a Java class.
*
* @param type
* Type name, possibly with a package.
* @param pkg
* Optional package prefix to prepend.
* @param array
* {@code true} if this is an array type.
*
* @return The Java class.
*/
public static Class<?> typeToJavaClass(String type, String pkg, boolean array)
{
if (type.startsWith("jobject"))
{
type = type.substring(type.lastIndexOf(' ') + 1, type.length() - 1);
try
{
if (array)
{
String prim = javaPrimitiveTypeCode(type);
if (prim != null)
{
throw new UnsupportedOperationException("Java primitive arrays can't be used from 4GL code.");
}
boolean isObject = prim == null;
return Class.forName("[" + (isObject ? "L" : "" ) + type + (isObject ? ";" : ""));
}
else
{
return Class.forName(type);
}
}
catch (ClassNotFoundException ex)
{
return null;
}
}
else if (type.startsWith("object<"))
{
type = "object";
}
if (!array && type.indexOf('.') < 0 && (pkg == null || pkg.isEmpty()))
{
// this must be a native type
switch (type)
{
case "int":
return int.class;
case "byte":
return byte.class;
case "short":
return short.class;
case "long":
return long.class;
case "float":
return float.class;
case "double":
return double.class;
case "boolean":
return boolean.class;
case "char":
return char.class;
}
}
Class<?> cls = null;
pkg = (pkg == null) ? "" : pkg;
// this is a 4GL type name as returned from ECW.expressionType() and possibly appended
// with [] (for indeterminate extent) or [%d] (for fixed extent); object references will
// have a classname in object<? extends __> format but non-object references will have
// the simple BDT basename (e.g. character or comhandle)
String jname = null;
if (array)
{
String prim = javaPrimitiveTypeCode(type);
prim = (prim == null) ? "L" : prim;
jname = "[" + prim + pkg + type + ";";
}
else
{
jname = pkg + type;
}
try
{
cls = Class.forName(jname);
}
catch (ClassNotFoundException cnfe)
{
// ignore
}
return cls;
}
/**
* Convert a primitive type name into its Java array type code.
*
* @param type
* Java primitive type name.
*
* @return Type code used in Java array names.
*/
private static String javaPrimitiveTypeCode(String type)
{
String code = null;
switch (type)
{
case "boolean":
code = "Z";
break;
case "byte":
code = "B";
break;
case "char":
code = "C";
break;
case "double":
code = "D";
break;
case "float":
code = "F";
break;
case "int":
code = "I";
break;
case "long":
code = "J";
break;
case "short":
code = "S";
break;
}
return code;
}
/**
* Confirm if the needed access level is allowed.
*
* @param access
* Access level of the resource, as a Java modifiers bitfield.
* @param needed
* Required access level as a Progress token type ({@code KW_PUBLIC},
* {@code KW_PROTECTD} or {@code KW_PRIVATE}).
*
* @return {@code true} if the given resource is accessable.
*/
private boolean checkAccessRights(int access, int needed)
{
access = javaAccessLevel(access);
needed = tokenTypeToJavaMod(needed);
if (access != 0 && needed != 0)
{
// 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 search
return (needed == Modifier.PRIVATE) ||
(needed == access) ||
(needed == Modifier.PROTECTED && access == Modifier.PUBLIC);
}
return false;
}
/**
* Convert the Progress token type for an access mode into the Java Modifier access mode.
*
* @param access
* Access mode ({@code KW_PUBLIC}, {@code KW_PROTECTD} or {@code KW_PRIVATE}).
*
* @return {@code Modifier.PRIVATE}, {@code Modifier.PROTECTED}, {@code Modifier.PUBLIC}
* or 0 if there is an error.
*/
private static int tokenTypeToJavaMod(int access)
{
int mod = 0;
switch (access)
{
case KW_PUBLIC:
mod = Modifier.PUBLIC;
break;
case KW_PROTECTD:
mod = Modifier.PROTECTED;
break;
case KW_PRIVATE:
mod = Modifier.PRIVATE;
break;
}
return mod;
}
/**
* Determine the type of the Java class.
*
* @param cls
* The class to inspect.
*
* @return The decoded type.
*/
private static OOType decodeOOType(Class<?> cls)
{
OOType type = OOType.CLASS;
if (cls.isInterface())
{
type = OOType.INTERFACE;
}
else if (cls.isEnum())
{
type = OOType.ENUM;
}
return type;
}
/**
* Obtain the array of Java classes that represent an exact match. This will include
* an array type if it is an extent parameter.
*
* @param parms
* The parameter list as passed by the 4GL code.
*
* @return The 2 dimensional array of class instances matching each type. The 2nd
* dimension is only non-null if this is an object type in which case the
* 2nd dimension will be the unwrapped type of the reference.
*/
private static Class<?>[][] getExactMatchCriteria(ParameterKey[] parms)
{
Class<?>[][] cls = new Class<?>[parms.length][2];
for (int i = 0; i < parms.length; i++)
{
cls[i][0] = parameterTypeToJavaClass(parms[i].type);
if (cls[i][0] == null && parms[i].type.startsWith("object"))
{
cls[i][0] = jobject.class;
}
cls[i][1] = unwrapObjectTypeToJavaClass(parms[i].type);
if (cls[i][1] == null && cls[i][0] == jobject.class)
{
cls[i][1] = Object.class;
}
}
return cls;
}
/**
* Obtain the array of sets of Java classes that represent near matches. This will include
* an array type if it is an extent parameter.
*
* @param exact
* The two dimensional array of exact matches.
*
* @return The array of sets of class instances matching each type.
*/
private static Set<Class<?>>[] getNearMatchCriteria(Class<?>[][] exact)
{
Set<Class<?>>[] near = new HashSet[exact.length];
for (int i = 0; i < exact.length; i++)
{
near[i] = getNearMatches(exact[i][0]);
}
return near;
}
/**
* Obtain the set of data types that are near matches. A near match is one which the
* original type can be converted to without loss of data.
*
* @param cls
* The class for which to find the near matches.
*
* @return The set of near matches. The set may be empty if there are no near matches.
*/
private static Set<Class<?>> getNearMatches(Class<?> cls)
{
Set<Class<?>> set = new HashSet();
String name = cls.getName();
String simple = nonArrayType(name);
boolean extent = (name.charAt(0) == '[');
switch (simple)
{
case "com.goldencode.p2j.util.character":
set.add(typeToJavaClass("java.lang.String", null, extent));
set.add(typeToJavaClass("com.goldencode.p2j.util.longchar", null, extent));
break;
case "com.goldencode.p2j.util.date":
set.add(typeToJavaClass("java.util.Date", null, extent));
set.add(typeToJavaClass("com.goldencode.p2j.util.datetime", null, extent));
set.add(typeToJavaClass("com.goldencode.p2j.util.datetimetz", null, extent));
break;
case "com.goldencode.p2j.util.datetime":
set.add(typeToJavaClass("java.sql.Timestamp", null, extent));
set.add(typeToJavaClass("com.goldencode.p2j.util.datetimetz", null, extent));
break;
case "com.goldencode.p2j.util.decimal":
set.add(typeToJavaClass("double", null, extent));
set.add(typeToJavaClass("java.lang.Double", null, extent));
set.add(typeToJavaClass("java.math.BigDecimal", null, extent));
break;
case "com.goldencode.p2j.util.integer":
set.add(typeToJavaClass("int", null, extent));
set.add(typeToJavaClass("java.lang.Integer", null, extent));
set.add(typeToJavaClass("long", null, extent));
set.add(typeToJavaClass("java.lang.Long", null, extent));
set.add(typeToJavaClass("double", null, extent));
set.add(typeToJavaClass("java.lang.Double", null, extent));
set.add(typeToJavaClass("java.math.BigDecimal", null, extent));
break;
case "com.goldencode.p2j.util.int64":
set.add(typeToJavaClass("long", null, extent));
set.add(typeToJavaClass("java.lang.Long", null, extent));
set.add(typeToJavaClass("java.math.BigDecimal", null, extent));
break;
case "com.goldencode.p2j.util.logical":
set.add(typeToJavaClass("boolean", null, extent));
set.add(typeToJavaClass("java.lang.Boolean", null, extent));
break;
case "com.goldencode.p2j.util.longchar":
set.add(typeToJavaClass("java.lang.String", null, extent));
break;
case "com.goldencode.p2j.util.raw":
set.add(typeToJavaClass("byte", null, true));
break;
case "com.goldencode.p2j.util.unknown":
set.add(unknown.class); // java has no Class associated with null, so use "unknown"
break;
}
return set;
}
/**
* Map the given class to a valid 4GL token type for a variable. The return
* value will be between {@code BEGIN_VAR_TYPE} and {@code END_VAR_TYPE}.
*
* @param cls
* The class to inspect.
*
* @return The variable token type or -1 if something went wrong with the decode.
*/
public static int decodeVarType(Class<?> cls)
{
int type = -1;
String name = cls.getName();
String simple = nonArrayType(name);
// special case where we want to detect byte arrays
if (name.charAt(0) == '[' && "byte".equals(simple))
{
simple = "byte[]";
}
switch (simple)
{
case "char":
case "java.lang.String":
case "com.goldencode.p2j.util.character":
type = VAR_CHAR;
break;
case "com.goldencode.p2j.util.comhandle":
type = VAR_COM_HANDLE;
break;
case "java.util.Date":
case "com.goldencode.p2j.util.date":
type = VAR_DATE;
break;
case "java.sql.Timestamp":
case "com.goldencode.p2j.util.datetime":
type = VAR_DATETIME;
break;
case "com.goldencode.p2j.util.datetimetz":
type = VAR_DATETIME_TZ;
break;
case "float":
case "double":
case "java.lang.Float":
case "java.lang.Double":
case "java.math.BigDecimal":
case "com.goldencode.p2j.util.decimal":
type = VAR_DEC;
break;
case "com.goldencode.p2j.util.handle":
type = VAR_HANDLE;
break;
case "byte":
case "short":
case "int":
case "java.lang.Byte":
case "java.lang.Short":
case "java.lang.Integer":
case "com.goldencode.p2j.util.integer":
type = VAR_INT;
break;
case "long":
case "java.lang.Long":
case "com.goldencode.p2j.util.int64":
type = VAR_INT64;
break;
case "boolean":
case "java.lang.Boolean":
case "com.goldencode.p2j.util.logical":
type = VAR_LOGICAL;
break;
case "com.goldencode.p2j.util.longchar":
type = VAR_LONGCHAR;
break;
case "com.goldencode.p2j.util.memptr":
type = VAR_MEMPTR;
break;
case "byte[]":
case "com.goldencode.p2j.util.raw":
type = VAR_RAW;
break;
case "com.goldencode.p2j.util.recid":
type = VAR_RECID;
break;
case "com.goldencode.p2j.util.rowid":
type = VAR_ROWID;
break;
// must be an object type
default:
type = VAR_CLASS;
}
return type;
}
/**
* Map the given class to a valid 4GL token type for a method call. The return
* value will be between {@code BEGIN_OO_METH} and {@code END_OO_METH}.
*
* @param cls
* The class to inspect.
*
* @return The method call token type or -1 if something went wrong with the decode.
*/
private static int decodeReturnType(Class<?> cls)
{
int type = -1;
String name = cls.getName();
String simple = nonArrayType(name);
// special case where we want to detect byte arrays
if (name.charAt(0) == '[' && "byte".equals(simple))
{
simple = "byte[]";
}
switch (simple)
{
case "char":
case "java.lang.String":
case "com.goldencode.p2j.util.character":
type = OO_METH_CHAR;
break;
case "com.goldencode.p2j.util.comhandle":
type = OO_METH_COM_HANDLE;
break;
case "java.util.Date":
case "com.goldencode.p2j.util.date":
type = OO_METH_DATE;
break;
case "java.sql.Timestamp":
case "com.goldencode.p2j.util.datetime":
type = OO_METH_DATETIME;
break;
case "com.goldencode.p2j.util.datetimetz":
type = OO_METH_DATETIME_TZ;
break;
case "float":
case "double":
case "java.lang.Float":
case "java.lang.Double":
case "java.math.BigDecimal":
case "com.goldencode.p2j.util.decimal":
type = OO_METH_DEC;
break;
case "com.goldencode.p2j.util.handle":
type = OO_METH_HANDLE;
break;
case "byte":
case "short":
case "int":
case "java.lang.Byte":
case "java.lang.Short":
case "java.lang.Integer":
case "com.goldencode.p2j.util.integer":
type = OO_METH_INT;
break;
case "long":
case "java.lang.Long":
case "com.goldencode.p2j.util.int64":
type = OO_METH_INT64;
break;
case "boolean":
case "java.lang.Boolean":
case "com.goldencode.p2j.util.logical":
type = OO_METH_LOGICAL;
break;
case "com.goldencode.p2j.util.longchar":
type = OO_METH_LONGCHAR;
break;
case "com.goldencode.p2j.util.memptr":
type = OO_METH_MEMPTR;
break;
case "byte[]":
case "com.goldencode.p2j.util.raw":
type = OO_METH_RAW;
break;
case "com.goldencode.p2j.util.recid":
type = OO_METH_RECID;
break;
case "com.goldencode.p2j.util.rowid":
type = OO_METH_ROWID;
break;
case "void":
type = OO_METH_VOID;
break;
// must be an object type
default:
type = OO_METH_CLASS;
}
return type;
}
/**
* Convert the Java type specification into a simple type name that does not include any
* array prefix. The array notation, if present, is normalized into the regular type
* name syntax.
*
* @param name
* The name to decode.
*
* @return The simple, normalized form of any array name or the input text if it is not
* an array form.
*/
private static String nonArrayType(String name)
{
String simple = name;
// strip off any array definitions (which only appear at the beginning), there can be
// multiple (e.g. [[Z is a 2 dimensional boolean array)
if (name.charAt(0) == '[')
{
simple = name.substring(name.lastIndexOf('[') + 1);
// at this point there should be at least 1 char (for L also a class name)
switch (simple.charAt(0))
{
case 'Z':
simple = "boolean";
break;
case 'B':
simple = "byte";
break;
case 'C':
simple = "char";
break;
case 'L':
simple = simple.substring(1);
if (simple.endsWith(";"))
{
// array of objects have the class name ending with a ';'
simple = simple.substring(0, simple.length() - 1);
}
break;
case 'D':
simple = "double";
break;
case 'F':
simple = "float";
break;
case 'I':
simple = "int";
break;
case 'J':
simple = "long";
break;
case 'S':
simple = "short";
break;
}
}
return simple;
}
/**
* Store a method.
*/
private static class MethodMatch
{
/** The method or c'tor being matched. */
protected Executable method = null;
/** The parameters that need to be converted/wrapped. */
protected Class<?>[] cvt = null;
/**
* Construct an instance.
*
* @param method
* The method being matched.
*/
public MethodMatch(Executable method)
{
this.method = method;
}
/**
* Report if this is or is not an exact match.
*
* @return Always {@code true}.
*/
public boolean isExact()
{
return true;
}
}
/**
* Store a method and its match criteria in a sortable form.
*/
private static class FuzzyMatch
extends MethodMatch
implements Comparable<FuzzyMatch>
{
/** Source for each instance's unique ordinal, constantly growing over time. */
private static int next = 0;
/** Number of exact matches. */
private int exact = 0;
/** Number of object unwrapping matches. */
private int unwrap = 0;
/** Number of "near" type equivalent matches. */
private int near = 0;
/** Number of parent matches. */
private int parent = 0;
/** Unique fallback value to force ordering based on construction order. */
private int ordinal;
/**
* Construct an instance.
*
* @param method
* The method or c'tor being matched.
* @param exact
* The number of exact matches.
* @param unwrap
* The number of object unwrap matches.
* @param near
* The number of near type equivalents.
* @param parent
* The number of parent matches.
* @param cvt
* The parameters that need to be converted/wrapped.
*/
public FuzzyMatch(Executable method,
int exact,
int unwrap,
int near,
int parent,
Class<?>[] cvt)
{
super(method);
this.exact = exact;
this.unwrap = unwrap;
this.near = near;
this.parent = parent;
this.cvt = cvt;
this.ordinal = next++;
}
/**
* Report if this is or is not an exact match.
*
* @return Always {@code false}.
*/
public boolean isExact()
{
return false;
}
/**
* Compares this instance with the given instance for purposes of sorting.
* <p>
* The precedence order (higher precedence sorts first):
* <p>
* <ol>
* <li> The combined number of exact matches and unwrap matches.
* <li> Near matches.
* <li> Parent matches.
* <li> The order in which the method was returned by Java reflection.
* </ol>
*
* @param other
* The instance being compared to the "this" instance.
*
* @return Negative if this is less than, zero if the objects are equivalent, or
* positive if the given instance is greater than this instance.
*/
public int compareTo(FuzzyMatch other)
{
if (other == null)
{
throw new NullPointerException();
}
int result = 0;
if (this.method != other.method)
{
if (this.exact == other.exact && this.unwrap == other.unwrap)
{
if (this.near == other.near)
{
if (this.parent == other.parent)
{
// if all other ordering information is the same, the ordinal controls
result = this.ordinal - other.ordinal;
}
else
{
result = this.parent - other.parent;
}
}
else
{
result = this.near - other.near;
}
}
else
{
result = (this.exact + this.unwrap) - (other.exact + other.unwrap);
}
}
return result;
}
}
}