FuzzyMethodLookup.java
/*
** Module : FuzzyMethodLookup.java
** Abstract : Method resolution when there are multiple overloads which may match.
**
** Copyright (c) 2023-2025, Golden Code Development Corporation.
**
** -#- -I- --Date-- ---------------------------------------Description----------------------------------------
** 001 CA 20230928 Initial version.
** CA 20231007 Arguments for direct method calls which can't be emitted as direct java calls (as they
** are considered 'dynamic poly') must be wrapped in a 'polyArg' to check the type.
** 002 SB 20231218 Changed isWideningTypeMatch so that for callee type 'object' the function always
** returns true. For type 'object' the caller's type can always be matched to a wider type.
** 003 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.
** 004 CA 20250425 An unknown '?' literal can not match a BUFFER parameter.
*/
/*
** 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.util.*;
import java.util.function.*;
import java.util.stream.*;
import com.goldencode.ast.Aast;
/**
* Implements the rules to perform fuzzy method lookup.
*/
public abstract class FuzzyMethodLookup
{
/**
* Get the class name where this method lookup starts from.
*
* @return See above.
*/
protected abstract String getClassName();
/**
* Detect if the callee class is an instance of an implemented interface of the caller class.
*
* @param caller
* The caller type.
* @param callee
* The callee type.
*
* @return {@code true} if the given class is an implemented interface in this class.
*/
protected abstract boolean isImplemented(String caller, String callee);
/**
* Detect if the callee class is an instance of a parent class or an implemented interface of a parent
* class, of the caller.
*
* @param caller
* The caller type.
* @param callee
* The callee type to check.
* @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.
*/
protected abstract boolean isInheritedFrom(String caller, String callee, Runnable incrementer);
/**
* Reports if the class name is Progress.Lang.ParameterList and the method name is {@code setParameter()}.
*
* @param method
* The method name.
*
* @return {@code true} if this is a setParameter call.
*/
protected abstract boolean isSetParam(String method);
/**
* 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.
*/
protected abstract List<MatchMetrics> candidates(String name,
int num,
int access,
boolean isStatic,
boolean internal,
boolean first,
Set<SignatureKey> exist,
boolean constructor);
/**
* 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
* 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 node
* The AST referencing this method.
* @param constructor
* Flag indicating this must resolve a constructor.
*
* @return Resource found or <code>null</code> if no match exists.
*/
protected MethodSearchResult fuzzyMethodLookup(String name,
ParameterKey[] caller,
int access,
boolean isStatic,
boolean internal,
Aast node,
boolean constructor)
{
boolean first = isSetParam(name);
List<MatchMetrics> list = candidates(name, caller.length, access, isStatic, internal, first, null,
constructor);
boolean debug = false;
if (debug)
{
System.out.println("\n\n--------------------fuzzyMethodLookup START--------------------\n");
System.out.printf("\n CALLER %s\n PARAMETERS: %d\n",
(node == null ? "n/a" : node.dumpTree(true)),
caller.length);
for (int i = 0; i < caller.length; i++)
{
System.out.printf(" %s\n", caller[i]);
}
for (MatchMetrics m : list)
{
System.out.printf("DEBUG: %s\n", m.data);
}
}
// quick out if there are no possible matches
if (list.isEmpty())
{
if (debug)
System.out.println("--------------------fuzzyMethodLookup QUICK OUT--------------------");
return null;
}
// special case for ParameterList.setParameter()
if (first && list.size() == 1)
{
return new MethodSearchResult(list.get(0).data, null);
}
String[] error = new String[1];
boolean polyArgs = false;
boolean[] dynamicaPolyArgs = new boolean[] { false };
// TODO: constructors
// PHASE 1: cut down the list of all matches to include only those which are potentially valid
if (debug)
System.out.println("\n\n---PHASE 1 START---\n");
// process each parameter from left to right; the processing will vary by the parameter type; some
// types (e.g. objects) have multi-step checks and other types are a simple comparison; at each
// step of the way the list of possible candidates will be reduced until there are only candidates
// left which COULD match all criteria; at that point if there are more than one then we have an
// additional disambiguation step, a dynamic invocation scenario or there is some ambiguity (which
// should not happen if the code compiles in the 4GL)
// process the caller's parameters, left to right; for each caller parameter we examine the matching
// parameter in the list of candidates, removing any candidates in the list that cannot match
for (int i = 0; i < caller.length; i++)
{
final int idx = i;
final Integer callerMode = caller[i].mode;
// if the caller is passing Java types, then we match them as if they were 4GL types so the get
// converted here; if they are already 4GL types then no change will happen
final String callerType = fromJava(caller[i].type);
// these are all specific to the current parameter
Predicate<MatchMetrics> exactMatch = (m) -> callerType.equals(fromJava(m.data.getSignature(idx).type));
Predicate<MatchMetrics> isTable = (m) -> m.data.getSignature(idx).type.startsWith("TEMP-TABLE");
Predicate<MatchMetrics> isTH = (m) -> "TABLE-HANDLE".equals(m.data.getSignature(idx).type);
Predicate<MatchMetrics> isDataset = (m) -> m.data.getSignature(idx).type.startsWith("DATASET <");
Predicate<MatchMetrics> isDH = (m) -> "DATASET-HANDLE".equals(m.data.getSignature(idx).type);
Predicate<MatchMetrics> modeMatch = (m) -> callerMode.equals(m.data.getSignature(idx).mode);
Predicate<MatchMetrics> allowUnkn =
(m) -> !"unknown".equals(callerType) || !m.data.getSignature(idx).type.startsWith("BUFFER <");
list.removeIf(allowUnkn.negate());
// exclude any candidates which cannot ever match due to a caller MODE constraint; a null for the
// caller's mode is a wildcard that can match any mode in the parameter definition; this means that
// when the caller's mode is null, no matches can be excluded
if (callerMode != null)
{
list.removeIf(modeMatch.negate());
if (debug)
System.out.printf("PARM %d: mode %d\n", idx, list.size());
}
// TYPE-specific processing
boolean poly = (callerType == null) || callerType.equals("BaseDataType");
// TYPE wildcards: unknown value or BDT (POLY cases) match any TYPE
if (poly || callerType.equals("unknown"))
{
list.stream().forEach((m) -> m.wildcard++);
if (poly)
polyArgs = true;
// no cases can be excluded based on type; the list remains unchanged
if (debug)
System.out.printf("PARM %d: type wildcard %d\n", idx, list.size());
}
else
{
// table handles
if (callerType.equals("TABLE-HANDLE") ||
callerType.startsWith("TABLE <") ||
callerType.startsWith("TEMP-TABLE <"))
{
// at this point we can't know if we have a match, we can just remove anything that is not
// a table-handle or table; we defer the matching to the next phase once all other removals
// are done
list.removeIf(isTH.or(isTable).negate());
list.stream().forEach((m) -> m.cvt++);
if (debug)
System.out.printf("PARM %d: TH %d\n", idx, list.size());
}
// dataset handles
else if (callerType.equals("DATASET-HANDLE") || callerType.startsWith("DATASET <"))
{
// at this point we can't know if we have a match, we can just remove anything that is not
// a dataset-handle or dataset; we defer the matching to the next phase once all other
// removals are done
list.removeIf(isDH.or(isDataset).negate());
list.stream().forEach((m) -> m.cvt++);
if (debug)
System.out.printf("PARM %d: DH %d\n", idx, list.size());
}
// buffers
else if (callerType.startsWith("BUFFER"))
{
// only an exact match works (handled above); no fuzzy matching here
list.removeIf(exactMatch.negate());
list.stream().forEach((m) -> m.exact++);
if (debug)
System.out.printf("PARM %d: buf %d\n", idx, list.size());
}
// must be one of the BaseDataType wrapper classes
else
{
final int extIdx = callerType.indexOf("[");
final int extVal = (extIdx == -1) ? 0 : getExtent(callerType);
final String basicType = (extIdx == -1) ? callerType : callerType.substring(0, extIdx);
// extents
processExtentMatches(list, extVal, idx);
if (debug)
System.out.printf("PARM %d: after extent processing %d\n", idx, list.size());
// primitive and OO types widening/narrowing
// WARNING: this code is not idempotent, we modify the MemberMetrics instances of any that
// are matched; this is BAD BAD BAD, but the widening/narrowing calculation is very
// expensive so it makes no sense to duplicate that work
Predicate<MatchMetrics> isTypeMatch = (mem) ->
{
String base = mem.data.getSignature(idx).scalarType();
Runnable increment = () -> mem.ooLvl[idx]++;
// exact match to the base portion of the type will match for any mode (and is the only
// possible match for input-output mode); for the rest of the cases we check the
// parameter mode at the definition (the caller might be unspecified (wildcard) but the
// definition must be specified
if (basicType.equals(base))
{
// yes, this is not idempotent; get over it
mem.exact++;
return true;
}
else if (mem.data.getSignature(idx).mode == ProgressParserTokenTypes.KW_INPUT)
{
boolean widen = isWideningTypeMatch(basicType, base, increment);
if (widen)
{
// yes, this is not idempotent; get over it
mem.cvt++;
}
else if (caller[idx].fromExpression)
{
// a single case is handled at this time: a INT64 argument to an INTEGER parameter
if ("int64".equals(basicType) && "integer".equals(base))
{
mem.cvt++;
widen = true;
}
}
// only INPUT arguments are allowed in this dynamic poly arg mode
dynamicaPolyArgs[0] = dynamicaPolyArgs[0] || caller[idx].dynamicPoly;
return widen || caller[idx].dynamicPoly;
}
else if (mem.data.getSignature(idx).mode == ProgressParserTokenTypes.KW_OUTPUT)
{
boolean narrow = isNarrowingTypeMatch(basicType, base, increment);
if (narrow)
{
// yes, this is not idempotent; get over it
mem.cvt++;
}
return narrow;
}
return false;
};
// WARNING: because isTypeMatch is not idempotent we make sure to only process it once
list.removeIf(isTypeMatch.negate());
if (debug)
System.out.printf("PARM %d: BDT type match %d\n", idx, list.size());
}
}
}
if (debug)
{
System.out.println("\n\n---PHASE 1 END---\n\n");
for (MatchMetrics m : list)
{
System.out.printf("DEBUG: %s\n", m);
}
}
// PHASE 2: if there are > 1 candidates we need to disambiguate based on priorities
// unknown value, wildcard modes cannot be used to disambiguate
if (list.size() > 1 || ((polyArgs || dynamicaPolyArgs[0]) && list.size() == 1))
{
MethodSearchResult dyn = null;
if (polyArgs || dynamicaPolyArgs[0])
{
if (debug)
System.out.println("\n\n---------fuzzyMethodLookup DYNAMIC POLY!---------\n");
// POLY arguments activates pseudo-dynamic mode, where the conversion emits the signature of the
// expected method to be called, and the runtime double-checks the resolved method's signature
// against the expected method's signature
MatchMetrics match = list.get(list.size() - 1);
dyn = new MethodSearchResult(match.data, match.overrides, polyArgs);
return dyn;
}
dyn = new MethodSearchResult();
// TABLE-HANDLE/DATASET-HANDLE arguments deferred processing; we don't calculate this earlier
// because some possible matches that would have been seen then may have been dropped by
// processing of later parameters; this means that we must handle the possible compile time and
// runtime matches here
final int[] numTB = new int[caller.length];
final int[] numTH = new int[caller.length];
final int[] numDS = new int[caller.length];
final int[] numDH = new int[caller.length];
for (int i = 0; i < caller.length; i++)
{
final int idx = i;
String callerType = fromJava(caller[i].type);
Predicate<MatchMetrics> isTH = (m) -> "TABLE-HANDLE".equals(m.data.getSignature(idx).type);
Predicate<MatchMetrics> isDH = (m) -> "DATASET-HANDLE".equals(m.data.getSignature(idx).type);
Predicate<MatchMetrics> exactMatch = (m) -> callerType.equals(fromJava(m.data.getSignature(idx).type));
// table parameters
if (callerType.startsWith("TEMP-TABLE"))
{
// check for any exact TYPE matches
if (!processExactMatches(list, exactMatch))
{
// allow a match to a table-handle if there is only 1 table-handle option
filterList(list, isTH, idx, callerType, error);
}
if (debug)
System.out.printf("PARM %d: TT %d\n", idx, list.size());
}
// dataset parameters
else if (callerType.startsWith("DATASET <"))
{
// check for any exact TYPE matches
if (!processExactMatches(list, exactMatch))
{
// allow a match to a dataset-handle if there is only 1 dataset-handle option
filterList(list, isDH, idx, callerType, error);
}
if (debug)
System.out.printf("PARM %d: DS %d\n", idx, list.size());
}
if ("TABLE-HANDLE".equals(callerType))
{
Predicate<MatchMetrics> isTable = (m) -> m.data.getSignature(idx).type.startsWith("TEMP-TABLE");
Consumer<MatchMetrics> trackT = (m) ->
{
if (isTable.test(m))
{
numTB[idx]++;
}
else if (isTH.test(m))
{
numTH[idx]++;
}
};
list.forEach(trackT);
if (numTH[idx] == 1 && numTB[idx] == 0)
{
list.removeIf(isTH.negate());
break;
}
else if (numTH[idx] == 0 && numTB[idx] == 1)
{
list.removeIf(isTable.negate());
break;
}
else if ((numTH[idx] + numTB[idx]) > 1)
{
// dynamic mode
if (debug)
System.out.println("\n\n---------fuzzyMethodLookup DYNAMIC TABLE-HANDLE!---------\n");
return dyn;
}
else
{
// no matches
list.clear();
error[0] = String.format("No possible matches for parameter %d of type %s.",
idx,
callerType);
break;
}
}
if ("DATASET-HANDLE".equals(callerType))
{
Predicate<MatchMetrics> isDataset = (m) -> m.data.getSignature(idx).type.startsWith("DATASET <");
Consumer<MatchMetrics> trackD = (m) ->
{
if (isDataset.test(m))
{
numDS[idx]++;
}
else if (isDH.test(m))
{
numDH[idx]++;
}
};
list.forEach(trackD);
if (numDH[idx] == 1 && numDS[idx] == 0)
{
list.removeIf(isDH.negate());
break;
}
else if (numDH[idx] == 0 && numDS[idx] == 1)
{
list.removeIf(isDataset.negate());
break;
}
else if ((numDH[idx] + numDS[idx]) > 1)
{
// dynamic mode
if (debug)
System.out.println("\n\n---------fuzzyMethodLookup DYNAMIC DATASET-HANDLE!---------\n");
return dyn;
}
else
{
// no matches
list.clear();
error[0] = String.format("No possible matches for parameter %d of type %s.",
idx,
callerType);
break;
}
}
}
}
// exact vs fuzzy weighting disambiguation if needed
if (list.size() > 1)
{
// use the natural ordering of the metrics class to find the best match
Collections.sort(list, Collections.reverseOrder());
MatchMetrics best = list.get(0);
MatchMetrics runnerUp = list.get(1);
// safety check; if it fails we report the ambiguity below
if (best.compareTo(runnerUp) == 1)
{
// clear everything except the best match
list = new LinkedList<>();
list.add(best);
}
}
if (list.size() == 1)
{
// "there can be only one"
if (debug)
System.out.println("\n\n----------------fuzzyMethodLookup HIGHLANDER!----------------\n");
MatchMetrics match = list.get(0);
return new MethodSearchResult(match.data, match.overrides);
}
else if (list.isEmpty())
{
if (debug)
{
// if we still have ambiguous matches, display any stored error
if (error[0] != null)
{
System.out.println(error[0]);
}
System.out.println("\n\n----------------fuzzyMethodLookup NO MATCH SUCKA!----------------\n");
}
return null;
}
System.out.println("WARNING: more than one method def found using fuzzy lookup: " + name +
" from class " + getClassName());
System.out.printf("\n CALLER %s\n PARAMETERS: %d\n", node.dumpTree(true), caller.length);
for (int i = 0; i < caller.length; i++)
{
System.out.printf(" %s\n", caller[i]);
}
Iterator<MatchMetrics> iter = list.iterator();
int num = 0;
while (iter.hasNext())
{
MatchMetrics dat = iter.next();
System.out.printf(" MATCH %d: %s\n\n", num, dat.toString());
num++;
}
if (debug)
System.out.println("\n\n--------------------fuzzyMethodLookup BITTER END---------------------\n");
// return the first one in the sorted list
MatchMetrics match = list.get(0);
return new MethodSearchResult(match.data, match.overrides);
}
/**
* Convert the specified Java-style type to a legacy BDT compatible type, if possible.
*
* @param type
* The Java type, in <code>jobject> extends<</code> format.
*
* @return The converted type (if conversion was made) or the original type, otherwise.
*/
private String fromJava(String type)
{
if (type == null)
{
return null;
}
if (type.startsWith("jobject<? extends"))
{
String jtype = type.substring("jobject<? extends ".length(), type.length() - 1);
if (jtype.equals("java.lang.Integer") ||
jtype.equals("java.lang.Short") ||
jtype.equals("java.lang.Byte"))
{
type = "integer";
}
else if (jtype.equals("java.lang.Double") ||
jtype.equals("java.lang.Float") ||
jtype.equals("java.math.BigDecimal"))
{
type = "decimal";
}
else if (jtype.equals("java.lang.Boolean"))
{
type = "logical";
}
else if (jtype.equals("java.lang.String"))
{
type = "character";
}
else if (jtype.equals("java.util.Date"))
{
type = "date";
}
else if (jtype.equals("java.sql.Timestamp"))
{
type = "datetime";
}
// TODO: byte[] to raw
}
return type;
}
/**
* Check if the caller's type can be matched to a wider type in the callee's signature.
* <p>
* Primitive types widen as follows:
* <pre>
* Caller Type Callee Parameter Types
* -------------- ----------------------
* integer int64, decimal
* int64 decimal
* character longchar
* date datetime, datetime-tz
* datetime datetime-tz
* </pre>
* <p>
* Object types widen by these rules:
* <ol>
* <li> We check if the callee matches any of the interfaces implemented by the caller type's specific class.
* <li> We check if the callee matches any of the parent class hierarchy (and their implemented interfaces)
* of the caller type's specific class.
* <li> We check if the callee is Progress.Lang.Object. This check has lowest priority.
* </ol>
*
* @param caller
* The caller's passed instance's data type.
* @param callee
* The candidate callee method's parameter data type.
* @param incrementer
* Code to be executed to increment the inheritance level.
*
* @return {@code true} if a widening operation is possible.
*/
private boolean isWideningTypeMatch(String caller, String callee, Runnable incrementer)
{
if (("int64".equals(caller) && "decimal".equals(callee)) ||
("integer".equals(caller) && ("int64".equals(callee) || "decimal".equals(callee))) ||
("character".equals(caller) && "longchar".equals(callee)) ||
("datetime".equals(caller) && "datetimetz".equals(callee)) ||
("date".equals(caller) && ("datetime".equals(callee) || "datetimetz".equals(callee))))
{
// to properly compute the best match, we need the distance between the caller and callee's type.
incrementer.run();
if (("integer".equals(caller) && "decimal".equals(callee)) ||
("date".equals(caller) && "datetimetz".equals(callee)))
{
incrementer.run();
}
return true;
}
if (caller.startsWith("object") && callee.startsWith("object"))
{
// check if the target is in the interface list of the caller type's specific class
if (isImplemented(caller, callee))
{
return true;
}
// check if the target matches the caller type's parent class hierarchy (including implemented
// interfaces)
if (isInheritedFrom(caller, callee, incrementer))
{
return true;
}
// lowest priority/last ditch check
return callee.toLowerCase().indexOf("progress.lang.object") > 0;
}
if (callee.startsWith("Object"))
{
return true;
}
return false;
}
/**
* Check if the callee's type can be written back to a wider caller's type, when viewed from the
* caller to callee, this looks like a "narrowing" operation.
* <p>
* Primitive types narrow as follows:
* <pre>
* Caller Type Callee Parameter Types
* -------------- ----------------------
* int64 integer
* decimal integer, int64
* longchar character
* datetime date
* datetime-tz date, datetime
* </pre>
* <p>
* Object types narrow by these rules:
* <ol>
* <li> We check if the callee matches a child class of the caller type's specific class. The
* nearest child class is matched with highest priority.
* </ol>
*
* @param caller
* The caller's passed instance's data type.
* @param callee
* The candidate callee method's parameter data type.
* @param incrementer
* Code to be executed to increment the inheritance level.
*
* @return {@code true} if a narrowing operation is possible.
*/
private boolean isNarrowingTypeMatch(String caller, String callee, Runnable incrementer)
{
if (("int64".equals(caller) && "integer".equals(callee)) ||
("decimal".equals(caller) && ("integer".equals(callee) || "int64".equals(callee))) ||
("longchar".equals(caller) && "character".equals(callee)) ||
("datetime".equals(caller) && "date".equals(callee)) ||
("datetimetz".equals(caller) && ("date".equals(callee) || "datetime".equals(callee))))
{
// to properly compute the best match, we need the distance between the caller and callee's type.
incrementer.run();
if (("decimal".equals(caller) && "integer".equals(callee)) ||
("datetimetz".equals(caller) && "date".equals(callee)))
{
incrementer.run();
}
return true;
}
if (caller.startsWith("object") && callee.startsWith("object"))
{
// check if the target parameter is a child class of the caller (this explicitly ignores
// implemented interfaces which is how the 4GL does it)
return isInheritedFrom(callee, caller, incrementer);
}
return false;
}
/**
* Check if there is only 1 or more possible match for the given predicate, if so then remove all
* candidates from the list except for the possible matches.
*
* @param list
* The list to edit.
* @param test
* The predicate to test.
* @param idx
* The parameter index for error messages.
* @param callerType
* The caller type for error messages.
* @param error
* A location to store an error message for the caller.
*/
private boolean filterList(List<MatchMetrics> list,
Predicate<MatchMetrics> test,
int idx,
String callerType,
String[] error)
{
boolean result = false;
List<MatchMetrics> matches = list.stream().filter(test).collect(Collectors.toList());
int num = matches.size();
if (num >= 1)
{
result = true;
matches.stream().forEach((m) -> m.cvt++);
list.removeIf(test.negate());
}
else
{
error[0] = String.format("No possible matches for parameter %d of type %s.", idx, callerType);
}
if (!result)
{
list.clear();
}
return result;
}
/**
* Remove any non-exact matches from the given list.
*
* @param list
* The list to edit.
* @param exactMatch
* The test for an exact match.
*
* @return {@code true} if edits were made.
*/
private boolean processExactMatches(List<MatchMetrics> list, Predicate<MatchMetrics> exactMatch)
{
// calculate the exact TYPE matches
List<MatchMetrics> exacts = list.stream().filter(exactMatch).collect(Collectors.toList());
if (!exacts.isEmpty())
{
exacts.stream().forEach((m) -> m.exact++);
// there is at least 1 exact match, remove everything else
list.removeIf(exactMatch.negate());
return true;
}
return false;
}
/**
* Remove any candidates from the list which do not match the caller's extent. The following are
* possible matches:
* <p>
* <ul>
* <li> Neither the caller nor the parameter definition have an extent specification.
* <li> Both the caller and the parameter definition have an indeterminate extent.
* <li> The caller has a fixed extent value and the parameter definition has the same fixed extent
* value.
* <li> The caller has a fixed extent value and the parameter definition has an indeterminate
* extent. This is a fuzzy match (all the above criteria are the same as exact matches).
*
* @param list
* The list of candidates.
* @param callerExtVal
* Caller's extent value (-1 means indeterminate, 0 is scalar and any positive value is
* the fixed extent).
* @param idx
* Parameter number being processed.
*/
private void processExtentMatches(List<MatchMetrics> list, int callerExtVal, int idx)
{
Predicate<MatchMetrics> test = null;
Consumer<MatchMetrics> mark = (m) -> m.extExact++;
// no extent in caller
if (callerExtVal == 0)
{
// no extent in the parameter definition
test = (mem) -> mem.data.getSignature(idx).type.indexOf("[") == -1;
// no marking needed here
mark = (m) -> {};
}
// indeterminate extent in caller
else if (callerExtVal == -1)
{
// indeterminate extent in the parameter definition
test = (mem) -> mem.data.getSignature(idx).type.indexOf("[") != -1 &&
getExtent(mem.data.getSignature(idx).type) == -1;
}
// fixed extent in caller
else
{
test = (mem) ->
{
int extIdx = mem.data.getSignature(idx).type.indexOf("[");
if (extIdx != -1)
{
// the parameter definition is an extent
int parmExtVal = getExtent(mem.data.getSignature(idx).type);
// fixed extents match exactly or the parm is indeterminate
return callerExtVal == parmExtVal || parmExtVal == -1;
}
else
{
// the parameter definition is NOT an extent
return false;
}
};
// this will only be used for where we already know extIdx != -1 above which means it is an
// extent at the parameter definition (it may be either fixed or indeterminate)
mark = (mem) ->
{
// the parameter definition is an extent
int parmExtVal = getExtent(mem.data.getSignature(idx).type);
if (callerExtVal == parmExtVal)
{
// both are fixed extents match exactly
mem.extExact++;
}
else if (parmExtVal == -1)
{
// or the parm is indeterminate
mem.extCvt++;
}
};
}
list.removeIf(test.negate());
list.stream().forEach(mark);
}
/**
* Get the extent value from a parameter's type. If dynamic extent, return -1.
*
* @param sig
* The parameter's type.
*
* @return The extent value.
*/
private int getExtent(String sig)
{
int idx1 = sig.indexOf("[");
int idx2 = sig.indexOf("]");
if ((idx1 + 1 == idx2) || (idx1 == -1)) // Matches [] and no brackets (at least left one)
{
return -1;
}
String x = sig.substring(idx1 + 1, idx2);
return Integer.parseInt(x);
}
}