package bluej.parser;
import java.io.Reader;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import threadchecker.OnThread;
import threadchecker.Tag;
import bluej.debugger.gentype.BadInheritanceChainException;
import bluej.debugger.gentype.GenTypeClass;
import bluej.debugger.gentype.GenTypeDeclTpar;
import bluej.debugger.gentype.GenTypeParameter;
import bluej.debugger.gentype.GenTypeSolid;
import bluej.debugger.gentype.GenTypeTpar;
import bluej.debugger.gentype.GenTypeWildcard;
import bluej.debugger.gentype.IntersectionType;
import bluej.debugger.gentype.JavaPrimitiveType;
import bluej.debugger.gentype.JavaType;
import bluej.debugger.gentype.MethodReflective;
import bluej.debugger.gentype.Reflective;
import bluej.debugmgr.NamedValue;
import bluej.debugmgr.ValueCollection;
import bluej.debugmgr.codepad.DeclaredVar;
import bluej.parser.entity.ConstantFloatValue;
import bluej.parser.entity.ConstantIntValue;
import bluej.parser.entity.EntityResolver;
import bluej.parser.entity.JavaEntity;
import bluej.parser.entity.PackageEntity;
import bluej.parser.entity.PackageOrClass;
import bluej.parser.entity.TypeEntity;
import bluej.parser.entity.ValueEntity;
import bluej.utility.Debug;
import bluej.utility.JavaNames;
import bluej.utility.JavaReflective;
import bluej.utility.JavaUtils;
| Parsing routines for the code pad.<p>
|
| This is pretty tricky stuff, we try to following the Java Language Specification
| (JLS) where possible.
|
| @author Davin McCall
|
public class TextAnalyzer
{
private EntityResolver parentResolver;
private String packageScope;
private ValueCollection objectBench;
private List<DeclaredVar> declVars;
private String amendedCommand;
private ImportsCollection imports;
private String importCandidate;
| TextParser constructor. Defines the class loader and package scope
| for evaluation.
|
public TextAnalyzer(EntityResolver parentResolver, String packageScope, ValueCollection ob)
{
this.parentResolver = parentResolver;
this.packageScope = packageScope;
this.objectBench = ob;
imports = new ImportsCollection();
}
| Set a new class loader, and clear the imports list.
|
public void newClassLoader(ClassLoader newLoader)
{
imports.clear();
}
| Parse a string entered into the code pad. Return is null if the string
| is a statement; otherwise the string is an expression and the returned
| string if the type of the expression (empty if the type cannot be determined).
|
| <p>After calling this method, getDeclaredVars() and getAmendedCommand() can be
| called - see the documentation for those methods respectively.
|
| <p>If the parsed string is then executed, the confirmCommand() method should
| subsequently be called.
|
@OnThread(Tag.FXPlatform)
public String parseCommand(String command)
{
importCandidate = "";
amendedCommand = command;
declVars = Collections.emptyList();
EntityResolver resolver = getResolver();
Reflective accessRef = new DummyReflective(JavaNames.combineNames(packageScope, "$SHELL"));
TypeEntity accessType = new TypeEntity(accessRef);
TextParser parser = new TextParser(resolver, command, accessType, true);
try {
try {
parser.parseImportStatement();
if (parser.atEnd()) {
amendedCommand = "";
importCandidate = command;
return null;
}
}
catch (ParseFailure e) {
}
CodepadVarParser vparser = new CodepadVarParser(resolver, command);
try {
if (vparser.parseVariableDeclarations() != null) {
declVars = vparser.getVariables();
if (! declVars.isEmpty()) {
for (DeclaredVar var : declVars) {
if (! var.isInitialized() && ! var.isFinal()) {
amendedCommand += "\n" + var.getName();
String text;
JavaType declVarType = var.getDeclaredType();
if (declVarType.isPrimitive()) {
if (declVarType.isNumeric()) {
text = " = 0";
}
else {
text = " = false";
}
}
else {
text = " = null";
}
amendedCommand += text + ";\n";
}
}
return null;
}
}
}
catch (ParseFailure e) {
}
parser = new TextParser(resolver, command, accessType, true);
try {
parser.parseExpression();
if (parser.atEnd()) {
JavaEntity exprType = parser.getExpressionType();
if (exprType == null) {
return "";
}
JavaEntity rval = exprType.resolveAsValue();
if (rval != null) {
if (rval.getType().typeIs(JavaType.JT_VOID)) {
return null;
}
JavaType rtype = rval.getType();
if (rtype.isPrimitive() || rtype.getArrayComponent() != null) {
return rtype.toString();
}
return rtype.asSolid().getReferenceSupertypes()[0].toString();
}
return "";
}
}
catch (ParseFailure e) {
}
}
catch (Throwable t) {
Debug.reportError("Exception in parser", t);
}
return null;
}
private EntityResolver getResolver()
{
EntityResolver resolver = new EntityResolver()
{
@Override
public TypeEntity resolveQualifiedClass(String name)
{
return parentResolver.resolveQualifiedClass(name);
}
@Override
public PackageOrClass resolvePackageOrClass(String name, Reflective querySource)
{
String pkgScopePrefix = packageScope;
if (packageScope.length() > 0) {
pkgScopePrefix += ".";
}
TypeEntity entity = imports.getTypeImport(name);
if (entity != null)
{
return entity;
}
TypeEntity rval = parentResolver.resolveQualifiedClass(pkgScopePrefix + name);
if (rval != null) {
return rval;
}
rval = parentResolver.resolveQualifiedClass("java.lang." + name);
if (rval != null) {
return rval;
}
entity = imports.getTypeImportWC(name);
if (entity != null) {
return entity;
}
return new PackageEntity(name, this);
}
@Override
@OnThread(value = Tag.FXPlatform, ignoreParent = true)
public JavaEntity getValueEntity(String name, Reflective querySource)
{
NamedValue obVal = objectBench.getNamedValue(name);
if (obVal != null) {
return new ValueEntity(obVal.getGenType());
}
List<JavaEntity> importStaticVals = imports.getStaticImports(name);
if (importStaticVals != null && !importStaticVals.isEmpty()) {
return importStaticVals.get(0).getSubentity(name, querySource);
}
importStaticVals = imports.getStaticWildcardImports();
if (importStaticVals != null) {
for (JavaEntity importStatic : importStaticVals) {
importStatic = importStatic.resolveAsType();
if (importStatic == null) {
continue;
}
JavaEntity entity = importStatic.getSubentity(name, querySource);
if (entity != null) {
return entity;
}
}
}
return resolvePackageOrClass(name, querySource);
}
};
return resolver;
}
| Called to confirm that the recently parsed command has successfully
| executed. This allows TextParser to update internal state to reflect
| changes caused by the execution of the command.
|
@OnThread(Tag.FXPlatform)
public void confirmCommand()
{
if (importCandidate.length() != 0) {
Reader r = new StringReader(importCandidate);
CodepadImportParser parser = new CodepadImportParser(getResolver(), r);
parser.parseImportStatement();
if (parser.isStaticImport()) {
if (parser.isWildcardImport()) {
imports.addStaticWildcardImport(parser.getImportEntity()
.resolveAsType(), null, null);
}
else {
imports.addStaticImport(parser.getMemberName(),
parser.getImportEntity().resolveAsType(), null, null);
}
}
else {
if (parser.isWildcardImport()) {
imports.addWildcardImport(parser.getImportEntity().resolveAsPackageOrClass(), null, null);
}
else {
JavaEntity importEntity = parser.getImportEntity();
TypeEntity classEnt = importEntity.resolveAsType();
String name = classEnt.getType().toString(true);
imports.addNormalImport(name, classEnt, null, null);
}
}
}
}
| Get a list of the variables declared in the recently parsed statement
| block. The return is a List of TextParser.DeclaredVar
|
public List getDeclaredVars()
{
return declVars;
}
| Get the amended command string, which has initializers inserted for variable
| declarations which were missing initializers.
|
public String getAmendedCommand()
{
return amendedCommand;
}
| Return the imports collection as a sequence of java import statements.
|
public String getImportStatements()
{
return imports.toString() + importCandidate;
}
| Java 1.4 & prior version of trinary "? :" operator. See JLS 2nd ed.
|* section 15.25.
*
* @throws RecognitionException
* @throws SemanticException
*/
// private JavaType questionOperator14(AST node) throws RecognitionException, SemanticException
|
|// {}// AST trueAlt = node.getFirstChild().getNextSibling();
|
|// AST falseAlt = trueAlt.getNextSibling();
|
|// ExprValue trueAltEv = getExpressionType(trueAlt);
|
|// ExprValue falseAltEv = getExpressionType(falseAlt);
|
|// JavaType trueAltType = trueAltEv.getType();
|
|// JavaType falseAltType = falseAltEv.getType();
|
|//
|
|// // if the operands have the same type, that is the result type
|
|// if (trueAltType.equals(falseAltType))
|
|// return trueAltType;
|
|//
|
|// if (trueAltType.isNumeric() && falseAltType.isNumeric()) {}// // if one type is short and the other is byte, result type is short
|
|// if (trueAltType.typeIs(JavaType.JT_SHORT) && falseAltType.typeIs(JavaType.JT_BYTE))
|
|// return JavaPrimitiveType.getShort();
|
|// if (falseAltType.typeIs(JavaType.JT_SHORT) && trueAltType.typeIs(JavaType.JT_BYTE))
|
|// return JavaPrimitiveType.getShort();
|
|//
|
|// // if one type is byte/short/char and the other is a constant of
|
|// // type int whose value fits, the result type is byte/short/char
|
|// if (falseAltType.typeIs(JavaType.JT_INT) && falseAltEv.knownValue()) {}// int fval = falseAltEv.intValue();
|
|// if (isMinorInteger(trueAltType) && trueAltType.couldHold(fval))
|
|// return trueAltType;
|
|// }
|
|// if (trueAltType.typeIs(JavaType.JT_INT) && trueAltEv.knownValue()) {}// int fval = trueAltEv.intValue();
|
|// if (isMinorInteger(falseAltType) && falseAltType.couldHold(fval))
|
|// return falseAltType;
|
|// }
|
|//
|
|// // binary numeric promotion is applied
|
|// return binaryNumericPromotion(trueAltType, falseAltType);
|
|// }
|
|//
|
|// // otherwise it must be possible to convert one type to the other by
|
|// // assignment conversion:
|
|// if (trueAltType.isAssignableFrom(falseAltType))
|
|// return trueAltType;
|
|// if (falseAltType.isAssignableFrom(trueAltType))
|
|// return falseAltType;
|
|//
|
|// throw new SemanticException();
|
|// }
|
|/**
| Test if a given type is one of the "minor" integral types: byte, char
|* or short.
*/
// private static boolean isMinorInteger(JavaType a)
// {}// return a.typeIs(JavaType.JT_BYTE) || a.typeIs(JavaType.JT_CHAR) || a.typeIs(JavaType.JT_SHORT);
|
|// }
|
|/**
| Test whether the given value fits in the range representable by byte, short or char
|
private static boolean doesValueFitIntType(long value, JavaType type)
{
if (type.typeIs(JavaType.JT_BYTE)) {
return value <= Byte.MAX_VALUE && value >= Byte.MIN_VALUE;
}
else if (type.typeIs(JavaType.JT_CHAR)) {
return value <= Character.MAX_VALUE && value >= Character.MIN_VALUE;
}
else if (type.typeIs(JavaType.JT_SHORT)) {
return value <= Short.MAX_VALUE && value >= Short.MIN_VALUE;
}
return false;
}
| Java 1.5 version of the trinary "? :" operator.
|* See JLS section 15.25. Note that JLS 3rd ed. differs extensively
* from JLS 2nd edition. The changes are not backwards compatible.
*/
public static ValueEntity questionOperator15(ValueEntity condition, ValueEntity trueAlt, ValueEntity falseAlt)
|
|{
|
|JavaType trueAltType = trueAlt.getType();
|
|JavaType falseAltType = falseAlt.getType();
|
|JavaType conditionType = condition.getType();
|
|// The condition must be boolean:
|
|if (conditionType == null || ! conditionType.typeIs(JavaType.JT_BOOLEAN)) {
|
|return null;
|
|}
|
|// if we don't know the type of both alternatives, we don't
|
|// know the result type:
|
|if (trueAltType == null || falseAltType == null) {
|
|return null;
|
|}
|
|// Neither argument can be a void type.
|
|if (trueAltType.isVoid() || falseAltType.isVoid()) {
|
|return null;
|
|}
|
|// if the second & third arguments have the same type, then
|
|// that is the result type:
|
|if (trueAltType.equals(falseAltType)) {
|
|if (condition.hasConstantBooleanValue() && ValueEntity.isConstant(trueAlt) && ValueEntity.isConstant(falseAlt)) {
|
|return condition.getConstantBooleanValue() ? trueAlt : falseAlt;
|
|}
|
|return new ValueEntity(trueAltType);
|
|}
|
|// If one of the operands is the null type and the other is a reference type,
|
|// the type of the conditional expression is that of the reference type:
|
|if (trueAltType.isNull() && !falseAltType.isPrimitive()) {
|
|// The result cannot be a compile-time constant as expression contains a null
|
|return new ValueEntity(falseAltType);
|
|}
|
|// Otherwise:
|
|String trueAltStr = trueAltType.toString();
|
|String falseAltStr = falseAltType.toString();
|
|boolean trueIsByte = trueAltStr.equals("byte") || trueAltStr.equals("java.lang.Byte");
boolean falseIsShort = falseAltStr.equals("short") || falseAltStr.equals("java.lang.Short");
boolean falseIsByte = falseAltStr.equals("byte") || falseAltStr.equals("java.lang.Byte");
boolean trueIsShort = trueAltStr.equals("short") || trueAltStr.equals("java.lang.Short");
if (trueIsByte && falseIsShort || falseIsByte && trueIsShort) {
if (condition.hasConstantBooleanValue() && trueAlt.hasConstantIntValue() && falseAlt.hasConstantIntValue()) {
long intVal = condition.getConstantBooleanValue() ? trueAlt.getConstantIntValue()
|
|: falseAlt.getConstantIntValue();
|
|return new ConstantIntValue(null, JavaPrimitiveType.getShort(), intVal);
|
|}
|
|return new ValueEntity(JavaPrimitiveType.getShort());
|
|}
|
|// If one of the types is byte/short/char (possibly boxed) and the other is of type int, and is a constant
|
|// whose value fits in the first type, then the result is of the first type (unboxed).
|
|JavaType trueUnboxed = unBox(trueAltType);
|
|boolean trueIsSmallInt = trueUnboxed.typeIs(JavaType.JT_BYTE) || trueUnboxed.typeIs(JavaType.JT_SHORT)
|
||| trueUnboxed.typeIs(JavaType.JT_CHAR);
|
|if (trueIsSmallInt && falseAltType.typeIs(JavaType.JT_INT) && falseAlt.hasConstantIntValue()) {
|
|long fval = falseAlt.getConstantIntValue();
|
|if (doesValueFitIntType(fval, trueAltType)) {
|
|if (trueAlt.hasConstantIntValue() && condition.hasConstantBooleanValue()) {
|
|long val = condition.getConstantBooleanValue() ? trueAlt.getConstantIntValue()
|
|: falseAlt.getConstantIntValue();
|
|return new ConstantIntValue(null, trueAltType, val);
|
|}
|
|return new ValueEntity(trueUnboxed);
|
|}
|
|}
|
|JavaType falseUnboxed = unBox(falseAltType);
|
|boolean falseIsSmallInt = falseUnboxed.typeIs(JavaType.JT_BYTE) || falseUnboxed.typeIs(JavaType.JT_SHORT)
|
||| falseUnboxed.typeIs(JavaType.JT_CHAR);
|
|if (falseIsSmallInt && trueAltType.typeIs(JavaType.JT_INT) && trueAlt.hasConstantIntValue()) {
|
|long fval = trueAlt.getConstantIntValue();
|
|if (doesValueFitIntType(fval, falseAltType)) {
|
|if (falseAlt.hasConstantIntValue() && condition.hasConstantBooleanValue()) {
|
|long val = condition.getConstantBooleanValue() ? trueAlt.getConstantIntValue()
|
|: falseAlt.getConstantIntValue();
|
|return new ConstantIntValue(null, falseUnboxed, val);
|
|}
|
|return new ValueEntity(falseUnboxed);
|
|}
|
|}
|
|// Binary numeric promotion?
|
|if (trueUnboxed.isNumeric() && falseUnboxed.isNumeric()) {
|
|JavaType rtype = binaryNumericPromotion(trueUnboxed, falseUnboxed);
|
|if (condition.hasConstantBooleanValue() && ValueEntity.isConstant(trueAlt) && ValueEntity.isConstant(falseAlt)) {
|
|ValueEntity relevantAlt = condition.getConstantBooleanValue() ? trueAlt : falseAlt;
|
|if (rtype.typeIs(JavaType.JT_DOUBLE) || rtype.typeIs(JavaType.JT_FLOAT)) {
|
|double val;
|
|if (relevantAlt.getType().typeIs(JavaType.JT_DOUBLE) || relevantAlt.getType().typeIs(JavaType.JT_FLOAT)) {
|
|val = relevantAlt.getConstantFloatValue();
|
|}
|
|else {
|
|// the relevant alternative is an integer promoted to a float
|
|val = relevantAlt.getConstantIntValue();
|
|}
|
|return new ConstantFloatValue(rtype, val);
|
|}
|
|long val = condition.getConstantBooleanValue() ? trueAlt.getConstantIntValue()
|
|: falseAlt.getConstantIntValue();
|
|return new ConstantIntValue(null, rtype, val);
|
|}
|
|return new ValueEntity(rtype);
|
|}
|
|// No - reference types.
|
|GenTypeSolid trueBoxed = boxType(trueAltType);
|
|GenTypeSolid falseBoxed = boxType(falseAltType);
|
|GenTypeSolid rtype = GenTypeSolid.lub(new GenTypeSolid[] {trueBoxed, falseBoxed
|
|});
|
|return new ValueEntity(rtype.getCapture());
|
|}
|
|/**
| binary numeric promotion, as defined by JLS section 5.6.2. Both
| operands must be (possibly boxed) numeric types.
|
public static JavaType binaryNumericPromotion(JavaType a, JavaType b)
{
JavaType ua = unBox(a);
JavaType ub = unBox(b);
if (ua.typeIs(JavaType.JT_DOUBLE) || ub.typeIs(JavaType.JT_DOUBLE))
return JavaPrimitiveType.getDouble();
if (ua.typeIs(JavaType.JT_FLOAT) || ub.typeIs(JavaType.JT_FLOAT))
return JavaPrimitiveType.getFloat();
if (ua.typeIs(JavaType.JT_LONG) || ub.typeIs(JavaType.JT_LONG))
return JavaPrimitiveType.getLong();
if (ua.isNumeric() && ub.isNumeric()) {
return JavaPrimitiveType.getInt();
}
return null;
}
| Unary numeric promotion, as defined by JLS 3rd ed. section 5.6.1
| Return is null if argument type is not convertible to a numeric type.
|
public static JavaType unaryNumericPromotion(JavaType a)
{
JavaType ua = unBox(a);
if (ua.typeIs(JavaType.JT_DOUBLE))
return JavaPrimitiveType.getDouble();
if (ua.typeIs(JavaType.JT_FLOAT))
return JavaPrimitiveType.getFloat();
if (ua.typeIs(JavaType.JT_LONG))
return JavaPrimitiveType.getLong();
if (ua.isNumeric()) {
return JavaPrimitiveType.getInt();
}
return null;
}
| Check whether a particular method is callable with particular
| parameters. If so return information about how specific the call is.
| If the parameters cannot be applied to this method, return null.
|
| @param targetType The type of object/class to which the method is
| being applied
| @param targs The explicitly specified type arguments used in the
| invocation of a generic method (list of GenTypeClass)
| @param m The method to check
| @param args The types of the arguments supplied to the method
| @return A record with information about the method call
| @throws RecognitionException
|
private static MethodCallDesc isMethodApplicable(GenTypeClass targetType, List<GenTypeParameter> targs, MethodReflective m, JavaType [] args)
{
boolean methodIsVarargs = m.isVarArgs();
MethodCallDesc rdesc = null;
rdesc = isMethodApplicable(targetType, targs, m, args, false);
if (rdesc == null && methodIsVarargs) {
rdesc = isMethodApplicable(targetType, targs, m, args, true);
}
return rdesc;
}
| Check whether a particular method is callable with particular
| parameters. If so return information about how specific the call is.
| If the parameters cannot be applied to this method, return null.<p>
|
| Normally this is called by the other variant of this method, which
| does not take the varargs parameter.
|
| @param targetType The type of object/class to which the method is
| being applied
| @param targs The explicitly specified type parameters used in the
| invocation of a generic method (list of GenTypeClass)
| @param m The method to check
| @param args The types of the arguments supplied to the method
| @param varargs Whether to expand vararg parameters
| @return A record with information about the method call
| @throws RecognitionException
|
private static MethodCallDesc isMethodApplicable(GenTypeClass targetType,
List<GenTypeParameter> targs, MethodReflective m, JavaType [] args, boolean varargs)
{
boolean rawTarget = targetType.isRaw();
boolean boxingRequired = false;
List<JavaType> mparams = m.getParamTypes();
if (varargs) {
if (mparams.size() > args.length + 1)
return null;
GenTypeSolid lastArgType = mparams.get(mparams.size() - 1).asSolid();
JavaType vaType = lastArgType.getArrayComponent();
List<JavaType> expandedParams = new ArrayList<JavaType>(args.length);
expandedParams.addAll(mparams);
expandedParams.remove(expandedParams.size() - 1);
for (int i = mparams.size() - 1; i < args.length; i++) {
expandedParams.add(vaType);
}
mparams = expandedParams;
}
else {
if (mparams.size() != args.length)
return null;
}
List<GenTypeDeclTpar> tparams = Collections.emptyList();
if ((! rawTarget) || m.isStatic()) {
tparams = m.getTparTypes();
tparams = (tparams != null) ? tparams : Collections.<GenTypeDeclTpar>emptyList();
}
if (! targs.isEmpty() && ! tparams.isEmpty() && targs.size() != tparams.size())
return null;
Map<String,GenTypeParameter> tparMap;
if (rawTarget) {
if (m.isStatic()) {
tparMap = new HashMap<String,GenTypeParameter>();
}
else {
tparMap = null;
}
}
else {
tparMap = targetType.getMap();
}
if (! tparams.isEmpty() && targs.isEmpty()) {
for (Iterator<GenTypeDeclTpar> i = tparams.iterator(); i.hasNext(); ) {
GenTypeDeclTpar tpar = i.next();
tparMap.put(tpar.getTparName(), tpar);
}
Map<String,Set<GenTypeSolid>> tlbConstraints = new HashMap<String,Set<GenTypeSolid>>();
Map<String,GenTypeSolid> teqConstraints = new HashMap<String,GenTypeSolid>();
Map<String,GenTypeSolid> tubConstraints = new HashMap<String,GenTypeSolid>();
for (int i = 0; i < mparams.size(); i++) {
if (mparams.get(i).isPrimitive()) {
continue;
}
GenTypeSolid mparam = (GenTypeSolid) mparams.get(i);
mparam = mparam.mapTparsToTypes(tparMap).asType().asSolid();
processAtoFConstraint(args[i], mparam, tlbConstraints, teqConstraints, tubConstraints);
}
targs = new ArrayList<GenTypeParameter>();
Iterator<GenTypeDeclTpar> i = tparams.iterator();
while (i.hasNext()){
GenTypeDeclTpar fTpar = i.next();
String tparName = fTpar.getTparName();
GenTypeSolid eqConstraint = teqConstraints.get(tparName);
if (eqConstraint == null) {
Set<GenTypeSolid> lbConstraintSet = tlbConstraints.get(tparName);
if (lbConstraintSet != null) {
GenTypeSolid [] lbounds = lbConstraintSet.toArray(new GenTypeSolid[lbConstraintSet.size()]);
eqConstraint = GenTypeSolid.lub(lbounds);
}
else {
eqConstraint = tubConstraints.get(tparName);
if (eqConstraint == null) {
eqConstraint = fTpar.getBound();
}
}
}
targs.add(eqConstraint);
tparMap.put(tparName, eqConstraint);
}
}
else {
Iterator<GenTypeDeclTpar> formalI = tparams.iterator();
Iterator<GenTypeParameter> actualI = targs.iterator();
while (formalI.hasNext()){
GenTypeDeclTpar formalTpar = formalI.next();
GenTypeSolid argTpar = (GenTypeSolid) actualI.next();
GenTypeSolid [] formalUbounds = formalTpar.upperBounds();
for (int i = 0; i < formalUbounds.length; i++) {
formalUbounds[i] = (GenTypeSolid) formalUbounds[i].mapTparsToTypes(tparMap);
if (formalUbounds[i].isAssignableFrom(argTpar))
break;
if (i == formalUbounds.length - 1)
return null;
}
tparMap.put(formalTpar.getTparName(), argTpar);
}
}
for (int i = 0; i < args.length; i++) {
JavaType formalArg = mparams.get(i);
JavaType givenParam = args[i];
formalArg = formalArg.mapTparsToTypes(tparMap).getUpperBound();
if (! formalArg.isAssignableFrom(givenParam)) {
if (! formalArg.isAssignableFrom(boxType(givenParam))) {
if (! formalArg.isAssignableFrom(unBox(givenParam))) {
return null;
}
}
boxingRequired = true;
}
}
JavaType rType = m.getReturnType().mapTparsToTypes(tparMap).asType().getCapture();
return new MethodCallDesc(m, mparams, varargs, boxingRequired, rType);
}
| Process a type inference constraint of the form "A is convertible to F".
|* Note F must be a valid formal parameter: it can't be a wildcard with multiple
* bounds or an intersection type.
*
* @param a The argument type
* @param f The formal parameter type
* @param tlbConstraints lower bound constraints (a Map to Set of GenTypeSolid)
| @param teqConstraints equality constraints (a Map to GenTypeSolid)
|
private static void processAtoFConstraint(JavaType a, GenTypeSolid f,
Map<String,Set<GenTypeSolid>> tlbConstraints,
Map<String,GenTypeSolid> teqConstraints,
Map<String,GenTypeSolid> tubConstraints)
{
a = boxType(a);
if (a == null) {
return;
}
if (f instanceof GenTypeTpar) {
GenTypeTpar t = (GenTypeTpar) f;
Set<GenTypeSolid> constraintsSet = tlbConstraints.get(t.getTparName());
if (constraintsSet == null) {
constraintsSet = new HashSet<GenTypeSolid>();
tlbConstraints.put(t.getTparName(), constraintsSet);
}
constraintsSet.add(a.asSolid());
}
else if (f.getArrayComponent() != null) {
if (a.getArrayComponent() != null) {
if (f.getArrayComponent() instanceof GenTypeSolid) {
a = a.getArrayComponent();
f = (GenTypeSolid) f.getArrayComponent();
processAtoFConstraint(a, f, tlbConstraints, teqConstraints, tubConstraints);
}
}
}
else {
GenTypeClass cf = f.asClass();
Map<String,GenTypeParameter> fMap = cf.getMap();
if (fMap != null && a.asSolid() != null) {
GenTypeClass [] asts = a.asSolid().getReferenceSupertypes();
for (int i = 0; i < asts.length; i++) {
try {
GenTypeClass aMapped = asts[i].mapToSuper(cf.classloaderName());
Map<String,GenTypeParameter> aMap = aMapped.getMap();
if (aMap != null) {
Iterator<String> j = fMap.keySet().iterator();
while (j.hasNext()){
String tpName = j.next();
GenTypeParameter fPar = fMap.get(tpName);
GenTypeParameter aPar = aMap.get(tpName);
processAtoFtpar(aPar, fPar, tlbConstraints, teqConstraints, tubConstraints);
}
}
}
catch (BadInheritanceChainException bice) {
}
}
}
}
return;
}
| Process type parameters from a type inference constraint A convertible-to F.
|
private static void processAtoFtpar(GenTypeParameter aPar, GenTypeParameter fPar,
Map<String,Set<GenTypeSolid>> tlbConstraints, Map<String,GenTypeSolid> teqConstraints,
Map<String,GenTypeSolid> tubConstraints)
{
if (fPar instanceof GenTypeSolid) {
if (aPar instanceof GenTypeSolid) {
processAeqFConstraint((GenTypeSolid) aPar, (GenTypeSolid) fPar, tlbConstraints, teqConstraints);
}
}
else {
GenTypeSolid flbound = fPar.getLowerBound();
if (flbound != null) {
GenTypeSolid albound = aPar.getLowerBound();
if (albound != null) {
processFtoAConstraint(albound, flbound, tlbConstraints, teqConstraints, tubConstraints);
}
}
else {
GenTypeSolid [] fubounds = fPar.getUpperBound().asSolid().getIntersectionTypes();
GenTypeSolid [] aubounds = aPar.getUpperBound().asSolid().getIntersectionTypes();
if (fubounds.length > 0 && aubounds.length > 0) {
processAtoFConstraint(IntersectionType.getIntersection(aubounds), fubounds[0],
tlbConstraints, teqConstraints, tubConstraints);
}
}
}
}
| Process a type inference constraint of the form "A is equal to F".
|*/
private static void processAeqFConstraint(GenTypeSolid a, GenTypeSolid f,
Map<String,Set<GenTypeSolid>> tlbConstraints, Map<String,GenTypeSolid> teqConstraints)
{
if (f instanceof GenTypeTpar) {
|
|// The constraint T == A is implied.
|
|GenTypeTpar t = (GenTypeTpar) f;
|
|teqConstraints.put(t.getTparName(), a);
|
|}
|
|else if (f.getArrayComponent() instanceof GenTypeSolid) {
|
|// "If F = U[] ... if A is an array type V[], or a type variable with an
// upper bound that is an array type V[]..."
GenTypeSolid [] asts;
if (a instanceof GenTypeDeclTpar)
asts = ((GenTypeDeclTpar) a).upperBounds();
else{ asts = new GenTypeSolid[] {a};
}
|
|for (int i = 0; i < asts.length; i++) {
|
|JavaType act = asts[i].getArrayComponent();
|
|if (act instanceof GenTypeSolid) {
|
|processAeqFConstraint((GenTypeSolid) act, (GenTypeSolid) f.getArrayComponent(), tlbConstraints, teqConstraints);
|
|}
|
|}
|
|}
|
|else {
|
|GenTypeClass cf = f.asClass();
|
|GenTypeClass af = a.asClass();
|
|if (af != null && cf != null) {
|
|if (cf.classloaderName().equals(af.classloaderName())) {
|
|Map<String,GenTypeParameter> fMap = cf.getMap();
|
|Map<String,GenTypeParameter> aMap = af.getMap();
|
|if (fMap != null && aMap != null) {
|
|Iterator<String> j = fMap.keySet().iterator();
|
|while (j.hasNext()){
|
|String tpName = j.next();
|
|GenTypeParameter fPar = fMap.get(tpName);
|
|GenTypeParameter aPar = aMap.get(tpName);
|
|processAeqFtpar(aPar, fPar, tlbConstraints, teqConstraints);
|
|}
|
|}
|
|}
|
|}
|
|}
|
|}
|
|private static GenTypeSolid getSolidUpperBound(GenTypeParameter tpar)
|
|{
|
|JavaType ubound = tpar.getUpperBound();
|
|if (ubound != null) {
|
|return ubound.asSolid();
|
|}
|
|return null;
|
|}
|
|/**
| Process type parameters from a type inference constraint A equal-to F.
|
private static void processAeqFtpar(GenTypeParameter aPar, GenTypeParameter fPar,
Map<String,Set<GenTypeSolid>> tlbConstraints, Map<String,GenTypeSolid> teqConstraints)
{
if (aPar instanceof GenTypeSolid && fPar instanceof GenTypeSolid) {
processAeqFConstraint((GenTypeSolid) aPar, (GenTypeSolid) fPar, tlbConstraints, teqConstraints);
}
p.public else if(aPar instanceof GenTypeWildcard && fPar instanceof GenTypeWildcard)
{
GenTypeSolid flBound = fPar.getLowerBound();
GenTypeSolid fuBound = getSolidUpperBound(fPar);
if (flBound != null) {
GenTypeSolid alBound = aPar.getLowerBound();
if (alBound != null)
processAeqFConstraint(alBound, flBound, tlbConstraints, teqConstraints);
}
else if (fuBound != null) {
GenTypeSolid auBound = getSolidUpperBound(aPar);
if (auBound != null) {
processAeqFConstraint(auBound, fuBound, tlbConstraints, teqConstraints);
}
}
}
}
| Process a type inference constraint of the form "F is convertible to A".
|*/
private static void processFtoAConstraint(GenTypeSolid a, GenTypeSolid f,
Map<String,Set<GenTypeSolid>> tlbConstraints, Map<String,GenTypeSolid> teqConstraints,
Map<String,GenTypeSolid> tubConstraints)
|
|{
|
|// If F = T, then T <: A is implied:
|
|if (f instanceof GenTypeTpar) {
|
|GenTypeTpar ftpar = (GenTypeTpar) f;
|
|GenTypeSolid ubcons = tubConstraints.get(ftpar.getTparName());
|
|if (ubcons == null) {
|
|ubcons = a;
|
|}
|
|else {
|
|ubcons = IntersectionType.getIntersection(new GenTypeSolid[] {ubcons, a
|
|});
|
|}
|
|tubConstraints.put(ftpar.getTparName(), ubcons);
|
|}
|
|// If F = U[] ...
|
|else if (f.getArrayComponent() instanceof GenTypeSolid) {
|
|// "If F = U[] ... if A is an array type V[], or a type variable with an
// upper bound that is an array type V[]..."
GenTypeSolid [] asts;
if (a instanceof GenTypeDeclTpar) {
asts = ((GenTypeDeclTpar) a).upperBounds();
}
else {
asts = new GenTypeSolid[] {a
|
|};
|
|}
|
|for (int i = 0; i < asts.length; i++) {
|
|JavaType act = asts[i].getArrayComponent().asSolid();
|
|if (act != null) {
|
|processFtoAConstraint((GenTypeSolid) act, (GenTypeSolid) f.getArrayComponent(),
|
|tlbConstraints, teqConstraints, tubConstraints);
|
|}
|
|}
|
|}
|
|// If F has form G<..,..>
|
|else if (f.asClass() != null && a.asClass() != null) {
|
|GenTypeClass cf = f.asClass();
|
|GenTypeClass af = a.asClass();
|
|try {
|
|GenTypeClass fMapped = cf.mapToSuper(af.classloaderName());
|
|Map<String,GenTypeParameter> aMap = af.getMap();
|
|Map<String,GenTypeParameter> fMap = fMapped.getMap();
|
|if (aMap != null && fMap != null) {
|
|Iterator<String> j = fMap.keySet().iterator();
|
|while (j.hasNext()){
|
|String tpName = j.next();
|
|GenTypeParameter fPar = fMap.get(tpName);
|
|GenTypeParameter aPar = aMap.get(tpName);
|
|processFtoAtpar(aPar, fPar, tlbConstraints, teqConstraints, tubConstraints);
|
|}
|
|}
|
|}
|
|catch (BadInheritanceChainException bice) {
|
|}
|
|}
|
|}
|
|/**
| Process type parameters from a type inference constraint F convertible-to A.
|
private static void processFtoAtpar(GenTypeParameter aPar, GenTypeParameter fPar,
Map<String,Set<GenTypeSolid>> tlbConstraints, Map<String,GenTypeSolid> teqConstraints,
Map<String,GenTypeSolid> tubConstraints)
{
if (fPar instanceof GenTypeSolid) {
if (aPar instanceof GenTypeSolid) {
processAeqFConstraint((GenTypeSolid) aPar, (GenTypeSolid) fPar, tlbConstraints, teqConstraints);
}
else {
GenTypeSolid alBound = aPar.getLowerBound();
if (alBound != null) {
processAtoFConstraint(alBound, (GenTypeSolid) fPar, tlbConstraints, teqConstraints, tubConstraints);
}
else {
GenTypeSolid auBound = aPar.getUpperBound().asSolid();
if (auBound != null) {
processFtoAConstraint(auBound, fPar.asSolid(), tlbConstraints, teqConstraints, tubConstraints);
}
}
}
}
else {
GenTypeSolid flBound = fPar.getLowerBound();
if (flBound != null) {
if (aPar instanceof GenTypeWildcard) {
GenTypeSolid alBound = aPar.getLowerBound();
if (alBound != null) {
processAtoFConstraint(alBound, flBound, tlbConstraints, teqConstraints, tubConstraints);
}
}
else {
GenTypeSolid fuBound = fPar.getUpperBound().asSolid();
GenTypeSolid auBound = aPar.getUpperBound().asSolid();
if (fuBound != null && auBound != null) {
GenTypeSolid [] fuBounds = fuBound.getIntersectionTypes();
GenTypeSolid [] auBounds = auBound.getIntersectionTypes();
processFtoAConstraint(auBounds[0], fuBounds[0], tlbConstraints, teqConstraints, tubConstraints);
}
}
}
}
}
| Get the candidate list of methods with the given name and argument types. The returned
| list will be the maximally specific methods (as defined by the JLS 15.12.2.5). The
| methods returned in the list might not be <i>appropriate</i> as according to JLS 15.12.3.
|
| @param methodName The name of the method
| @param targetType The type to search for declarations of this method (must have had
| capture conversion applied, if necessary)
| @param argumentTypes The types of the arguments supplied in the method invocation
| @param typeArgs The type arguments, if any, supplied in the method invocation
| @return an ArrayList of MethodCallDesc - the list of candidate methods
| @throws RecognitionException
|
public static ArrayList getSuitableMethods(String methodName,
GenTypeSolid targetType, JavaType [] argumentTypes, List<GenTypeParameter> typeArgs,
Reflective accessType)
{
ArrayList<MethodCallDesc> suitableMethods = new ArrayList<MethodCallDesc>();
Stack<GenTypeSolid> targetTypes = new Stack<GenTypeSolid>();
targetTypes.push(targetType);
Set<GenTypeSolid> doneTypes = new HashSet<GenTypeSolid>();
while (! targetTypes.isEmpty()){
GenTypeSolid topType = targetTypes.pop();
GenTypeClass targetClass = topType.asClass();
if (targetClass == null) {
GenTypeSolid [] bounds = topType.getUpperBounds();
for (int i = 0; i < bounds.length; i++) {
if (doneTypes.add(bounds[i])) {
targetTypes.push(bounds[i]);
}
}
continue;
}
Reflective ref = targetClass.getReflective();
List<GenTypeClass> supers = ref.getSuperTypes();
Map<String,GenTypeParameter> tparMap = targetClass.getMap();
for (GenTypeClass superType : supers) {
GenTypeClass mapped = superType.mapTparsToTypes(tparMap);
if (doneTypes.add(mapped)) {
targetTypes.push(mapped);
}
}
Map<String,Set<MethodReflective>> methodMap = targetClass.getReflective()
.getDeclaredMethods();
Set<MethodReflective> methods = methodMap.get(methodName);
if (methods == null) {
continue;
}
for (MethodReflective method : methods) {
if (!JavaUtils.checkMemberAccess(method.getDeclaringType(), targetType, accessType,
method.getModifiers(), false)) {
continue;
}
MethodCallDesc mcd = isMethodApplicable(targetClass, typeArgs, method, argumentTypes);
if (mcd != null) {
boolean replaced = false;
for (int j = 0; j < suitableMethods.size(); j++) {
MethodCallDesc mc = suitableMethods.get(j);
int compare = mcd.compareSpecificity(mc);
if (compare == 1) {
suitableMethods.remove(j);
j--;
}
else if (compare == -1) {
replaced = true;
break;
}
}
if (! replaced)
suitableMethods.add(mcd);
}
}
}
return suitableMethods;
}
| Unbox a type, if it is a class type which represents a primitive type
| in object form (eg. java.lang.Integer).<p>
|
| Other class types are returned unchanged.<p>
|
| To determine whether unboxing occurred, compare the result with the
| object which was passed in. (The same object will be returned if no
| unboxing took place).
|
| @param b The type to unbox
| @return The unboxed type
|
public static JavaType unBox(JavaType b)
{
GenTypeClass c = b.asClass();
if (c != null) {
String cName = c.classloaderName();
if (cName.equals("java.lang.Integer")) {
return JavaPrimitiveType.getInt();
}
else if (cName.equals("java.lang.Long")) {
return JavaPrimitiveType.getLong();
}
else if (cName.equals("java.lang.Short")) {
return JavaPrimitiveType.getShort();
}
else if (cName.equals("java.lang.Byte")) {
return JavaPrimitiveType.getByte();
}
else if (cName.equals("java.lang.Character")) {
return JavaPrimitiveType.getChar();
}
else if (cName.equals("java.lang.Float")) {
return JavaPrimitiveType.getFloat();
}
else if (cName.equals("java.lang.Double")) {
return JavaPrimitiveType.getDouble();
}
else if (cName.equals("java.lang.Boolean")) {
return JavaPrimitiveType.getBoolean();
}
}
return b;
}
| Box a type, if it is a primitive type such as "int".<p>
|*
* Other types are returned unchanged, however void/null types return null.<p>
*
* @param b The type to box
* @return The boxed type
*/
private static GenTypeSolid boxType(JavaType u)
{
|
|if (u.isPrimitive()) {
|
|if (u.typeIs(JavaType.JT_INT)) {
|
|return new GenTypeClass(new JavaReflective(Integer.class));
|
|}
|
|else if (u.typeIs(JavaType.JT_LONG)) {
|
|return new GenTypeClass(new JavaReflective(Long.class));
|
|}
|
|else if (u.typeIs(JavaType.JT_SHORT)) {
|
|return new GenTypeClass(new JavaReflective(Short.class));
|
|}
|
|else if (u.typeIs(JavaType.JT_BYTE)) {
|
|return new GenTypeClass(new JavaReflective(Byte.class));
|
|}
|
|else if (u.typeIs(JavaType.JT_CHAR)) {
|
|return new GenTypeClass(new JavaReflective(Character.class));
|
|}
|
|else if (u.typeIs(JavaType.JT_FLOAT)) {
|
|return new GenTypeClass(new JavaReflective(Float.class));
|
|}
|
|else if (u.typeIs(JavaType.JT_DOUBLE)) {
|
|return new GenTypeClass(new JavaReflective(Double.class));
|
|}
|
|else if (u.typeIs(JavaType.JT_BOOLEAN)) {
|
|return new GenTypeClass(new JavaReflective(Boolean.class));
|
|}
|
|}
|
|return u.asSolid();
|
|}
|
|/**
| Conditionally box a type. The type is only boxed if the boolean flag
| passed in the second parameter is true.<p>
|
| This is a helper method to improve readability.<p>
|
| @see TextAnalyzer#boxType(JavaType)
|
| @param u The type to box
| @param box The flag indicating whether boxing should occur
| @return
|
| A simple structure to hold various information about a method call.
|
| @author Davin McCall
|
public static class MethodCallDesc
{
public MethodReflective method;
public List<JavaType> argTypes;
public boolean vararg;
public boolean autoboxing;
public JavaType retType;
| Constructor for MethodCallDesc.
|
| @param m The method being called
| @param argTypes The effective types of the arguments, as an
| ordered list
| @param vararg Whether the method is being called as a vararg
| method
| @param autoboxing Whether autoboxing is required for parameters
| @param retType The effective return type
|
public MethodCallDesc(MethodReflective m, List<JavaType> argTypes, boolean vararg, boolean autoboxing, JavaType retType)
{
this.method = m;
this.argTypes = argTypes;
this.vararg = vararg;
this.autoboxing = autoboxing;
this.retType = retType;
}
| Find out which (if any) method call is strictly more specific than the
| other. Both calls must be valid calls to the same method with the same
| number of parameters.
|
| @param other The method to compare with
| @return 1 if this method is more specific;
| -1 if the other method is more specific;
| 0 if neither method is more specific than the other.
|
| See JLS 15.12.2.5 (by "more specific", we mean what the JLS calls
|
* "strictly more specific", more or less. We also take arity into account.
* This method effectively combines and performs phases 1-3 as specified by
* JLS 15.12.2.2 - 15.12.2.4
*/
public int compareSpecificity(MethodCallDesc other)
|
|
{
|
|
if (other.vararg && ! vararg) {
|
|
return 1; // we are more specific
|
|
}
|
|
if (! other.vararg && vararg) {
|
|
return -1; // we are less specific
|
|
}
|
|
// I am reasonably sure this gives the same result as the algorithm
|
|
// described in the JLS section 15.12.2.5, and it has the advantage
|
|
// of being a great deal simpler.
|
|
Iterator<JavaType> i = argTypes.iterator();
|
|
Iterator<JavaType> j = other.argTypes.iterator();
|
|
int upCount = 0;
|
|
int downCount = 0;
|
|
while (i.hasNext()){
|
|
JavaType myArg = i.next();
|
|
JavaType otherArg = j.next();
|
|
if (myArg.isAssignableFrom(otherArg)) {
|
|
if (! otherArg.isAssignableFrom(myArg))
|
|
upCount++;
|
|
}
|
|
else if (otherArg.isAssignableFrom(myArg)) {
|
|
downCount++;
|
|
}
|
|
}
|
|
if (upCount > 0 && downCount == 0) {
|
|
return -1; // other is more specific
|
|
}
|
|
else if (downCount > 0 && upCount == 0) {
|
|
return 1; // other is less specific
|
|
}
|
|
return 0;
|
|
}
|
|
}
|
|
}