/* * 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. */ /* * ForeignFunctionInfo.java * Created: May 6, 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 org.openquark.cal.internal.serialization.ModuleSerializationTags; import org.openquark.cal.internal.serialization.RecordInputStream; import org.openquark.cal.internal.serialization.RecordOutputStream; import org.openquark.cal.internal.serialization.RecordInputStream.RecordHeaderInfo; import org.openquark.util.Pair; /** * Class used to provide the information needed to call a CAL foreign function. * The CAL foreign function can be, as a Java entity, one of: * <ol> * <li> a Java (non-static) method * <li> a Java static method * <li> a Java (non-static) field * <li> a Java static field * <li> a Java constructor * <li> a Java identity cast (conversion) (see the JVM spec section 2.6 for the definition of the various conversions) * <li> a Java widening primitive cast (conversion) * <li> a Java narrowing primitive cast (conversion) * <li> a Java widening reference cast (conversion) * <li> a Java narrowing reference cast (conversion) * <li> a Java instanceof operator * <li> a Java null reference value * <li> a Java isNull reference check * <li> a Java isNotNull reference check * <li> array creation (new array) * <li> array length * <li> array subscript * <li> array update * <li> a Java class literal * </ol> * <p> * Note in particular that the Java entity need not be a Java function e.g. conversions corresponds to casts in Java. * They are however exposed as functions within CAL. * <p> * This class must remain immutable, as objects of the class are intended to be shared. * <p> * Creation date: (May 6, 2002) * @author Bo Ilic */ public abstract class ForeignFunctionInfo { /** CAL function name corresponding to this ForeignFunctionInfo object */ private final QualifiedName calName; /** whether a method, static method, field, static field, constructor or cast. */ private final JavaKind javaKind; /** * The array of possible record tags used in calls to {@link RecordInputStream#findRecord(short[])} by * the {@link #load} method. */ private static final short[] SERIALIZATION_RECORD_TAGS = new short[] { ModuleSerializationTags.FOREIGN_FUNCTION_INFO_INVOCATION, ModuleSerializationTags.FOREIGN_FUNCTION_INFO_CAST, ModuleSerializationTags.FOREIGN_FUNCTION_INFO_INSTANCE_OF, ModuleSerializationTags.FOREIGN_FUNCTION_INFO_NULL_LITERAL, ModuleSerializationTags.FOREIGN_FUNCTION_INFO_NULL_CHECK, ModuleSerializationTags.FOREIGN_FUNCTION_INFO_NEW_ARRAY, ModuleSerializationTags.FOREIGN_FUNCTION_INFO_LENGTH_ARRAY, ModuleSerializationTags.FOREIGN_FUNCTION_INFO_SUBSCRIPT_ARRAY, ModuleSerializationTags.FOREIGN_FUNCTION_INFO_UPDATE_ARRAY, ModuleSerializationTags.FOREIGN_FUNCTION_INFO_CLASS_LITERAL }; /** * Type-safe enumeration describing the different kinds of Java entities that can be imported into CAL * via a foreign function declaration. * * @author Bo Ilic */ public static final class JavaKind { private static final int serializationSchema = 0; private final String description; private final int ordinal; private static final int METHOD_ORDINAL = 0; private static final int STATIC_METHOD_ORDINAL = 1; private static final int FIELD_ORDINAL = 2; private static final int STATIC_FIELD_ORDINAL = 3; private static final int CONSTRUCTOR_ORDINAL = 4; private static final int IDENTITY_CAST_ORDINAL = 5; private static final int WIDENING_PRIMITIVE_CAST_ORDINAL = 6; private static final int NARROWING_PRIMITIVE_CAST_ORDINAL = 7; private static final int WIDENING_REFERENCE_CAST_ORDINAL = 8; private static final int NARROWING_REFERENCE_CAST_ORDINAL = 9; private static final int INSTANCE_OF_ORDINAL = 10; private static final int NULL_LITERAL_ORDINAL = 11; private static final int NULL_CHECK_ORDINAL = 12; private static final int NEW_ARRAY_ORDINAL = 13; private static final int LENGTH_ARRAY_ORDINAL = 14; private static final int SUBSCRIPT_ARRAY_ORDINAL = 15; private static final int UPDATE_ARRAY_ORDINAL = 16; private static final int CLASS_LITERAL_ORDINAL = 17; public static final JavaKind METHOD = new JavaKind ("method", METHOD_ORDINAL); public static final JavaKind STATIC_METHOD = new JavaKind ("static method", STATIC_METHOD_ORDINAL); public static final JavaKind FIELD = new JavaKind ("field", FIELD_ORDINAL); public static final JavaKind STATIC_FIELD = new JavaKind ("static field", STATIC_FIELD_ORDINAL); public static final JavaKind CONSTRUCTOR = new JavaKind ("constructor", CONSTRUCTOR_ORDINAL); /** * A cast between any Java type T and itself. */ public static final JavaKind IDENTITY_CAST = new JavaKind ("identity cast", IDENTITY_CAST_ORDINAL); /** * A valid cast from a Java primitive type T to a Java primitive type S that is not an identity cast * and such that the numeric value is preserved exactly e.g. casting an int to a long. Casting a float * to a double is also a widening primitive cast, but only necessarily preserve the value exactly in strictfp mode. */ public static final JavaKind WIDENING_PRIMITIVE_CAST = new JavaKind("widening primitive cast", WIDENING_PRIMITIVE_CAST_ORDINAL); /** * A valid cast from a Java primitive type T to a Java primitive type S that is not an identity cast nor a widening primitive * cast. Such casts can lose precision about the numeric value e.g. casting long to a byte. */ public static final JavaKind NARROWING_PRIMITIVE_CAST = new JavaKind("narrowing primitive cast", NARROWING_PRIMITIVE_CAST_ORDINAL); /** * A cast of a Java reference type to a supertype. */ public static final JavaKind WIDENING_REFERENCE_CAST = new JavaKind("widening reference cast", WIDENING_REFERENCE_CAST_ORDINAL); /** * A cast of a Java reference type to another Java reference type that is not an identity cast or widening reference cast. * These are casts whose validy must be checked at runtime. Java does not allow narrowing casts that can be guaranteed to * fail at compile time. */ public static final JavaKind NARROWING_REFERENCE_CAST = new JavaKind("narrowing reference cast", NARROWING_REFERENCE_CAST_ORDINAL); /** * A call to the Java "instanceof" primitive operator. */ public static final JavaKind INSTANCE_OF = new JavaKind("instanceof", INSTANCE_OF_ORDINAL); /** * A Java literal null value. */ public static final JavaKind NULL_LITERAL = new JavaKind("null", NULL_LITERAL_ORDINAL); /** * Either "expr == null" or "expr != null". */ public static final JavaKind NULL_CHECK = new JavaKind("nullCheck", NULL_CHECK_ORDINAL); /** * A Java class literal (of Java type java.lang.Class). */ public static final JavaKind CLASS_LITERAL = new JavaKind("class", CLASS_LITERAL_ORDINAL); public static final JavaKind NEW_ARRAY = new JavaKind("newArray", NEW_ARRAY_ORDINAL); public static final JavaKind LENGTH_ARRAY = new JavaKind("lengthArray", LENGTH_ARRAY_ORDINAL); public static final JavaKind UPDATE_ARRAY = new JavaKind("updateArray", UPDATE_ARRAY_ORDINAL); public static final JavaKind SUBSCRIPT_ARRAY = new JavaKind("subscriptArray", SUBSCRIPT_ARRAY_ORDINAL); private JavaKind (final String description, final int ordinal) { this.description = description; this.ordinal = ordinal; } /** * @return true for a method (static or not), field (static or not) or constructor invocation. */ public boolean isInvocation() { return isMethod() || isField() || isConstructor(); } /** * @return boolean true if this is a method or static method */ public boolean isMethod() { return this == METHOD || this == STATIC_METHOD; } /** * @return boolean true if this is a field or static field */ public boolean isField() { return this == FIELD || this == STATIC_FIELD; } /** * @return boolean true if this is a constructor */ public boolean isConstructor() { return this == CONSTRUCTOR; } /** * @return boolean true if this is a cast */ public boolean isCast() { switch (ordinal) { case IDENTITY_CAST_ORDINAL: case WIDENING_PRIMITIVE_CAST_ORDINAL: case NARROWING_PRIMITIVE_CAST_ORDINAL: case WIDENING_REFERENCE_CAST_ORDINAL: case NARROWING_REFERENCE_CAST_ORDINAL: return true; default: return false; } } /** * @return true if this is the "instanceof" JavaKind. */ public boolean isInstanceOf() { return this == INSTANCE_OF; } public boolean isNullLiteral() { return this == NULL_LITERAL; } public boolean isNullCheck() { return this == NULL_CHECK; } /** * @return true if this is the "class" JavaKind. */ public boolean isClassLiteral() { return this == CLASS_LITERAL; } /** * @return boolean true if this is a static method, static field, or constructor */ public boolean isStatic() { return this == STATIC_METHOD || this == STATIC_FIELD || this == CONSTRUCTOR; } /** * {@inheritDoc} */ @Override public String toString () { return description; } /** * Write the JavaKind instance to the RecordOutputStream. * @param s * @throws IOException */ final void write (final RecordOutputStream s) throws IOException { s.startRecord(ModuleSerializationTags.FOREIGN_FUNCTION_INFO_JAVA_KIND, serializationSchema); s.writeByte(ordinal); s.endRecord(); } /** * Load an instance of JavaKind from the RecordInputStream. * @param s * @param moduleName the name of the module being loaded * @param msgLogger the logger to which to log deserialization messages. * @return an instance of Kind * @throws IOException */ static final JavaKind load (final RecordInputStream s, final ModuleName moduleName, final CompilerMessageLogger msgLogger) throws IOException { // Look for Record header. final RecordHeaderInfo rhi = s.findRecord(ModuleSerializationTags.FOREIGN_FUNCTION_INFO_JAVA_KIND); if(rhi == null) { throw new IOException("Unable to find ForeignFunctionInfo.JavaKind record header."); } DeserializationHelper.checkSerializationSchema(rhi.getSchema(), serializationSchema, moduleName, "ForeignFunctionInfo.JavaKind", msgLogger); final byte ordinalValue = s.readByte(); s.skipRestOfRecord(); switch (ordinalValue) { case METHOD_ORDINAL: return METHOD; case STATIC_METHOD_ORDINAL: return STATIC_METHOD; case FIELD_ORDINAL: return FIELD; case STATIC_FIELD_ORDINAL: return STATIC_FIELD; case CONSTRUCTOR_ORDINAL: return CONSTRUCTOR; case IDENTITY_CAST_ORDINAL: return IDENTITY_CAST; case WIDENING_PRIMITIVE_CAST_ORDINAL: return WIDENING_PRIMITIVE_CAST; case NARROWING_PRIMITIVE_CAST_ORDINAL: return NARROWING_PRIMITIVE_CAST; case WIDENING_REFERENCE_CAST_ORDINAL: return WIDENING_REFERENCE_CAST; case NARROWING_REFERENCE_CAST_ORDINAL: return NARROWING_REFERENCE_CAST; case INSTANCE_OF_ORDINAL: return INSTANCE_OF; case NULL_LITERAL_ORDINAL: return NULL_LITERAL; case NULL_CHECK_ORDINAL: return NULL_CHECK; case NEW_ARRAY_ORDINAL: return NEW_ARRAY; case LENGTH_ARRAY_ORDINAL: return LENGTH_ARRAY; case UPDATE_ARRAY_ORDINAL: return UPDATE_ARRAY; case SUBSCRIPT_ARRAY_ORDINAL: return SUBSCRIPT_ARRAY; case CLASS_LITERAL_ORDINAL: return CLASS_LITERAL; } throw new IOException("Unable to resolve JavaKind with ordinal: " + ordinalValue); } } /** * An abstract base class for a resolver which resolves a pair of classes at the same time, potentially running validity * checks on the pair of classes. This class is meant to act as an adapter, for it provides a * {@link org.openquark.cal.compiler.ForeignEntityProvider.Resolver Resolver} implementation for each of the first and second * class in the pair. * * @author Joseph Wong */ private static abstract class ClassPairResolver { /** * The cached pair of classes. Initially null. A value will be assigned on the first call to * {@link #synchronizedGet}. This field must be accessed in a thread-safe manner. */ private Pair<Class<?>, Class<?>> pair; /** * A Resolver for the first class in the pair. */ private final ForeignEntityProvider.Resolver<Class<?>> firstResolver; /** * A Resolver for the second class in the pair. */ private final ForeignEntityProvider.Resolver<Class<?>> secondResolver; /** * Constructor for this abstract base class. * @param firstDisplayName a display name for the first class. * @param secondDisplayName a display name for the second class. */ ClassPairResolver(final String firstDisplayName, final String secondDisplayName) { firstResolver = new ForeignEntityProvider.Resolver<Class<?>>(firstDisplayName) { @Override Class<?> resolve(final ForeignEntityProvider.MessageHandler messageHandler) throws UnableToResolveForeignEntityException { return synchronizedGet(messageHandler).fst(); } }; secondResolver = new ForeignEntityProvider.Resolver<Class<?>>(secondDisplayName) { @Override Class<?> resolve(final ForeignEntityProvider.MessageHandler messageHandler) throws UnableToResolveForeignEntityException { return synchronizedGet(messageHandler).snd(); } }; } /** * Resolves the pair of classes from their specs. * @param messageHandler the MessageHandler to use for handling {@link CompilerMessage}s. * @return the pair of classes. * @throws UnableToResolveForeignEntityException if this exception is thrown by the message handler, it is propagated. */ abstract Pair<Class<?>, Class<?>>resolveClassPair(ForeignEntityProvider.MessageHandler messageHandler) throws UnableToResolveForeignEntityException; /** * Returns the pair of classes, running the resolution logic and caching its result on first invocation. * @return the pair of classes. * @throws UnableToResolveForeignEntityException if this exception is thrown by the message handler, it is propagated. */ private final synchronized Pair<Class<?>, Class<?>> synchronizedGet(final ForeignEntityProvider.MessageHandler messageHandler) throws UnableToResolveForeignEntityException { if (pair == null) { pair = resolveClassPair(messageHandler); } return pair; } /** * @return a Resolver for the first class in the pair. */ ForeignEntityProvider.Resolver<Class<?>> getFirstResolver() { return firstResolver; } /** * @return a Resolver for the second class in the pair. */ ForeignEntityProvider.Resolver<Class<?>> getSecondResolver() { return secondResolver; } } /** * Information about a CAL foreign function that is in fact a Java invocation of a * field (static or non-static), method (static or non-static) or constructor. * * @author Bo Ilic */ public static final class Invocation extends ForeignFunctionInfo { private static final int serializationSchema = 0; /** Provider for the field, method or constructor that this foreign function corresponds to. */ private final ForeignEntityProvider<? extends AccessibleObject> javaProxyProvider; /** * Provider for the class from which to invoke a method or field (both static and non-static) which cannot be null, * unless this is a constructor invocation. * <i>The provider itself will be null only for constructors.</i> * * It is sometimes necessary to invoke a method/field from a class other than which it was defined. * For example, if package scope class A defines a static public field f, and public class B extends A, * then B.f in a different package will not result in a compilation error but A.f will. * * Or for example, if package scope class A defines a non-static public method m, and public class B extends A, * then in a different package we cannot invoke m on an object of type B if: * - the invocation is done via reflection, or * - the reference is first cast to the method's declared type, in this case A, i.e. ((A)b).m() */ private final ForeignEntityProvider<Class<?>> invocationClassProvider; /** * Provider for the class corresponding to the declared return type of the foreign function, which cannot be null. */ private final ForeignEntityProvider<Class<?>> declaredReturnTypeProvider; /** number of arguments, considered as a CAL foreign function. */ private int nArguments = -1; // the initial sentinel value is -1, which will be replaced by the correct value on first access /** * Private constructor for a method, field or constructor invocation. * @param calName CAL name of the foreign function e.g. Prelude.sin * @param invocationClassProvider provider for the class from which to invoke a method or field (both static and non-static) which cannot be null, * unless this is a constructor invocation. * @param javaProxyProvider provider for the accessible object corresponding to the foreign function e.g. "java.lang.Math.sqrt", which can be * a Java method, constructor or field. * @param declaredReturnTypeProvider provider for the class corresponding to the declared return type of the foreign function, which cannot be null. * @param javaKind the JavaKind describing the kind of Java entity represented (static/non-static method, static/non-static field, or constructor) */ private Invocation(final QualifiedName calName, final ForeignEntityProvider<Class<?>> invocationClassProvider, final ForeignEntityProvider<? extends AccessibleObject> javaProxyProvider, final ForeignEntityProvider<Class<?>> declaredReturnTypeProvider, final JavaKind javaKind) { super(calName, javaKind); if (javaProxyProvider == null || declaredReturnTypeProvider == null) { throw new NullPointerException(); } this.invocationClassProvider = invocationClassProvider; // can be null this.javaProxyProvider = javaProxyProvider; this.declaredReturnTypeProvider = declaredReturnTypeProvider; } /** * Factory method for a static method invocation. * @param calName CAL name of the foreign function e.g. Prelude.sin * @param invocationClassProvider provider for the class from which to invoke a static method. The provider cannot be null, and the provided class cannot be null. * @param javaProxyProvider provider for the accessible object corresponding to the foreign function e.g. "java.lang.Math.sqrt", which must be * a Java method. * @param declaredReturnTypeProvider provider for the class corresponding to the declared return type of the foreign function, which cannot be null. * @return an Invocation instance. */ static Invocation makeStaticMethod(final QualifiedName calName, final ForeignEntityProvider<Class<?>> invocationClassProvider, final ForeignEntityProvider<Method> javaProxyProvider, final ForeignEntityProvider<Class<?>> declaredReturnTypeProvider) { return new Invocation(calName, invocationClassProvider, javaProxyProvider, declaredReturnTypeProvider, JavaKind.STATIC_METHOD); } /** * Factory method for a non-static method invocation. * @param calName CAL name of the foreign function e.g. Prelude.sin * @param invocationClassProvider provider for the class from which to invoke a non-static method. The provider cannot be null, and the provided class cannot be null. * @param javaProxyProvider provider for the accessible object corresponding to the foreign function e.g. "java.lang.Math.sqrt", which must be * a Java method. * @param declaredReturnTypeProvider provider for the class corresponding to the declared return type of the foreign function, which cannot be null. * @return an Invocation instance. */ static Invocation makeNonStaticMethod(final QualifiedName calName, final ForeignEntityProvider<Class<?>> invocationClassProvider, final ForeignEntityProvider<Method> javaProxyProvider, final ForeignEntityProvider<Class<?>> declaredReturnTypeProvider) { return new Invocation(calName, invocationClassProvider, javaProxyProvider, declaredReturnTypeProvider, JavaKind.METHOD); } /** * Factory method for a static field access. * @param calName CAL name of the foreign function e.g. Prelude.sin * @param invocationClassProvider provider for the class from which to invoke a static field. The provider cannot be null, and the provided class cannot be null. * @param javaProxyProvider provider for the accessible object corresponding to the foreign function e.g. "java.lang.Math.sqrt", which must be * a Java field. * @param declaredReturnTypeProvider provider for the class corresponding to the declared return type of the foreign function, which cannot be null. * @return an Invocation instance. */ static Invocation makeStaticField(final QualifiedName calName, final ForeignEntityProvider<Class<?>> invocationClassProvider, final ForeignEntityProvider<Field> javaProxyProvider, final ForeignEntityProvider<Class<?>> declaredReturnTypeProvider) { return new Invocation(calName, invocationClassProvider, javaProxyProvider, declaredReturnTypeProvider, JavaKind.STATIC_FIELD); } /** * Factory method for a non-static field access. * @param calName CAL name of the foreign function e.g. Prelude.sin * @param invocationClassProvider provider for the class from which to invoke a non-static field. The provider cannot be null, and the provided class cannot be null. * @param javaProxyProvider provider for the accessible object corresponding to the foreign function e.g. "java.lang.Math.sqrt", which must be * a Java field. * @param declaredReturnTypeProvider provider for the class corresponding to the declared return type of the foreign function, which cannot be null. * @return an Invocation instance. */ static Invocation makeNonStaticField(final QualifiedName calName, final ForeignEntityProvider<Class<?>> invocationClassProvider, final ForeignEntityProvider<Field> javaProxyProvider, final ForeignEntityProvider<Class<?>> declaredReturnTypeProvider) { return new Invocation(calName, invocationClassProvider, javaProxyProvider, declaredReturnTypeProvider, JavaKind.FIELD); } /** * Factory method for a constructor invocation. * @param calName CAL name of the foreign function e.g. Prelude.sin * @param javaProxyProvider provider for the accessible object corresponding to the foreign function e.g. "java.lang.Math.sqrt", which must be * a Java constructor. * @param declaredReturnTypeProvider provider for the class corresponding to the declared return type of the foreign function, which cannot be null. * @return an Invocation instance. */ static Invocation makeConstructor(final QualifiedName calName, final ForeignEntityProvider<Constructor<?>> javaProxyProvider, final ForeignEntityProvider<Class<?>> declaredReturnTypeProvider) { return new Invocation(calName, null, javaProxyProvider, declaredReturnTypeProvider, JavaKind.CONSTRUCTOR); } /** * The class from which to invoke a method or field (both static and non-static) which cannot be null, * unless this is a constructor invocation. * * It is sometimes necessary to invoke a method/field from a class other than which it was defined. * For example, if package scope class A defines a static public field f, and public class B extends A, * then B.f in a different package will not result in a compilation error but A.f will. * * Or for example, if package scope class A defines a non-static public method m, and public class B extends A, * then in a different package we cannot invoke m on an object of type B if: * - the invocation is done via reflection, or * - the reference is first cast to the method's declared type, in this case A, i.e. ((A)b).m() * * @return the class from which to invoke a method or field (both static and non-static) which cannot be null, * unless this is a constructor invocation. * @throws UnableToResolveForeignEntityException */ public Class<?> getInvocationClass() throws UnableToResolveForeignEntityException { if (invocationClassProvider == null) { return null; } else { return invocationClassProvider.get(); } } /** * Creation date: (June 28, 2002) * @return AccessibleObject the field, method or constructor that this foreign function corresponds to. * @throws UnableToResolveForeignEntityException */ public AccessibleObject getJavaProxy() throws UnableToResolveForeignEntityException { return javaProxyProvider.get(); } /** * {@inheritDoc} */ @Override public synchronized int getNArguments() throws UnableToResolveForeignEntityException { // We want to avoid recalculating this every time, since getParameterTypes() involves copying a Class array every time. // Thus we cache the value on first access. // todo-jowong can synchronization be removed if nArguments is made a *volatile* field? if (nArguments == -1) { final int nArgs; final JavaKind javaKind = getJavaKind(); if (javaKind.isMethod()) { final int nParams = ((Method)getJavaProxy()).getParameterTypes().length; if (javaKind.isStatic()) { nArgs = nParams; } else { nArgs = nParams + 1; // not static, so the invocation target itself is the first arg, and the other params come after } } else if (javaKind.isField()) { if (javaKind.isStatic()) { nArgs = 0; } else { nArgs = 1; // not static, so the invocation target itself is an argument } } else if (javaKind.isConstructor()) { nArgs = ((Constructor<?>)getJavaProxy()).getParameterTypes().length; } else { throw new IllegalStateException(); } // set the calculated value into the cache field nArguments = nArgs; } return nArguments; } /** {@inheritDoc} */ @Override public Class<?> getJavaArgumentType(final int argN) throws UnableToResolveForeignEntityException { if (getJavaProxy() instanceof Method) { return ((Method)getJavaProxy()).getParameterTypes()[argN]; } else if (getJavaProxy() instanceof Field) { throw new IndexOutOfBoundsException(); } else { return ((Constructor<?>)getJavaProxy()).getParameterTypes()[argN]; } } /** {@inheritDoc} */ @Override public Class<?> getJavaReturnType() throws UnableToResolveForeignEntityException { if (getJavaProxy() instanceof Method) { return ((Method)getJavaProxy()).getReturnType(); } else if (getJavaProxy() instanceof Field) { return ((Field)getJavaProxy()).getType(); } else { return ((Constructor<?>)getJavaProxy()).getDeclaringClass(); } } /** {@inheritDoc} */ @Override public void resolveForeignEntities() throws UnableToResolveForeignEntityException { // Force resolution by calling get() on each of the ForeignEntityProviders javaProxyProvider.get(); if (invocationClassProvider != null) { invocationClassProvider.get(); } declaredReturnTypeProvider.get(); } /** * {@inheritDoc} */ @Override public String toString() { final StringBuilder result = new StringBuilder ("foreign ").append(super.calName); result.append(" [kind = ").append(super.javaKind).append("]"); result.append(" [java = ").append(javaProxyProvider).append("]"); return result.toString(); } /** * {@inheritDoc} */ @Override final void write (final RecordOutputStream s) throws IOException, UnableToResolveForeignEntityException { s.startRecord(ModuleSerializationTags.FOREIGN_FUNCTION_INFO_INVOCATION, serializationSchema); super.write(s); if (getJavaKind().isMethod()) { final Method m = (Method)getJavaProxy(); final Class<?> declaringClassOrInvocationClass = getInvocationClass() != null ? getInvocationClass() : m.getDeclaringClass(); s.writeUTF(declaringClassOrInvocationClass.getName()); s.writeUTF(m.getName()); final Class<?>[] pTypes = m.getParameterTypes(); s.writeShortCompressed(pTypes.length); for (int i = 0; i < pTypes.length; ++i) { s.writeUTF(pTypes[i].getName()); } s.writeUTF(declaredReturnTypeProvider.get().getName()); } else if (getJavaKind().isField()) { final Field f = (Field)getJavaProxy(); final Class<?> declaringClassOrInvocationClass = getInvocationClass() != null ? getInvocationClass() : f.getDeclaringClass(); s.writeUTF(declaringClassOrInvocationClass.getName()); s.writeUTF(f.getName()); s.writeUTF(f.getType().getName()); s.writeUTF(declaredReturnTypeProvider.get().getName()); } else if (getJavaKind().isConstructor()) { final Constructor<?> c = (Constructor<?>)getJavaProxy(); s.writeUTF(c.getDeclaringClass().getName()); final Class<?>[] pTypes = c.getParameterTypes(); s.writeShortCompressed(pTypes.length); for (int i = 0; i < pTypes.length; ++i) { s.writeUTF(pTypes[i].getName()); } s.writeUTF(declaredReturnTypeProvider.get().getName()); } else { throw new IOException ("Unknown or invalid foreign function kind encountered saving " + getCalName().getQualifiedName()); } s.endRecord(); } /** * Load an instance of ForeignFunctionInfo from the RecordInputStream. * Read position will be before the record header. * @param s * @param moduleName the name of the module being loaded * @param foreignClassLoader the classloader to use to resolve foreign classes. * @param msgLogger the logger to which to log deserialization messages. * @return an instance of ForeignFunctionInfo, or null if there was a problem resolving classes. * @throws IOException */ private static final ForeignFunctionInfo loadInvocation (final RecordInputStream s, final int schema, final ModuleName moduleName, final ClassLoader foreignClassLoader, final CompilerMessageLogger msgLogger) throws IOException { DeserializationHelper.checkSerializationSchema(schema, serializationSchema, moduleName, "ForeignFunctionInfo.Invocation", msgLogger); //read the superclass fields final QualifiedName calName = s.readQualifiedName(); final JavaKind javaKind = JavaKind.load(s, moduleName, msgLogger); ForeignFunctionInfo foreignFunctionInfo; if (javaKind.isMethod()) { foreignFunctionInfo = loadMethod(s, calName, javaKind.isStatic(), foreignClassLoader, msgLogger); } else if (javaKind.isField()) { foreignFunctionInfo = loadField(s, calName, javaKind.isStatic(), foreignClassLoader, msgLogger); } else if (javaKind.isConstructor()) { foreignFunctionInfo = loadConstructor(s, calName, foreignClassLoader, msgLogger); } else { throw new IOException ("Unknown foreign function kind encountered loading ForeignFunctionInfo " + calName); } s.skipRestOfRecord(); return foreignFunctionInfo; } /** * Read position will be before the containing class name. * @param s * @param calName the name of the cal function being loaded - for use in error strings * @param isStatic true if the Java method is static. * @param foreignClassLoader the classloader to use to resolve foreign classes. * @param msgLogger the logger to which to log deserialization messages. * @return ForeignFunctionInfo * @throws IOException */ private static final ForeignFunctionInfo loadMethod(final RecordInputStream s, final QualifiedName calName, final boolean isStatic, final ClassLoader foreignClassLoader, final CompilerMessageLogger msgLogger) throws IOException { final String declaringClassOrInvocationClassName = s.readUTF(); final ForeignEntityProvider<Class<?>> declaringClassOrInvocationClassProvider = DeserializationHelper.classProviderForName(declaringClassOrInvocationClassName, foreignClassLoader, "ForeignFunctionInfo " + calName, CompilerMessage.Identifier.makeFunction(calName), msgLogger); if (declaringClassOrInvocationClassProvider == null) { return null; } final String funcName = s.readUTF(); final int nJArgs = s.readShortCompressed(); final ForeignEntityProvider<Class<?>> argClassProviders[] = ForeignFunctionChecker.makeForeignEntityProviderClassArray(nJArgs); for (int i = 0; i < nJArgs; ++i) { final String argClassName = s.readUTF(); final ForeignEntityProvider<Class<?>> argClassProvider = DeserializationHelper.classProviderForName(argClassName, foreignClassLoader, "ForeignFunctionInfo " + calName, CompilerMessage.Identifier.makeFunction(calName), msgLogger); if (argClassProvider == null) { return null; } argClassProviders[i] = argClassProvider; } final String declaredReturnTypeName = s.readUTF(); final ForeignEntityProvider<Class<?>> declaredReturnTypeProvider = DeserializationHelper.classProviderForName(declaredReturnTypeName, foreignClassLoader, "ForeignFunctionInfo " + calName, CompilerMessage.Identifier.makeFunction(calName), msgLogger); if (declaredReturnTypeProvider == null) { return null; } final SourceRange sourceRange = new SourceRange(calName.getModuleName().toSourceText()); final String javaName = declaringClassOrInvocationClassName + "." + funcName; return ForeignFunctionChecker.makeForeignFunctionInfoForMethod(calName, javaName, isStatic, sourceRange, funcName, declaringClassOrInvocationClassName, declaringClassOrInvocationClassProvider, argClassProviders, declaredReturnTypeProvider, msgLogger, true); } /** * Read position will be before the declaring (non-static field) or invocation (static field) class name. * @param s * @param calName the name of the cal function being loaded - for use in error strings * @param isStatic true if the Java method is static. * @param foreignClassLoader the classloader to use to resolve foreign classes. * @param msgLogger the logger to which to log deserialization messages. * @return a field instance, or null if there was a problem resolving classes. * @throws IOException */ private static final ForeignFunctionInfo loadField(final RecordInputStream s, final QualifiedName calName, final boolean isStatic, final ClassLoader foreignClassLoader, final CompilerMessageLogger msgLogger) throws IOException { final String declaringClassOrInvocationClassName = s.readUTF(); final ForeignEntityProvider<Class<?>> declaringClassOrInvocationClassProvider = DeserializationHelper.classProviderForName(declaringClassOrInvocationClassName, foreignClassLoader, "ForeignFunctionInfo " + calName, CompilerMessage.Identifier.makeFunction(calName), msgLogger); if (declaringClassOrInvocationClassProvider == null) { return null; } final String fieldName = s.readUTF(); final String fieldClassName = s.readUTF(); final ForeignEntityProvider<Class<?>> fieldClassProvider = DeserializationHelper.classProviderForName(fieldClassName, foreignClassLoader, "ForeignFunctionInfo " + calName, CompilerMessage.Identifier.makeFunction(calName), msgLogger); if (fieldClassProvider == null) { return null; } final String declaredReturnTypeName = s.readUTF(); final ForeignEntityProvider<Class<?>> declaredReturnTypeProvider = DeserializationHelper.classProviderForName(declaredReturnTypeName, foreignClassLoader, "ForeignFunctionInfo " + calName, CompilerMessage.Identifier.makeFunction(calName), msgLogger); if (declaredReturnTypeProvider == null) { return null; } final SourceRange sourceRange = new SourceRange(calName.getModuleName().toSourceText()); final String javaName = declaringClassOrInvocationClassName + "." + fieldName; return ForeignFunctionChecker.makeForeignFunctionInfoForField(calName, javaName, isStatic, sourceRange, fieldName, fieldClassName, declaringClassOrInvocationClassProvider, declaredReturnTypeProvider, msgLogger, true); } /** * Read position will be before the containing class name. * @param s * @param calName the name of the cal function being loaded - for use in error strings * @param foreignClassLoader the classloader to use to resolve foreign classes. * @param msgLogger the logger to which to log deserialization messages. * @return a constructor instance, or null if there was a problem resolving classes. * @throws IOException */ private static final ForeignFunctionInfo loadConstructor(final RecordInputStream s, final QualifiedName calName, final ClassLoader foreignClassLoader, final CompilerMessageLogger msgLogger) throws IOException { final String declaringClassName = s.readUTF(); final ForeignEntityProvider<Class<?>> declaringClassProvider = DeserializationHelper.classProviderForName(declaringClassName, foreignClassLoader, "ForeignFunctionInfo " + calName, CompilerMessage.Identifier.makeFunction(calName), msgLogger); if (declaringClassProvider == null) { return null; } final int nJArgs = s.readShortCompressed(); final ForeignEntityProvider<Class<?>> argClassProviders[] = ForeignFunctionChecker.makeForeignEntityProviderClassArray(nJArgs); for (int i = 0; i < nJArgs; ++i) { final String argClassName = s.readUTF(); final ForeignEntityProvider<Class<?>> argClassProvider = DeserializationHelper.classProviderForName(argClassName, foreignClassLoader, "ForeignFunctionInfo " + calName, CompilerMessage.Identifier.makeFunction(calName), msgLogger); if (argClassProvider == null) { return null; } argClassProviders[i] = argClassProvider; } final String declaredReturnTypeName = s.readUTF(); final ForeignEntityProvider<Class<?>> declaredReturnTypeProvider = DeserializationHelper.classProviderForName(declaredReturnTypeName, foreignClassLoader, "ForeignFunctionInfo " + calName, CompilerMessage.Identifier.makeFunction(calName), msgLogger); if (declaredReturnTypeProvider == null) { return null; } final SourceRange sourceRange = new SourceRange(calName.getModuleName().toSourceText()); final String javaName = declaringClassName; return ForeignFunctionChecker.makeForeignFunctionInfoForConstructor(calName, javaName, sourceRange, declaringClassProvider, argClassProviders, declaredReturnTypeProvider, msgLogger, true); } } /** * Information about a CAL foreign function that is in fact a Java cast. * * @author Bo Ilic */ public static final class Cast extends ForeignFunctionInfo { private static final int serializationSchema = 0; /** Provider for the Java type of the expression being cast, which is always non-null. */ private final ForeignEntityProvider<Class<?>> argumentTypeProvider; /** Provider for the Java type of the result of the cast, which is always non-null. */ private final ForeignEntityProvider<Class<?>> resultTypeProvider; /** * Constructor for a foreign cast. */ Cast(final QualifiedName calName, final JavaKind kind, final ForeignEntityProvider<Class<?>> castArgumentTypeProvider, final ForeignEntityProvider<Class<?>> castResultTypeProvider) { super(calName, kind); if (!kind.isCast()) { throw new IllegalArgumentException(); } if (castArgumentTypeProvider == null || castResultTypeProvider == null) { throw new NullPointerException(); } this.argumentTypeProvider = castArgumentTypeProvider; this.resultTypeProvider = castResultTypeProvider; } /** * {@inheritDoc} */ @Override public int getNArguments() { return 1; } /** {@inheritDoc} */ @Override public Class<?> getJavaArgumentType(final int argN) throws UnableToResolveForeignEntityException { if (argN == 0) { return argumentTypeProvider.get(); } throw new IndexOutOfBoundsException(); } /** {@inheritDoc} */ @Override public Class<?> getJavaReturnType() throws UnableToResolveForeignEntityException { return resultTypeProvider.get(); } /** {@inheritDoc} */ @Override public void resolveForeignEntities() throws UnableToResolveForeignEntityException { // Force resolution by calling get() on each of the ForeignEntityProviders argumentTypeProvider.get(); resultTypeProvider.get(); } /** * {@inheritDoc} */ @Override public String toString() { final StringBuilder result = new StringBuilder ("foreign ").append(super.calName); result.append(" [kind = ").append(super.javaKind).append("]"); result.append(" [cast = ").append(argumentTypeProvider).append(" to ").append(resultTypeProvider).append("]"); return result.toString(); } /** * {@inheritDoc} */ @Override final void write (final RecordOutputStream s) throws IOException, UnableToResolveForeignEntityException { s.startRecord(ModuleSerializationTags.FOREIGN_FUNCTION_INFO_CAST, serializationSchema); super.write(s); s.writeUTF(argumentTypeProvider.get().getName()); s.writeUTF(resultTypeProvider.get().getName()); s.endRecord(); } /** * Load an instance of ForeignFunctionInfo.Cast from the RecordInputStream. * Read position will be before the record header. * @param s * @param moduleName the name of the module being loaded * @param foreignClassLoader the classloader to use to resolve foreign classes. * @param msgLogger the logger to which to log deserialization messages. * @return an instance of ForeignFunctionInfo, or null if there was a problem resolving classes. * @throws IOException */ private static final ForeignFunctionInfo load (final RecordInputStream s, final int schema, final ModuleName moduleName, final ClassLoader foreignClassLoader, final CompilerMessageLogger msgLogger) throws IOException { DeserializationHelper.checkSerializationSchema(schema, serializationSchema, moduleName, "ForeignFunctionInfo.Cast", msgLogger); //read the superclass fields final QualifiedName calName = s.readQualifiedName(); final JavaKind javaKind = JavaKind.load(s, moduleName, msgLogger); final String castArgumentTypeName = s.readUTF(); final String castResultTypeName = s.readUTF(); final ClassPairResolver castArgumentAndResultTypeResolver = new ClassPairResolver(castArgumentTypeName, castResultTypeName) { @Override Pair<Class<?>, Class<?>> resolveClassPair(final ForeignEntityProvider.MessageHandler msgLogger) throws UnableToResolveForeignEntityException { final Class<?> castArgumentType = DeserializationHelper.classForName(castArgumentTypeName, foreignClassLoader, "ForeignFunctionInfo.Cast " + calName, CompilerMessage.Identifier.makeFunction(calName), msgLogger); final Class<?> castResultType = DeserializationHelper.classForName(castResultTypeName, foreignClassLoader, "ForeignFunctionInfo.Cast " + calName, CompilerMessage.Identifier.makeFunction(calName), msgLogger); //the actualJavaKind could have changed from the serialized javaKind because the underlying Java types may have //changed since the CAL cmi file was saved. For example, a Java class could have been declared final, which could make //a previously valid Java cast into a static compile-time error in Java, and thus a static compile-time error in CAL. //Alternatively, changes in the Java class hierarchy can cause a widening reference cast to become a narrowing reference //cast or vice versa. // //Note that there will never be a change of primitive cast types, since to change a primitive cast type, one must //change the foreign declaration whose implementation type is a primitive Java type, which would automatically //invalidate the compiled CAL file. final JavaKind actualJavaKind = ForeignFunctionChecker.checkForeignCast(castArgumentType, castResultType); if (javaKind != actualJavaKind) { //"The underlying Java types involved in the CAL foreign function {0} have changed. Recompilation is needed." msgLogger.handleMessage(new CompilerMessage( new SourceRange(moduleName.toSourceText()), CompilerMessage.Identifier.makeFunction(calName), new MessageKind.Error.InvalidJavaTypeChangeOnLoading(calName))); } return new Pair<Class<?>, Class<?>>(castArgumentType, castResultType); } }; final ForeignEntityProvider<Class<?>> castArgumentTypeProvider = ForeignEntityProvider.make(msgLogger, castArgumentAndResultTypeResolver.getFirstResolver()); if (castArgumentTypeProvider == null) { return null; } final ForeignEntityProvider<Class<?>> castResultTypeProvider = ForeignEntityProvider.make(msgLogger, castArgumentAndResultTypeResolver.getSecondResolver()); if (castResultTypeProvider == null) { return null; } s.skipRestOfRecord(); return new Cast(calName, javaKind, castArgumentTypeProvider, castResultTypeProvider); } } /** * Information about a CAL foreign function that is in fact a Java instanceof operator. * * These are CAL functions with type: SomeForeignJavaReferenceType -> Boolean; * * @author Bo Ilic */ public static final class InstanceOf extends ForeignFunctionInfo { private static final int serializationSchema = 0; /** Provider for the Java type of expr in "expr instanceof T", which will not be null. */ private final ForeignEntityProvider<Class<?>> argumentTypeProvider; /** Provider for the Java type T in the expression "expr instanceof T", which will not be null. */ private final ForeignEntityProvider<Class<?>> instanceOfTypeProvider; /** * Constructor for a foreign instanceof. */ InstanceOf(final QualifiedName calName, final ForeignEntityProvider<Class<?>> argumentTypeProvider, final ForeignEntityProvider<Class<?>> instanceOfTypeProvider) { super(calName, JavaKind.INSTANCE_OF); if (argumentTypeProvider == null || instanceOfTypeProvider == null) { throw new NullPointerException(); } this.argumentTypeProvider = argumentTypeProvider; this.instanceOfTypeProvider = instanceOfTypeProvider; } /** * @return the Java type T in the expression "expr instanceof T". Will not be null. */ public Class<?> getInstanceOfType() throws UnableToResolveForeignEntityException { return instanceOfTypeProvider.get(); } /** * {@inheritDoc} */ @Override public int getNArguments() { return 1; } /** {@inheritDoc} */ @Override public Class<?> getJavaArgumentType(final int argN) throws UnableToResolveForeignEntityException { if (argN == 0) { return argumentTypeProvider.get(); } throw new IndexOutOfBoundsException(); } /** {@inheritDoc} */ @Override public Class<?> getJavaReturnType() { return boolean.class; } /** {@inheritDoc} */ @Override public void resolveForeignEntities() throws UnableToResolveForeignEntityException { // Force resolution by calling get() on each of the ForeignEntityProviders argumentTypeProvider.get(); instanceOfTypeProvider.get(); } /** * {@inheritDoc} */ @Override public String toString() { final StringBuilder result = new StringBuilder ("foreign ").append(super.calName); result.append(" [kind = ").append(super.javaKind).append("]"); result.append(" [").append(argumentTypeProvider).append(" instanceof ").append(instanceOfTypeProvider).append("]"); return result.toString(); } /** * {@inheritDoc} */ @Override final void write (final RecordOutputStream s) throws IOException, UnableToResolveForeignEntityException { s.startRecord(ModuleSerializationTags.FOREIGN_FUNCTION_INFO_INSTANCE_OF, serializationSchema); super.write(s); s.writeUTF(argumentTypeProvider.get().getName()); s.writeUTF(instanceOfTypeProvider.get().getName()); s.endRecord(); } /** * Load an instance of ForeignFunctionInfo.InstanceOf from the RecordInputStream. * Read position will be before the record header. * @param s * @param moduleName the name of the module being loaded * @param foreignClassLoader the classloader to use to resolve foreign classes. * @param msgLogger the logger to which to log deserialization messages. * @return an instance of ForeignFunctionInfo, or null if there was a problem resolving classes. * @throws IOException */ private static final ForeignFunctionInfo load (final RecordInputStream s, final int schema, final ModuleName moduleName, final ClassLoader foreignClassLoader, final CompilerMessageLogger msgLogger) throws IOException { DeserializationHelper.checkSerializationSchema(schema, serializationSchema, moduleName, "ForeignFunctionInfo.InstanceOf", msgLogger); //read the superclass fields final QualifiedName calName = s.readQualifiedName(); //todoBI we currently only have one instanceof JavaKind. However, in the future we may have more if we want to optimize out //instanceof no-ops e.g. expr instanceof Object. Typically there is no reason to write such code in CAL so we don't bother //optimizing this out. final JavaKind javaKind = JavaKind.load(s, moduleName, msgLogger); if (!javaKind.isInstanceOf()) { throw new IOException(); } final String argumentTypeName = s.readUTF(); final String instanceOfTypeName = s.readUTF(); final ClassPairResolver argumentAndInstanceOfTypeResolver = new ClassPairResolver(argumentTypeName, instanceOfTypeName) { @Override Pair<Class<?>, Class<?>> resolveClassPair(final ForeignEntityProvider.MessageHandler messageHandler) throws UnableToResolveForeignEntityException { final Class<?> argumentType = DeserializationHelper.classForName(argumentTypeName, foreignClassLoader, "ForeignFunctionInfo.InstanceOf " + calName, CompilerMessage.Identifier.makeFunction(calName), messageHandler); final Class<?> instanceOfType = DeserializationHelper.classForName(instanceOfTypeName, foreignClassLoader, "ForeignFunctionInfo.InstanceOf " + calName, CompilerMessage.Identifier.makeFunction(calName), messageHandler); //It could happen that upon loading, an instanceof check is no longer valid since it would result in a compile-time //error in Java because it no longer corresponds to a possibly valid Java cast. final JavaKind javaCastKind = ForeignFunctionChecker.checkForeignCast(argumentType, instanceOfType); if (javaCastKind == null) { //"The underlying Java types involved in the CAL foreign function {0} have changed. Recompilation is needed." messageHandler.handleMessage(new CompilerMessage( new SourceRange(moduleName.toSourceText()), CompilerMessage.Identifier.makeFunction(calName), new MessageKind.Error.InvalidJavaTypeChangeOnLoading(calName))); } return new Pair<Class<?>, Class<?>>(argumentType, instanceOfType); } }; final ForeignEntityProvider<Class<?>> argumentTypeProvider = ForeignEntityProvider.make(msgLogger, argumentAndInstanceOfTypeResolver.getFirstResolver()); if (argumentTypeProvider == null) { return null; } final ForeignEntityProvider<Class<?>> instanceOfTypeProvider = ForeignEntityProvider.make(msgLogger, argumentAndInstanceOfTypeResolver.getSecondResolver()); if (instanceOfTypeProvider == null) { return null; } s.skipRestOfRecord(); return new InstanceOf(calName, argumentTypeProvider, instanceOfTypeProvider); } } /** * Information about a CAL foreign function that is in fact a Java null literal (of * a specific CAL type) * <p> * These are CAL functions of type T (i.e. 0 arguments), where T is a foreign CAL type whose implementation type * is a Java reference type. * * @author Bo Ilic */ public static final class NullLiteral extends ForeignFunctionInfo { private static final int serializationSchema = 0; /** Provider for the Java reference type T of the Java null literal */ private final ForeignEntityProvider<Class<?>> resultTypeProvider; /** * Constructor for a foreign null. */ NullLiteral(final QualifiedName calName, final ForeignEntityProvider<Class<?>> resultTypeProvider) { super(calName, JavaKind.NULL_LITERAL); if (resultTypeProvider == null) { throw new NullPointerException(); } this.resultTypeProvider = resultTypeProvider; } /** * {@inheritDoc} */ @Override public int getNArguments() { return 0; } /** {@inheritDoc} */ @Override public Class<?> getJavaArgumentType(final int argN) { throw new IndexOutOfBoundsException(); } /** {@inheritDoc} */ @Override public Class<?> getJavaReturnType() throws UnableToResolveForeignEntityException { return resultTypeProvider.get(); } /** {@inheritDoc} */ @Override public void resolveForeignEntities() throws UnableToResolveForeignEntityException { // Force resolution by calling get() on each of the ForeignEntityProviders resultTypeProvider.get(); } /** * {@inheritDoc} */ @Override public String toString() { final StringBuilder result = new StringBuilder ("foreign ").append(super.calName); result.append(" [kind = ").append(super.javaKind).append("]"); result.append(" [null ").append(resultTypeProvider).append("]"); return result.toString(); } /** * {@inheritDoc} */ @Override final void write (final RecordOutputStream s) throws IOException, UnableToResolveForeignEntityException { s.startRecord(ModuleSerializationTags.FOREIGN_FUNCTION_INFO_NULL_LITERAL, serializationSchema); super.write(s); s.writeUTF(getJavaReturnType().getName()); s.endRecord(); } /** * Load an instance of ForeignFunctionInfo.NullLiteral from the RecordInputStream. * Read position will be before the record header. * @param s * @param moduleName the name of the module being loaded * @param foreignClassLoader the classloader to use to resolve foreign classes. * @param msgLogger the logger to which to log deserialization messages. * @return an instance of ForeignFunctionInfo, or null if there was a problem resolving classes. * @throws IOException */ private static final ForeignFunctionInfo load (final RecordInputStream s, final int schema, final ModuleName moduleName, final ClassLoader foreignClassLoader, final CompilerMessageLogger msgLogger) throws IOException { DeserializationHelper.checkSerializationSchema(schema, serializationSchema, moduleName, "ForeignFunctionInfo.NullLiteral", msgLogger); //read the superclass fields final QualifiedName calName = s.readQualifiedName(); final JavaKind javaKind = JavaKind.load(s, moduleName, msgLogger); if (javaKind != JavaKind.NULL_LITERAL) { throw new IOException(); } final String resultTypeName = s.readUTF(); final ForeignEntityProvider<Class<?>> resultTypeProvider = DeserializationHelper.classProviderForName(resultTypeName, foreignClassLoader, "ForeignFunctionInfo.NullLiteral " + calName, CompilerMessage.Identifier.makeFunction(calName), msgLogger); if (resultTypeProvider == null) { return null; } s.skipRestOfRecord(); return new NullLiteral(calName, resultTypeProvider); } } /** * Information about a CAL foreign function that is in fact a Java Class literal (of * the Java type java.lang.Class), referring to a particular Java type R. * <p> * These are CAL functions of type T (i.e. 0 arguments), where T is a foreign CAL type whose implementation type * is java.lang.Class, its superclass java.lang.Object, or one of its superinterfaces. * * @author Joseph Wong */ public static final class ClassLiteral extends ForeignFunctionInfo { private static final int serializationSchema = 0; /** * Provider for the referent type, i.e. the Java type R where this literal corresponds to R.class, which cannot be null. * It may be a primitive type, a reference type, or an array type. */ private final ForeignEntityProvider<Class<?>> referentTypeProvider; /** * Provider for the class corresponding to the declared return type of the foreign function, which cannot be null. */ private final ForeignEntityProvider<Class<?>> declaredReturnTypeProvider; /** * Constructor for a foreign class literal. * @param calName CAL name of the foreign function * @param referentTypeProvider provider for the referent type, i.e. the Java type R where this literal corresponds to R.class, which cannot be null. * It may be a primitive type, a reference type, or an array type. * @param declaredReturnTypeProvider provider for the class corresponding to the declared return type of the foreign function, which cannot be null. */ ClassLiteral(final QualifiedName calName, final ForeignEntityProvider<Class<?>> referentTypeProvider, final ForeignEntityProvider<Class<?>> declaredReturnTypeProvider) { super(calName, JavaKind.CLASS_LITERAL); if (referentTypeProvider == null || declaredReturnTypeProvider == null) { throw new NullPointerException(); } this.referentTypeProvider = referentTypeProvider; this.declaredReturnTypeProvider = declaredReturnTypeProvider; } /** * {@inheritDoc} */ @Override public int getNArguments() { return 0; } /** * @return the referent type, i.e. the Java type R where this literal corresponds to R.class. */ public Class<?> getReferentType() throws UnableToResolveForeignEntityException { return referentTypeProvider.get(); } /** {@inheritDoc} */ @Override public Class<?> getJavaArgumentType(final int argN) { throw new IndexOutOfBoundsException(); } /** {@inheritDoc} */ @Override public Class<?> getJavaReturnType() { return Class.class; } /** {@inheritDoc} */ @Override public void resolveForeignEntities() throws UnableToResolveForeignEntityException { // Force resolution by calling get() on each of the ForeignEntityProviders referentTypeProvider.get(); declaredReturnTypeProvider.get(); } /** * {@inheritDoc} */ @Override public String toString() { final StringBuilder result = new StringBuilder ("foreign ").append(super.calName); result.append(" [kind = ").append(super.javaKind).append("]"); result.append(" [class ").append(referentTypeProvider).append("]"); return result.toString(); } /** * {@inheritDoc} */ @Override final void write (final RecordOutputStream s) throws IOException, UnableToResolveForeignEntityException { s.startRecord(ModuleSerializationTags.FOREIGN_FUNCTION_INFO_CLASS_LITERAL, serializationSchema); super.write(s); s.writeUTF(getReferentType().getName()); s.writeUTF(declaredReturnTypeProvider.get().getName()); s.endRecord(); } /** * Load an instance of ForeignFunctionInfo.ClassLiteral from the RecordInputStream. * Read position will be before the record header. * @param s * @param moduleName the name of the module being loaded * @param foreignClassLoader the classloader to use to resolve foreign classes. * @param msgLogger the logger to which to log deserialization messages. * @return an instance of ForeignFunctionInfo, or null if there was a problem resolving classes. * @throws IOException */ private static final ForeignFunctionInfo load (final RecordInputStream s, final int schema, final ModuleName moduleName, final ClassLoader foreignClassLoader, final CompilerMessageLogger msgLogger) throws IOException { DeserializationHelper.checkSerializationSchema(schema, serializationSchema, moduleName, "ForeignFunctionInfo.NullLiteral", msgLogger); //read the superclass fields final QualifiedName calName = s.readQualifiedName(); final JavaKind javaKind = JavaKind.load(s, moduleName, msgLogger); if (javaKind != JavaKind.CLASS_LITERAL) { throw new IOException(); } final String referentTypeName = s.readUTF(); final ForeignEntityProvider<Class<?>> referentTypeProvider = DeserializationHelper.classProviderForName(referentTypeName, foreignClassLoader, "ForeignFunctionInfo.ClassLiteral " + calName, CompilerMessage.Identifier.makeFunction(calName), msgLogger); if (referentTypeProvider == null) { return null; } final String declaredReturnTypeName = s.readUTF(); final ForeignEntityProvider<Class<?>> declaredReturnTypeProvider = DeserializationHelper.classProviderForName(declaredReturnTypeName, foreignClassLoader, "ForeignFunctionInfo " + calName, CompilerMessage.Identifier.makeFunction(calName), msgLogger); if (declaredReturnTypeProvider == null) { return null; } s.skipRestOfRecord(); final SourceRange sourceRange = new SourceRange(calName.getModuleName().toSourceText()); return ForeignFunctionChecker.makeForeignFunctionInfoForClassLiteral(calName, referentTypeName, sourceRange, referentTypeProvider, declaredReturnTypeProvider, msgLogger, true); } } /** * Information about a CAL foreign function that is in fact a Java null check. That is either * "javaExpr == null" or "javaExpr != null" where javaExpr has a Java reference type. * <p> * * As a CAL function, this has the CAL type T -> Prelude.Boolean * where T is a foreign type corresponding to a Java reference type. * Note that the return type is normally Prelude.Boolean but can also be any foreign type with Java * implementation type "boolean". * As usual, all types in the foreign function declaration must be visible, and their implementations must * be visible, within the module in which the foreign function is defined. * * @author Bo Ilic */ public static final class NullCheck extends ForeignFunctionInfo { private static final int serializationSchema = 0; /** Provider for the Java reference type T of the Java expression to be null checked, which cannot be null. */ private final ForeignEntityProvider<Class<?>> argumentTypeProvider; /** true for the isNull check, false for the isNotNull check. */ private final boolean checkIsNull; NullCheck(final QualifiedName calName, final ForeignEntityProvider<Class<?>> argumentTypeProvider, final boolean checkIsNull) { super(calName, JavaKind.NULL_CHECK); if (argumentTypeProvider == null) { throw new NullPointerException(); } this.argumentTypeProvider = argumentTypeProvider; this.checkIsNull = checkIsNull; } /** * {@inheritDoc} */ @Override public int getNArguments() { return 1; } /** {@inheritDoc} */ @Override public Class<?> getJavaArgumentType(final int argN) throws UnableToResolveForeignEntityException { if (argN == 0) { return argumentTypeProvider.get(); } throw new IndexOutOfBoundsException(); } /** {@inheritDoc} */ @Override public Class<?> getJavaReturnType() { return boolean.class; } /** {@inheritDoc} */ @Override public void resolveForeignEntities() throws UnableToResolveForeignEntityException { // Force resolution by calling get() on each of the ForeignEntityProviders argumentTypeProvider.get(); } /** * @return true for the isNull check, false for the isNotNull check. */ public boolean checkIsNull() { return checkIsNull; } /** * {@inheritDoc} */ @Override public String toString() { final StringBuilder result = new StringBuilder ("foreign ").append(super.calName); result.append(" [kind = ").append(super.javaKind).append("]"); if (checkIsNull) { result.append(" [isNull "); } else { result.append(" [isNotNull "); } result.append(argumentTypeProvider).append("]"); return result.toString(); } /** * {@inheritDoc} */ @Override final void write (final RecordOutputStream s) throws IOException, UnableToResolveForeignEntityException { s.startRecord(ModuleSerializationTags.FOREIGN_FUNCTION_INFO_NULL_CHECK, serializationSchema); super.write(s); s.writeUTF(argumentTypeProvider.get().getName()); s.writeBoolean(checkIsNull); s.endRecord(); } /** * Load an instance of ForeignFunctionInfo.NullCheck from the RecordInputStream. * Read position will be before the record header. * @param s * @param moduleName the name of the module being loaded * @param foreignClassLoader the classloader to use to resolve foreign classes. * @param msgLogger the logger to which to log deserialization messages. * @return an instance of ForeignFunctionInfo, or null if there was a problem resolving classes. * @throws IOException */ private static final ForeignFunctionInfo load (final RecordInputStream s, final int schema, final ModuleName moduleName, final ClassLoader foreignClassLoader, final CompilerMessageLogger msgLogger) throws IOException { DeserializationHelper.checkSerializationSchema(schema, serializationSchema, moduleName, "ForeignFunctionInfo.NullCheck", msgLogger); //read the superclass fields final QualifiedName calName = s.readQualifiedName(); final JavaKind javaKind = JavaKind.load(s, moduleName, msgLogger); if (javaKind != JavaKind.NULL_CHECK) { throw new IOException(); } final String argumentTypeName = s.readUTF(); final ForeignEntityProvider<Class<?>> argumentTypeProvider = DeserializationHelper.classProviderForName(argumentTypeName, foreignClassLoader, "ForeignFunctionInfo.NullCheck " + calName, CompilerMessage.Identifier.makeFunction(calName), msgLogger); if (argumentTypeProvider == null) { return null; } final boolean checkIsNull = s.readBoolean(); s.skipRestOfRecord(); return new NullCheck(calName, argumentTypeProvider, checkIsNull); } } /** * Information about a CAL foreign function that is in fact a Java new array creation. * <p> * * The Java expression: * new T[s1][s2]...[sm][]...[] * is considered as a CAL function of m arguments: * Int -> Int -> ... -> Int -> CT * where CT is a CAL type whose implementation type is an n-dimensional Java array type with element type T. * In particular, we require that m >= 1 and m <= n to satisfy the constraints on Java array creation. * <p> * * Note that the component size arguments can be any foreign type with Java implementation type "int" and not just * Prelude.Int. * * @author Bo Ilic */ public static final class NewArray extends ForeignFunctionInfo { private static final int serializationSchema = 0; /** * the number of supplied int arguments specifying dimension sizes. Must be >= 1 and <= the number of dimensions * in the array. */ private final int nSizeArgs; /** * Provider for the Java type of the resulting array */ private final ForeignEntityProvider<Class<?>> newArrayTypeProvider; NewArray(final QualifiedName calName, final int nSizeArgs, final ForeignEntityProvider<Class<?>> newArrayTypeProvider) { super(calName, JavaKind.NEW_ARRAY); if (newArrayTypeProvider == null) { throw new NullPointerException(); } this.nSizeArgs = nSizeArgs; this.newArrayTypeProvider = newArrayTypeProvider; } /** * {@inheritDoc} */ @Override public int getNArguments() { return nSizeArgs; } /** {@inheritDoc} */ @Override public Class<?> getJavaArgumentType(final int argN) { if (argN >= 0 && argN < nSizeArgs) { return int.class; } throw new IndexOutOfBoundsException(); } /** {@inheritDoc} */ @Override public Class<?> getJavaReturnType() throws UnableToResolveForeignEntityException { return newArrayTypeProvider.get(); } /** {@inheritDoc} */ @Override public void resolveForeignEntities() throws UnableToResolveForeignEntityException { // Force resolution by calling get() on each of the ForeignEntityProviders newArrayTypeProvider.get(); } /** * For example, if this newArray function returns an int[][][], and 2 sizing arguments are supplied, then this is int[]. * @return Class. */ public Class<?> getComponentType() throws UnableToResolveForeignEntityException { return ForeignFunctionChecker.getSubscriptedArrayType(getJavaReturnType(), nSizeArgs); } /** * {@inheritDoc} */ @Override public String toString() { final StringBuilder result = new StringBuilder ("foreign ").append(super.calName); result.append(" [kind = ").append(super.javaKind).append("]"); result.append(" [nSizeArgs = ").append(nSizeArgs).append(", type = "); result.append(newArrayTypeProvider).append("]"); return result.toString(); } /** * {@inheritDoc} */ @Override final void write (final RecordOutputStream s) throws IOException, UnableToResolveForeignEntityException { s.startRecord(ModuleSerializationTags.FOREIGN_FUNCTION_INFO_NEW_ARRAY, serializationSchema); super.write(s); s.writeInt(nSizeArgs); s.writeUTF(getJavaReturnType().getName()); s.endRecord(); } /** * Load an instance of ForeignFunctionInfo.NewArray from the RecordInputStream. * Read position will be before the record header. * @param s * @param moduleName the name of the module being loaded * @param foreignClassLoader the classloader to use to resolve foreign classes. * @param msgLogger the logger to which to log deserialization messages. * @return an instance of ForeignFunctionInfo, or null if there was a problem resolving classes. * @throws IOException */ private static final ForeignFunctionInfo load (final RecordInputStream s, final int schema, final ModuleName moduleName, final ClassLoader foreignClassLoader, final CompilerMessageLogger msgLogger) throws IOException { DeserializationHelper.checkSerializationSchema(schema, serializationSchema, moduleName, "ForeignFunctionInfo.NewArray", msgLogger); //read the superclass fields final QualifiedName calName = s.readQualifiedName(); final JavaKind javaKind = JavaKind.load(s, moduleName, msgLogger); if (javaKind != JavaKind.NEW_ARRAY) { throw new IOException(); } final int nSizeArgs = s.readInt(); final String newArrayTypeName = s.readUTF(); final ForeignEntityProvider<Class<?>> newArrayTypeProvider = DeserializationHelper.classProviderForName(newArrayTypeName, foreignClassLoader, "ForeignFunctionInfo.NewArray " + calName, CompilerMessage.Identifier.makeFunction(calName), msgLogger); if (newArrayTypeProvider == null) { return null; } s.skipRestOfRecord(); return new NewArray(calName, nSizeArgs, newArrayTypeProvider); } } /** * Information about a CAL foreign function that is in fact a call to the length * field of an array reference type. * <p> * * As a CAL function it is of type: * CT -> Int * where CT is a CAL type whose implementation type is a Java array type. The return type is Prelude.Int (or * any other foreign type with Java implementation type "int"). * * @author Bo Ilic */ public static final class LengthArray extends ForeignFunctionInfo { private static final int serializationSchema = 0; private final ForeignEntityProvider<Class<?>> arrayTypeProvider; LengthArray(final QualifiedName calName, final ForeignEntityProvider<Class<?>> arrayTypeProvider) { super(calName, JavaKind.LENGTH_ARRAY); if (arrayTypeProvider == null) { throw new NullPointerException(); } this.arrayTypeProvider = arrayTypeProvider; } /** * {@inheritDoc} */ @Override public int getNArguments() { return 1; } /** {@inheritDoc} */ @Override public Class<?> getJavaArgumentType(final int argN) throws UnableToResolveForeignEntityException { if (argN == 0) { return arrayTypeProvider.get(); } throw new IndexOutOfBoundsException(); } /** {@inheritDoc} */ @Override public Class<?> getJavaReturnType() { return int.class; } /** {@inheritDoc} */ @Override public void resolveForeignEntities() throws UnableToResolveForeignEntityException { // Force resolution by calling get() on each of the ForeignEntityProviders arrayTypeProvider.get(); } /** * {@inheritDoc} */ @Override public String toString() { final StringBuilder result = new StringBuilder ("foreign ").append(super.calName); result.append(" [kind = ").append(super.javaKind).append("]"); result.append(" [type = "); result.append(arrayTypeProvider).append("]"); return result.toString(); } /** * {@inheritDoc} */ @Override final void write (final RecordOutputStream s) throws IOException, UnableToResolveForeignEntityException { s.startRecord(ModuleSerializationTags.FOREIGN_FUNCTION_INFO_LENGTH_ARRAY, serializationSchema); super.write(s); s.writeUTF(arrayTypeProvider.get().getName()); s.endRecord(); } /** * Load an instance of ForeignFunctionInfo.LengthArray from the RecordInputStream. * Read position will be before the record header. * @param s * @param moduleName the name of the module being loaded * @param foreignClassLoader the classloader to use to resolve foreign classes. * @param msgLogger the logger to which to log deserialization messages. * @return an instance of ForeignFunctionInfo, or null if there was a problem resolving classes. * @throws IOException */ private static final ForeignFunctionInfo load (final RecordInputStream s, final int schema, final ModuleName moduleName, final ClassLoader foreignClassLoader, final CompilerMessageLogger msgLogger) throws IOException { DeserializationHelper.checkSerializationSchema(schema, serializationSchema, moduleName, "ForeignFunctionInfo.NewArray", msgLogger); //read the superclass fields final QualifiedName calName = s.readQualifiedName(); final JavaKind javaKind = JavaKind.load(s, moduleName, msgLogger); if (javaKind != JavaKind.LENGTH_ARRAY) { throw new IOException(); } final String arrayTypeName = s.readUTF(); final ForeignEntityProvider<Class<?>> arrayTypeProvider = DeserializationHelper.classProviderForName(arrayTypeName, foreignClassLoader, "ForeignFunctionInfo.NewArray " + calName, CompilerMessage.Identifier.makeFunction(calName), msgLogger); if (arrayTypeProvider == null) { return null; } s.skipRestOfRecord(); return new LengthArray(calName, arrayTypeProvider); } } /** * Information about a CAL foreign function that is in fact a Java array subscript operator. * <p> * * The Java expression: * expr[index1][index2]...[index_m] * is considered as a CAL function of m + 1 arguments: * T1 -> Prelude.Int -> Prelude.Int -> ... -> Prelude.Int -> T2 * where T1 is a CAL type whose implementation type is an n-dimensional Java array type * and T2 is a CAL type whose implementation type is the Java type resulting in subscripting the array with m indices * (or one assignment compatible with this type). * In particular, we require that m >= 1 and m <= n to satisfy the constraints on subscripting. * <p> * * Note that the subscript arguments can be any foreign type with Java implementation type "int" and not just * Prelude.Int. * * @author Bo Ilic */ public static final class SubscriptArray extends ForeignFunctionInfo { private static final int serializationSchema = 0; /** * the number of supplied int arguments specifying dimension sizes. Must be >= 1 and <= the number of dimensions * in the array. */ private final int nSubscriptArgs; /** Provider for the type of the array to be subscripted. */ private final ForeignEntityProvider<Class<?>> arrayTypeProvider; /** Provider for the type returned by the CAL function, which is assignment compatible with the subscripted type. */ private final ForeignEntityProvider<Class<?>> resultTypeProvider; SubscriptArray(final QualifiedName calName, final int nSubscriptArgs, final ForeignEntityProvider<Class<?>> arrayTypeProvider, final ForeignEntityProvider<Class<?>> resultTypeProvider) { super(calName, JavaKind.SUBSCRIPT_ARRAY); if (arrayTypeProvider == null) { throw new NullPointerException(); } this.nSubscriptArgs = nSubscriptArgs; this.arrayTypeProvider = arrayTypeProvider; this.resultTypeProvider = resultTypeProvider; } /** * {@inheritDoc} */ @Override public int getNArguments() { return nSubscriptArgs + 1; } /** {@inheritDoc} */ @Override public Class<?> getJavaArgumentType(final int argN) throws UnableToResolveForeignEntityException { if (argN == 0) { return arrayTypeProvider.get(); } else if (argN > 0 && argN <= nSubscriptArgs) { return int.class; } throw new IndexOutOfBoundsException(); } /** {@inheritDoc} */ @Override public Class<?> getJavaReturnType() throws UnableToResolveForeignEntityException { return resultTypeProvider.get(); } /** {@inheritDoc} */ @Override public void resolveForeignEntities() throws UnableToResolveForeignEntityException { // Force resolution by calling get() on each of the ForeignEntityProviders arrayTypeProvider.get(); resultTypeProvider.get(); } /** * {@inheritDoc} */ @Override public String toString() { final StringBuilder result = new StringBuilder ("foreign ").append(super.calName); result.append(" [kind = ").append(super.javaKind).append("]"); result.append(" [nSubscriptArgs = ").append(nSubscriptArgs).append(", arrayType = "); result.append(arrayTypeProvider).append(", resultType = ").append(resultTypeProvider).append("]"); return result.toString(); } /** * {@inheritDoc} */ @Override final void write (final RecordOutputStream s) throws IOException, UnableToResolveForeignEntityException { s.startRecord(ModuleSerializationTags.FOREIGN_FUNCTION_INFO_SUBSCRIPT_ARRAY, serializationSchema); super.write(s); s.writeInt(nSubscriptArgs); s.writeUTF(arrayTypeProvider.get().getName()); s.writeUTF(resultTypeProvider.get().getName()); s.endRecord(); } /** * Load an instance of ForeignFunctionInfo.SubscriptArray from the RecordInputStream. * Read position will be before the record header. * @param s * @param moduleName the name of the module being loaded * @param foreignClassLoader the classloader to use to resolve foreign classes. * @param msgLogger the logger to which to log deserialization messages. * @return an instance of ForeignFunctionInfo, or null if there was a problem resolving classes. * @throws IOException */ private static final ForeignFunctionInfo load (final RecordInputStream s, final int schema, final ModuleName moduleName, final ClassLoader foreignClassLoader, final CompilerMessageLogger msgLogger) throws IOException { DeserializationHelper.checkSerializationSchema(schema, serializationSchema, moduleName, "ForeignFunctionInfo.SubscriptArray", msgLogger); //read the superclass fields final QualifiedName calName = s.readQualifiedName(); final JavaKind javaKind = JavaKind.load(s, moduleName, msgLogger); if (javaKind != JavaKind.SUBSCRIPT_ARRAY) { throw new IOException(); } final int nSubscriptArgs = s.readInt(); final String arrayTypeName = s.readUTF(); final String resultTypeName = s.readUTF(); final ClassPairResolver arrayAndResultTypeResolver = new ClassPairResolver(arrayTypeName, resultTypeName) { @Override Pair<Class<?>, Class<?>> resolveClassPair(final ForeignEntityProvider.MessageHandler messageHandler) throws UnableToResolveForeignEntityException { final Class<?> arrayType = DeserializationHelper.classForName(arrayTypeName, foreignClassLoader, "ForeignFunctionInfo.SubscriptArray " + calName, CompilerMessage.Identifier.makeFunction(calName), messageHandler); final Class<?> resultType = DeserializationHelper.classForName(resultTypeName, foreignClassLoader, "ForeignFunctionInfo.SubscriptArray " + calName, CompilerMessage.Identifier.makeFunction(calName), messageHandler); //We need to check that resultType is still assignment compatible with the subscripted array type after loading. //This could be broken by changes in the inheritence hierarchy of the underlying Java classes. // //note: we should not explicitly check that subscriptedArrayType == null and fail on that case with the message below. Rather we will //fail with a NPE since this would be a CAL implementation bug. This is because if arrayType cannot be subscripted nSubscriptArgs times, //then the type of arrayType must have changed. But that would invalidate the compiled module that this foreign function declaration //depends on. final Class<?> subscriptedArrayType = ForeignFunctionChecker.getSubscriptedArrayType(arrayType, nSubscriptArgs); if (!resultType.isAssignableFrom(subscriptedArrayType)) { //"The underlying Java types involved in the CAL foreign function {0} have changed. Recompilation is needed." messageHandler.handleMessage(new CompilerMessage( new SourceRange(moduleName.toSourceText()), CompilerMessage.Identifier.makeFunction(calName), new MessageKind.Error.InvalidJavaTypeChangeOnLoading(calName))); } return new Pair<Class<?>, Class<?>>(arrayType, resultType); } }; final ForeignEntityProvider<Class<?>> arrayTypeProvider = ForeignEntityProvider.make(msgLogger, arrayAndResultTypeResolver.getFirstResolver()); if (arrayTypeProvider == null) { return null; } final ForeignEntityProvider<Class<?>> resultTypeProvider = ForeignEntityProvider.make(msgLogger, arrayAndResultTypeResolver.getSecondResolver()); if (resultTypeProvider == null) { return null; } s.skipRestOfRecord(); return new SubscriptArray(calName, nSubscriptArgs, arrayTypeProvider, resultTypeProvider); } } /** * Information about a CAL foreign function that is in fact a Java array update operator. * <p> * * The Java expression: * arrayExpr[index1][index2]...[index_m] = elemExpr * is considered as a CAL function of m + 2 arguments: * T1 -> Prelude.Int -> Prelude.Int -> ... -> Prelude.Int -> T2 -> T2 * where T1 is a CAL type whose implementation type is an n-dimensional Java array type * and T2 is a CAL type whose implementation type is the Java type resulting in subscripting the array with m indices. * In particular, we require that m >= 1 and m <= n to satisfy the constraints on subscripting. Note that the update * function just returns the value that is being updated. * <p> * * Note that the subscript arguments can be any foreign type with Java implementation type "int" and not just * Prelude.Int. * * @author Bo Ilic */ public static final class UpdateArray extends ForeignFunctionInfo { private static final int serializationSchema = 0; /** * the number of supplied int arguments specifying dimension sizes. Must be >= 1 and <= the number of dimensions * in the array. */ private final int nSubscriptArgs; /** the type of the array having one of its elements updated. */ private final ForeignEntityProvider<Class<?>> arrayTypeProvider; /** the type of the element being updated. */ private final ForeignEntityProvider<Class<?>> elementTypeProvider; UpdateArray(final QualifiedName calName, final int nSubscriptArgs, final ForeignEntityProvider<Class<?>> arrayTypeProvider, final ForeignEntityProvider<Class<?>> elementTypeProvider) { super(calName, JavaKind.UPDATE_ARRAY); if (arrayTypeProvider == null) { throw new NullPointerException(); } this.nSubscriptArgs = nSubscriptArgs; this.arrayTypeProvider = arrayTypeProvider; this.elementTypeProvider = elementTypeProvider; } /** * {@inheritDoc} */ @Override public int getNArguments() { return nSubscriptArgs + 2; } /** {@inheritDoc} */ @Override public Class<?> getJavaArgumentType(final int argN) throws UnableToResolveForeignEntityException { if (argN == 0) { return arrayTypeProvider.get(); } else if (argN > 0 && argN <= nSubscriptArgs) { return int.class; } else if (argN == nSubscriptArgs + 1) { return elementTypeProvider.get(); } throw new IndexOutOfBoundsException(); } /** {@inheritDoc} */ @Override public Class<?> getJavaReturnType() throws UnableToResolveForeignEntityException { return elementTypeProvider.get(); } /** {@inheritDoc} */ @Override public void resolveForeignEntities() throws UnableToResolveForeignEntityException { // Force resolution by calling get() on each of the ForeignEntityProviders arrayTypeProvider.get(); elementTypeProvider.get(); } /** * {@inheritDoc} */ @Override public String toString() { final StringBuilder result = new StringBuilder ("foreign ").append(super.calName); result.append(" [kind = ").append(super.javaKind).append("]"); result.append(" [nUpdateArgs = ").append(nSubscriptArgs).append(", type = "); result.append(arrayTypeProvider).append("]"); return result.toString(); } /** * {@inheritDoc} */ @Override final void write (final RecordOutputStream s) throws IOException, UnableToResolveForeignEntityException { s.startRecord(ModuleSerializationTags.FOREIGN_FUNCTION_INFO_UPDATE_ARRAY, serializationSchema); super.write(s); s.writeInt(nSubscriptArgs); s.writeUTF(arrayTypeProvider.get().getName()); s.writeUTF(elementTypeProvider.get().getName()); s.endRecord(); } /** * Load an instance of ForeignFunctionInfo.UpdateArray from the RecordInputStream. * Read position will be before the record header. * @param s * @param moduleName the name of the module being loaded * @param foreignClassLoader the classloader to use to resolve foreign classes. * @param msgLogger the logger to which to log deserialization messages. * @return an instance of ForeignFunctionInfo, or null if there was a problem resolving classes. * @throws IOException */ private static final ForeignFunctionInfo load (final RecordInputStream s, final int schema, final ModuleName moduleName, final ClassLoader foreignClassLoader, final CompilerMessageLogger msgLogger) throws IOException { DeserializationHelper.checkSerializationSchema(schema, serializationSchema, moduleName, "ForeignFunctionInfo.UpdateArray", msgLogger); //read the superclass fields final QualifiedName calName = s.readQualifiedName(); final JavaKind javaKind = JavaKind.load(s, moduleName, msgLogger); if (javaKind != JavaKind.UPDATE_ARRAY) { throw new IOException(); } final int nSubscriptArgs = s.readInt(); final String arrayTypeName = s.readUTF(); final String elementTypeName = s.readUTF(); final ClassPairResolver arrayAndElementTypeResolver = new ClassPairResolver(arrayTypeName, elementTypeName) { @Override Pair<Class<?>, Class<?>> resolveClassPair(final ForeignEntityProvider.MessageHandler messageHandler) throws UnableToResolveForeignEntityException { final Class<?> arrayType = DeserializationHelper.classForName(arrayTypeName, foreignClassLoader, "ForeignFunctionInfo.UpdateArray " + calName, CompilerMessage.Identifier.makeFunction(calName), messageHandler); final Class<?> elementType = DeserializationHelper.classForName(elementTypeName, foreignClassLoader, "ForeignFunctionInfo.UpdateArray " + calName, CompilerMessage.Identifier.makeFunction(calName), messageHandler); //We need to check that elementType is still assignment compatible with the subscripted array type after loading. //This could be broken by changes in the inheritence hierarchy of the underlying Java classes. // //note: we should not explicitly check that subscriptedArrayType == null and fail on that case with the message below. Rather we will //fail with a NPE since this would be a CAL implementation bug. This is because if arrayType cannot be subscripted nSubscriptArgs times, //then the type of arrayType must have changed. But that would invalidate the compiled module that this foreign function declaration //depends on. final Class<?> subscriptedArrayType = ForeignFunctionChecker.getSubscriptedArrayType(arrayType, nSubscriptArgs); if (!subscriptedArrayType.isAssignableFrom(elementType)) { //"The underlying Java types involved in the CAL foreign function {0} have changed. Recompilation is needed." messageHandler.handleMessage(new CompilerMessage( new SourceRange(moduleName.toSourceText()), CompilerMessage.Identifier.makeFunction(calName), new MessageKind.Error.InvalidJavaTypeChangeOnLoading(calName))); } return new Pair<Class<?>, Class<?>>(arrayType, elementType); } }; final ForeignEntityProvider<Class<?>> arrayTypeProvider = ForeignEntityProvider.make(msgLogger, arrayAndElementTypeResolver.getFirstResolver()); if (arrayTypeProvider == null) { return null; } final ForeignEntityProvider<Class<?>> elementTypeProvider = ForeignEntityProvider.make(msgLogger, arrayAndElementTypeResolver.getSecondResolver()); if (elementTypeProvider == null) { return null; } s.skipRestOfRecord(); return new UpdateArray(calName, nSubscriptArgs, arrayTypeProvider, elementTypeProvider); } } private ForeignFunctionInfo(final QualifiedName calName, final JavaKind javaKind) { if (calName == null || javaKind == null) { throw new NullPointerException(); } this.calName = calName; this.javaKind = javaKind; } /** * Creation date: (June 28, 2002) * @return QualifiedName name of the foreign function in CAL e.g. "Cal.Core.Prelude.isLowerCase" */ final public QualifiedName getCalName() { return calName; } /** * @return Class the return type of the foreign entity as a Java class. Java methods that return void will * return Void.class here. * @throws UnableToResolveForeignEntityException */ abstract public Class<?> getJavaReturnType() throws UnableToResolveForeignEntityException; /** * @param argN a zero-based argument index * @return Class the Java class corresponding to the argN argument of the CAL foreign function. * @throws UnableToResolveForeignEntityException */ abstract public Class<?> getJavaArgumentType(int argN) throws UnableToResolveForeignEntityException; /** * Creation date: (June 28, 2002) * @return int number of arguments as a CAL function. * @throws UnableToResolveForeignEntityException */ abstract public int getNArguments() throws UnableToResolveForeignEntityException; /** * Force the resolution of any associated foreign entities. * @throws UnableToResolveForeignEntityException */ abstract public void resolveForeignEntities() throws UnableToResolveForeignEntityException; /** * Creation date: (June 28, 2002) * @return whether a method, static method, field, static field, constructor or cast */ final public JavaKind getJavaKind() { return javaKind; } /** * String representation of this value. This function handles inconsistent state ForeignFunctionInfo * objects and so should be useful for debugging. * Creation date: (May 7, 2002) * @return String */ @Override abstract public String toString(); /** * Write this instance of ForeignFunctionInfo to the RecordOutputStream. * @param s * @throws IOException * @throws UnableToResolveForeignEntityException */ void write (final RecordOutputStream s) throws IOException, UnableToResolveForeignEntityException { s.writeQualifiedName(calName); javaKind.write(s); } /** * Load an instance of ForeignFunctionInfo from the RecordInputStream. * Read position will be before the record header. * @param s * @param moduleName the name of the module being loaded * @param foreignClassLoader the classloader to use to resolve foreign classes. * @param msgLogger the logger to which to log deserialization messages. * @return an instance of ForeignFunctionInfo, or null if there was a problem resolving classes. * @throws IOException */ static final ForeignFunctionInfo load (final RecordInputStream s, final ModuleName moduleName, final ClassLoader foreignClassLoader, final CompilerMessageLogger msgLogger) throws IOException { // Load the record header and determine which actual class we are loading. final RecordHeaderInfo rhi = s.findRecord(SERIALIZATION_RECORD_TAGS); if (rhi == null) { throw new IOException ("Unable to find record header for ForeignFunctionInfo."); } switch(rhi.getRecordTag()) { case ModuleSerializationTags.FOREIGN_FUNCTION_INFO_INVOCATION: { return Invocation.loadInvocation(s, rhi.getSchema(), moduleName, foreignClassLoader, msgLogger); } case ModuleSerializationTags.FOREIGN_FUNCTION_INFO_CAST: { return Cast.load(s, rhi.getSchema(), moduleName, foreignClassLoader, msgLogger); } case ModuleSerializationTags.FOREIGN_FUNCTION_INFO_INSTANCE_OF: { return InstanceOf.load(s, rhi.getSchema(), moduleName, foreignClassLoader, msgLogger); } case ModuleSerializationTags.FOREIGN_FUNCTION_INFO_NULL_LITERAL: { return NullLiteral.load(s, rhi.getSchema(), moduleName, foreignClassLoader, msgLogger); } case ModuleSerializationTags.FOREIGN_FUNCTION_INFO_NULL_CHECK: { return NullCheck.load(s, rhi.getSchema(), moduleName, foreignClassLoader, msgLogger); } case ModuleSerializationTags.FOREIGN_FUNCTION_INFO_NEW_ARRAY: { return NewArray.load(s, rhi.getSchema(), moduleName, foreignClassLoader, msgLogger); } case ModuleSerializationTags.FOREIGN_FUNCTION_INFO_LENGTH_ARRAY: { return LengthArray.load(s, rhi.getSchema(), moduleName, foreignClassLoader, msgLogger); } case ModuleSerializationTags.FOREIGN_FUNCTION_INFO_SUBSCRIPT_ARRAY: { return SubscriptArray.load(s, rhi.getSchema(), moduleName, foreignClassLoader, msgLogger); } case ModuleSerializationTags.FOREIGN_FUNCTION_INFO_UPDATE_ARRAY: { return UpdateArray.load(s, rhi.getSchema(), moduleName, foreignClassLoader, msgLogger); } case ModuleSerializationTags.FOREIGN_FUNCTION_INFO_CLASS_LITERAL: { return ClassLiteral.load(s, rhi.getSchema(), moduleName, foreignClassLoader, msgLogger); } default: { throw new IOException("Unexpected record tag " + rhi.getRecordTag() + " encountered loading ForeignFunctionInfo."); } } } }