/*
* Copyright (c) 2007 BUSINESS OBJECTS SOFTWARE LIMITED
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of Business Objects nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/*
* ForeignFunctionChecker.java
* Created: July 9, 2002
* By: Bo Ilic
*/
package org.openquark.cal.compiler;
import java.io.IOException;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.EmptyVisitor;
import org.openquark.cal.internal.compiler.ForeignEntityResolver;
import org.openquark.cal.internal.javamodel.JavaTypeName;
import org.openquark.cal.module.Cal.Core.CAL_Prelude;
import org.openquark.cal.services.IdentifierUtils;
/**
* Used for type checking the foreign function declarations within a module.
* Creation date: (July 9, 2002).
* @author Bo Ilic
*/
final class ForeignFunctionChecker {
/**
* The default name to assign to arguments corresponding to object references.
* eg. the first arg of a foreign function representing an instance method.
*/
private static final String DEFAULT_OBJECT_REF_ARG_NAME = "objectRef"; // "instance" is a keyword in cal.
/**
* (String->URL) Cache -- used by getClassReader().
* Map from fully-qualified class name to the URL calculated to correspond to the class file data for that class.
*
* Note: this is probably ok as a static synchronized map.
*/
private final Map<String, URL> classNameToURLCacheMap = new HashMap<String, URL>();
private final CALCompiler compiler;
private final ModuleTypeInfo currentModuleTypeInfo;
/**
* Constructor ForeignFunctionChecker.
* @param compiler
* @param currentModuleTypeInfo
*/
ForeignFunctionChecker(final CALCompiler compiler, final ModuleTypeInfo currentModuleTypeInfo) {
if (compiler == null || currentModuleTypeInfo == null) {
throw new NullPointerException();
}
this.compiler = compiler;
this.currentModuleTypeInfo = currentModuleTypeInfo;
}
/**
* Adds the foreign function declarations to the environment.
* @param functionEnv
* @param foreignFunctionDefnNodes
*/
Env calculateForeignFunctionTypes(Env functionEnv, final List<ParseTreeNode> foreignFunctionDefnNodes) throws UnableToResolveForeignEntityException {
for (final ParseTreeNode foreignFunctionNode : foreignFunctionDefnNodes) {
foreignFunctionNode.verifyType(CALTreeParserTokenTypes.FOREIGN_FUNCTION_DECLARATION);
final Function function = calculateForeignEntity(foreignFunctionNode);
functionEnv = Env.extend(functionEnv, function);
}
return functionEnv;
}
/**
* Analyzes an individual foreign function declaration.
* Creation date: (July 9, 2002)
* @param foreignFunctionDeclarationNode
*/
private Function calculateForeignEntity (final ParseTreeNode foreignFunctionDeclarationNode) throws UnableToResolveForeignEntityException {
foreignFunctionDeclarationNode.verifyType(CALTreeParserTokenTypes.FOREIGN_FUNCTION_DECLARATION);
final ParseTreeNode optionalCALDocNode = foreignFunctionDeclarationNode.firstChild();
optionalCALDocNode.verifyType(CALTreeParserTokenTypes.OPTIONAL_CALDOC_COMMENT);
final ParseTreeNode externalNameNode = optionalCALDocNode.nextSibling();
externalNameNode.verifyType(CALTreeParserTokenTypes.STRING_LITERAL);
final String externalName = StringEncoder.unencodeString(externalNameNode.getText ());
SourceRange errorRange = externalNameNode.getSourceRange();
final ModuleName currentModuleName = currentModuleTypeInfo.getModuleName();
if (errorRange == null) {
// Default to the current module if no source range available.
errorRange = new SourceRange(currentModuleName.toSourceText());
}
final ParseTreeNode accessModifierNode = externalNameNode.nextSibling();
final Scope scope = CALTypeChecker.getScopeModifier(accessModifierNode);
final ParseTreeNode typeDeclarationNode = accessModifierNode.nextSibling();
final TypeExpr typeExpr = compiler.getTypeChecker().calculateDeclaredType(typeDeclarationNode);
final Class<?>[] signatureTypes = calculateForeignTypeSignature(typeExpr, errorRange);
final ParseTreeNode functionNameNode = typeDeclarationNode.firstChild();
functionNameNode.verifyType(CALTreeParserTokenTypes.VAR_ID);
final String functionName = functionNameNode.getText();
final ForeignFunctionInfo foreignFunctionInfo = makeForeignFunctionInfo (QualifiedName.make(currentModuleName, functionName), externalName, signatureTypes, errorRange);
// Get the argument names from the line number table if any.
final String[] namedArguments = getArgumentNames(foreignFunctionInfo, typeExpr);
final Function function = Function.makeTopLevelFunction(QualifiedName.make(currentModuleName, functionName), namedArguments, typeExpr, scope);
function.setForeignFunctionInfo(foreignFunctionInfo);
function.setTypeCheckingDone();
return function;
}
/**
* For the special JVM operators foreign function declarations (i.e. not corresponding to Java method, field or constructor invocations
* such as "lengthArray" etc) it is possible to come up with some reasonable argument names in most cases.
* @param foreignFunctionInfo
* @return String[]
*/
private String[] getArgumentNamesForNonInvocationJavaKinds(final ForeignFunctionInfo foreignFunctionInfo) throws UnableToResolveForeignEntityException {
//todoBI handle the other non-invocation Java Kinds
final ForeignFunctionInfo.JavaKind javaKind = foreignFunctionInfo.getJavaKind();
if (javaKind == ForeignFunctionInfo.JavaKind.NEW_ARRAY) {
final int nArgs = foreignFunctionInfo.getNArguments();
if (nArgs == 1) {
return new String[] {"size"};
} else {
final String[] args = new String[nArgs];
for (int i = 0; i < nArgs; ++i) {
args[i] = "size" + (i + 1);
}
return args;
}
} else if (javaKind == ForeignFunctionInfo.JavaKind.LENGTH_ARRAY) {
return new String[] {"array"};
} else if (javaKind == ForeignFunctionInfo.JavaKind.SUBSCRIPT_ARRAY) {
final int nArgs = foreignFunctionInfo.getNArguments();
if (nArgs == 2) {
return new String[] {"array", "index"};
} else {
final String[] args = new String[nArgs];
args[0] = "array";
for (int i = 1; i < nArgs; ++i) {
args[i] = "index" + i;
}
return args;
}
} else if (javaKind == ForeignFunctionInfo.JavaKind.UPDATE_ARRAY) {
final int nArgs = foreignFunctionInfo.getNArguments();
if (nArgs == 3) {
return new String[] {"array", "index", "newValue"};
} else {
final String[] args = new String[nArgs];
args[0] = "array";
args[nArgs - 1] = "newValue";
for (int i = 1; i < nArgs - 1; ++i) {
args[i] = "index" + i;
}
return args;
}
}
return null;
}
/**
* Get argument names for a foreign function.
* <p>
* For fields and instance methods, the first name in the returned array will be a generated name based on the name of the entity.
* <p>
* For methods and constructors, other arg names are calculated by visiting the bytecode for the foreign class using ASM,
* and using the names from the local variable table of the relevant method if any.
* If the foreign class does not contain this table (for instance, if it were not compiled with debug attributes),
* the returned array will be empty, except for the first argument for instance methods as mentioned above.
* <p>
* @param foreignFunctionInfo
* @param typeExpr the type of the entity.
*
* @return the arguments calculated for the foreign function object.
* null if none could be calculated.
* For Functions and Constructors, names will be calculated from the line number table of the relevant class' compiled debug info, if any.
* For non-static Fields and Methods, the name of the first argument will be generated based on a default name.
* Names can contain nulls for parameters which don't have the relevant debug info (eg. if the line number table is not present).
*/
private String[] getArgumentNames(final ForeignFunctionInfo foreignFunctionInfo, final TypeExpr typeExpr) throws UnableToResolveForeignEntityException {
if (!foreignFunctionInfo.getJavaKind().isInvocation()) {
return getArgumentNamesForNonInvocationJavaKinds(foreignFunctionInfo);
}
final AccessibleObject accessibleObject = ((ForeignFunctionInfo.Invocation)foreignFunctionInfo).getJavaProxy();
final String entityName = foreignFunctionInfo.getCalName().getUnqualifiedName();
if (accessibleObject instanceof Field) {
final Field field = (Field)accessibleObject;
// If an instance, the only argument is the object ref.
// Otherwise, no arguments.
final boolean isStatic = Modifier.isStatic(field.getModifiers());
if (isStatic) {
return null;
} else {
final String objectRefName = getObjectRefName(typeExpr);
return new String[]{objectRefName};
}
}
// ao is not a Field, so it should be a Constructor or a Method.
Class<?> classToRead;
final String methodName;
Class<?>[] parameterClasses;
Class<?> returnClass;
// Actually three possibilities - cannot both be true.
boolean isStaticMethod;
final boolean isConstructor;
if (accessibleObject instanceof Constructor) {
final Constructor<?> constructor = (Constructor<?>)accessibleObject;
classToRead = constructor.getDeclaringClass();
methodName = "<init>";
parameterClasses = constructor.getParameterTypes();
returnClass = Void.TYPE;
isStaticMethod = false;
isConstructor = true;
} else {
// Must be a method.
final Method method = (Method)accessibleObject;
classToRead = method.getDeclaringClass();
methodName = method.getName();
parameterClasses = method.getParameterTypes();
returnClass = method.getReturnType();
isStaticMethod = Modifier.isStatic(method.getModifiers());
isConstructor = false;
}
// Calculate the method descriptor.
// Note: if we upgrade to Java 5.0, this will need to be updated for formal type parameters (ie. for generics..).
final Type[] parameterTypes = new Type[parameterClasses.length];
for (int i = 0; i < parameterClasses.length; i++) {
parameterTypes[i] = Type.getType(parameterClasses[i]);
}
final Type returnType = Type.getType(returnClass);
final String methodDescriptor = Type.getMethodDescriptor(returnType, parameterTypes);
// Instantiate a class reader to read the bytecode of the class with the relevant method's name.
ClassReader classReader;
try {
classReader = getClassReader(classToRead.getName());
} catch (final IOException ioe) {
// Class not found.
return null;
}
// Start with an empty array for the arguments.
// Note that for an instance method, the first arg will be the object reference.
final String[] argumentNames = new String[parameterTypes.length + ((isStaticMethod || isConstructor) ? 0 : 1)];
// Create a class visitor which will create an appropriate method visitor when it encounters the desired constructor.
final ClassVisitor classVisitor = new EmptyVisitor() {
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
// Check the method name.
if (!name.equals(methodName)) {
return null;
}
// Check for the expected method descriptor.
if (!desc.equals(methodDescriptor)) {
return null;
}
// Found the desired method.
// Return a new method visitor.
return new EmptyVisitor() {
@Override
public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) {
// For constructors:
// Objects are created by calling <init> on an uninitialized class instance.
// The first arg is the instance, the rest of the args are the constructor args in order.
// ie. args are: (objectRef, arg0, arg1, ...).
// The object ref isn't an arg to the actual constructor, so shift all args left one.
if (isConstructor) {
index -= 1;
// The object ref.
if (index < 0) {
return;
}
}
// Check for a method variable.
if (index >= argumentNames.length) {
// Must be a local variable, not a method variable.
return;
}
// Set the name in the array.
argumentNames[index] = IdentifierUtils.makeIdentifierName(name);
}
};
}
};
// Visit the class.
final int flags = ClassReader.SKIP_FRAMES; // Don't skip debug info or code.
classReader.accept(classVisitor, flags);
// For an instance method, if there are var names, the name of var index 0 seems to come out as "this".
// This doesn't make much sense in the context of entities, so rename to a name based on the type if possible.
if (!(isStaticMethod || isConstructor)) {
final String objectRefName = getObjectRefName(typeExpr);
// Check that there isn't already an argument with the name we want to give it..
if (!entityName.equals(objectRefName) && !Arrays.asList(argumentNames).subList(1, argumentNames.length).contains(objectRefName)) {
argumentNames[0] = objectRefName;
} else {
// try again.
if (!entityName.equals(DEFAULT_OBJECT_REF_ARG_NAME) && !Arrays.asList(argumentNames).subList(1, argumentNames.length).contains(DEFAULT_OBJECT_REF_ARG_NAME)) {
argumentNames[0] = DEFAULT_OBJECT_REF_ARG_NAME;
} else {
// Oh well, just leave the argument as "this".
}
}
}
return argumentNames;
}
/**
* Get a ClassReader for the indicated class.
* @param name the fully-qualified name of the class. eg. "java.lang.String"
* @return a ClassReader for the class.
* @throws IOException if a problem occurs during creation of the ClassReader, eg. the class doesn't exist on the classpath.
*/
private ClassReader getClassReader(final String name) throws IOException {
// Check for an existing entry.
URL classURL = classNameToURLCacheMap.get(name);
if (classURL == null) {
// slashify and convert to a file name. eg. "java.lang.String" -> "/java/lang/String.class"
final String slashifiedFileName = name.replace('.','/') + ".class";
// Get the url.
classURL = currentModuleTypeInfo.getModule().getForeignClassLoader().getResource(slashifiedFileName);
if (classURL == null) {
throw new IOException("Could not find class named " + name + " on the classpath.");
}
// Add the cache entry.
classNameToURLCacheMap.put(name, classURL);
}
return new ClassReader(classURL.openStream());
}
/**
* Get the argument name for an object reference.
* @param typeExpr the type of the generated foreign function.
* @return the name for the object reference.
* This is calculated by getting the first type piece in the type expr, and returning its unqualified name if the
* type piece is a type constructor.
*/
private static String getObjectRefName(final TypeExpr typeExpr) {
final TypeExpr firstTypePiece = typeExpr.getTypePieces()[0];
if (firstTypePiece instanceof TypeConsApp) {
return IdentifierUtils.makeIdentifierName(((TypeConsApp)firstTypePiece).getName().getUnqualifiedName(), false);
}
return DEFAULT_OBJECT_REF_ARG_NAME;
}
/**
* Creates the ForeignFunctionInfo for the foreign function, which is the information necessary to call the foreign function
* at run-time. In the process, it does a bunch of checks to verify that the foreign function does indeed exist.
*
* Creation date: (June 28, 2002)
*
* @param calName QualifiedName name of the foreign function in CAL
* @param externalName String string describing the java entity called by the foreign function
* e.g. "static method java.lang.Character.isUpperCase"
* @param signatureTypes Class<?>[] the type signature, mapped to Java types. For example, if
* the foreign function has type Int->Boolean this is [Integer.TYPE, Boolean.TYPE]
* @param errorRange position in the source stream to attribute user errors found during this call
* @return ForeignFunctionInfo info needed to call the foreign function
*/
private ForeignFunctionInfo makeForeignFunctionInfo(
final QualifiedName calName,
final String externalName,
final Class<?>[] signatureTypes,
final SourceRange errorRange) {
//parse the externalName to determine what kind of foreign entity this CAL foreign function will map onto.
//valid patterns are:
//method externalUnqualifiedMethodName
//static method externalQualifiedMethodName
//field externalUnqualifiedFieldName
//static field externalQualifiedFieldName
//constructor externalQualifiedTypeName?
//cast
//instanceof externalQualifiedTypeName
//null
//isNull
//isNotNull
//class externalQualifiedTypeName
final StringTokenizer tokenizer = new StringTokenizer (externalName, " \t");
final int nTokens = tokenizer.countTokens();
ForeignFunctionInfo.JavaKind kind = null;
String token;
if (nTokens == 1) {
token = tokenizer.nextToken();
if (token.equals("constructor")) {
return makeForeignConstructor (calName, null, signatureTypes, errorRange);
} else if (token.equals("cast")) {
return makeForeignCast(calName, signatureTypes, errorRange);
} else if (token.equals("null")) {
return makeForeignNull(calName, signatureTypes, errorRange);
} else if (token.equals("isNull")) {
return makeForeignNullCheck(calName, signatureTypes, errorRange, true);
} else if (token.equals("isNotNull")) {
return makeForeignNullCheck(calName, signatureTypes, errorRange, false);
} else if (token.equals("newArray")) {
return makeForeignNewArray(calName, signatureTypes, errorRange);
} else if (token.equals("lengthArray")) {
return makeForeignLengthArray(calName, signatureTypes, errorRange);
} else if (token.equals("subscriptArray")) {
return makeForeignSubscriptArray(calName, signatureTypes, errorRange);
} else if (token.equals("updateArray")) {
return makeForeignUpdateArray(calName, signatureTypes, errorRange);
} else {
compiler.logMessage(new CompilerMessage(errorRange, new MessageKind.Error.InvalidExternalNameStringFormat(externalName)));
}
} else if (nTokens == 2) {
token = tokenizer.nextToken();
if (token.equals("method")) {
kind = ForeignFunctionInfo.JavaKind.METHOD;
} else if (token.equals("field")) {
kind = ForeignFunctionInfo.JavaKind.FIELD;
} else if (token.equals("constructor")) {
kind = ForeignFunctionInfo.JavaKind.CONSTRUCTOR;
} else if (token.equals("instanceof")) {
kind = ForeignFunctionInfo.JavaKind.INSTANCE_OF;
} else if (token.equals("class")) {
kind = ForeignFunctionInfo.JavaKind.CLASS_LITERAL;
} else {
// 'method', 'field', 'constructor', 'instanceof' or 'class' is expected rather than '{token}'.
compiler.logMessage(new CompilerMessage(errorRange, new MessageKind.Error.ExpectingMethodFieldOrConstructor(token)));
}
} else if (nTokens == 3) {
token = tokenizer.nextToken();
if (!token.equals("static")) {
// 'static' is expected rather than '{token}'.
compiler.logMessage(new CompilerMessage(errorRange, new MessageKind.Error.ExpectingStatic(token)));
}
token = tokenizer.nextToken ();
if (token.equals("method")) {
kind = ForeignFunctionInfo.JavaKind.STATIC_METHOD;
} else if (token.equals("field")) {
kind = ForeignFunctionInfo.JavaKind.STATIC_FIELD;
} else {
// 'method' or 'field' is expected rather than '{token}'.
compiler.logMessage(new CompilerMessage(errorRange, new MessageKind.Error.ExpectingMethodOrField(token)));
}
} else {
// Invalid external name string format "{externalName}". A valid example: "static method java.lang.isUpperCase".
compiler.logMessage(new CompilerMessage(errorRange, new MessageKind.Error.InvalidExternalNameStringFormat(externalName)));
}
final String javaName = tokenizer.nextToken();
if (kind.isMethod ()) {
return makeForeignMethod (calName, javaName, kind.isStatic(), signatureTypes, errorRange);
} else if (kind.isField ()) {
return makeForeignField (calName, javaName, kind.isStatic(), signatureTypes, errorRange);
} else if (kind.isConstructor ()) {
return makeForeignConstructor (calName, javaName, signatureTypes, errorRange);
} else if (kind.isInstanceOf()) {
return makeForeignInstanceOf (calName, javaName, signatureTypes, errorRange);
} else if (kind.isClassLiteral()) {
return makeForeignClassLiteral(calName, javaName, signatureTypes, errorRange);
}
throw new IllegalStateException();
}
/**
* Converts an array to a string by applying toString to all the elements.
* Handy for error messages.
* @param array array to convert to a string displaying all elements
* @return representation of the array as a string
*/
static String makeClassArrayString (final Class<?>[] array) {
final StringBuilder result = new StringBuilder("[");
for (int i = 0; i < array.length; ++i) {
if (i > 0) {
result.append(", ");
}
result.append(makeJavaSourceClassName(array[i]));
}
result.append (']');
return result.toString();
}
/**
* A helper function for displaying class names in error messages.
* @param type
* @return friendly Java source name for the type. For example, instead of "[Z" we get "int[]"
*/
private static String makeJavaSourceClassName(final Class<?> type) {
return JavaTypeName.getFullJavaSourceName(type);
}
/**
* Make a ForeignFunctionInfo object from its description in a CAL foreign function declaration
* as a Java method or report an error if this can't be done.
*
* Creation date: (June 26, 2002).
* @param calName name of the foreign function in CAL.
* @param javaName name of the Java method e.g. "java.lang.Character.isUpperCase". Unqualified for non-static methods and qualified for static methods.
* @param isStatic true if the Java method is static.
* @param signatureTypes declared CAL signature of the method, mapped to Java classes.
* @param errorRange CAL source position to refer to when reporting compilation errors arising from this call.
* @return make a ForeignFunctionInfo object from its description in a CAL foreign function declaration
* as a Java method or report an error if this can't be done.
*/
private ForeignFunctionInfo makeForeignMethod (final QualifiedName calName, final String javaName, final boolean isStatic, final Class<?>[] signatureTypes, final SourceRange errorRange) {
final int periodPosition = javaName.lastIndexOf('.');
//the unqualified name of the Java method e.g. "cos"
final String methodName;
//the qualified name of the Java class on which the method is invoked
final String qualifiedClassName;
//the provider for the class on which the method is invoked
final ForeignEntityProvider<Class<?>> objectTypeProvider;
final int signatureLength = signatureTypes.length;
//check that the method exists with the specified arguments
ForeignEntityProvider<Class<?>>[] argumentTypeProviders;
if (isStatic) {
if (periodPosition == -1) {
//"The Java name {0} must be fully qualified (i.e. include the package name)."
compiler.logMessage(new CompilerMessage(
errorRange,
CompilerMessage.Identifier.makeFunction(calName),
new MessageKind.Error.JavaNameMustBeFullyQualified(javaName)));
}
argumentTypeProviders = makeForeignEntityProviderClassArray(signatureLength - 1);
for (int i = 0, n = signatureLength - 1; i < n; i++) {
argumentTypeProviders[i] = ForeignEntityProvider.makeStrict(signatureTypes[i]);
}
qualifiedClassName = javaName.substring(0, periodPosition);
methodName = javaName.substring(periodPosition + 1);
objectTypeProvider = ForeignEntityProvider.makeStrict(getObjectType(qualifiedClassName, errorRange, calName));
} else {
//must have an argument for the instance object to apply the non-static method on
if (signatureLength < 2) {
// Non-static methods must have an object instance and a return type.
compiler.logMessage(new CompilerMessage(
errorRange,
CompilerMessage.Identifier.makeFunction(calName),
new MessageKind.Error.NonStaticMethodsMustHaveInstanceAndReturnType()));
}
if (periodPosition != -1) {
//"The Java name {0} must be unqualified (i.e. omit the package name)."
compiler.logMessage(new CompilerMessage(
errorRange,
CompilerMessage.Identifier.makeFunction(calName),
new MessageKind.Error.JavaNameMustBeUnqualified(javaName)));
}
final Class<?> instanceType = signatureTypes[0];
argumentTypeProviders = makeForeignEntityProviderClassArray(signatureLength - 2);
for (int i = 0, n = signatureLength - 2; i < n; i++) {
argumentTypeProviders[i] = ForeignEntityProvider.makeStrict(signatureTypes[i + 1]);
}
//the method descriptor is unqualified e.g. "method equals"
qualifiedClassName = instanceType.getName();
methodName = javaName;
objectTypeProvider = ForeignEntityProvider.makeStrict(instanceType);
}
final ForeignEntityProvider<Class<?>> declaredReturnTypeProvider = ForeignEntityProvider.makeStrict(signatureTypes [signatureLength - 1]);
return makeForeignFunctionInfoForMethod(calName, javaName, isStatic, errorRange, methodName, qualifiedClassName, objectTypeProvider, argumentTypeProviders, declaredReturnTypeProvider, compiler.getMessageLogger(), false);
}
/**
* Make a ForeignFunctionInfo object for a method.
* @param calName name of the foreign function in CAL.
* @param javaName name of the Java method e.g. "java.lang.Character.isUpperCase". Unqualified for non-static methods and qualified for static methods.
* @param isStatic true if the Java method is static.
* @param errorRange CAL source position to refer to when reporting compilation errors arising from this call.
* @param methodName the unqualified name of the method.
* @param qualifiedClassName the qualified name of the class containing the method.
* @param objectTypeProvider the provider object for the class containing the method.
* @param argumentTypeProviders an array of provider objects corresponding to the types of the arguments.
* @param declaredReturnTypeProvider the provider object corresponding to the declared return type of the foreign function.
* @param msgLogger the message logger for logging error messages.
* @param isLoading whether this is done as part of loading a serialized ForeignFunctionInfo object.
* @return a ForeignFunctionInfo object for the named method.
*/
static ForeignFunctionInfo makeForeignFunctionInfoForMethod(final QualifiedName calName, final String javaName, final boolean isStatic, final SourceRange errorRange, final String methodName, final String qualifiedClassName, final ForeignEntityProvider<Class<?>> objectTypeProvider, final ForeignEntityProvider<Class<?>>[] argumentTypeProviders, final ForeignEntityProvider<Class<?>> declaredReturnTypeProvider, final CompilerMessageLogger msgLogger, final boolean isLoading) {
final String foreignEntityNameDisplayString = qualifiedClassName + "." + methodName;
final ForeignEntityProvider.Resolver<Method> methodResolver = new ForeignEntityProvider.Resolver<Method>(foreignEntityNameDisplayString) {
@Override
Method resolve(final ForeignEntityProvider.MessageHandler messageHandler) throws UnableToResolveForeignEntityException {
final Class<?> objectType = objectTypeProvider.get();
if (objectType == null) {
return null;
}
final Class<?>[] argumentTypes = getArrayOfClassesFromProviders(argumentTypeProviders);
if (argumentTypes == null) {
return null;
}
final Class<?> declaredReturnType = declaredReturnTypeProvider.get();
if (declaredReturnType == null) {
return null;
}
final ForeignEntityResolver.ResolutionResult<Method> methodResolution = ForeignEntityResolver.resolveMethod(isStatic, objectType, methodName, argumentTypes, declaredReturnType);
final ForeignEntityResolver.ResolutionStatus resolutionStatus = methodResolution.getStatus();
final Method method = methodResolution.getResolvedEntity();
if (resolutionStatus == ForeignEntityResolver.ResolutionStatus.SUCCESS) {
// the resolution was successful, so no need to report errors
} else if (resolutionStatus == ForeignEntityResolver.ResolutionStatus.NO_SUCH_ENTITY) {
// Could not find the method {javaName} with given argument types {makeArrayString(argumentTypes)}.
messageHandler.handleMessage(new CompilerMessage(
errorRange,
CompilerMessage.Identifier.makeFunction(calName),
new MessageKind.Error.CouldNotFindMethodWithGivenArgumentTypes(javaName, makeClassArrayString(argumentTypes))));
} else if (resolutionStatus == ForeignEntityResolver.ResolutionStatus.SECURITY_VIOLATION) {
// Security violation trying to access {javaName}.
messageHandler.handleMessage(new CompilerMessage(
errorRange,
CompilerMessage.Identifier.makeFunction(calName),
new MessageKind.Error.SecurityViolationTryingToAccess(javaName)));
} else if (resolutionStatus == ForeignEntityResolver.ResolutionStatus.DEPENDEE_CLASS_NOT_FOUND) {
// The Java class {notFoundClass} was not found. This class is required by {javaName}.
messageHandler.handleMessage(new CompilerMessage(
errorRange,
CompilerMessage.Identifier.makeFunction(calName),
new MessageKind.Error.DependeeJavaClassNotFound(methodResolution.getAssociatedMessage(), javaName)));
} else if (resolutionStatus == ForeignEntityResolver.ResolutionStatus.CANNOT_LOAD_CLASS) {
// The definition of Java class {qualifiedClassName} could not be loaded.
messageHandler.handleMessage(new CompilerMessage(
errorRange,
CompilerMessage.Identifier.makeFunction(calName),
new MessageKind.Error.JavaClassDefinitionCouldNotBeLoaded(qualifiedClassName)));
} else if (resolutionStatus == ForeignEntityResolver.ResolutionStatus.LINKAGE_ERROR) {
// The java class {qualifiedClassName} was found, but there were problems with using it.
// Class: {LinkageError.class}
// Message: {e.getMessage()}
messageHandler.handleMessage(new CompilerMessage(
errorRange,
CompilerMessage.Identifier.makeFunction(calName),
new MessageKind.Error.ProblemsUsingJavaClass(qualifiedClassName, (LinkageError)methodResolution.getThrowable())));
} else if (resolutionStatus == ForeignEntityResolver.ResolutionStatus.EXPECT_STATIC_FOUND_NON_STATIC) {
// Could not find the static method {javaName} with given argument types {makeClassArrayString(argumentTypes)}.
messageHandler.handleMessage(new CompilerMessage(
errorRange,
CompilerMessage.Identifier.makeFunction(calName),
new MessageKind.Error.CouldNotFindStaticMethodWithGivenArgumentTypes(javaName, makeClassArrayString(argumentTypes))));
} else if (resolutionStatus == ForeignEntityResolver.ResolutionStatus.EXPECT_NON_STATIC_FOUND_STATIC) {
// Could not find the non-static method {javaName} with given argument types {makeClassArrayString(argumentTypes)}.
messageHandler.handleMessage(new CompilerMessage(
errorRange,
CompilerMessage.Identifier.makeFunction(calName),
new MessageKind.Error.CouldNotFindNonStaticMethodWithGivenArgumentTypes(javaName, makeClassArrayString(argumentTypes))));
} else if (resolutionStatus == ForeignEntityResolver.ResolutionStatus.ACTUAL_RETURN_TYPE_DOES_NOT_MATCH_DECLARED_RETURN_TYPE) {
// Foreign function {javaName} cannot return a value of type {declaredReturnType}.
messageHandler.handleMessage(new CompilerMessage(
errorRange,
CompilerMessage.Identifier.makeFunction(calName),
new MessageKind.Error.ForeignFunctionReturnsWrongType(javaName, makeJavaSourceClassName(declaredReturnType))));
} else {
// Some other unexpected status
throw new IllegalStateException("Unexpected status: " + resolutionStatus);
}
if (isLoading && resolutionStatus != ForeignEntityResolver.ResolutionStatus.SUCCESS) {
//"The underlying Java types involved in the CAL foreign function {0} have changed. Recompilation is needed."
messageHandler.handleMessage(new CompilerMessage(
errorRange,
CompilerMessage.Identifier.makeFunction(calName),
new MessageKind.Error.InvalidJavaTypeChangeOnLoading(calName)));
}
return method;
}
};
final ForeignEntityProvider<Method> methodProvider;
// If we are compiling, always resolve the method eagerly. If we are loading from serialized form, we defer the decision to the machine configuration.
if (isLoading) {
methodProvider = ForeignEntityProvider.make(msgLogger, methodResolver);
} else {
methodProvider = ForeignEntityProvider.makeStrict(msgLogger, methodResolver);
}
if (methodProvider == null) {
return null;
}
if (isStatic) {
return ForeignFunctionInfo.Invocation.makeStaticMethod(calName, objectTypeProvider, methodProvider, declaredReturnTypeProvider);
} else {
return ForeignFunctionInfo.Invocation.makeNonStaticMethod(calName, objectTypeProvider, methodProvider, declaredReturnTypeProvider);
}
}
/**
* Make a ForeignFunctionInfo object from its description in a CAL foreign function declaration
* as a Java field or report an error if this can't be done.
*
* Creation date: (June 26, 2002).
* @param calName name of the foreign function in CAL.
* @param javaName name of the Java field e.g. "java.lang.Math.E". Unqualified for non-static fields and qualified for static fields.
* @param isStatic true if the Java field is static.
* @param signatureTypes declared CAL signature of the field, mapped to Java classes.
* @param errorRange CAL source position to refer to when reporting compilation errors arising from this call.
* @return make a ForeignFunctionInfo object from its description in a CAL foreign function declaration
* as a Java field or report an error if this can't be done.
*/
private ForeignFunctionInfo makeForeignField (final QualifiedName calName, final String javaName, final boolean isStatic, final Class<?>[] signatureTypes, final SourceRange errorRange) {
final int periodPosition = javaName.lastIndexOf('.');
//the unqualified name of the field in Java
final String fieldName;
//the qualified name of the Java class on which this field is invoked
final String qualifiedClassName;
//the provider for the class on which the field is invoked
final ForeignEntityProvider<Class<?>> objectTypeProvider;
final int signatureLength = signatureTypes.length;
//check that the field exists with the specified arguments
if (isStatic) {
if (signatureLength != 1) {
// The static field {0} must have no arguments.
compiler.logMessage(new CompilerMessage(
errorRange,
CompilerMessage.Identifier.makeFunction(calName),
new MessageKind.Error.StaticFieldMustHaveNoArguments(javaName)));
}
if (periodPosition == -1) {
//"The Java name {0} must be fully qualified (i.e. include the package name)."
compiler.logMessage(new CompilerMessage(
errorRange,
CompilerMessage.Identifier.makeFunction(calName),
new MessageKind.Error.JavaNameMustBeFullyQualified(javaName)));
}
qualifiedClassName = javaName.substring(0, periodPosition);
fieldName = javaName.substring(periodPosition + 1);
objectTypeProvider = ForeignEntityProvider.makeStrict(getObjectType(qualifiedClassName, errorRange, calName));
} else {
//must have an argument for the instance object to apply the non-static field on
if (signatureLength != 2) {
// Non-static fields must have an object instance and a return type.
compiler.logMessage(new CompilerMessage(
errorRange,
CompilerMessage.Identifier.makeFunction(calName),
new MessageKind.Error.NonStaticFieldsMustHaveInstanceAndReturnType()));
}
if (periodPosition != -1) {
//"The Java name {0} must be unqualified (i.e. omit the package name)."
compiler.logMessage(new CompilerMessage(
errorRange,
CompilerMessage.Identifier.makeFunction(calName),
new MessageKind.Error.JavaNameMustBeUnqualified(javaName)));
}
final Class<?> instanceType = signatureTypes[0];
//the field descriptor is unqualified
qualifiedClassName = instanceType.getName();
fieldName = javaName;
objectTypeProvider = ForeignEntityProvider.makeStrict(instanceType);
}
final ForeignEntityProvider<Class<?>> declaredReturnTypeProvider = ForeignEntityProvider.makeStrict(signatureTypes [signatureLength - 1]);
return makeForeignFunctionInfoForField(calName, javaName, isStatic, errorRange, fieldName, qualifiedClassName, objectTypeProvider, declaredReturnTypeProvider, compiler.getMessageLogger(), false);
}
/**
* Make a ForeignFunctionInfo object for a field.
* @param calName name of the foreign function in CAL.
* @param javaName name of the Java field e.g. "java.lang.Math.E". Unqualified for non-static fields and qualified for static fields.
* @param isStatic true if the Java field is static.
* @param errorRange CAL source position to refer to when reporting compilation errors arising from this call.
* @param fieldName the unqualified name of the field.
* @param qualifiedClassName the qualified name of the class containing the field.
* @param objectTypeProvider the provider object for the class containing the field.
* @param declaredReturnTypeProvider the provider object corresponding to the declared return type of the foreign function.
* @param msgLogger the message logger for logging error messages.
* @param isLoading whether this is done as part of loading a serialized ForeignFunctionInfo object.
* @return a ForeignFunctionInfo object for the named field.
*/
static ForeignFunctionInfo makeForeignFunctionInfoForField(final QualifiedName calName, final String javaName, final boolean isStatic, final SourceRange errorRange, final String fieldName, final String qualifiedClassName, final ForeignEntityProvider<Class<?>> objectTypeProvider, final ForeignEntityProvider<Class<?>> declaredReturnTypeProvider, final CompilerMessageLogger msgLogger, final boolean isLoading) {
final String foreignEntityNameDisplayString = qualifiedClassName + "." + fieldName;
final ForeignEntityProvider.Resolver<Field> fieldResolver = new ForeignEntityProvider.Resolver<Field>(foreignEntityNameDisplayString) {
@Override
Field resolve(final ForeignEntityProvider.MessageHandler messageHandler) throws UnableToResolveForeignEntityException {
final Class<?> objectType = objectTypeProvider.get();
if (objectType == null) {
return null;
}
final Class<?> declaredReturnType = declaredReturnTypeProvider.get();
if (declaredReturnType == null) {
return null;
}
final ForeignEntityResolver.ResolutionResult<Field> fieldResolution = ForeignEntityResolver.resolveField(isStatic, objectType, fieldName, declaredReturnType);
final ForeignEntityResolver.ResolutionStatus resolutionStatus = fieldResolution.getStatus();
final Field field = fieldResolution.getResolvedEntity();
if (resolutionStatus == ForeignEntityResolver.ResolutionStatus.SUCCESS) {
// the resolution was successful, so no need to report errors
} else if (resolutionStatus == ForeignEntityResolver.ResolutionStatus.NO_SUCH_ENTITY) {
// Could not find the field {javaName}.
messageHandler.handleMessage(
new CompilerMessage(
errorRange,
CompilerMessage.Identifier.makeFunction(calName),
new MessageKind.Error.CouldNotFindField(javaName)));
} else if (resolutionStatus == ForeignEntityResolver.ResolutionStatus.SECURITY_VIOLATION) {
// Security violation trying to access {javaName}.
messageHandler.handleMessage(new CompilerMessage(
errorRange,
CompilerMessage.Identifier.makeFunction(calName),
new MessageKind.Error.SecurityViolationTryingToAccess(javaName)));
} else if (resolutionStatus == ForeignEntityResolver.ResolutionStatus.DEPENDEE_CLASS_NOT_FOUND) {
// The Java class {notFoundClass} was not found. This class is required by {javaName}.
messageHandler.handleMessage(new CompilerMessage(
errorRange,
CompilerMessage.Identifier.makeFunction(calName),
new MessageKind.Error.DependeeJavaClassNotFound(fieldResolution.getAssociatedMessage(), javaName)));
} else if (resolutionStatus == ForeignEntityResolver.ResolutionStatus.CANNOT_LOAD_CLASS) {
// The definition of Java class {qualifiedClassName} could not be loaded.
messageHandler.handleMessage(new CompilerMessage(
errorRange,
CompilerMessage.Identifier.makeFunction(calName),
new MessageKind.Error.JavaClassDefinitionCouldNotBeLoaded(qualifiedClassName)));
} else if (resolutionStatus == ForeignEntityResolver.ResolutionStatus.LINKAGE_ERROR) {
// The java class {qualifiedClassName} was found, but there were problems with using it.
// Class: {LinkageError.class}
// Message: {e.getMessage()}
messageHandler.handleMessage(new CompilerMessage(
errorRange,
CompilerMessage.Identifier.makeFunction(calName),
new MessageKind.Error.ProblemsUsingJavaClass(qualifiedClassName, (LinkageError)fieldResolution.getThrowable())));
} else if (resolutionStatus == ForeignEntityResolver.ResolutionStatus.EXPECT_STATIC_FOUND_NON_STATIC) {
// Could not find the static field {javaName}.
messageHandler.handleMessage(new CompilerMessage(
errorRange,
CompilerMessage.Identifier.makeFunction(calName),
new MessageKind.Error.CouldNotFindStaticField(javaName)));
} else if (resolutionStatus == ForeignEntityResolver.ResolutionStatus.EXPECT_NON_STATIC_FOUND_STATIC) {
// Could not find the non-static field {javaName}.
messageHandler.handleMessage(new CompilerMessage(
errorRange,
CompilerMessage.Identifier.makeFunction(calName),
new MessageKind.Error.CouldNotFindNonStaticField(javaName)));
} else if (resolutionStatus == ForeignEntityResolver.ResolutionStatus.ACTUAL_RETURN_TYPE_DOES_NOT_MATCH_DECLARED_RETURN_TYPE) {
// Field {javaName} cannot return a value of type {declaredReturnType}.
messageHandler.handleMessage(new CompilerMessage(
errorRange,
CompilerMessage.Identifier.makeFunction(calName),
new MessageKind.Error.FieldReturnsWrongType(javaName, makeJavaSourceClassName(declaredReturnType))));
} else {
// Some other unexpected status
throw new IllegalStateException("Unexpected status: " + resolutionStatus);
}
if (isLoading && resolutionStatus != ForeignEntityResolver.ResolutionStatus.SUCCESS) {
//"The underlying Java types involved in the CAL foreign function {0} have changed. Recompilation is needed."
messageHandler.handleMessage(new CompilerMessage(
errorRange,
CompilerMessage.Identifier.makeFunction(calName),
new MessageKind.Error.InvalidJavaTypeChangeOnLoading(calName)));
}
return field;
}
};
final ForeignEntityProvider<Field> fieldProvider;
// If we are compiling, always resolve the field eagerly. If we are loading from serialized form, we defer the decision to the machine configuration.
if (isLoading) {
fieldProvider = ForeignEntityProvider.make(msgLogger, fieldResolver);
} else {
fieldProvider = ForeignEntityProvider.makeStrict(msgLogger, fieldResolver);
}
if (fieldProvider == null) {
return null;
}
if (isStatic) {
return ForeignFunctionInfo.Invocation.makeStaticField(calName, objectTypeProvider, fieldProvider, declaredReturnTypeProvider);
} else {
return ForeignFunctionInfo.Invocation.makeNonStaticField(calName, objectTypeProvider, fieldProvider, declaredReturnTypeProvider);
}
}
/**
* Make a ForeignFunctionInfo object from its description in a CAL foreign function declaration
* as a Java constructor or report an error if this can't be done.
*
* Creation date: (June 26, 2002).
* @param calName name of the foreign function in CAL.
* @param maybeJavaName name of the Java type to construct e.g. "java.lang.StringBuilder". Can be null, in which case the CAL function's return type is used.
* @param signatureTypes declared CAL signature of the constructor, mapped to Java classes.
* @param errorRange CAL source position to refer to when reporting compilation errors arising from this call.
* @return make a ForeignFunctionInfo object from its description in a CAL foreign function declaration
* as a Java constructor or report an error if this can't be done.
*/
private ForeignFunctionInfo makeForeignConstructor(
final QualifiedName calName,
final String maybeJavaName,
final Class<?>[] signatureTypes,
final SourceRange errorRange) {
final int signatureLength = signatureTypes.length;
final ForeignEntityProvider<Class<?>>[] argumentTypeProviders = makeForeignEntityProviderClassArray(signatureLength - 1);
for (int i = 0, n = signatureLength - 1; i < n; i++) {
argumentTypeProviders[i] = ForeignEntityProvider.makeStrict(signatureTypes[i]);
}
//the Class in which the constructor is defined
final ForeignEntityProvider<Class<?>> objectTypeProvider;
final String javaName;
final ForeignEntityProvider<Class<?>> declaredReturnTypeProvider;
if (maybeJavaName != null) {
javaName = maybeJavaName;
//get the object type from javaName. This might be necessary because the actual CAL function's return type may be a supertype
//e.g. foreign unsafe import jvm "constructor java.util.ArrayList" :: Int -> JList;
objectTypeProvider = ForeignEntityProvider.makeStrict(getObjectType(javaName, errorRange, calName));
declaredReturnTypeProvider = ForeignEntityProvider.makeStrict(signatureTypes [signatureLength - 1]);
} else {
//get the object type from the CAL function's signature.
//e.g. foreign unsafe import jvm "constructor" :: Int -> JArrayList;
//the we invoke on java.util.ArrayList
objectTypeProvider = ForeignEntityProvider.makeStrict(signatureTypes [signatureLength - 1]);
try {
javaName = makeJavaSourceClassName(objectTypeProvider.get()); //must not be null for error messages
} catch (final UnableToResolveForeignEntityException e) {
// this should never happen with a strict ForeignEntityProvider
final IllegalStateException illegalStateException = new IllegalStateException("An UnableToResolveForeignEntityException should not be thrown by a strict ForeignEntityProvider");
illegalStateException.initCause(e);
throw illegalStateException;
}
declaredReturnTypeProvider = objectTypeProvider;
}
return makeForeignFunctionInfoForConstructor(calName, javaName, errorRange, objectTypeProvider, argumentTypeProviders, declaredReturnTypeProvider, compiler.getMessageLogger(), false);
}
/**
* Make a ForeignFunctionInfo object for a constructor.
* @param calName name of the foreign function in CAL.
* @param javaName name of the Java type to construct e.g. "java.lang.StringBuilder". <b>Cannot</b> be null.
* @param errorRange CAL source position to refer to when reporting compilation errors arising from this call.
* @param objectTypeProvider the provider object for the class containing the constructor.
* @param argumentTypeProviders an array of provider objects corresponding to the types of the arguments.
* @param declaredReturnTypeProvider the provider object corresponding to the declared return type of the foreign function.
* @param msgLogger the message logger for logging error messages.
* @param isLoading whether this is done as part of loading a serialized ForeignFunctionInfo object.
* @return a ForeignFunctionInfo object for the named constructor.
*/
static ForeignFunctionInfo makeForeignFunctionInfoForConstructor(final QualifiedName calName, final String javaName, final SourceRange errorRange, final ForeignEntityProvider<Class<?>> objectTypeProvider, final ForeignEntityProvider<Class<?>>[] argumentTypeProviders, final ForeignEntityProvider<Class<?>> declaredReturnTypeProvider, final CompilerMessageLogger msgLogger, final boolean isLoading) {
final ForeignEntityProvider.Resolver<Constructor<?>> constructorResolver = new ForeignEntityProvider.Resolver<Constructor<?>>(javaName) {
@Override
Constructor<?> resolve(final ForeignEntityProvider.MessageHandler messageHandler) throws UnableToResolveForeignEntityException {
final Class<?> objectType = objectTypeProvider.get();
if (objectType == null) {
return null;
}
final Class<?>[] argumentTypes = getArrayOfClassesFromProviders(argumentTypeProviders);
if (argumentTypes == null) {
return null;
}
final Class<?> declaredReturnType = declaredReturnTypeProvider.get();
if (declaredReturnType == null) {
return null;
}
//check that the constructor exists with the specified arguments
final ForeignEntityResolver.ResolutionResult<Constructor<?>> constructorResolution = ForeignEntityResolver.resolveConstructor(objectType, argumentTypes, declaredReturnType);
final ForeignEntityResolver.ResolutionStatus resolutionStatus = constructorResolution.getStatus();
final Constructor<?> constructor = constructorResolution.getResolvedEntity();
if (resolutionStatus == ForeignEntityResolver.ResolutionStatus.SUCCESS) {
// the resolution was successful, so no need to report errors
} else if (resolutionStatus == ForeignEntityResolver.ResolutionStatus.NO_SUCH_ENTITY) {
// Could not find the constructor {javaName} with the given argument types {makeArrayString(argumentTypes)}."));
messageHandler.handleMessage(
new CompilerMessage(
errorRange,
CompilerMessage.Identifier.makeFunction(calName),
new MessageKind.Error.CouldNotFindConstructorWithGivenArgumentTypes(javaName, makeClassArrayString(argumentTypes))));
} else if (resolutionStatus == ForeignEntityResolver.ResolutionStatus.SECURITY_VIOLATION) {
// Security violation trying to access {javaName}.
messageHandler.handleMessage(new CompilerMessage(
errorRange,
CompilerMessage.Identifier.makeFunction(calName),
new MessageKind.Error.SecurityViolationTryingToAccess(javaName)));
} else if (resolutionStatus == ForeignEntityResolver.ResolutionStatus.DEPENDEE_CLASS_NOT_FOUND) {
// The Java class {notFoundClass} was not found. This class is required by {javaName}.
messageHandler.handleMessage(new CompilerMessage(
errorRange,
CompilerMessage.Identifier.makeFunction(calName),
new MessageKind.Error.DependeeJavaClassNotFound(constructorResolution.getAssociatedMessage(), javaName)));
} else if (resolutionStatus == ForeignEntityResolver.ResolutionStatus.CANNOT_LOAD_CLASS) {
// The definition of Java class {javaName} could not be loaded.
messageHandler.handleMessage(new CompilerMessage(
errorRange,
CompilerMessage.Identifier.makeFunction(calName),
new MessageKind.Error.JavaClassDefinitionCouldNotBeLoaded(javaName)));
} else if (resolutionStatus == ForeignEntityResolver.ResolutionStatus.LINKAGE_ERROR) {
// The java class {javaName} was found, but there were problems with using it.
// Class: {LinkageError.class}
// Message: {e.getMessage()}
messageHandler.handleMessage(new CompilerMessage(
errorRange,
CompilerMessage.Identifier.makeFunction(calName),
new MessageKind.Error.ProblemsUsingJavaClass(javaName, (LinkageError)constructorResolution.getThrowable())));
} else if (resolutionStatus == ForeignEntityResolver.ResolutionStatus.ACTUAL_RETURN_TYPE_DOES_NOT_MATCH_DECLARED_RETURN_TYPE) {
// Constructor {javaName} cannot return a value of type {declaredReturnType}.
messageHandler.handleMessage(new CompilerMessage(
errorRange,
CompilerMessage.Identifier.makeFunction(calName),
new MessageKind.Error.ConstructorReturnsWrongType(javaName, makeJavaSourceClassName(declaredReturnType))));
} else {
// Some other unexpected status
throw new IllegalStateException("Unexpected status: " + resolutionStatus);
}
if (isLoading && resolutionStatus != ForeignEntityResolver.ResolutionStatus.SUCCESS) {
//"The underlying Java types involved in the CAL foreign function {0} have changed. Recompilation is needed."
messageHandler.handleMessage(new CompilerMessage(
errorRange,
CompilerMessage.Identifier.makeFunction(calName),
new MessageKind.Error.InvalidJavaTypeChangeOnLoading(calName)));
}
return constructor;
}
};
final ForeignEntityProvider<Constructor<?>> constructorProvider;
// If we are compiling, always resolve the constructor eagerly. If we are loading from serialized form, we defer the decision to the machine configuration.
if (isLoading) {
constructorProvider = ForeignEntityProvider.make(msgLogger, constructorResolver);
} else {
constructorProvider = ForeignEntityProvider.makeStrict(msgLogger, constructorResolver);
}
if (constructorProvider == null) {
return null;
}
return ForeignFunctionInfo.Invocation.makeConstructor(calName, constructorProvider, declaredReturnTypeProvider);
}
/**
* Make a ForeignFunctionInfo object from its description in a CAL foreign function declaration
* as a Java instanceof or report an error if this can't be done.
*
* @param calName name of the foreign function in CAL.
* @param instanceOfTypeName the name of the Java type T in "expr instanceof T".
* @param signatureTypes declared CAL signature of the cast, mapped to Java classes.
* @param errorRange CAL source position to refer to when reporting compilation errors arising from this call.
* @return make a ForeignFunctionInfo object from its description in a CAL foreign function declaration
* as a Java instanceof or report an error if this can't be done.
*/
private ForeignFunctionInfo makeForeignInstanceOf(final QualifiedName calName, final String instanceOfTypeName, final Class<?>[] signatureTypes, final SourceRange errorRange) {
if (signatureTypes.length != 2) {
//"A foreign function declaration for an ''instanceof'' must have exactly 1 argument(s)."
compiler.logMessage(new CompilerMessage(errorRange,
new MessageKind.Error.ForeignFunctionDeclarationMustHaveExactlyNArgs("instanceof", 1)));
return null;
}
final Class<?> argumentType = signatureTypes[0];
final Class<?> returnType = signatureTypes[1];
if (returnType != boolean.class) {
//"A foreign function declaration for an ''instanceof'' must return Cal.Core.Prelude.Boolean (or a foreign type with Java implementation type boolean)."
compiler.logMessage(new CompilerMessage(errorRange,
new MessageKind.Error.ForeignFunctionDeclarationMustReturnBoolean("instanceof")));
}
// getObjectType handles array types such as "java.lang.String[]" and "int[][]"
final Class<?> instanceOfType = getObjectType (instanceOfTypeName, errorRange, calName);
//can't do an instanceof with primitive types
if (argumentType.isPrimitive()) {
//"The implementation type must be a Java reference type and not the Java primitive type ''{0}''."
compiler.logMessage(new CompilerMessage(errorRange,
new MessageKind.Error.ForeignFunctionDeclarationReferenceTypeExpected(argumentType)));
return null;
}
if (instanceOfType.isPrimitive()) {
//"The implementation type must be a Java reference type and not the Java primitive type ''{0}''."
compiler.logMessage(new CompilerMessage(errorRange,
new MessageKind.Error.ForeignFunctionDeclarationReferenceTypeExpected(instanceOfType)));
return null;
}
final ForeignFunctionInfo.JavaKind kind = checkForeignCast(argumentType, instanceOfType);
if (kind == null) {
//"If ''expr'' has Java type ''{0}'', then ''expr instanceof {1}'' is an invalid Java expression."
compiler.logMessage(new CompilerMessage(errorRange,
new MessageKind.Error.ForeignFunctionDeclarationInvalidInstanceOf(argumentType, instanceOfType)));
return null;
}
return new ForeignFunctionInfo.InstanceOf(calName, ForeignEntityProvider.makeStrict(argumentType), ForeignEntityProvider.makeStrict(instanceOfType));
}
/**
* Creates, after checking, foreign "null" functions such as:
*
* foreign unsafe import jvm "null" nullString :: String;
*/
private ForeignFunctionInfo makeForeignNull(final QualifiedName calName, final Class<?>[] signatureTypes, final SourceRange errorRange) {
if (signatureTypes.length != 1) {
//"A foreign function declaration for ''{0}'' must have exactly {1} argument(s)."
compiler.logMessage(new CompilerMessage(errorRange,
new MessageKind.Error.ForeignFunctionDeclarationMustHaveExactlyNArgs("null", 0)));
return null;
}
final Class<?> returnType = signatureTypes[0];
//check that the null is for a reference type, and not a primitive type
if (returnType.isPrimitive()) {
//"The implementation type must be a Java reference type and not the Java primitive type ''{0}''."
compiler.logMessage(new CompilerMessage(errorRange,
new MessageKind.Error.ForeignFunctionDeclarationReferenceTypeExpected(returnType)));
return null;
}
return new ForeignFunctionInfo.NullLiteral(calName, ForeignEntityProvider.makeStrict(returnType));
}
/**
* Creates, after checking, foreign "isNull" and "isNotNull" functions such as:
*
* foreign unsafe import jvm "isNull" isNullString :: String -> Boolean;
* foreign unsafe import jvm "isNotNull" isNotNullJObject :: JObject -> Boolean;
*/
private ForeignFunctionInfo makeForeignNullCheck(final QualifiedName calName, final Class<?>[] signatureTypes, final SourceRange errorRange, final boolean checkIsNull) {
final String nullCheckOp = checkIsNull ? "isNull" : "isNotNull";
if (signatureTypes.length != 2) {
//"A foreign function declaration for ''{0}'' must have exactly {1} argument(s)."
compiler.logMessage(new CompilerMessage(errorRange,
new MessageKind.Error.ForeignFunctionDeclarationMustHaveExactlyNArgs(nullCheckOp, 1)));
return null;
}
final Class<?> argumentType = signatureTypes[0];
final Class<?> returnType = signatureTypes[1];
//check that the null check is for a reference type, and not a primitive type
if (argumentType.isPrimitive()) {
//"The implementation type must be a Java reference type and not the Java primitive type ''{0}''."
compiler.logMessage(new CompilerMessage(errorRange,
new MessageKind.Error.ForeignFunctionDeclarationReferenceTypeExpected(argumentType)));
return null;
}
if (returnType != boolean.class) {
//"A foreign function declaration for ''{0}'' must return Cal.Core.Prelude.Boolean (or a foreign type with Java implementation type boolean)."
compiler.logMessage(new CompilerMessage(errorRange,
new MessageKind.Error.ForeignFunctionDeclarationMustReturnBoolean(nullCheckOp)));
}
return new ForeignFunctionInfo.NullCheck(calName, ForeignEntityProvider.makeStrict(argumentType), checkIsNull);
}
/**
* Creates, after checking, a foreign class literal function such as:
* <pre>
* foreign unsafe import jvm "class void" voidClass :: JClass;
* foreign unsafe import jvm "class int" intClass :: JClass;
* foreign unsafe import jvm "class java.util.List" listClass :: JClass;
* foreign unsafe import jvm "class int[]" intArrayClass :: JClass;
* foreign unsafe import jvm "class java.lang.String[][]" stringArrayArrayClass :: JClass;
* </pre>
*
* @param calName the name of the CAL foreign function.
* @param referentTypeName the referent type, i.e. the Java type R where this literal corresponds to R.class.
* @param signatureTypes declared CAL signature of the foreign function, mapped to Java classes.
* @param errorRange CAL source range to refer to when reporting compilation errors arising from this call.
* @return a ForeignFunctionInfo representing the foreign class literal function.
*/
private ForeignFunctionInfo makeForeignClassLiteral(final QualifiedName calName, final String referentTypeName, final Class<?>[] signatureTypes, final SourceRange errorRange) {
if (signatureTypes.length != 1) {
//"A foreign function declaration for ''{0}'' must have exactly {1} argument(s)."
compiler.logMessage(new CompilerMessage(
errorRange,
CompilerMessage.Identifier.makeFunction(calName),
new MessageKind.Error.ForeignFunctionDeclarationMustHaveExactlyNArgs("class", 0)));
return null;
}
final ForeignEntityProvider<Class<?>> declaredReturnTypeProvider = ForeignEntityProvider.makeStrict(signatureTypes[0]);
// the referent type, i.e. the Java type R where this literal corresponds to R.class.
final Class<?> referentType;
if (referentTypeName.equals("void")) {
// the pseudo-type void is explicitly allowed for use with a foreign class literal declaration
referentType = void.class;
} else {
// check whether the named type is a primitive type
final Class<?> primitiveType = ForeignEntityResolver.getPrimitiveType(referentTypeName);
if (primitiveType != null) {
referentType = primitiveType;
} else {
// not a primitive type, so it may be an array type or a reference type
referentType = getObjectType(referentTypeName, errorRange, calName);
}
}
final ForeignEntityProvider<Class<?>> referentTypeProvider = ForeignEntityProvider.makeStrict(referentType);
return makeForeignFunctionInfoForClassLiteral(calName, referentTypeName, errorRange, referentTypeProvider, declaredReturnTypeProvider, compiler.getMessageLogger(), false);
}
/**
* Make a ForeignFunctionInfo object for a class literal.
* @param calName name of the foreign function in CAL.
* @param referentTypeName the referent type, i.e. the Java type R where this literal corresponds to R.class.
* @param errorRange CAL source range to refer to when reporting compilation errors arising from this call.
* @param referentTypeProvider the provider object corresponding to the referent type.
* @param declaredReturnTypeProvider the provider object corresponding to the declared return type of the foreign function.
* @param msgLogger the message logger for logging error messages.
* @param isLoading whether this is done as part of loading a serialized ForeignFunctionInfo object.
* @return a ForeignFunctionInfo object for the class literal.
*/
static ForeignFunctionInfo makeForeignFunctionInfoForClassLiteral(final QualifiedName calName, final String referentTypeName, final SourceRange errorRange, final ForeignEntityProvider<Class<?>> referentTypeProvider, final ForeignEntityProvider<Class<?>> declaredReturnTypeProvider, final CompilerMessageLogger msgLogger, final boolean isLoading) {
final String foreignEntityNameDisplayString = referentTypeName + ".class";
final ForeignEntityProvider.Resolver<Class<?>> checkedReferentTypeResolver = new ForeignEntityProvider.Resolver<Class<?>>(foreignEntityNameDisplayString) {
@Override
Class<?> resolve(final ForeignEntityProvider.MessageHandler messageHandler) throws UnableToResolveForeignEntityException {
final Class<?> declaredReturnType = declaredReturnTypeProvider.get();
// The declared return type must be java.lang.Class, or its superclass java.lang.Object, or one of its superinterfaces.
if (!declaredReturnType.isAssignableFrom(Class.class)) {
final String javaName = foreignEntityNameDisplayString;
// Foreign function {javaName} cannot return a value of type {declaredReturnType}.
messageHandler.handleMessage(new CompilerMessage(
errorRange,
CompilerMessage.Identifier.makeFunction(calName),
new MessageKind.Error.ForeignFunctionReturnsWrongType(javaName, makeJavaSourceClassName(declaredReturnType))));
}
return referentTypeProvider.get();
}
};
final ForeignEntityProvider<Class<?>> checkedReferentTypeProvider;
// If we are compiling, always check the referent type eagerly. If we are loading from serialized form, we defer the decision to the machine configuration.
if (isLoading) {
checkedReferentTypeProvider = ForeignEntityProvider.make(msgLogger, checkedReferentTypeResolver);
} else {
checkedReferentTypeProvider = ForeignEntityProvider.makeStrict(msgLogger, checkedReferentTypeResolver);
}
if (checkedReferentTypeProvider == null) {
return null;
}
return new ForeignFunctionInfo.ClassLiteral(calName, referentTypeProvider, declaredReturnTypeProvider);
}
/**
* A helper function to calculate the number of dimensions in array given its Java type
* @param c
* @return 0 if not an array type, otherwise the dimension of the array.
*/
private static int getArrayDimension(final Class<?> c) {
int dim = 0;
for (Class<?> elemClass = c.getComponentType(); elemClass != null; elemClass = elemClass.getComponentType()) {
++dim;
}
return dim;
}
/**
* For example, if c is the class for int[][][] and nSubscripts == 2, this will return int[].
* @param c
* @param nSubscripts
* @return Class of the subscripted array type or null if can't subscript c to the desired level.
*/
static Class<?> getSubscriptedArrayType(final Class<?> c, final int nSubscripts) {
Class<?> subscriptedArrayType = c;
for (int i = 0; i < nSubscripts; i++) {
subscriptedArrayType = subscriptedArrayType.getComponentType();
if (subscriptedArrayType == null) {
return null;
}
}
return subscriptedArrayType;
}
/**
* Creates, after checking, foreign "newArray" functions such as:
* foreign unsafe import jvm "newArray" :: Int -> Int -> JIntIntIntArray;
*
* This creates a foreign type for a 3 dimensional int array, supplying the sizes of the first 2 dimensions
*
*/
private ForeignFunctionInfo makeForeignNewArray(final QualifiedName calName, final Class<?>[] signatureTypes, final SourceRange errorRange) {
final int signatureLength = signatureTypes.length;
//must have at least an array and an argument for the size of its first dimension
if (signatureLength < 2) {
//"The type signature is incompatible with the ''{0}'' external descriptor."
compiler.logMessage(new CompilerMessage(errorRange,
new MessageKind.Error.ForeignFunctionDeclarationInvalidCalType("newArray")));
return null;
}
//check that the arguments specifying the sizes of dimensions are all ints
final int nSizeArgs = signatureLength - 1;
for (int i = 0; i < nSizeArgs; ++i) {
if (signatureTypes[i] != int.class) {
//"The type signature is incompatible with the ''{0}'' external descriptor."
compiler.logMessage(new CompilerMessage(errorRange,
new MessageKind.Error.ForeignFunctionDeclarationInvalidCalType("newArray")));
return null;
}
}
final Class<?> newArrayType = signatureTypes[signatureLength - 1];
//can't supply the sizes of more dimensions than the array actually has
if (nSizeArgs > getArrayDimension(newArrayType)) {
//"The type signature is incompatible with the ''{0}'' external descriptor."
compiler.logMessage(new CompilerMessage(errorRange,
new MessageKind.Error.ForeignFunctionDeclarationInvalidCalType("newArray")));
return null;
}
return new ForeignFunctionInfo.NewArray(calName, nSizeArgs, ForeignEntityProvider.makeStrict(newArrayType));
}
/**
* Creates, after checking, foreign "lengthArray" functions such as:
* foreign unsafe import jvm "lengthArray" :: JIntArrayInt -> Int;
*
*/
private ForeignFunctionInfo makeForeignLengthArray(final QualifiedName calName, final Class<?>[] signatureTypes, final SourceRange errorRange) {
if (signatureTypes.length != 2) {
//"A foreign function declaration for ''{0}'' must have exactly {1} argument(s)."
compiler.logMessage(new CompilerMessage(errorRange,
new MessageKind.Error.ForeignFunctionDeclarationMustHaveExactlyNArgs("lengthArray", 1)));
return null;
}
final Class<?> arrayType = signatureTypes[0];
final Class<?> returnType = signatureTypes[1];
//must have signature arrayType -> int
if (!arrayType.isArray()
|| returnType != int.class) {
//"The type signature is incompatible with the ''{0}'' external descriptor."
compiler.logMessage(new CompilerMessage(errorRange,
new MessageKind.Error.ForeignFunctionDeclarationInvalidCalType("newArray")));
return null;
}
return new ForeignFunctionInfo.LengthArray(calName, ForeignEntityProvider.makeStrict(arrayType));
}
/**
* Creates, after checking, foreign "subscriptArray" functions such as:
* foreign unsafe import jvm "subscriptArray" :: JInt5Array -> Int -> Int -> JInt3Array;
* where JInt4Array has Java type int[][][][][]
* and JInt2Array has Java type int[][][]
*/
private ForeignFunctionInfo makeForeignSubscriptArray(final QualifiedName calName, final Class<?>[] signatureTypes, final SourceRange errorRange) {
final int signatureLength = signatureTypes.length;
//must have at least an array subscripted by at least 1 dimension, and the result
if (signatureLength < 3) {
//"The type signature is incompatible with the ''{0}'' external descriptor."
compiler.logMessage(new CompilerMessage(errorRange,
new MessageKind.Error.ForeignFunctionDeclarationInvalidCalType("subscriptArray")));
return null;
}
final int nSubscriptArgs = signatureLength - 2;
//check that the arguments specifying the subscipting are all ints
for (int i = 1; i < signatureLength - 1; ++i) {
if (signatureTypes[i] != int.class) {
//"The type signature is incompatible with the ''{0}'' external descriptor."
compiler.logMessage(new CompilerMessage(errorRange,
new MessageKind.Error.ForeignFunctionDeclarationInvalidCalType("subscriptArray")));
return null;
}
}
final Class<?> arrayType = signatureTypes[0];
final Class<?> resultType = signatureTypes[signatureLength - 1];
final Class<?> subscriptedArrayType = getSubscriptedArrayType(arrayType, nSubscriptArgs);
//1) must not subscipt by more dimensions than the array has
//
//2) the CAL type of the function need not match perfectly, but must be assignable e.g.
//foreign unsafe import jvm "subscriptArray" :: JStringArray -> Int -> JObject;
//is OK. This is consistent with our treatment of foreign functions corresponding to Java methods, fields and constructors.
if (subscriptedArrayType == null
|| !resultType.isAssignableFrom(subscriptedArrayType)) {
//"The type signature is incompatible with the ''{0}'' external descriptor."
compiler.logMessage(new CompilerMessage(errorRange,
new MessageKind.Error.ForeignFunctionDeclarationInvalidCalType("subscriptArray")));
return null;
}
return new ForeignFunctionInfo.SubscriptArray(calName, nSubscriptArgs, ForeignEntityProvider.makeStrict(arrayType), ForeignEntityProvider.makeStrict(resultType));
}
/**
* Creates, after checking, foreign "updateArray" functions such as:
* foreign unsafe import jvm "updateArray" :: JInt5Array -> Int -> Int -> JInt3Array -> JInt3Array;
* where JInt4Array has Java type int[][][][][]
* and JInt2Array has Java type int[][][]
*/
private ForeignFunctionInfo makeForeignUpdateArray(final QualifiedName calName, final Class<?>[] signatureTypes, final SourceRange errorRange) {
final int signatureLength = signatureTypes.length;
//must have an array to update, an index, the updated value and a returned value
if (signatureLength < 4) {
//"The type signature is incompatible with the ''{0}'' external descriptor."
compiler.logMessage(new CompilerMessage(errorRange,
new MessageKind.Error.ForeignFunctionDeclarationInvalidCalType("updateArray")));
return null;
}
final int nSubscriptArgs = signatureLength - 3;
//check that the arguments specifying the subscipting are all ints
for (int i = 1; i < signatureLength - 2; ++i) {
if (signatureTypes[i] != int.class) {
//"The type signature is incompatible with the ''{0}'' external descriptor."
compiler.logMessage(new CompilerMessage(errorRange,
new MessageKind.Error.ForeignFunctionDeclarationInvalidCalType("updateArray")));
return null;
}
}
final Class<?> arrayType = signatureTypes[0];
final Class<?> elementType = signatureTypes[signatureLength - 2];
final Class<?> resultType = signatureTypes[signatureLength - 1];
final Class<?> subscriptedArrayType = getSubscriptedArrayType(arrayType, nSubscriptArgs);
//1) must not subscipt by more dimensions than the array has
//
//2) the CAL type of the function need not match perfectly, but must be assignable e.g.
//foreign unsafe import jvm "subscriptArray" :: JObjectArray -> Int -> String -> ();
//is OK.
if (subscriptedArrayType == null
|| !subscriptedArrayType.isAssignableFrom(elementType)) {
//"The type signature is incompatible with the ''{0}'' external descriptor."
compiler.logMessage(new CompilerMessage(errorRange,
new MessageKind.Error.ForeignFunctionDeclarationInvalidCalType("updateArray")));
return null;
}
if (resultType != elementType) {
//"The type signature is incompatible with the ''{0}'' external descriptor."
compiler.logMessage(new CompilerMessage(errorRange,
new MessageKind.Error.ForeignFunctionDeclarationInvalidCalType("updateArray")));
return null;
}
return new ForeignFunctionInfo.UpdateArray(calName, nSubscriptArgs, ForeignEntityProvider.makeStrict(arrayType), ForeignEntityProvider.makeStrict(elementType));
}
/**
* Make a ForeignFunctionInfo object from its description in a CAL foreign function declaration
* as a Java conversion (cast) or report an error if this can't be done.
*
* @param calName name of the foreign function in CAL.
* @param signatureTypes declared CAL signature of the cast, mapped to Java classes.
* @param errorRange CAL source position to refer to when reporting compilation errors arising from this call.
* @return make a ForeignFunctionInfo object from its description in a CAL foreign function declaration
* as a Java cast or report an error if this can't be done.
*/
private ForeignFunctionInfo makeForeignCast(final QualifiedName calName, final Class<?>[] signatureTypes, final SourceRange errorRange) {
if (signatureTypes.length != 2) {
compiler.logMessage(new CompilerMessage(errorRange,
new MessageKind.Error.ForeignFunctionDeclarationMustHaveExactlyNArgs("cast", 1)));
return null;
}
final Class<?> argumentType = signatureTypes[0];
final Class<?> returnType = signatureTypes[1];
final ForeignFunctionInfo.JavaKind kind = checkForeignCast(argumentType, returnType);
if (kind == null) {
compiler.logMessage(new CompilerMessage(errorRange,
new MessageKind.Error.ForeignFunctionDeclarationInvalidCast(argumentType, returnType)));
return null;
}
return new ForeignFunctionInfo.Cast(calName, kind, ForeignEntityProvider.makeStrict(argumentType), ForeignEntityProvider.makeStrict(returnType));
}
/**
* Helper function to determine if there is a legal cast between 2 Java types, and if so, what kind of
* cast is it e.g. an identity cast, a widening primitive cast, a narrowing primitive cast, a widening
* reference cast or a narrowing reference cast.
* @param argumentType
* @param returnType
* @return the JavaKind of the cast, or null if there is no legal Java cast between these types.
*/
static ForeignFunctionInfo.JavaKind checkForeignCast(final Class<?> argumentType, final Class<?> returnType) {
if (argumentType == null || returnType == null) {
throw new NullPointerException();
}
//classify the kind of cast. Note that some of the tests here are order dependent.
if (argumentType == void.class || returnType == void.class) {
//void is not a first-class Java type and doesn't even qualify for the identity cast
return null;
} else if (argumentType == returnType) {
return ForeignFunctionInfo.JavaKind.IDENTITY_CAST;
} else if (argumentType.isPrimitive()) {
//can't cast:
//1) from a primitive Java type to a non-primitive Java type
//2) if any type is void (this is caught above however to exclude treating void -> void as an identity cast)
//3) from a boolean to any other primitive type
//4) to a boolean from any other primitive type
if (!returnType.isPrimitive()
|| (argumentType == boolean.class && returnType != boolean.class)
|| (argumentType != boolean.class && returnType == boolean.class)) {
return null;
} else if (
(argumentType == float.class
&& returnType == double.class)
|| (argumentType == long.class
&& (returnType == float.class || returnType == double.class))
|| (argumentType == int.class
&& (returnType == long.class || returnType == float.class || returnType == double.class))
|| ((argumentType == char.class || argumentType == short.class)
&& (returnType == int.class || returnType == long.class || returnType == float.class || returnType == double.class))
|| (argumentType == byte.class
&& (returnType == short.class || returnType == int.class || returnType == long.class || returnType == float.class || returnType == double.class))) {
return ForeignFunctionInfo.JavaKind.WIDENING_PRIMITIVE_CAST;
} else {
return ForeignFunctionInfo.JavaKind.NARROWING_PRIMITIVE_CAST;
}
} else {
//can't cast:
//1) from a reference type to a primitive type
//2) if the cast is neither a widening nor a narrowing reference conversion
if (returnType.isPrimitive()) {
return null;
} else if (returnType.isAssignableFrom(argumentType)) {
return ForeignFunctionInfo.JavaKind.WIDENING_REFERENCE_CAST;
} else if (isNarrowingReferenceCast(argumentType, returnType)) {
return ForeignFunctionInfo.JavaKind.NARROWING_REFERENCE_CAST;
} else {
return null;
}
}
}
/**
* This helper method assumes that argumentType cannot be cast to returnType by other means
* (primitive casts, widening casts, identity cast).
*
* The more complicated tests here are as specified in section 2.6.5 of the JVM specification.
* Java gives compile time erros for casts that the compiler can guarantee will always cause
* ClassCastExceptions. This is the cause of the subtlety of some of the conditions below.
*
* @param argumentType
* @param returnType
* @return true if argumentType can be cast to returnType via a narrowing reference cast.
*/
private static boolean isNarrowingReferenceCast(final Class<?> argumentType, final Class<?> returnType) {
if (argumentType.isAssignableFrom(returnType)) {
//covers the following cases explicitly mentioned in section 2.6.5 of the JVM spec:
//from any class type S to any class type T, provided that S is a superclass of T
//from Object to any array type
//from Object to any interface type
//from any interface type J to any class type T that is final, provided T implements J
return true;
} else if (
(isClassType(argumentType))
&& !Modifier.isFinal(argumentType.getModifiers())
&& returnType.isInterface()) {
//we should also check that argumentType does not implement returnType, in which case it would be a widening cast
//but this is caught by an earlier test
return true;
} else if (argumentType.isInterface() && isClassType(returnType) && !Modifier.isFinal(returnType.getModifiers())) {
return true;
} else if (interfacesWithoutConflictingMethods(argumentType, returnType)) {
return true;
} else if (argumentType.isArray() && returnType.isArray()) {
final Class<?> argumentElementType = argumentType.getComponentType();
final Class<?> returnElementType = returnType.getComponentType();
if (argumentElementType.isPrimitive() || returnElementType.isPrimitive()) {
return false;
}
return isNarrowingReferenceCast (argumentElementType, returnElementType);
}
return false;
}
private static boolean isClassType(final Class<?> c) {
return !c.isInterface() && !c.isArray() && !c.isPrimitive();
}
/**
* @param class1
* @param class2
* @return true if class1 and class2 are both interfaces, and they do not declare a method with the same name
* and signature but different return types
*/
private static boolean interfacesWithoutConflictingMethods(final Class<?> class1, final Class<?> class2) {
//to generalize this method to work for non-interfaces would require more work. This is
//because Class.getMethods only returns public methods, which is all that interfaces can have.
if (!class1.isInterface() || !class2.isInterface()) {
return false;
}
final Method[] methods1;
try {
methods1 = class1.getMethods();
} catch (final SecurityException securityException) {
//todoBI handle this. Should log an error
throw securityException;
} catch (final NoClassDefFoundError noClassDefFoundError) {
//todoBI handle this. Should log an error
throw noClassDefFoundError;
} catch (final LinkageError linkageError) {
//todoBI handle this. Should log an error
throw linkageError;
}
for (int i = 0, nMethods1 = methods1.length; i < nMethods1; ++i) {
final Method method1 = methods1[i];
final String methodName = method1.getName();
final Class<?>[] paramTypes = method1.getParameterTypes();
Method method2 = null;
try {
method2 = class2.getMethod(methodName, paramTypes);
} catch (final NoSuchMethodException noSuchMethodException) {
//OK- class2 does not have the method
} catch (final SecurityException securityException) {
//todoBI handle this. Should log an error
throw securityException;
} catch (final NoClassDefFoundError noClassDefFoundError) {
//todoBI handle this. Should log an error
throw noClassDefFoundError;
} catch (final LinkageError linkageError) {
//todoBI handle this. Should log an error
throw linkageError;
}
if (method2 != null && (method1.getReturnType() != method2.getReturnType())) {
//the 2 interfaces have a method with the same name and signature but different return types
return false;
}
}
return true;
}
/**
* Helper function to instantiate a Java class object arising from foreign function.
* The Java class must be public in scope or else an error is logged.
*
* @param qualifiedClassName fully qualified class name e.g. "java.lang.String"
* @param errorRange line in CAL source to refer to in the event of an error
* @param calFunctionName the name of the associated CAL function.
* @return Class
*/
private Class<?> getObjectType (final String qualifiedClassName, final SourceRange errorRange, final QualifiedName calFunctionName) {
final ForeignEntityResolver.ResolutionResult<Class<?>> classResolution = ForeignEntityResolver.resolveClass(ForeignEntityResolver.javaSourceReferenceNameToJvmInternalName(qualifiedClassName), currentModuleTypeInfo.getModule().getForeignClassLoader());
final ForeignEntityResolver.ResolutionStatus resolutionStatus = classResolution.getStatus();
final Class<?> objectType = classResolution.getResolvedEntity();
if (resolutionStatus == ForeignEntityResolver.ResolutionStatus.SUCCESS) {
// the resolution was sucecssful, so no need to report errors
} else if (resolutionStatus == ForeignEntityResolver.ResolutionStatus.NO_SUCH_ENTITY) {
// The Java class {qualifiedClassName} was not found.
compiler.logMessage(new CompilerMessage(
errorRange,
CompilerMessage.Identifier.makeFunction(calFunctionName),
new MessageKind.Error.JavaClassNotFound(qualifiedClassName)));
} else if (resolutionStatus == ForeignEntityResolver.ResolutionStatus.DEPENDEE_CLASS_NOT_FOUND) {
// The Java class {notFoundClass} was not found. This class is required by {qualifiedClassName}.
compiler.logMessage(new CompilerMessage(
errorRange,
CompilerMessage.Identifier.makeFunction(calFunctionName),
new MessageKind.Error.DependeeJavaClassNotFound(classResolution.getAssociatedMessage(), qualifiedClassName)));
} else if (resolutionStatus == ForeignEntityResolver.ResolutionStatus.CANNOT_LOAD_CLASS) {
// The definition of Java class {qualifiedClassName} could not be loaded.
compiler.logMessage(new CompilerMessage(
errorRange,
CompilerMessage.Identifier.makeFunction(calFunctionName),
new MessageKind.Error.JavaClassDefinitionCouldNotBeLoaded(qualifiedClassName)));
} else if (resolutionStatus == ForeignEntityResolver.ResolutionStatus.CANNOT_INITIALIZE_CLASS) {
// The Java class {qualifiedClassName} could not be initialized.
compiler.logMessage(new CompilerMessage(
errorRange,
CompilerMessage.Identifier.makeFunction(calFunctionName),
new MessageKind.Error.JavaClassCouldNotBeInitialized(qualifiedClassName)));
} else if (resolutionStatus == ForeignEntityResolver.ResolutionStatus.LINKAGE_ERROR) {
// The java class {qualifiedClassName} was found, but there were problems with using it.
// Class: {LinkageError.class}
// Message: {e.getMessage()}
compiler.logMessage(new CompilerMessage(
errorRange,
CompilerMessage.Identifier.makeFunction(calFunctionName),
new MessageKind.Error.ProblemsUsingJavaClass(qualifiedClassName, (LinkageError)classResolution.getThrowable())));
} else if (resolutionStatus == ForeignEntityResolver.ResolutionStatus.NOT_ACCESSIBLE) {
//"The Java type ''{0}'' is not accessible. It does not have public scope or is in an unnamed package."
//the parameter {0} will be replaced by "class java.lang.Foo" or "interface java.lang.Foo" as appropriate.
compiler.logMessage(
new CompilerMessage(
errorRange,
CompilerMessage.Identifier.makeFunction(calFunctionName),
new MessageKind.Error.ExternalClassNotAccessible(objectType)));
} else {
// Some other unexpected status
throw new IllegalStateException("Unexpected status: " + resolutionStatus);
}
return objectType;
}
/**
* Maps the CAL type signature to Java types, if possible.
* For example Int -> Char -> JString is mapped to [int.class, char.class, String.class].
* @param typeExpr typeExpression of the foreign function withing CAL
* @param errorRange CAL source position to refer to when reporting compilation errors arising from this call.
*/
private Class<?>[] calculateForeignTypeSignature(final TypeExpr typeExpr, final SourceRange errorRange) throws UnableToResolveForeignEntityException {
final TypeExpr[] typePieces = typeExpr.getTypePieces();
final int nTypePieces = typePieces.length;
final Class<?>[] signatureTypes = new Class[nTypePieces];
for (int i = 0; i < nTypePieces; ++i) {
Class<?> javaType = null;
//the argument and return types of the function must resolve to
//one of the built-in types that map to the standard Java primitive types.
//i.e. char, boolean, byte, short, int, long, float, double, void
//or one of the visible types introduced by foreign data declarations.
final TypeExpr calType = typePieces[i].prune();
if (calType instanceof TypeConsApp) {
final QualifiedName typeConsName = ((TypeConsApp)calType).getName();
//Prelude.Unit and Prelude.Boolean are special cases in that they are algebraic types that however correspond
//to foreign types for the purposes of declaring foreign functions.
if (typeConsName.equals(CAL_Prelude.TypeConstructors.Unit)) {
javaType = void.class;
} else if (typeConsName.equals(CAL_Prelude.TypeConstructors.Boolean)) {
javaType = boolean.class;
} else {
final TypeConstructor typeCons = currentModuleTypeInfo.getVisibleTypeConstructor(typeConsName);
ForeignTypeInfo foreignTypeInfo;
if (typeCons != null && (foreignTypeInfo = typeCons.getForeignTypeInfo()) != null) {
//check that the implementation of the foreign CAL type as a Java type is visible to the client code
final ModuleName currentModuleName = currentModuleTypeInfo.getModuleName();
final ModuleName entityModuleName = typeConsName.getModuleName();
final Scope implementationVisibility = foreignTypeInfo.getImplementationVisibility();
if (currentModuleName.equals(entityModuleName) ||
implementationVisibility.isPublic() ||
(implementationVisibility.isProtected() && currentModuleTypeInfo.getImportedModule(entityModuleName).hasFriendModule(currentModuleName))) {
javaType = foreignTypeInfo.getForeignType();
} else {
// The implementation of {calType} as a foreign type is not visible within this module.
compiler.logMessage(new CompilerMessage(errorRange, new MessageKind.Error.ImplementationAsForeignTypeNotVisible(calType.toString())));
}
} else {
//argument type was not resolved to a built in primitive type with a mapping to Java built-in primitive type.
//or to a visible foreign data type with visible implementation.
compiler.logMessage(new CompilerMessage(errorRange, new MessageKind.Error.TypeNotSupportedForForeignCalls(calType.toString())));
}
}
} else if (calType instanceof RecordType ||
calType instanceof TypeVar ||
calType instanceof TypeApp) {
compiler.logMessage(new CompilerMessage(errorRange, new MessageKind.Error.TypeNotSupportedForForeignCalls(calType.toString())));
} else {
throw new IllegalStateException();
}
signatureTypes[i] = javaType;
}
return signatureTypes;
}
/**
* Obtains the classes provided by an array of class providers, and returns them in an array.
* @param classProviders an array of class providers.
* @return the corresponding array of Class objects, or null if any of the providers returned a null value.
* @throws UnableToResolveForeignEntityException if any of the providers could not resolve a Class object.
*/
private static Class<?>[] getArrayOfClassesFromProviders(final ForeignEntityProvider<Class<?>>[] classProviders) throws UnableToResolveForeignEntityException {
final int nProviders = classProviders.length;
final Class<?>[] classes = new Class[nProviders];
for (int i = 0; i < nProviders; i++) {
final Class<?> theClass = classProviders[i].get();
classes[i] = theClass;
if (theClass == null) {
return null;
}
}
return classes;
}
/**
* A helper method to create an array with the type {@code ForeignEntityProvider<Class<?>>[]} via an unchecked cast.
* @param length the length of the array.
* @return the array.
*/
@SuppressWarnings("unchecked")
static ForeignEntityProvider<Class<?>>[] makeForeignEntityProviderClassArray(final int length) {
return new ForeignEntityProvider[length];
}
}