Package org.openquark.cal.compiler

Source Code of org.openquark.cal.compiler.ForeignFunctionInfo$NullCheck

/*
* 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.");
            }
        }   
    }
 
}
TOP

Related Classes of org.openquark.cal.compiler.ForeignFunctionInfo$NullCheck

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.