/*
* 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.
*/
/*
* StandaloneJarBuilder.java
* Created: May 17, 2007
* By: Joseph Wong
*/
package org.openquark.cal.internal.machine.lecc;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Modifier;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipOutputStream;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.commons.EmptyVisitor;
import org.objectweb.asm.signature.SignatureReader;
import org.objectweb.asm.signature.SignatureVisitor;
import org.openquark.cal.caldoc.CALDocToJavaDocUtilities;
import org.openquark.cal.caldoc.CALDocToJavaDocUtilities.JavadocCrossReferenceGenerator;
import org.openquark.cal.compiler.ClassMethod;
import org.openquark.cal.compiler.DataConstructor;
import org.openquark.cal.compiler.ForeignTypeInfo;
import org.openquark.cal.compiler.FunctionalAgent;
import org.openquark.cal.compiler.IdentifierInfo;
import org.openquark.cal.compiler.ModuleName;
import org.openquark.cal.compiler.ModuleTypeInfo;
import org.openquark.cal.compiler.QualifiedName;
import org.openquark.cal.compiler.Scope;
import org.openquark.cal.compiler.ScopedEntityNamingPolicy;
import org.openquark.cal.compiler.SourceModelTraverser;
import org.openquark.cal.compiler.TypeConsApp;
import org.openquark.cal.compiler.TypeExpr;
import org.openquark.cal.compiler.UnableToResolveForeignEntityException;
import org.openquark.cal.compiler.CALDocComment.ModuleReference;
import org.openquark.cal.compiler.CALDocComment.ScopedEntityReference;
import org.openquark.cal.compiler.SourceModel.Constraint.TypeClass;
import org.openquark.cal.internal.javamodel.AsmJavaBytecodeGenerator;
import org.openquark.cal.internal.javamodel.JavaClassRep;
import org.openquark.cal.internal.javamodel.JavaConstructor;
import org.openquark.cal.internal.javamodel.JavaExpression;
import org.openquark.cal.internal.javamodel.JavaFieldDeclaration;
import org.openquark.cal.internal.javamodel.JavaGenerationException;
import org.openquark.cal.internal.javamodel.JavaMethod;
import org.openquark.cal.internal.javamodel.JavaOperator;
import org.openquark.cal.internal.javamodel.JavaReservedWords;
import org.openquark.cal.internal.javamodel.JavaSourceGenerator;
import org.openquark.cal.internal.javamodel.JavaStatement;
import org.openquark.cal.internal.javamodel.JavaTypeName;
import org.openquark.cal.internal.javamodel.JavaExpression.LiteralWrapper;
import org.openquark.cal.internal.javamodel.JavaStatement.JavaDocComment;
import org.openquark.cal.internal.machine.lecc.LECCModule.FunctionGroupInfo;
import org.openquark.cal.internal.module.Cal.Core.CAL_Prelude_internal;
import org.openquark.cal.internal.runtime.lecc.LECCMachineConfiguration;
import org.openquark.cal.internal.runtime.lecc.RTValue;
import org.openquark.cal.internal.runtime.lecc.StandaloneJarGeneratedCodeInfo;
import org.openquark.cal.internal.runtime.lecc.StandaloneJarResourceAccess;
import org.openquark.cal.machine.MachineFunction;
import org.openquark.cal.module.Cal.Core.CAL_Prelude;
import org.openquark.cal.runtime.ExecutionContext;
import org.openquark.cal.runtime.ExecutionContextProperties;
import org.openquark.cal.runtime.MachineType;
import org.openquark.cal.runtime.ResourceAccess;
import org.openquark.cal.services.CALWorkspace;
import org.openquark.cal.services.GemEntity;
import org.openquark.cal.services.ModulePackager;
import org.openquark.cal.services.ProgramModelManager;
import org.openquark.cal.services.WorkspaceManager;
import org.openquark.util.FileSystemHelper;
import org.openquark.util.Pair;
/**
* This class implements a builder for constructing a <em>standalone JAR</em>, which may contain
* application and library classes.
* <p>
* A standalone JAR may package up a CAL application by gathering together all the generated runtime
* classes necessary for running a specific CAL function, and a class containing a
* <code>public static void main(String[] args)</code> method which runs the CAL function directly
* (without having to first initialize a CAL workspace). This makes it possible to package up a CAL
* function into a command-line application.
* <p>
* To run the CAL function using the standalone JAR, simply include the standalone JAR, the CAL
* platform jars, and any required external jars on the classpath, and specify the name of the
* generated class as the one to run. Command line arguments will be passed into the CAL function.
* <p>
* A standalone JAR may also package up one or more library classes - a library class is a non-instatiatable class
* containing static methods corresponding to the functions and data constructors defined in a particular
* CAL module. This makes it possible to expose CAL libraries in Java, by defining API modules in CAL (whose
* functions may include code marshalling to/from foreign types), from which library classes are
* generated.
* <p>
* This utility only works with the LECC machine.
* <p>
* In this version, we support generating standalone applications for CAL functions with the type
* <code>[String] -> ()</code>. In this case, the command line arguments array will be marshalled
* into a CAL list of Strings.
*
* @see org.openquark.cal.services.StandaloneJarTool the command-line interface for this JAR builder.
*
* @author Joseph Wong
*/
public final class StandaloneJarBuilder {
/** The name of the String[] parameter of the main method in the generated main class. */
private static final String ARGS_PARAMETER_NAME = "args";
/** The default user-friendly name to use for the execution context variable. */
private static final String EXECUTION_CONTEXT_USER_FRIENDLY_NAME = "executionContext";
/** A debugging flag to enable the generation of private entities in library classes. */
private static final boolean ENABLE_GENERATION_OF_PRIVATE_ENTITIES_FOR_DEBUGGING = false;
/**
* This class implements a bytecode visitor (based on the ASM library) which identifies the names of
* all LECC generated classes that are referenced in the bytecode of a specific class.
*
* @author Joseph Wong
*/
private static final class GeneratedClassDependencyFindingVisitor extends EmptyVisitor {
/**
* This class implements a signature visitor which identifiers the names of all LECC generated classes
* that are referenced in a class, method, or field signature.
*
* @author Joseph Wong
*/
private final class GeneratedClassDependencyFindingSignatureVisitor implements SignatureVisitor {
/**
* The last class name encountered by this visitor. This field is important for building the fully
* qualified name of an inner class (because the argument to {@link #visitInnerClassType} is only the
* unqualified name of the inner class).
*/
String lastClassName;
/** {@inheritDoc} */
public SignatureVisitor visitArrayType() {
return this;
}
/** {@inheritDoc} */
public void visitBaseType(final char descriptor) {}
/** {@inheritDoc} */
public SignatureVisitor visitClassBound() {
return this;
}
/** {@inheritDoc} */
public void visitClassType(final String name) {
lastClassName = name;
processInternalClassName(name);
}
/** {@inheritDoc} */
public void visitEnd() {}
/** {@inheritDoc} */
public SignatureVisitor visitExceptionType() {
return this;
}
/** {@inheritDoc} */
public void visitFormalTypeParameter(final String name) {}
/** {@inheritDoc} */
public void visitInnerClassType(final String name) {
final String fullInnerClassName = lastClassName + "." + name;
lastClassName = fullInnerClassName;
processInternalClassName(fullInnerClassName);
}
/** {@inheritDoc} */
public SignatureVisitor visitInterface() {
return this;
}
/** {@inheritDoc} */
public SignatureVisitor visitInterfaceBound() {
return this;
}
/** {@inheritDoc} */
public SignatureVisitor visitParameterType() {
return this;
}
/** {@inheritDoc} */
public SignatureVisitor visitReturnType() {
return this;
}
/** {@inheritDoc} */
public SignatureVisitor visitSuperclass() {
return this;
}
/** {@inheritDoc} */
public void visitTypeArgument() {}
/** {@inheritDoc} */
public SignatureVisitor visitTypeArgument(final char wildcard) {
return this;
}
/** {@inheritDoc} */
public void visitTypeVariable(final String name) {}
}
/**
* The SignatureVisitor instance for collecting the class names appearing in class/method/field signatures.
*/
private final SignatureVisitor signatureVisitor = new GeneratedClassDependencyFindingSignatureVisitor();
/**
* The set of names of generated classes that have been encountered so far.
*/
private final SortedSet<String> foundClassNames = new TreeSet<String>();
/**
* @return the set of names of generated classes that have been encountered so far.
*/
SortedSet<String> getFoundClassNames() {
return foundClassNames;
}
/**
* Processes a field type signature.
* @param signature a field type signature. Can be null.
*/
private void processFieldTypeSignature(final String signature) {
if (signature != null) {
new SignatureReader(signature).acceptType(signatureVisitor);
}
}
/**
* Processes a class signature or a method type signature.
* @param signature a class signature or a method type signature. Can be null.
*/
private void processClassOrMethodTypeSignature(final String signature) {
if (signature != null) {
new SignatureReader(signature).accept(signatureVisitor);
}
}
/**
* Processes an internal class name (internal as in <code>java/lang/Object</code> rather than <code>java.lang.Object</code>).
* If the class name refers to a generated class, then it is added to the set {@link #foundClassNames}.
*
* @param internalClassName the internal class name.
*/
private void processInternalClassName(final String internalClassName) {
if (internalClassName.startsWith("org/openquark/cal_")) {
foundClassNames.add(internalClassName.replace('/', '.'));
}
}
/** {@inheritDoc} */
@Override
public void visit(final int version, final int access, final String name, final String signature, final String superName, final String[] interfaces) {
processClassOrMethodTypeSignature(signature);
processInternalClassName(superName);
for (final String interfaceName : interfaces) {
processInternalClassName(interfaceName);
}
super.visit(version, access, name, signature, superName, interfaces);
}
/** {@inheritDoc} */
@Override
public AnnotationVisitor visitAnnotation(final String desc, final boolean visible) {
throw new UnsupportedOperationException("We don't currently generate LECC code with annotations.");
//processClassOrMethodTypeSignature(desc);
//return super.visitAnnotation(desc, visible);
}
/** {@inheritDoc} */
@Override
public AnnotationVisitor visitAnnotation(final String name, final String desc) {
throw new UnsupportedOperationException("We don't currently generate LECC code with annotations.");
//processClassOrMethodTypeSignature(desc);
//return super.visitAnnotation(name, desc);
}
/** {@inheritDoc} */
@Override
public void visitEnum(final String name, final String desc, final String value) {
throw new UnsupportedOperationException("We don't currently generate LECC code with Java 5 enumerations.");
//processClassOrMethodTypeSignature(desc);
//super.visitEnum(name, desc, value);
}
/** {@inheritDoc} */
@Override
public FieldVisitor visitField(final int access, final String name, final String desc, final String signature, final Object value) {
processClassOrMethodTypeSignature(desc);
processFieldTypeSignature(signature);
return super.visitField(access, name, desc, signature, value);
}
/** {@inheritDoc} */
@Override
public void visitFieldInsn(final int opcode, final String owner, final String name, final String desc) {
processInternalClassName(owner);
processClassOrMethodTypeSignature(desc);
super.visitFieldInsn(opcode, owner, name, desc);
}
/** {@inheritDoc} */
@Override
public void visitInnerClass(final String name, final String outerName, final String innerName, final int access) {
processInternalClassName(name);
// we don't process the outer class name, because we should have already visited the outer class declaration.
super.visitInnerClass(name, outerName, innerName, access);
}
/** {@inheritDoc} */
@Override
public void visitLocalVariable(final String name, final String desc, final String signature, final Label start, final Label end, final int index) {
processClassOrMethodTypeSignature(desc);
processFieldTypeSignature(signature);
super.visitLocalVariable(name, desc, signature, start, end, index);
}
/** {@inheritDoc} */
@Override
public MethodVisitor visitMethod(final int access, final String name, final String desc, final String signature, final String[] exceptions) {
processClassOrMethodTypeSignature(desc);
processClassOrMethodTypeSignature(signature);
return super.visitMethod(access, name, desc, signature, exceptions);
}
/** {@inheritDoc} */
@Override
public void visitMethodInsn(final int opcode, final String owner, final String name, final String desc) {
processInternalClassName(owner);
processClassOrMethodTypeSignature(desc);
super.visitMethodInsn(opcode, owner, name, desc);
}
/** {@inheritDoc} */
@Override
public void visitMultiANewArrayInsn(final String desc, final int dims) {
processClassOrMethodTypeSignature(desc);
super.visitMultiANewArrayInsn(desc, dims);
}
/** {@inheritDoc} */
@Override
public void visitOuterClass(final String owner, final String name, final String desc) {
processInternalClassName(owner);
processInternalClassName(owner + "$" + name);
processClassOrMethodTypeSignature(desc);
super.visitOuterClass(owner, name, desc);
}
/** {@inheritDoc} */
@Override
public AnnotationVisitor visitParameterAnnotation(final int parameter, final String desc, final boolean visible) {
throw new UnsupportedOperationException("We don't currently generate LECC code with annotations.");
//processClassOrMethodTypeSignature(desc);
//return super.visitParameterAnnotation(parameter, desc, visible);
}
/** {@inheritDoc} */
@Override
public void visitTypeInsn(final int opcode, final String desc) {
// The ASM documentation says that the descriptor can either be an array descriptor, or an internal class name.
if (desc.startsWith("[")) {
processClassOrMethodTypeSignature(desc);
} else {
processInternalClassName(desc);
}
super.visitTypeInsn(opcode, desc);
}
}
/**
* This subclass of {@link JarOutputStream} overrides {@link JarOutputStream#putNextEntry} to log the
* addition of each new entry to a progress monitor.
*
* @author Joseph Wong
*/
private static final class LoggingJarOutputStream extends JarOutputStream {
/** The progress monitor to be used. */
private final Monitor monitor;
/**
* Constructs an instance of this class.
* @param outputStream the actual output stream.
* @param manifest the manifest.
* @param monitor the progress monitor to be used.
* @throws IOException
*/
private LoggingJarOutputStream(final OutputStream outputStream, final Manifest manifest, final Monitor monitor) throws IOException {
super(outputStream);
// First set up the monitor (needed by putNextEntry)
if (monitor == null) {
throw new NullPointerException();
}
this.monitor = monitor;
// Then, write out the manifest.
final ZipEntry zipEntry = new ZipEntry(JarFile.MANIFEST_NAME);
putNextEntry(zipEntry);
try {
manifest.write(new BufferedOutputStream(this));
} finally {
closeEntry();
}
}
/**
* {@inheritDoc}
*/
@Override
public void putNextEntry(final ZipEntry ze) throws IOException {
super.putNextEntry(ze);
monitor.addingFile(ze.getName());
}
}
/**
* This subclass of {@link ZipOutputStream} overrides {@link ZipOutputStream#putNextEntry} to log the
* addition of each new entry to a progress monitor.
*
* @author Joseph Wong
*/
private static final class LoggingSourceZipOutputStream extends ZipOutputStream {
/** The progress monitor to be used. */
private final Monitor monitor;
/**
* Constructs an instance of this class.
* @param outputStream the actual output stream.
* @param monitor the progress monitor to be used.
*/
private LoggingSourceZipOutputStream(final OutputStream outputStream, final Monitor monitor) {
super(outputStream);
if (monitor == null) {
throw new NullPointerException();
}
this.monitor = monitor;
}
/**
* {@inheritDoc}
*/
@Override
public void putNextEntry(final ZipEntry ze) throws IOException {
super.putNextEntry(ze);
monitor.addingSourceFile(ze.getName());
}
}
/**
* This interface specifies a progress monitor to be used with a {@link StandaloneJarBuilder} for
* monitoring the progress of the JAR building operation.
*
* @author Joseph Wong
*/
public static interface Monitor {
/**
* Invoked when the {@link StandaloneJarBuilder} has started adding files to the JAR.
* @param jarName the name of the JAR being built.
*/
public void jarBuildingStarted(String jarName);
/**
* Invoked when the {@link StandaloneJarBuilder} is adding a file to the JAR.
* @param fileName the name of file being added.
*/
public void addingFile(String fileName);
/**
* Invoked when the {@link StandaloneJarBuilder} is skipping a function because its type contains type class constraints.
* @param name the name of the function being skipped.
*/
public void skippingFunctionWithTypeClassConstraints(QualifiedName name);
/**
* Invoked when the {@link StandaloneJarBuilder} is skipping a class method because its type contains type class constraints.
* @param name the name of the function being skipped.
*/
public void skippingClassMethod(QualifiedName name);
/**
* Invoked when the {@link StandaloneJarBuilder} is adding a source file to the source zip.
* @param fileName the name of file being added.
*/
public void addingSourceFile(String fileName);
/**
* Invoked when the {@link StandaloneJarBuilder} has finished building a JAR.
* @param jarName the name of the JAR being built.
* @param success true if the operation was successful; false otherwise.
*/
public void jarBuildingDone(String jarName, boolean success);
}
/**
* This is a default implementation of the Monitor interface which does nothing.
*
* This class is meant to be used through its singleton instance.
*
* @author Joseph Wong
*/
public static final class DefaultMonitor implements Monitor {
/** Private constructor. */
private DefaultMonitor() {}
// default implementation
public void jarBuildingStarted(final String jarName) {}
public void addingFile(final String fileName) {}
public void skippingFunctionWithTypeClassConstraints(final QualifiedName name) {}
public void skippingClassMethod(final QualifiedName name) {}
public void addingSourceFile(final String fileName) {}
public void jarBuildingDone(final String jarName, final boolean success) {}
/** Singleton instance. */
public static final DefaultMonitor INSTANCE = new DefaultMonitor();
}
/**
* This exception class represents an invalid configuration for the JAR building operation.
*
* @author Joseph Wong
*/
public static final class InvalidConfigurationException extends Exception {
private static final long serialVersionUID = -69241695569382720L;
/**
* Constructs an instance of this exception.
* @param message the message string.
*/
private InvalidConfigurationException(final String message) {
super(message);
}
}
/**
* This exception class represents some internal problem that occurred during the JAR building operation.
*
* @author Joseph Wong
*/
public static final class InternalProblemException extends Exception {
private static final long serialVersionUID = 5489404664329733927L;
/**
* Constructs an instance of this exception.
* @param cause the underlying cause.
*/
private InternalProblemException(final Exception cause) {
super(cause);
}
}
/**
* Represents information about an argument or return type for a generated library method.
*
* @author Joseph Wong
*/
private static final class ClassTypeInfo {
/**
* The underlying Java type represented. Can be null to indicate that the CAL type is not a foreign type.
*/
private final Class<?> classType;
/**
* The name of the type as exposed as an argument or return type of the generated library method.
*/
private final JavaTypeName exposedTypeName;
/**
* The name of the {@link RTValue} subclass for representing values of this type.
*/
private final JavaTypeName rtValueTypeName;
/**
* The name of the {@link RTValue} method for unmarshalling the underlying value. Can be null to indicate that the CAL type is not a foreign type.
*/
private final String unmarshallingMethodName;
/**
* Private constructor.
* @param classType the underlying Java type represented. Can be null to indicate that the CAL type is not a foreign type.
* @param exposedTypeName the name of the type as exposed as an argument or return type of the generated library method.
* @param rtValueTypeName the name of the {@link RTValue} subclass for representing values of this type.
* @param unmarshallingMethodName the name of the {@link RTValue} method for unmarshalling the underlying value. Can be null to indicate that the CAL type is not a foreign type.
*/
private ClassTypeInfo(final Class<?> classType, final JavaTypeName exposedTypeName, final JavaTypeName rtValueTypeName, final String unmarshallingMethodName) {
if (exposedTypeName == null || rtValueTypeName == null) {
throw new NullPointerException();
}
if (classType == null || unmarshallingMethodName == null) {
if (!(classType == null && unmarshallingMethodName == null)) {
throw new IllegalArgumentException("classType and unmarshallingMethodName must be both null, or both non-null");
}
}
this.classType = classType;
this.exposedTypeName = exposedTypeName;
this.rtValueTypeName = rtValueTypeName;
this.unmarshallingMethodName = unmarshallingMethodName;
}
/**
* Factory method for creating instances.
* @param classType the underlying Java type represented. Can be null to indicate that the CAL type is not a foreign type.
* @return an instance of this class.
*/
private static ClassTypeInfo make(final Class<?> classType) {
if (classType == null) {
return makeNonForeign();
}
final JavaTypeName rtValueTypeName;
final String unmarshallingMethodName;
if (classType == char.class) {
rtValueTypeName = JavaTypeNames.RTDATA_CHAR;
unmarshallingMethodName = "getCharValue";
} else if (classType == boolean.class) {
rtValueTypeName = JavaTypeNames.RTDATA_BOOLEAN;
unmarshallingMethodName = "getBooleanValue";
} else if (classType == byte.class) {
rtValueTypeName = JavaTypeNames.RTDATA_BYTE;
unmarshallingMethodName = "getByteValue";
} else if (classType == short.class) {
rtValueTypeName = JavaTypeNames.RTDATA_SHORT;
unmarshallingMethodName = "getShortValue";
} else if (classType == int.class) {
rtValueTypeName = JavaTypeNames.RTDATA_INT;
unmarshallingMethodName = "getIntValue";
} else if (classType == long.class) {
rtValueTypeName = JavaTypeNames.RTDATA_LONG;
unmarshallingMethodName = "getLongValue";
} else if (classType == float.class) {
rtValueTypeName = JavaTypeNames.RTDATA_FLOAT;
unmarshallingMethodName = "getFloatValue";
} else if (classType == double.class) {
rtValueTypeName = JavaTypeNames.RTDATA_DOUBLE;
unmarshallingMethodName = "getDoubleValue";
} else if (classType == String.class) {
rtValueTypeName = JavaTypeNames.RTDATA_STRING;
unmarshallingMethodName = "getStringValue";
} else if (classType == BigInteger.class) {
rtValueTypeName = JavaTypeNames.RTDATA_INTEGER;
unmarshallingMethodName = "getIntegerValue";
} else {
rtValueTypeName = JavaTypeNames.RTDATA_OPAQUE;
unmarshallingMethodName = "getOpaqueValue";
}
return new ClassTypeInfo(classType, JavaTypeName.make(classType), rtValueTypeName, unmarshallingMethodName);
}
/**
* Factory method for creating an instance representing a non foreign CAL type.
* @return an instance of this class.
*/
private static ClassTypeInfo makeNonForeign() {
return new ClassTypeInfo(null, JavaTypeName.CAL_VALUE, JavaTypeNames.RTVALUE, null);
}
/**
* @return the underlying Java type represented. Can be null to indicate that the CAL type is not a foreign type.
*/
private Class<?> getClassType() {
return classType;
}
/**
* @return the name of the type as exposed as an argument or return type of the generated library method.
*/
private JavaTypeName getExposedTypeName() {
return exposedTypeName;
}
/**
* @return the name of the {@link RTValue} subclass for representing values of this type.
*/
private JavaTypeName getRTValueTypeName() {
return rtValueTypeName;
}
/**
* @return the name of the {@link RTValue} method for unmarshalling the underlying value. Can be null to indicate that the CAL type is not a foreign type.
*/
private String getUnmarshallingMethodName() {
return unmarshallingMethodName;
}
/**
* @return the return type of the unmarshalling method. Can be null to indicate that the CAL type is not a foreign type.
*/
private JavaTypeName getReturnTypeOfUnmarshallingMethod() {
if (unmarshallingMethodName == null) {
return null;
} else {
if (rtValueTypeName.equals(JavaTypeNames.RTDATA_OPAQUE)) {
// getOpaqueValue()'s return type is java.lang.Object
return JavaTypeName.OBJECT;
} else {
// for other types, the return type is the primitive type represented by classType
return JavaTypeName.make(classType);
}
}
}
/**
* @return the argument type of the marshalling method. Can be null to indicate that the CAL type is not a foreign type.
*/
private JavaTypeName getArgumentTypeOfMarshallingMethod() {
// the same handling for return type of the unmarshalling method applies here
return getReturnTypeOfUnmarshallingMethod();
}
}
/**
* An enum representing the different scopes in Java.
*
* @author Joseph Wong
*/
public static enum JavaScope {
/**
* The public scope.
*/
PUBLIC(Modifier.PUBLIC),
/**
* The protected scope.
*/
PROTECTED(Modifier.PROTECTED),
/**
* The package (a.k.a. default) scope.
*/
PACKAGE(0),
/**
* The private scope.
*/
PRIVATE(Modifier.PRIVATE);
/**
* The associated modifier value, according to {@link Modifier}.
*/
private final int modifier;
/**
* @param modifier the associated modifier value, according to {@link Modifier}.
*/
JavaScope(final int modifier) {
this.modifier = modifier;
}
/**
* @return the associated modifier value, according to {@link Modifier}.
*/
int getModifier() {
return modifier;
}
}
/**
* The specification for a generated main class.
*
* @author Joseph Wong
*/
public static final class MainClassSpec {
/**
* The name of the main class to be generated.
*/
private final JavaTypeName className;
/**
* The name of the entry point function.
*/
private final QualifiedName entryPointName;
/**
* Private constructor.
* @param className the name of the main class to be generated.
* @param entryPointName the name of the entry point function.
*/
private MainClassSpec(final JavaTypeName className, final QualifiedName entryPointName) {
if (className == null || entryPointName == null) {
throw new NullPointerException();
}
this.className = className;
this.entryPointName = entryPointName;
}
/**
* Factory method for creating an instance of this class.
* @param className the name of the main class to be generated.
* @param entryPointName the name of the entry point function.
* @return an instance of this class.
* @throws InvalidConfigurationException if an invalid class name was specified.
*/
public static MainClassSpec make(final String className, final QualifiedName entryPointName) throws InvalidConfigurationException {
checkClassName(className);
return new MainClassSpec(JavaTypeName.make(className, false), entryPointName);
}
/**
* @return the name of the main class to be generated.
*/
public JavaTypeName getClassName() {
return className;
}
/**
* @return the name of the entry point function.
*/
public QualifiedName getEntryPointName() {
return entryPointName;
}
}
/**
* The specification for a generated library class.
*
* @author Joseph Wong
*/
public static final class LibraryClassSpec {
/**
* The scope of the class.
*/
private final JavaScope scope;
/**
* The name of the class.
*/
private final JavaTypeName className;
/**
* The name of the module.
*/
private final ModuleName moduleName;
/**
* Private constructor.
* @param scope the scope of the class.
* @param className the name of the class.
* @param moduleName the name of the module.
*/
private LibraryClassSpec(final JavaScope scope, final JavaTypeName className, final ModuleName moduleName) {
if (scope == null || className == null || moduleName == null) {
throw new NullPointerException();
}
this.scope = scope;
this.className = className;
this.moduleName = moduleName;
}
/**
* Factory method for creating an instance of this class.
* @param scope the scope of the class.
* @param className the name of the class.
* @param moduleName the name of the module.
* @return an instance of this class.
* @throws InvalidConfigurationException if an invalid class name was specified.
*/
public static LibraryClassSpec make(final JavaScope scope, final String className, final ModuleName moduleName) throws InvalidConfigurationException {
checkClassName(className);
return new LibraryClassSpec(scope, JavaTypeName.make(className, false), moduleName);
}
/**
* @return the scope of the class.
*/
public JavaScope getScope() {
return scope;
}
/**
* @return the name of the class.
*/
public JavaTypeName getClassName() {
return className;
}
/**
* @return the name of the module.
*/
public ModuleName getModuleName() {
return moduleName;
}
}
/**
* Implements a cross-reference generator capable of generating non-hyperlinked text for cross-references
* with a custom naming policy.
*
* @author Joseph Wong
*/
private static final class JavadocLinkGenerator extends JavadocCrossReferenceGenerator {
/**
* A mapping from functional agent names to names of the generated methods.
*/
private final Map<String, String> methodNameMapping;
/**
* The scoped entity naming policy to use.
*/
private final ScopedEntityNamingPolicy.UnqualifiedInCurrentModuleOrInPreludeIfUnambiguous namingPolicy;
/**
* Constructs an instance of this class.
* @param methodNameMapping a mapping from functional agent names to names of the generated methods.
* @param namingPolicy the scoped entity naming policy to use.
*/
private JavadocLinkGenerator(final Map<String, String> methodNameMapping, final ScopedEntityNamingPolicy.UnqualifiedInCurrentModuleOrInPreludeIfUnambiguous namingPolicy) {
if (methodNameMapping == null || namingPolicy == null) {
throw new NullPointerException();
}
this.methodNameMapping = methodNameMapping;
this.namingPolicy = namingPolicy;
}
/** {@inheritDoc} */
@Override
public String getModuleReferenceHTML(final ModuleReference reference) {
return reference.getName().toSourceText();
}
/** {@inheritDoc} */
@Override
public String getTypeConsReferenceHTML(final ScopedEntityReference reference) {
return namingPolicy.getName(new IdentifierInfo.TopLevel.TypeCons(reference.getName()));
}
/** {@inheritDoc} */
@Override
public String getDataConsReferenceHTML(final ScopedEntityReference reference) {
if (reference.getName().getModuleName().equals(namingPolicy.getCurrentModuleName())) {
return wrapWithEscapedLink(sanitizeMethodNameForJava(reference.getName().getUnqualifiedName(), methodNameMapping));
} else {
return namingPolicy.getName(new IdentifierInfo.TopLevel.DataCons(reference.getName()));
}
}
/** {@inheritDoc} */
@Override
public String getFunctionOrClassMethodReferenceHTML(final ScopedEntityReference reference) {
if (reference.getName().getModuleName().equals(namingPolicy.getCurrentModuleName())) {
return wrapWithEscapedLink(sanitizeMethodNameForJava(reference.getName().getUnqualifiedName(), methodNameMapping));
} else {
return namingPolicy.getName(new IdentifierInfo.TopLevel.FunctionOrClassMethod(reference.getName()));
}
}
/** {@inheritDoc} */
@Override
public String getTypeClassReferenceHTML(final ScopedEntityReference reference) {
return namingPolicy.getName(new IdentifierInfo.TopLevel.TypeClass(reference.getName()));
}
/** {@inheritDoc} */
@Override
public String postProcess(final String html) {
// todo-jowong do we want to generate the label, i.e. {@link #foo foo}, so as to make the generated Javadoc look good
// or do we want to generate simply {@link #foo}, which makes the code look good but the generated Javadoc ugly
return html
.replaceAll("<code><javadocLink>([^<]*)</javadocLink></code>", "{@link #$1 $1}")
.replaceAll("<javadocLink>([^<]*)</javadocLink>", "{@link #$1 $1}");
}
/**
* Wraps a Javadoc reference with an escaped link, to be processed later by {@link #postProcess}.
* @param referenceName the reference.
* @return the string for the escaped link.
*/
private String wrapWithEscapedLink(final String referenceName) {
return "<javadocLink>" + referenceName + "</javadocLink>";
}
}
/**
* Abstract class for representing a generator of the source zip file.
*
* @author Joseph Wong
*/
private static abstract class SourceGenerator {
/**
* Implements the null pattern - this is a generator that does nothing.
*
* @author Joseph Wong
*/
private static final class Null extends SourceGenerator {
/** {@inheritDoc} */
@Override
void generateSource(final ZipEntry zipEntry, final JavaClassRep classRep) {}
/** {@inheritDoc} */
@Override
void flushAndClose() {}
}
/**
* A generator for generating a source zip file.
*
* @author Joseph Wong
*/
private static final class ZipFile extends SourceGenerator {
/**
* The zip output stream.
*/
private final ZipOutputStream zos;
/**
* Constructs an instance of this class.
* @param zos the zip output stream.
*/
private ZipFile(final ZipOutputStream zos) {
if (zos == null) {
throw new NullPointerException();
}
this.zos = zos;
}
/** {@inheritDoc} */
@Override
void generateSource(final ZipEntry zipEntry, final JavaClassRep classRep) throws UnsupportedEncodingException, IOException, JavaGenerationException {
zos.putNextEntry(zipEntry);
try {
zos.write(JavaSourceGenerator.generateSourceCode(classRep).getBytes("UTF-8"));
} finally {
zos.closeEntry();
}
}
/** {@inheritDoc} */
@Override
void flushAndClose() throws IOException {
zos.flush();
zos.close();
}
}
/**
* Generates the source for the given Java class representation and put it in the zip file.
* @param zipEntry the associated zip entry.
* @param classRep the Java class representation.
* @throws UnsupportedEncodingException
* @throws IOException
* @throws JavaGenerationException
*/
abstract void generateSource(ZipEntry zipEntry, JavaClassRep classRep) throws UnsupportedEncodingException, IOException, JavaGenerationException;
/**
* Flushes and closes the underlying stream.
* @throws IOException
*/
abstract void flushAndClose() throws IOException;
}
/** For each supported literal type, this maps the Class of the boxed type to the Class of the unboxed type. */
private static final Map<Class<?>, Class<?>> supportedLiteralTypesBoxedToUnboxedClassMap = new HashMap<Class<?>, Class<?>>();
static {
supportedLiteralTypesBoxedToUnboxedClassMap.put(Character.class, char.class);
supportedLiteralTypesBoxedToUnboxedClassMap.put(Boolean.class, boolean.class);
supportedLiteralTypesBoxedToUnboxedClassMap.put(Byte.class, byte.class);
supportedLiteralTypesBoxedToUnboxedClassMap.put(Short.class, short.class);
supportedLiteralTypesBoxedToUnboxedClassMap.put(Integer.class, int.class);
supportedLiteralTypesBoxedToUnboxedClassMap.put(Float.class, float.class);
supportedLiteralTypesBoxedToUnboxedClassMap.put(Long.class, long.class);
supportedLiteralTypesBoxedToUnboxedClassMap.put(Double.class, double.class);
// String is special - it's not really a boxed type, so it maps to itself
supportedLiteralTypesBoxedToUnboxedClassMap.put(String.class, String.class);
// BigInteger is special - it's not really a boxed type, so it maps to itself
supportedLiteralTypesBoxedToUnboxedClassMap.put(BigInteger.class, BigInteger.class);
}
/** Private constructor. This class is not meant to be instantiated. */
private StandaloneJarBuilder() {}
/**
* Builds a standalone JAR based on the specified main class and library class specs, and writing the JAR to the named file location.
* Optionally a source zip file will also be generated.
* This method will not build the JAR if the arguments fail to pass the relevant semantic checks.
*
* @param outputFile the location of the output JAR to be written.
* @param srcZipOutputFile the local of the source zip file to be written. Can be null.
* @param mainClassSpecs a list of main class specs.
* @param libClassSpecs a list of library class specs.
* @param workspaceManager the WorkspaceManager which provides access to the program and the workspace.
* @param monitor the progress monitor to be used.
* @throws IOException
* @throws InvalidConfigurationException if an invalid configuration for the JAR building operation was specified.
* @throws InternalProblemException if some internal problem occurred during the JAR building operation.
* @throws UnableToResolveForeignEntityException
*/
public static void buildStandaloneJar(final File outputFile, final File srcZipOutputFile, final List<MainClassSpec> mainClassSpecs, final List<LibraryClassSpec> libClassSpecs, final WorkspaceManager workspaceManager, final Monitor monitor) throws IOException, InvalidConfigurationException, InternalProblemException, UnableToResolveForeignEntityException {
final File outputDirectory = outputFile.getParentFile();
if (outputDirectory != null) {
// If the file path has only the file name component, the outputDirectory is null
// and the file is intended to go into the current directory. Otherwise, we ensure
// the file's containing directory actually exists.
FileSystemHelper.ensureDirectoryExists(outputDirectory);
}
final FileOutputStream fileOutputStream = new FileOutputStream(outputFile);
////
/// Set up the source generator, if the source zip file location is not null.
//
final FileOutputStream srzZipFileOutputStream;
final BufferedOutputStream srcZipFileBufferedOutputStream;
final SourceGenerator sourceGen;
if (srcZipOutputFile != null) {
final File srcZipOutputDirectory = srcZipOutputFile.getParentFile();
if (srcZipOutputDirectory != null) {
// If the file path has only the file name component, the outputDirectory is null
// and the file is intended to go into the current directory. Otherwise, we ensure
// the file's containing directory actually exists.
FileSystemHelper.ensureDirectoryExists(outputDirectory);
}
srzZipFileOutputStream = new FileOutputStream(srcZipOutputFile);
srcZipFileBufferedOutputStream = new BufferedOutputStream(srzZipFileOutputStream, 1024);
final ZipOutputStream zos = new LoggingSourceZipOutputStream(srcZipFileBufferedOutputStream, monitor);
sourceGen = new SourceGenerator.ZipFile(zos);
} else {
srzZipFileOutputStream = null;
srcZipFileBufferedOutputStream = null;
sourceGen = new SourceGenerator.Null();
}
// keep track of whether the operation succeeded
boolean success = false;
try {
final BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream, 1024);
try {
buildStandaloneJarToOutputStream(outputFile.getAbsolutePath(), bufferedOutputStream, sourceGen, mainClassSpecs, libClassSpecs, workspaceManager, monitor);
} finally {
bufferedOutputStream.flush();
bufferedOutputStream.close();
if (srcZipFileBufferedOutputStream != null) {
srcZipFileBufferedOutputStream.flush();
srcZipFileBufferedOutputStream.close();
}
}
// if the operation reaches here without an exception, then it has succeeded
success = true;
} finally {
fileOutputStream.flush();
fileOutputStream.close();
if (srzZipFileOutputStream != null) {
srzZipFileOutputStream.flush();
srzZipFileOutputStream.close();
}
// if the operation failed with an exception, delete the output file
if (!success) {
outputFile.delete();
if (srcZipOutputFile != null) {
srcZipOutputFile.delete();
}
}
}
}
/**
* Builds a standalone JAR based on the specified main class and library class specs, and writing the JAR to the specified output stream.
* Optionally a source zip file will also be generated.
* This method will not build the JAR if the arguments fail to pass the relevant semantic checks.
*
* @param jarName the name of the JAR to be built.
* @param outputStream the output stream to which the JAR is to be written.
* @param sourceGen the generator to use for generating source files (into a zip file).
* @param mainClassSpecs a list of main class specs.
* @param libClassSpecs a list of library class specs.
* @param workspaceManager the WorkspaceManager which provides access to the program and the workspace.
* @param monitor the progress monitor to be used.
* @throws IOException
* @throws InvalidConfigurationException if an invalid configuration for the JAR building operation was specified.
* @throws InternalProblemException if some internal problem occurred during the JAR building operation.
* @throws UnableToResolveForeignEntityException
*/
private static void buildStandaloneJarToOutputStream(final String jarName, final OutputStream outputStream, final SourceGenerator sourceGen, final List<MainClassSpec> mainClassSpecs, final List<LibraryClassSpec> libClassSpecs, final WorkspaceManager workspaceManager, final Monitor monitor) throws IOException, InternalProblemException, InvalidConfigurationException, UnableToResolveForeignEntityException {
// Check the current machine configuration, and throw InvalidConfigurationException if there are problems
checkCurrentMachineConfiguration(workspaceManager);
// Keep track of whether the operation succeeded
boolean success = false;
monitor.jarBuildingStarted(jarName);
////
/// Create the manifest, and a JarOutputStream with the manifest.
//
final Manifest manifest = new Manifest();
final Attributes mainAttributes = manifest.getMainAttributes();
mainAttributes.putValue(Attributes.Name.MANIFEST_VERSION.toString(), "1.0");
if (!mainClassSpecs.isEmpty()) {
mainAttributes.putValue(Attributes.Name.MAIN_CLASS.toString(), mainClassSpecs.get(0).getClassName().getName());
}
final JarOutputStream jos = new LoggingJarOutputStream(outputStream, manifest, monitor);
final Set<String> classesAlreadyAdded = new HashSet<String>();
final Set<String> sourcesAlreadyAdded = new HashSet<String>();
final CALWorkspace workspace = workspaceManager.getWorkspace();
final SortedSet<ModuleName> allRequredModules = new TreeSet<ModuleName>();
try {
////
/// First generate the main classes (and their associated classes and resources)
//
for (final MainClassSpec spec : mainClassSpecs) {
final JavaTypeName mainClassName = spec.getClassName();
final QualifiedName entryPointName = spec.getEntryPointName();
// Check the main class's configuration, and throw InvalidConfigurationException if there are problems
checkMainClassConfiguration(entryPointName, workspaceManager);
final ModuleName rootModule = entryPointName.getModuleName();
////
/// Write out the main class and the generated classes required by the class.
//
final String mainClassRelativePath = mainClassName.getName().replace('.', '/') + ".class";
final ZipEntry mainClassEntry = new ZipEntry(mainClassRelativePath);
final byte[] mainClassBytecode;
try {
final JavaClassRep mainClassRep = makeMainClass(mainClassName, entryPointName, workspaceManager);
mainClassBytecode = AsmJavaBytecodeGenerator.encodeClass(mainClassRep);
// Write out the source for the class
final String javaFileRelativePath = mainClassName.getName().replace('.', '/') + ".java";
final ZipEntry javaFileEntry = new ZipEntry(javaFileRelativePath);
sourceGen.generateSource(javaFileEntry, mainClassRep);
writeClassAndOtherRequiredClassesAndGatherResources(workspaceManager, jos, classesAlreadyAdded, sourcesAlreadyAdded, allRequredModules, rootModule, mainClassEntry, mainClassBytecode, sourceGen);
} catch (final JavaGenerationException e) {
// We should not have problems generating the main class - if there are any then it is an internal problem
throw new InternalProblemException(e);
}
}
////
/// Then generate the library classes (and their associated classes and resources)
//
for (final LibraryClassSpec spec : libClassSpecs) {
final JavaTypeName libClassName = spec.getClassName();
final ModuleName libModuleName = spec.getModuleName();
// Check the library class's configuration, and throw InvalidConfigurationException if there are problems
checkLibraryClassConfiguration(libModuleName, workspaceManager);
////
/// Write out the library class and the generated classes required by the class.
//
final String libClassRelativePath = libClassName.getName().replace('.', '/') + ".class";
final ZipEntry libClassEntry = new ZipEntry(libClassRelativePath);
final byte[] libClassBytecode;
try {
final JavaClassRep libClassRep = makeLibraryClass(spec.getScope(), libClassName, libModuleName, workspaceManager, monitor);
libClassBytecode = AsmJavaBytecodeGenerator.encodeClass(libClassRep);
// Write out the source for the class
final String javaFileRelativePath = libClassName.getName().replace('.', '/') + ".java";
final ZipEntry javaFileEntry = new ZipEntry(javaFileRelativePath);
sourceGen.generateSource(javaFileEntry, libClassRep);
writeClassAndOtherRequiredClassesAndGatherResources(workspaceManager, jos, classesAlreadyAdded, sourcesAlreadyAdded, allRequredModules, libModuleName, libClassEntry, libClassBytecode, sourceGen);
} catch (final JavaGenerationException e) {
// We should not have problems generating the library class - if there are any then it is an internal problem
throw new InternalProblemException(e);
}
}
////
/// Write out the user resources for the required modules.
//
for (final ModuleName moduleName : allRequredModules) {
ModulePackager.writeUserResourcesToJar(workspace, moduleName, jos);
}
////
/// The End - if the operation reaches here without an exception, then we declare success.
//
success = true;
} catch (final RuntimeException e) {
// Runtime exceptions are really internal problems for us
throw new InternalProblemException(e);
} finally {
// Signal that we're done with the JAR building operation (whether it succeeded or failed)
monitor.jarBuildingDone(jarName, success);
try {
jos.flush();
jos.close();
sourceGen.flushAndClose();
} catch (final ZipException e) {
// we will ignore the zip errors on flushing and closing... especially since closing a
// ZipOutputStream that has not been written to causes an exception to be thrown:
// java.util.zip.ZipException: ZIP file must have at least one entry
}
}
}
/**
* Writes out the given class bytecode, and any other required classes (as determined by analyzing the bytecode) and
* required resources.
*
* @param workspaceManager the workspace manager.
* @param jos the JAR output stream.
* @param classesAlreadyAdded the classes already added to the JAR.
* @param sourcesAlreadyAdded the sources already added to the JAR.
* @param allRequredModules the names of all required modules.
* @param rootModule the root module.
* @param classEntry the zip entry for the given class.
* @param classBytecode the bytecode for the class to be written out and analyzed.
* @param sourceGen the generator to use for generating source files (into a zip file).
* @throws IOException
* @throws InternalProblemException
* @throws JavaGenerationException
*/
private static void writeClassAndOtherRequiredClassesAndGatherResources(
final WorkspaceManager workspaceManager,
final JarOutputStream jos,
final Set<String> classesAlreadyAdded,
final Set<String> sourcesAlreadyAdded,
final SortedSet<ModuleName> allRequredModules,
final ModuleName rootModule,
final ZipEntry classEntry,
final byte[] classBytecode,
final SourceGenerator sourceGen) throws IOException, InternalProblemException, JavaGenerationException {
final CALWorkspace workspace = workspaceManager.getWorkspace();
////
/// Write out the class.
//
jos.putNextEntry(classEntry);
try {
jos.write(classBytecode);
} finally {
jos.closeEntry();
}
////
/// Write out the generated classes required by the given class.
//
final Set<String> mainClassDependencies = findClassDependencies(classBytecode);
try {
addGeneratedClassesToJar(jos, mainClassDependencies, classesAlreadyAdded, sourcesAlreadyAdded, workspaceManager, sourceGen);
} catch (final ClassNotFoundException e) {
// We should not have problems adding the generated classes - if there are any then it is an internal problem
throw new InternalProblemException(e);
}
allRequredModules.addAll(findModuleDependencies(rootModule, workspace));
}
/**
* Checks the machine configuration specified by the user, and throws an exception if the configuration is invalid.
* @param programModelManager the ProgramModelManager which provides access to the program and machine configuration.
*
* @throws InvalidConfigurationException if an invalid configuration for the JAR building operation was specified.
*/
public static void checkCurrentMachineConfiguration(final ProgramModelManager programModelManager) throws InvalidConfigurationException {
// This utility only works with the LECC machine.
if (programModelManager.getMachineType() != MachineType.LECC) {
throw new InvalidConfigurationException(StandaloneJarBuilderMessages.getString("leccMachineOnly"));
}
}
/**
* Checks the main class's configuration specified by the user, and throws an exception if the configuration is invalid.
* @param entryPointName the name of the entry point function.
* @param workspaceManager the WorkspaceManager which provides access to the program and the workspace.
* @throws InvalidConfigurationException if an invalid configuration for the JAR building operation was specified.
*/
public static void checkMainClassConfiguration(final QualifiedName entryPointName, final WorkspaceManager workspaceManager) throws InvalidConfigurationException {
////
/// Run checks on the specified entry points. Currently we only support functional agents with the signature [String]->()
//
// Check that the entry point exists.
final GemEntity entryPointEntity = workspaceManager.getWorkspace().getGemEntity(entryPointName);
if (entryPointEntity == null) {
throw new InvalidConfigurationException(StandaloneJarBuilderMessages.getString("entryPointDoesNotExist", entryPointName.getQualifiedName()));
}
// Check that the entry point has the right type.
final ModuleName entryPointModuleName = entryPointName.getModuleName();
final TypeExpr entryPointType = entryPointEntity.getTypeExpr();
final TypeExpr requiredType = workspaceManager.getTypeChecker().getTypeFromString("[Prelude.String] -> ()", entryPointModuleName, null);
if (!TypeExpr.canPatternMatch(entryPointType, requiredType, workspaceManager.getModuleTypeInfo(entryPointModuleName))) {
throw new InvalidConfigurationException(StandaloneJarBuilderMessages.getString("incompatibleType", entryPointName.getQualifiedName(), entryPointType.toString(), requiredType.toString()));
}
}
/**
* Checks the library class's configuration specified by the user, and throws an exception if the configuration is invalid.
* @param libModuleName the library module name.
* @param programModelManager the ProgramModelManager which provides access to the program and machine configuration.
* @throws InvalidConfigurationException if an invalid configuration for the JAR building operation was specified.
*/
public static void checkLibraryClassConfiguration(final ModuleName libModuleName, final ProgramModelManager programModelManager) throws InvalidConfigurationException {
// Check that the module exists.
if (programModelManager.getModuleTypeInfo(libModuleName) == null) {
throw new InvalidConfigurationException(StandaloneJarBuilderMessages.getString("moduleDoesNotExist", libModuleName.toSourceText()));
}
}
/**
* Checks a generated class name specified by the user, and throws an exception if the name is invalid.
* @param className the name of the class to be generated.
*
* @throws InvalidConfigurationException if an invalid configuration for the JAR building operation was specified.
*/
private static void checkClassName(final String className) throws InvalidConfigurationException {
// Validate that the name is a valid Java class name
final String[] mainClassNameParts = className.split("\\.");
final int nMainClassNameParts = mainClassNameParts.length;
for (int i = 0; i < nMainClassNameParts; i++) {
final String mainClassNamePart = mainClassNameParts[i];
final int len = mainClassNamePart.length();
for (int j = 0; j < len; j++) {
final boolean isValid;
if (j == 0) {
isValid = Character.isJavaIdentifierStart(mainClassNamePart.charAt(j));
} else {
isValid = Character.isJavaIdentifierPart(mainClassNamePart.charAt(j));
}
if (!isValid) {
throw new InvalidConfigurationException(StandaloneJarBuilderMessages.getString("invalidClassName", className));
}
}
}
// We do not support generating classes into the default package
if (className.indexOf('.') == -1) {
throw new InvalidConfigurationException(StandaloneJarBuilderMessages.getString("classInDefaultPackageNotSupported", className));
}
}
/**
* Returns a set of all the dependencies of the specified module.
* @param rootModuleName the name of the module whose dependencies are to be collected.
* @param workspace the CAL workspace containing the module.
* @return a set of ModuleNames of the modules which the specified module depends on (directly or indirectly).
*/
private static SortedSet<ModuleName> findModuleDependencies(final ModuleName rootModuleName, final CALWorkspace workspace) {
final CALWorkspace.DependencyFinder depFinder = workspace.getDependencyFinder(Collections.singletonList(rootModuleName));
final SortedSet<ModuleName> result = new TreeSet<ModuleName>();
result.addAll(depFinder.getRootSet());
result.addAll(depFinder.getImportedModulesSet());
return result;
}
/**
* Writes the specified generated classes, and all the transitive closure of other generated classes they depend on,
* to the specified JarOutputStream.
*
* @param jos the output stream for the standalone JAR being generated.
* @param namesOfGeneratedClasses the names of the generated classes to be added to the JAR.
* @param classesAlreadyAdded a set of the names of classes already added to the JAR.
* @param sourcesAlreadyAdded a set of the sources already added to the JAR.
* @param programModelManager the ProgramModelManager which provides access to the program.
* @param sourceGen the generator to use for generating source files (into a zip file).
* @throws ClassNotFoundException
* @throws IOException
* @throws JavaGenerationException
*/
private static void addGeneratedClassesToJar(final JarOutputStream jos, final Set<String> namesOfGeneratedClasses, final Set<String> classesAlreadyAdded, final Set<String> sourcesAlreadyAdded, final ProgramModelManager programModelManager, final SourceGenerator sourceGen) throws ClassNotFoundException, IOException, JavaGenerationException {
final Set<String> classesToAdd = new TreeSet<String>(namesOfGeneratedClasses);
// We do not loop via an iterator, as entries are both added to and removed from set classesToAdd in each iteration.
while (!classesToAdd.isEmpty()) {
final String className = classesToAdd.iterator().next();
if (!classesAlreadyAdded.contains(className)) {
// Fetch the bytecode for the class via the CALClassLoader
final ModuleName moduleName = CALToJavaNames.getModuleNameFromPackageName(className);
final LECCModule module = (LECCModule)programModelManager.getModule(moduleName);
final CALClassLoader classLoader = module.getClassLoader();
final byte[] bytecode = classLoader.getBytecodeForClass(className);
// Add the class to the JAR
addClassToJar(jos, className, bytecode);
// Add the source to the JAR (if we are generating a source zip)
if (!(sourceGen instanceof SourceGenerator.Null)) {
final JavaClassRep classRep = classLoader.getClassRepWithInnerClasses(className);
final String sourceName = classRep.getClassName().getName();
if (!sourcesAlreadyAdded.contains(sourceName)) {
final String javaFileRelativePath = sourceName.replace('.', '/') + ".java";
final ZipEntry javaFileEntry = new ZipEntry(javaFileRelativePath);
sourceGen.generateSource(javaFileEntry, classRep);
sourcesAlreadyAdded.add(sourceName);
}
}
// Now find the dependencies of this class and add it to the set to be processed.
classesAlreadyAdded.add(className);
classesToAdd.addAll(findClassDependencies(bytecode));
}
classesToAdd.remove(className);
}
}
/**
* Finds the names of the generated classes depended upon by the specified bytecode.
* @param bytecode the bytecode to be scanned for its dependencies.
* @return a set of the names the generated classes depended upon by the specified bytecode.
*/
private static SortedSet<String> findClassDependencies(final byte[] bytecode) {
final ClassReader classReader = new ClassReader(bytecode);
final GeneratedClassDependencyFindingVisitor visitor = new GeneratedClassDependencyFindingVisitor();
classReader.accept(visitor, ClassReader.SKIP_FRAMES);
return visitor.getFoundClassNames();
}
/**
* Writes a class to a JAR via the specified JarOutputStream.
*
* @param jos the output stream for the standalone JAR being generated.
* @param className the name of the class to be added.
* @param bytecode the bytecode of the class to be added.
* @throws IOException
*/
private static void addClassToJar(final JarOutputStream jos, final String className, final byte[] bytecode) throws IOException {
final String relativePath = className.replace('.', '/') + ".class";
final ZipEntry entry = new ZipEntry(relativePath);
jos.putNextEntry(entry);
try {
jos.write(bytecode);
} finally {
jos.closeEntry();
}
}
/**
* Creates the representation for the main class (the class with the <code>main</code> method) from which the
* pertinent CAL function can be run directly.
*
* @param mainClassName the name of the main class to be generated.
* @param entryPointName the name of the entry point function.
* @param programModelManager the ProgramModelManager which provides access to the program.
* @return the representation of the main class.
*/
private static JavaClassRep makeMainClass(final JavaTypeName mainClassName, final QualifiedName entryPointName, final ProgramModelManager programModelManager) {
final LECCModule module = (LECCModule)programModelManager.getModule(entryPointName.getModuleName());
final JavaClassRep classRep = new JavaClassRep(mainClassName, JavaTypeName.OBJECT, Modifier.PUBLIC|Modifier.FINAL, new JavaTypeName[0]);
// Code fragment:
//
// private {MainClassConstructorName} {}
//
classRep.addConstructor(makePrivateConstructor(mainClassName));
// Code fragment:
//
// public static void main(final String[] args) throws CALExecutorException
//
final JavaMethod mainMethod = new JavaMethod(Modifier.PUBLIC|Modifier.STATIC, JavaTypeName.VOID, ARGS_PARAMETER_NAME, JavaTypeName.STRING_ARRAY, true, "main");
mainMethod.addThrows(JavaTypeName.CAL_EXECUTOR_EXCEPTION);
////
/// The classes in the standalone JAR are generated with the machine configuration at "build time".
/// We capture this configuration into a StandaloneJarGeneratedCodeInfo, to be checked at runtime
/// against the machine configuration then.
//
final JavaStatement.LocalVariableDeclaration generatedCodeInfoDecl = makeGeneratedCodeInfoDecl();
final JavaExpression.LocalVariable generatedCodeInfoLocalVar = generatedCodeInfoDecl.getLocalVariable();
mainMethod.addStatement(generatedCodeInfoDecl);
//
// Code fragment:
//
// if (!generatedCodeInfo.isCompatibleWithCurrentConfiguration()) {
// System.err.println(generatedCodeInfo.getConfigurationHints());
// return;
// }
//
final JavaStatement.Block configCheckFailureBody = new JavaStatement.Block();
configCheckFailureBody.addStatement(
new JavaStatement.ExpressionStatement(
new JavaExpression.MethodInvocation.Instance(
new JavaExpression.JavaField.Static(JavaTypeName.SYSTEM, "err", JavaTypeName.PRINT_STREAM),
"println",
new JavaExpression.MethodInvocation.Instance(
generatedCodeInfoLocalVar,
"getConfigurationHints",
JavaTypeName.STRING,
JavaExpression.MethodInvocation.InvocationType.VIRTUAL),
JavaTypeName.STRING,
JavaTypeName.VOID,
JavaExpression.MethodInvocation.InvocationType.VIRTUAL)));
configCheckFailureBody.addStatement(new JavaStatement.ReturnStatement());
final JavaStatement configCheck =
new JavaStatement.IfThenElseStatement(
new JavaExpression.OperatorExpression.Unary(
JavaOperator.LOGICAL_NEGATE,
new JavaExpression.MethodInvocation.Instance(
generatedCodeInfoLocalVar,
"isCompatibleWithCurrentConfiguration",
JavaTypeName.BOOLEAN,
JavaExpression.MethodInvocation.InvocationType.VIRTUAL)),
configCheckFailureBody);
mainMethod.addStatement(configCheck);
////
/// Generate code to obtain the class loader which loaded this main class.
///
/// It is necessary to obtain this class loader and pass it into the execution context and the resource access
/// because the class loader which loaded the CAL Platform classes may be an *ancestor* of the one which loaded
/// the standalone JAR (e.g. the bootstrap class loader), and thus may not have access to the foreign classes
/// and localized resources necessary for the standalone JAR to run.
//
// Code fragment:
//
// ClassLoader classloader = {MainClassName}.class.getClassLoader();
//
final JavaTypeName javaTypeName_ClassLoader = JavaTypeName.make(ClassLoader.class);
final JavaExpression classloaderInit =
new JavaExpression.MethodInvocation.Instance(
new JavaExpression.ClassLiteral(mainClassName),
"getClassLoader",
javaTypeName_ClassLoader,
JavaExpression.MethodInvocation.InvocationType.VIRTUAL);
final JavaExpression.LocalVariable classloaderLocalVar = new JavaExpression.LocalVariable("classloader", javaTypeName_ClassLoader);
final JavaStatement classloaderDecl = new JavaStatement.LocalVariableDeclaration(classloaderLocalVar, classloaderInit);
mainMethod.addStatement(classloaderDecl);
////
/// Generate code to set up the execution context to have access to a standalone-JAR-specific
/// ResourceAccess implementation.
//
// Code fragment:
//
// RTExecutionContext executionContext = new RTExecutionContext(
// new ExecutionContextProperties.Builder().toProperties(),
// new StandaloneRuntimeEnvironment(
// classloader,
// new StandaloneJarResourceAccess(classloader)));
//
final JavaTypeName javaTypeName_ExecutionContextProperties = JavaTypeName.make(ExecutionContextProperties.class);
final JavaTypeName javaTypeName_ResourceAccess = JavaTypeName.make(ResourceAccess.class);
final JavaExpression newRuntimeEnvironment =
new JavaExpression.ClassInstanceCreationExpression(
JavaTypeNames.STANDALONE_RUNTIME_ENVIRONMENT,
new JavaExpression[] {
classloaderLocalVar,
new JavaExpression.ClassInstanceCreationExpression(
JavaTypeName.make(StandaloneJarResourceAccess.class),
classloaderLocalVar,
javaTypeName_ClassLoader)},
new JavaTypeName[] {
javaTypeName_ClassLoader,
javaTypeName_ResourceAccess}
);
final JavaExpression executionContextInit =
new JavaExpression.ClassInstanceCreationExpression(
JavaTypeNames.RTEXECUTION_CONTEXT,
new JavaExpression[] {
new JavaExpression.MethodInvocation.Instance(
new JavaExpression.ClassInstanceCreationExpression(JavaTypeName.make(ExecutionContextProperties.Builder.class)),
"toProperties",
javaTypeName_ExecutionContextProperties,
JavaExpression.MethodInvocation.InvocationType.VIRTUAL),
newRuntimeEnvironment
},
new JavaTypeName[] {
javaTypeName_ExecutionContextProperties,
JavaTypeNames.RUNTIME_ENIVONMENT
});
final JavaExpression.LocalVariable executionContextLocalVar = new JavaExpression.LocalVariable(EXECUTION_CONTEXT_USER_FRIENDLY_NAME, JavaTypeNames.RTEXECUTION_CONTEXT);
final JavaStatement executionContextDecl = new JavaStatement.LocalVariableDeclaration(executionContextLocalVar, executionContextInit, true);
mainMethod.addStatement(executionContextDecl);
////
/// Generate code to run the entry point.
//
// Code fragment:
//
// {EntryPointClass's instance}
// .apply(Input_String_List.$instance.apply(new RTData.CAL_Opaque(args))
// .evaluate(executionContext);
//
final MachineFunction entryPointMachineFunction = module.getFunction(entryPointName);
final MachineFunction inputStringListMachineFunction = module.getFunction(CAL_Prelude_internal.Functions.inputStringList);
final JavaExpression runExpr =
makeEvaluateInvocationExpr(
makeApplyInvocationExpr(
getInstanceOfGeneratedClassJavaExpression(entryPointMachineFunction, module, executionContextLocalVar),
makeApplyInvocationExpr(
getInstanceOfGeneratedClassJavaExpression(inputStringListMachineFunction, module, executionContextLocalVar),
new JavaExpression.ClassInstanceCreationExpression(
JavaTypeNames.RTDATA_OPAQUE, new JavaExpression.MethodVariable(ARGS_PARAMETER_NAME), JavaTypeName.OBJECT))), executionContextLocalVar);
mainMethod.addStatement(new JavaStatement.ExpressionStatement(runExpr));
classRep.addMethod(mainMethod);
// We are finished with building the class.
return classRep;
}
/**
* Creates the representation of a private constructor.
* @param classJavaTypeName the name of the containing class.
* @return the private constructor for the class.
*/
private static JavaConstructor makePrivateConstructor(final JavaTypeName classJavaTypeName) {
// Code fragment:
//
// private {ClassConstructorName} {}
//
// Technically, the name to be used for the constructor is the unqualified portion of the class name.
// However, JavaTypeName.getUnqualifiedJavaSourceName() assumes that all '$' characters in the name
// arise from the name-mangling of inner class names, and converts them to '.'. Since the class is
// a top-level class, and since it is permissible to use '$' in a top-level class name, we have to use
// JavaTypeName.getInternalUnqualifiedName() so that if the main class name is "com.xyz.Foo$Bar",
// the constructor's name would end up as "Foo$Bar" rather than just "Bar".
//
// Also, this makes the assumption that if the class name has passed validation thus far, the JavaTypeName
// is in fact a JavaTypeName.Reference.Object
//
final String classConstructorName = ((JavaTypeName.Reference.Object)classJavaTypeName).getInternalUnqualifiedName();
return new JavaConstructor(Modifier.PRIVATE, classConstructorName);
}
/**
* Creates the representation for a library class from a CAL module.
*
* @param libClassScope the scope of the generated class.
* @param libClassName the name of the library class to be generated.
* @param moduleName the name of the module.
* @param programModelManager the ProgramModelManager which provides access to the program.
* @param monitor the progress monitor to be used.
* @return the representation of the library class.
* @throws UnableToResolveForeignEntityException
*/
private static JavaClassRep makeLibraryClass(final JavaScope libClassScope, final JavaTypeName libClassName, final ModuleName moduleName, final ProgramModelManager programModelManager, final Monitor monitor) throws UnableToResolveForeignEntityException {
final ModuleTypeInfo moduleTypeInfo = programModelManager.getModuleTypeInfo(moduleName);
final LECCModule module = (LECCModule)programModelManager.getModule(moduleName);
final ScopedEntityNamingPolicy.UnqualifiedInCurrentModuleOrInPreludeIfUnambiguous scopedEntityNamingPolicy =
new ScopedEntityNamingPolicy.UnqualifiedInCurrentModuleOrInPreludeIfUnambiguous(moduleTypeInfo);
final JavaClassRep classRep = new JavaClassRep(libClassName, JavaTypeName.OBJECT, libClassScope.getModifier()|Modifier.FINAL, new JavaTypeName[0]);
// Code fragment:
//
// private {LibClassConstructorName} {}
//
classRep.addConstructor(makePrivateConstructor(libClassName));
// We sort the functional agents, because the order returned by the ModuleTypeInfo is neither source order nor alphabetical order
// Case-sensitive ordering would order data constructors ahead of functions
final FunctionalAgent[] functionalAgents = moduleTypeInfo.getFunctionalAgents();
Arrays.sort(functionalAgents, new Comparator<FunctionalAgent>() {
public int compare(final FunctionalAgent a, final FunctionalAgent b) {
return a.getName().compareTo(b.getName());
}});
final Map<String, String> methodNameMapping = sanitizeMethodNamesForJava(functionalAgents);
final JavadocCrossReferenceGenerator crossRefGen = new JavadocLinkGenerator(methodNameMapping, scopedEntityNamingPolicy);
// Add each functional agent as a method if it has a visible scope and it is not an overloaded function
for (final FunctionalAgent functionalAgent : functionalAgents) {
if (isScopeVisibleAsJavaLibraryAPI(functionalAgent.getScope())) {
// Check that the functional agent has a type that can be handled.
final TypeExpr functionalAgentType = functionalAgent.getTypeExpr();
if (!hasTypeClassConstraints(functionalAgentType)) {
final Pair<List<ClassTypeInfo>, ClassTypeInfo> argTypesAndReturnType = getArgTypesAndReturnTypeForLibraryMethod(functionalAgentType);
final List<ClassTypeInfo> argTypes = argTypesAndReturnType.fst();
final ClassTypeInfo returnType = argTypesAndReturnType.snd();
final JavaMethod unboxedArgsVariant =
makeLibraryMethod(module, functionalAgent, functionalAgentType, argTypes, returnType, scopedEntityNamingPolicy, crossRefGen, methodNameMapping);
classRep.addMethod(unboxedArgsVariant);
if (hasNonCalValueArgument(argTypes)) {
// The first method has at least one unboxed/foreign argument, so we create a second version
// that takes all arguments as CalValues
final int nArgs = argTypes.size();
final List<ClassTypeInfo> calValueArgTypes = new ArrayList<ClassTypeInfo>();
for (int i = 0; i < nArgs; i++) {
calValueArgTypes.add(ClassTypeInfo.makeNonForeign());
}
final JavaMethod calValueArgsVariant =
makeLibraryMethod(module, functionalAgent, functionalAgentType, calValueArgTypes, returnType, scopedEntityNamingPolicy, crossRefGen, methodNameMapping);
classRep.addMethod(calValueArgsVariant);
}
} else {
// Report that the functional agent was skipped because its type contains type class constraints
if (functionalAgent instanceof ClassMethod) {
monitor.skippingClassMethod(functionalAgent.getName());
} else {
// an overloaded functional agent is really either a class method or a function, and never a data cons
monitor.skippingFunctionWithTypeClassConstraints(functionalAgent.getName());
}
}
}
}
// Add the Javadoc
classRep.setJavaDoc(
new JavaDocComment(CALDocToJavaDocUtilities.getJavadocFromCALDocComment(
moduleTypeInfo.getCALDocComment(), true, null, null, null,
scopedEntityNamingPolicy, crossRefGen, true, true, false, true)));
////
/// Add the static check
//
// Code fragment:
//
// private static boolean __checkConfig()
//
final JavaMethod checkConfigMethod = new JavaMethod(Modifier.PRIVATE|Modifier.STATIC, JavaTypeName.BOOLEAN, "__checkConfig");
////
/// The classes in the standalone JAR are generated with the machine configuration at "build time".
/// We capture this configuration into a StandaloneJarGeneratedCodeInfo, to be checked at runtime
/// against the machine configuration then.
//
final JavaStatement.LocalVariableDeclaration generatedCodeInfoDecl = makeGeneratedCodeInfoDecl();
final JavaExpression.LocalVariable generatedCodeInfoLocalVar = generatedCodeInfoDecl.getLocalVariable();
checkConfigMethod.addStatement(generatedCodeInfoDecl);
//
// Code fragment:
//
// if (!generatedCodeInfo.isCompatibleWithCurrentConfiguration()) {
// throw new IllegalStateException(generatedCodeInfo.getConfigurationHints());
// } else {
// return true;
// }
//
final JavaStatement.Block configCheckFailureBody = new JavaStatement.Block();
configCheckFailureBody.addStatement(
new JavaStatement.ThrowStatement(
new JavaExpression.ClassInstanceCreationExpression(
JavaTypeName.make(IllegalStateException.class),
new JavaExpression.MethodInvocation.Instance(
generatedCodeInfoLocalVar,
"getConfigurationHints",
JavaTypeName.STRING,
JavaExpression.MethodInvocation.InvocationType.VIRTUAL),
JavaTypeName.STRING)));
final JavaStatement configCheckSuccessBody = new JavaStatement.ReturnStatement(JavaExpression.LiteralWrapper.TRUE);
final JavaStatement configCheck =
new JavaStatement.IfThenElseStatement(
new JavaExpression.OperatorExpression.Unary(
JavaOperator.LOGICAL_NEGATE,
new JavaExpression.MethodInvocation.Instance(
generatedCodeInfoLocalVar,
"isCompatibleWithCurrentConfiguration",
JavaTypeName.BOOLEAN,
JavaExpression.MethodInvocation.InvocationType.VIRTUAL)),
configCheckFailureBody,
configCheckSuccessBody);
checkConfigMethod.addStatement(configCheck);
classRep.addMethod(checkConfigMethod);
// Code fragment:
//
// private static final boolean isCompatibleWithCurrentConfiguration = __checkConfig();
//
final JavaFieldDeclaration isCompatibleWithCurrentConfigurationField = new JavaFieldDeclaration(
Modifier.PRIVATE|Modifier.STATIC|Modifier.FINAL,
JavaTypeName.BOOLEAN,
"isCompatibleWithCurrentConfiguration",
new JavaExpression.MethodInvocation.Static(
libClassName, checkConfigMethod.getMethodName(), new JavaExpression[0], new JavaTypeName[0], JavaTypeName.BOOLEAN));
classRep.addFieldDeclaration(isCompatibleWithCurrentConfigurationField);
// We are finished with building the class.
return classRep;
}
/**
* Checks whether the given type expression contains a type class constraint (which means that a function having that type
* would have extra dictionary arguments added to the core function).
* @param typeExpr a type expression to check.
* @return true if the type expression contains a type class constraint; false otherwise.
*/
private static boolean hasTypeClassConstraints(final TypeExpr typeExpr) {
// todo-jowong could TypeExpr expose this functionality directly without having to go through the SourceModel?
class TypeClassConstraintFinder extends SourceModelTraverser<Void, Void> {
boolean foundTypeClassConstraint = false;
@Override
public Void visit_Constraint_TypeClass(final TypeClass typeClass, final Void arg) {
foundTypeClassConstraint = true;
return super.visit_Constraint_TypeClass(typeClass, arg);
}
}
final TypeClassConstraintFinder finder = new TypeClassConstraintFinder();
typeExpr.toSourceModel().accept(finder, null);
return finder.foundTypeClassConstraint;
}
/**
* Creates a representation of a library method in a library class corresponding to the given functional agent.
* The constraint placed on the agent is that it cannot be overloaded (i.e. take dictionary arguments).
*
* @param module the module containing the functional agent.
* @param functionalAgent the functional agent.
* @param functionalAgentType the type of the functional agent.
* @param argTypes the argument types.
* @param returnType the return type.
* @param scopedEntityNamingPolicy the scoped entity naming policy to use for comment generation.
* @param crossRefGen the cross reference generator for use in comment generation.
* @param methodNameMapping a mapping from functional agent names to names of the generated methods.
* @return the representation of the library method.
*/
private static JavaMethod makeLibraryMethod(final LECCModule module, final FunctionalAgent functionalAgent, final TypeExpr functionalAgentType, final List<ClassTypeInfo> argTypes, final ClassTypeInfo returnType, final ScopedEntityNamingPolicy scopedEntityNamingPolicy, final JavadocCrossReferenceGenerator crossRefGen, final Map<String, String> methodNameMapping) {
////
/// Figure out what the Java types should be for the arguments and return type
//
final int nFunctionalAgentArgs = argTypes.size();
final List<String> functionalAgentArgNames = new ArrayList<String>();
for (final String argNameFromCALDoc : CALDocToJavaDocUtilities.getArgumentNamesFromCALDocComment(functionalAgent.getCALDocComment(), functionalAgent, functionalAgentType)) {
functionalAgentArgNames.add(sanitizeVarNameForJava(argNameFromCALDoc));
}
final List<String> methodArgNames = new ArrayList<String>();
methodArgNames.addAll(functionalAgentArgNames);
final List<JavaTypeName> methodArgTypeNames = new ArrayList<JavaTypeName>();
for (final ClassTypeInfo classTypeInfo : argTypes) {
methodArgTypeNames.add(classTypeInfo.getExposedTypeName());
}
////
/// Add the execution context argument
//
final String executionContextArgName;
if (methodArgNames.contains(EXECUTION_CONTEXT_USER_FRIENDLY_NAME)) {
// the user friendly name collides with an existing argument name, so use the internal one - guaranteed not to collide
executionContextArgName = SCJavaDefn.EXECUTION_CONTEXT_NAME;
} else {
executionContextArgName = EXECUTION_CONTEXT_USER_FRIENDLY_NAME;
}
methodArgNames.add(executionContextArgName);
// We use the non-internal ExecutionContext type as the arg type, rather than the internal RTExecutionContext
// ...we will downcast on use
methodArgTypeNames.add(JavaTypeName.make(ExecutionContext.class));
final int nMethodArgs = nFunctionalAgentArgs + 1;
////
/// Build up the chain of .apply(...) calls, with each argument marshalled properly
/// We use the multi-argument apply() variants as much as possible
/// Then build up the evaluation of the application chain.
//
final MachineFunction functionalAgentMachineFunction = module.getFunction(functionalAgent.getName());
final JavaExpression executionContextArg = new JavaExpression.CastExpression(
JavaTypeNames.RTEXECUTION_CONTEXT,
new JavaExpression.MethodVariable(executionContextArgName));
final List<JavaExpression> marshalledArgs = new ArrayList<JavaExpression>();
for (int i = 0; i < nFunctionalAgentArgs; i++) {
marshalledArgs.add(makeRTValueMarshallingExpr(
argTypes.get(i), new JavaExpression.MethodVariable(methodArgNames.get(i))));
}
final JavaExpression resultExpr;
final boolean isLiteralResult;
// Special handling for literal value is required, because it may represent either an actual literal value
// or an enum data cons, or an alias to an enum data cons
final Object literalValue = functionalAgentMachineFunction.getLiteralValue();
if (literalValue != null) {
final Class<?> unboxedLiteralType = supportedLiteralTypesBoxedToUnboxedClassMap.get(literalValue.getClass());
if (unboxedLiteralType == null) {
throw new IllegalStateException("Unsupported literal type in machine function: " + literalValue.getClass());
}
isLiteralResult = true;
if (returnType.getExposedTypeName().equals(JavaTypeName.CAL_VALUE)) {
// an alias of a data constructor, which should be in an enumeration algebraic type
// so we just marshall the literal as the opaque representation of the enum value
if (unboxedLiteralType != int.class) {
throw new IllegalStateException("Unboxed literal type " + unboxedLiteralType + " is not the primitive type int.");
}
if (!LECCMachineConfiguration.TREAT_ENUMS_AS_INTS) {
throw new IllegalStateException("Enums are not treated as ints, but there is a MachineFunction with a literal value whose return type is CalValue");
}
resultExpr = makeRTValueMarshallingExpr(ClassTypeInfo.make(unboxedLiteralType), JavaExpression.LiteralWrapper.make(literalValue));
} else {
// not an aliased data constructor
if (!returnType.getExposedTypeName().equals(JavaTypeName.make(unboxedLiteralType))) {
throw new IllegalStateException("Unboxed literal type " + unboxedLiteralType + " does not match return type " + returnType.getExposedTypeName());
}
// emit a literal value directly
resultExpr = JavaExpression.LiteralWrapper.make(literalValue);
}
} else if (functionalAgentMachineFunction.isDataConstructor() && isEnumDataConsRepresentedAsPrimitiveInt((DataConstructor)functionalAgent)) {
// ***Optional optimization***
// a data constructor in an enumeration algebraic type
// so we just marshall the literal as the opaque representation of the enum value
if (!returnType.getExposedTypeName().equals(JavaTypeName.CAL_VALUE)) {
throw new IllegalStateException("The return type " + returnType.getExposedTypeName() + " is not the expected " + JavaTypeName.CAL_VALUE);
}
final DataConstructor dc = (DataConstructor)functionalAgent;
isLiteralResult = true;
resultExpr = makeRTValueMarshallingExpr(ClassTypeInfo.make(int.class), JavaExpression.LiteralWrapper.make(Integer.valueOf(dc.getOrdinal())));
} else {
isLiteralResult = false;
resultExpr =
makeEvaluateInvocationExpr(
makeApplyInvocationExprChain(
getInstanceOfGeneratedClassJavaExpression(functionalAgentMachineFunction, module, executionContextArg),
marshalledArgs),
executionContextArg);
}
////
/// Build up the method header, and make the return statement
//
final int modifier = Modifier.STATIC | mapScope(functionalAgent.getScope());
final boolean[] finalFlags = new boolean[nMethodArgs];
Arrays.fill(finalFlags, true);
final String methodName = sanitizeMethodNameForJava(functionalAgent.getName().getUnqualifiedName(), methodNameMapping);
// Special handling of unit return type in CAL - it gets mapped to a void return type in Java
final boolean isUnitResultType = functionalAgentType.getResultType().isNonParametricType(CAL_Prelude.TypeConstructors.Unit);
final JavaTypeName methodReturnTypeName;
if (isUnitResultType) {
methodReturnTypeName = JavaTypeName.VOID;
} else {
methodReturnTypeName = returnType.getExposedTypeName();
}
final JavaMethod method = new JavaMethod(
modifier,
methodReturnTypeName,
methodArgNames.toArray(new String[nMethodArgs]),
methodArgTypeNames.toArray(new JavaTypeName[nMethodArgs]),
finalFlags,
methodName);
if (isUnitResultType) {
// Code fragment:
//
// {functional agent's instance}
// .apply({marshalled arg #1}, ... {marshalled arg #4})
// ...
// .apply(... {marshalled arg #n})
// .evaluate(executionContext);
method.addStatement(new JavaStatement.ExpressionStatement(resultExpr));
} else if (isLiteralResult) {
// Code fragment:
//
// return {functional agent's literal value};
method.addStatement(new JavaStatement.ReturnStatement(resultExpr));
} else {
// Code fragment:
//
// return {functional agent's instance}
// .apply({marshalled arg #1}, ... {marshalled arg #4})
// ...
// .apply(... {marshalled arg #n})
// .evaluate(executionContext).{unmarshalling method}();
//
// OR (if unmarshalling involves casting)
//
// return ({result type})(java.lang.Object){functional agent's instance}
// .apply({marshalled arg #1}, ... {marshalled arg #4})
// ...
// .apply(... {marshalled arg #n})
// .evaluate(executionContext).getOpaqueValue();
method.addStatement(new JavaStatement.ReturnStatement(makeRTValueUnmarshallingExpr(returnType, resultExpr)));
}
// Add the throws declaration
method.addThrows(JavaTypeName.CAL_EXECUTOR_EXCEPTION);
// Add the Javadoc
method.setJavaDocComment(
new JavaDocComment(CALDocToJavaDocUtilities.getJavadocFromCALDocComment(
functionalAgent.getCALDocComment(), false, functionalAgent, functionalAgentType,
functionalAgentArgNames.toArray(new String[nFunctionalAgentArgs]),
scopedEntityNamingPolicy, crossRefGen, isUnitResultType, true, false, true)));
return method;
}
/**
* Returns whether the list of class type info for an argument list contains a non-CalValue type.
* @param classTypeInfos the list of class type info.
* @return true if there is at least one non-CalValue type; false otherwise.
*/
private static boolean hasNonCalValueArgument(final List<ClassTypeInfo> classTypeInfos) {
for (final ClassTypeInfo classTypeInfo : classTypeInfos) {
if (!classTypeInfo.getExposedTypeName().equals(JavaTypeName.CAL_VALUE)) {
return true;
}
}
return false;
}
/**
* Sanitizes the names of functional agents to be suitable for use as Java method names.
* @param functionalAgents the functional agents.
* @return a mapping from functional agent names to names of the generated methods.
*/
private static Map<String, String> sanitizeMethodNamesForJava(final FunctionalAgent[] functionalAgents) {
final Set<String> origNames = new HashSet<String>();
for (final FunctionalAgent functionalAgent : functionalAgents) {
origNames.add(functionalAgent.getName().getUnqualifiedName());
}
final Map<String, String> result = new HashMap<String, String>();
for (final String origName : origNames) {
String newName = origName;
// turn Java keywords like new into new_ while being careful about name collisions
while (JavaReservedWords.javaLanguageKeywords.contains(newName)) {
do {
newName += '_';
} while (origNames.contains(newName) || result.containsValue(newName));
}
result.put(origName, newName);
}
return result;
}
/**
* Sanitizes a name to be suitable for use as a Java method name, taking into consideration an existing name mapping.
* @param name the name to be sanitized.
* @param methodNameMapping a mapping from functional agent names to names of the generated methods.
* @return the sanitized name.
*/
private static String sanitizeMethodNameForJava(final String name, final Map<String, String> methodNameMapping) {
final String mappedName = methodNameMapping.get(name);
if (mappedName != null) {
return mappedName;
} else {
return sanitizeVarNameForJava(name);
}
}
/**
* Sanitizes a name to be suitable for use as a Java variable name.
* @param name the name to be sanitized.
* @return the sanitized name.
*/
private static String sanitizeVarNameForJava(final String name) {
if (JavaReservedWords.javaLanguageKeywords.contains(name)) {
// turn Java keywords like new into _new (note that _new is not a valid CAL variable name, and thus it is
// safe to make this substitution without having to consider whether the new name collides with other arg names)
return "_" + name;
} else {
// turn ordinal field names like #13 into _13
return name.replace('#', '_');
}
}
/**
* Returns a list of class type info for the arguments of a functional agent, and the class type info for the return value.
* @param functionalAgentType the type of the functional agent.
* @return a pair of (a list of class type info for the arguments, the class type info for the return value).
* @throws UnableToResolveForeignEntityException
*/
private static Pair<List<ClassTypeInfo>, ClassTypeInfo> getArgTypesAndReturnTypeForLibraryMethod(
final TypeExpr functionalAgentType) throws UnableToResolveForeignEntityException {
final List<ClassTypeInfo> dataTypes = new ArrayList<ClassTypeInfo>();
for (final TypeExpr typePiece : functionalAgentType.getTypePieces()) {
if (typePiece instanceof TypeConsApp) {
final TypeConsApp typeConsApp = (TypeConsApp)typePiece;
final ForeignTypeInfo foreignTypeInfo = typeConsApp.getForeignTypeInfo();
if (foreignTypeInfo != null && isScopeVisibleAsJavaLibraryAPI(foreignTypeInfo.getImplementationVisibility())) {
// we use the implementation type if the type is foreign and the implementation is visible
dataTypes.add(ClassTypeInfo.make(foreignTypeInfo.getForeignType()));
} else if (typeConsApp.isNonParametricType(CAL_Prelude.TypeConstructors.Boolean)) {
// Special handling required for Prelude.Boolean - it an algebraic type that has a custom RTData representation
dataTypes.add(ClassTypeInfo.make(boolean.class));
} else {
dataTypes.add(ClassTypeInfo.makeNonForeign());
}
} else {
dataTypes.add(ClassTypeInfo.makeNonForeign());
}
}
final int returnTypeIndex = dataTypes.size() - 1;
final ClassTypeInfo returnType = dataTypes.get(returnTypeIndex);
dataTypes.remove(returnTypeIndex);
return Pair.make(dataTypes, returnType);
}
/**
* Constructs a JavaExpression that marshalls a potentially unboxed type into an {@link RTValue}.
* @param classTypeInfo the class type info for the argument.
* @param argExpr the argument expression.
* @return an expression which marshalls the argument.
*/
private static JavaExpression makeRTValueMarshallingExpr(final ClassTypeInfo classTypeInfo, final JavaExpression argExpr) {
if (classTypeInfo.getClassType() == null) {
// a non-foreign type, so cast the CalValue down to an RTValue
return new JavaExpression.CastExpression(classTypeInfo.getRTValueTypeName(), argExpr);
} else {
// a foreign type, so run the make method on the appropriate RTData type
return new JavaExpression.MethodInvocation.Static(
classTypeInfo.getRTValueTypeName(),
"make",
argExpr,
classTypeInfo.getArgumentTypeOfMarshallingMethod(),
classTypeInfo.getRTValueTypeName());
}
}
/**
* Constructs a JavaExpression that unmarshalls an {@link RTValue} into a potentially unboxed type.
* @param classTypeInfo the class type info for the argument.
* @param argExpr the argument expression.
* @return an expression which unmarshalls the argument.
*/
private static JavaExpression makeRTValueUnmarshallingExpr(final ClassTypeInfo classTypeInfo, final JavaExpression argExpr) {
if (classTypeInfo.getClassType() == null) {
// a non-foreign type, so just return the RTValue as a CalValue
return argExpr;
} else if (classTypeInfo.getRTValueTypeName().equals(JavaTypeNames.RTDATA_OPAQUE)) {
// an unmarshalled opaque value needs a downcast from Object to the expected type
return new JavaExpression.CastExpression(
classTypeInfo.getExposedTypeName(),
new JavaExpression.MethodInvocation.Instance(
argExpr,
classTypeInfo.getUnmarshallingMethodName(),
classTypeInfo.getReturnTypeOfUnmarshallingMethod(),
JavaExpression.MethodInvocation.InvocationType.VIRTUAL));
} else {
// a special RTData type, so just run the unmarshalling method and the returned type should be the expected type
return new JavaExpression.MethodInvocation.Instance(
argExpr,
classTypeInfo.getUnmarshallingMethodName(),
classTypeInfo.getReturnTypeOfUnmarshallingMethod(),
JavaExpression.MethodInvocation.InvocationType.VIRTUAL);
}
}
/**
* Returns whether an entity of the given CAL scope should be exposed as part of the library API.
* @param scope the CAL scope.
* @return true if the scope is considered visible to the API; false otherwise.
*/
private static boolean isScopeVisibleAsJavaLibraryAPI(final Scope scope) {
if (ENABLE_GENERATION_OF_PRIVATE_ENTITIES_FOR_DEBUGGING) {
return true;
} else {
return !scope.isPrivate();
}
}
/**
* Maps a CAL scope to the corresponding Java scope for the purpose of generating API methods.
* @param scope the CAL scope.
* @return the corresponding Java scope.
*/
private static int mapScope(final Scope scope) {
if (scope.isPublic()) {
return Modifier.PUBLIC;
} else if (scope.isProtected()) {
return Modifier.PROTECTED;
} else {
return Modifier.PRIVATE;
}
}
/**
* Constructs a JavaStatement declaring a generated code info local variable for configuration checking.
* @return the java statement.
*/
private static JavaStatement.LocalVariableDeclaration makeGeneratedCodeInfoDecl() {
//
// Code fragment:
//
// StandaloneJarGeneratedCodeInfo generatedCodeInfo = new StandaloneJarGeneratedCodeInfo(
// {codeVersion},
// {runtimeStats},
// {callCounts},
// {debugCapable},
// {directPrimopCalls},
// {ignoringStrictnessAnnotations},
// {noninterruptibleRuntime});
//
final StandaloneJarGeneratedCodeInfo currentConfigGeneratedCodeInfo = StandaloneJarGeneratedCodeInfo.makeFromCurrentConfiguration();
final JavaTypeName javaTypeName_StandaloneJarGeneratedCodeInfo = JavaTypeName.make(StandaloneJarGeneratedCodeInfo.class);
final JavaExpression generatedCodeInfoInit =
new JavaExpression.ClassInstanceCreationExpression(
javaTypeName_StandaloneJarGeneratedCodeInfo,
new JavaExpression[] {
JavaExpression.LiteralWrapper.make(new Integer(currentConfigGeneratedCodeInfo.getCodeVersion())),
JavaExpression.LiteralWrapper.make(Boolean.valueOf(currentConfigGeneratedCodeInfo.generateRuntimeStats())),
JavaExpression.LiteralWrapper.make(Boolean.valueOf(currentConfigGeneratedCodeInfo.generateCallCounts())),
JavaExpression.LiteralWrapper.make(Boolean.valueOf(currentConfigGeneratedCodeInfo.isDebugCapable())),
JavaExpression.LiteralWrapper.make(Boolean.valueOf(currentConfigGeneratedCodeInfo.generateDirectPrimopCalls())),
JavaExpression.LiteralWrapper.make(Boolean.valueOf(currentConfigGeneratedCodeInfo.isIgnoringStrictnessAnnotations())),
JavaExpression.LiteralWrapper.make(Boolean.valueOf(currentConfigGeneratedCodeInfo.isNonInterruptableRuntime()))
},
new JavaTypeName[] {
JavaTypeName.INT,
JavaTypeName.BOOLEAN,
JavaTypeName.BOOLEAN,
JavaTypeName.BOOLEAN,
JavaTypeName.BOOLEAN,
JavaTypeName.BOOLEAN,
JavaTypeName.BOOLEAN
});
final JavaExpression.LocalVariable generatedCodeInfoLocalVar = new JavaExpression.LocalVariable("generatedCodeInfo", javaTypeName_StandaloneJarGeneratedCodeInfo);
return new JavaStatement.LocalVariableDeclaration(generatedCodeInfoLocalVar, generatedCodeInfoInit, true);
}
/**
* Constructs a JavaExpression for invoking the <code>apply(RTValue)</code> method.
* @param invocationTarget the target of the invocation.
* @param argExpr the expression evaluating to the RTValue argument.
* @return the JavaExpression for invoking the <code>apply</code> method.
*/
private static JavaExpression makeApplyInvocationExpr(final JavaExpression invocationTarget, final JavaExpression argExpr) {
return new JavaExpression.MethodInvocation.Instance(
invocationTarget,
"apply",
argExpr,
JavaTypeNames.RTVALUE,
JavaTypeNames.RTVALUE,
JavaExpression.MethodInvocation.InvocationType.VIRTUAL);
}
/**
* Constructs a chain of nested JavaExpression for invoking the maximal-arity variant of the <code>apply</code> method on the list of arguments.
* @param invocationTarget the target of the invocation.
* @param argExprs the list of expressions.
* @return the JavaExpression chain.
*/
private static JavaExpression makeApplyInvocationExprChain(final JavaExpression invocationTarget, final List<JavaExpression> argExprs) {
JavaExpression applicationChain = invocationTarget;
List<JavaExpression> remainder = argExprs;
// we will shrink the remainder by a maximum of 4 each iteration
while (remainder.size() > 0) {
final int remainderSize = remainder.size();
final int currentFragmentSize = Math.min(remainderSize, 4);
final List<JavaExpression> currentFragment = remainder.subList(0, currentFragmentSize);
final JavaTypeName[] argTypes = new JavaTypeName[currentFragmentSize];
Arrays.fill(argTypes, JavaTypeNames.RTVALUE);
applicationChain =
new JavaExpression.MethodInvocation.Instance(
applicationChain,
"apply",
currentFragment.toArray(new JavaExpression[currentFragmentSize]),
argTypes,
JavaTypeNames.RTVALUE,
JavaExpression.MethodInvocation.InvocationType.VIRTUAL);
remainder = remainder.subList(currentFragmentSize, remainderSize);
}
return applicationChain;
}
/**
* Constructs a JavaExpression for invoking the <code>evaluate(RTExecutionContext)</code> method.
* @param invocationTarget the target of the invocation.
* @param executionContextExpr the expression representing the execution context in the caller's context.
* @return the JavaExpression for invoking the <code>evaluate</code> method.
*/
private static JavaExpression makeEvaluateInvocationExpr(final JavaExpression invocationTarget, final JavaExpression executionContextExpr) {
return new JavaExpression.MethodInvocation.Instance(
invocationTarget,
"evaluate",
executionContextExpr,
JavaTypeNames.RTEXECUTION_CONTEXT,
JavaTypeNames.RTVALUE,
JavaExpression.MethodInvocation.InvocationType.VIRTUAL);
}
/**
* Create an instance of the generated class corresponding to the entry point supercombinator.
* @param mf the MachineFunction representing the CAL function for which an instance of the generated class is desired.
* @param module the module object for the module containing the machine function.
* @param executionContextExpr the expression representing the execution context in the caller's context.
* @return the java expression for obtaining the start point instance
*
* @see Executor#getInstanceOfGeneratedClass
*/
private static JavaExpression getInstanceOfGeneratedClassJavaExpression(final MachineFunction mf, final LECCModule module, final JavaExpression executionContextExpr) {
if (mf == null) {
throw new NullPointerException("MachineFunction argument cannot be null.");
}
// The specified target may be an alias of another function, or it may
// be defined as a literal value
// (either directly or from following an alias chain). In either case
// there is no source/class
// generated. So we need to either return the literal value or follow
// the alias to a
// function for which there is a generated source/class file.
final Object literalValue = mf.getLiteralValue();
if (literalValue != null) {
final LiteralWrapper wrappedLiteral = JavaExpression.LiteralWrapper.make(literalValue);
final Class<?> unboxedLiteralType = supportedLiteralTypesBoxedToUnboxedClassMap.get(wrappedLiteral.getClass());
if (unboxedLiteralType == null) {
throw new IllegalArgumentException("Unsupported literal type in machine function: " + literalValue.getClass());
}
return makeRTValueMarshallingExpr(ClassTypeInfo.make(unboxedLiteralType), wrappedLiteral);
}
final QualifiedName entryPointSCName;
if (mf.getAliasOf() != null) {
entryPointSCName = mf.getAliasOf();
} else {
entryPointSCName = mf.getQualifiedName();
}
final LECCModule startModule = (LECCModule)module.findModule(entryPointSCName.getModuleName());
final MachineFunction actualMachineFunction = startModule.getFunction(entryPointSCName);
// Munge the qualified name into a class name.
// Get local name and capitalised local name
if (actualMachineFunction.isDataConstructor()) {
// this is a DataConstructor
final String className = CALToJavaNames.createFullClassNameFromDC(entryPointSCName, startModule);
final DataConstructor dc = startModule.getModuleTypeInfo().getDataConstructor(entryPointSCName.getUnqualifiedName());
if (isEnumDataConsRepresentedAsPrimitiveInt(dc)) {
// an enum data cons treated as an int - we should just return the boxed ordinal
return makeRTValueMarshallingExpr(ClassTypeInfo.make(int.class), JavaExpression.LiteralWrapper.make(Integer.valueOf(dc.getOrdinal())));
} else {
if (className.endsWith("$TagDC")) {
if (entryPointSCName.equals(CAL_Prelude.DataConstructors.True)) {
return new JavaExpression.JavaField.Static(JavaTypeNames.RTDATA_BOOLEAN, "TRUE", JavaTypeNames.RTDATA_BOOLEAN);
} else if (entryPointSCName.equals(CAL_Prelude.DataConstructors.False)) {
return new JavaExpression.JavaField.Static(JavaTypeNames.RTDATA_BOOLEAN, "FALSE", JavaTypeNames.RTDATA_BOOLEAN);
} else {
final String outerClassName = className.substring(0, className.length() - 6);
return StandaloneJarBuilder.getTagDCStartPointInstanceJavaExpression(outerClassName, dc.getOrdinal());
}
} else {
return getStartPointInstanceJavaExpression(className, startModule, actualMachineFunction, executionContextExpr);
}
}
} else {
return getStartPointInstanceJavaExpression(
CALToJavaNames.createFullClassNameFromSC(entryPointSCName, startModule),
startModule, actualMachineFunction, executionContextExpr);
}
}
/**
* Returns whether the current machine configuration specifies that given data constructor is represented as an int.
* @param dataCons the data constructor to check.
* @return true if the data cons is represented as an int; false otherwise.
*/
private static boolean isEnumDataConsRepresentedAsPrimitiveInt(final DataConstructor dataCons) {
return LECCMachineConfiguration.TREAT_ENUMS_AS_INTS && TypeExpr.isEnumType(dataCons.getTypeConstructor());
}
/**
* Returns the java expression for creating an instance through the use of the factory method 'make'.
* The named class must be derived from RTValue.
* @param className the name of the class.
* @param module the module associated with the class.
* @param machineFunction the machine function associated with the class.
* @param executionContextExpr the expression representing the execution context in the caller's context.
* @return the java expression for creating an instance of the class.
*
* @see CALClassLoader#getStartPointInstance
*/
private static JavaExpression getStartPointInstanceJavaExpression(final String className, final LECCModule module, final MachineFunction machineFunction, final JavaExpression executionContextExpr) {
if (machineFunction == null) {
throw new NullPointerException ("Invalid MachineFunction in getStartPointInstanceJavaExpression() for " + className + ".");
}
final JavaTypeName javaTypeName = JavaTypeName.make(className, false);
if (machineFunction.isDataConstructor()) {
// This is a data constructor.
// Get the static 'make' method.
return new JavaExpression.MethodInvocation.Static(
javaTypeName,
"make",
JavaExpression.EMPTY_JAVA_EXPRESSION_ARRAY,
JavaTypeName.NO_ARGS,
javaTypeName);
}
final FunctionGroupInfo fgi = module.getFunctionGroupInfo(machineFunction);
if (fgi == null) {
throw new NullPointerException ("Invalid FunctionGroupInfo in getStartPointInstanceJavaExpression() for " + machineFunction.getQualifiedName() + ".");
}
if (machineFunction.getArity() == 0) {
// Get the static 'make' method.
if (fgi.getNCAFs() + fgi.getNZeroArityFunctions() <= 1) {
return new JavaExpression.MethodInvocation.Static(
javaTypeName,
"make",
executionContextExpr,
JavaTypeNames.RTEXECUTION_CONTEXT,
JavaTypeNames.RTFUNCTION);
} else {
final int functionIndex = fgi.getFunctionIndex(machineFunction.getName());
return new JavaExpression.MethodInvocation.Static(
javaTypeName,
"make",
new JavaExpression[] {JavaExpression.LiteralWrapper.make(new Integer(functionIndex)), executionContextExpr},
new JavaTypeName[] {JavaTypeName.INT, JavaTypeNames.RTEXECUTION_CONTEXT},
JavaTypeNames.RTFUNCTION);
}
}
// Access the static instance field.
final String instanceFieldName = CALToJavaNames.getInstanceFieldName(machineFunction.getQualifiedName(), module);
return new JavaExpression.JavaField.Static(javaTypeName, instanceFieldName, javaTypeName);
}
/**
* Returns the java expression for creating an instance through the use of the factory method 'getTagDC'.
* The named class must be derived from RTValue.
* @param className the name of the class.
* @param ordinal the ordinal of the data constructor.
* @return the java expression for creating an instance of the class.
*
* @see CALClassLoader#getTagDCStartPointInstance
*/
private static JavaExpression getTagDCStartPointInstanceJavaExpression(final String className, final int ordinal) {
final JavaTypeName javaTypeName = JavaTypeName.make(className, false);
final JavaTypeName javaTypeName_TagDC = JavaTypeName.make(className + "$TagDC", false);
return new JavaExpression.MethodInvocation.Static(
javaTypeName,
"getTagDC",
JavaExpression.LiteralWrapper.make(new Integer(ordinal)),
JavaTypeName.INT,
javaTypeName_TagDC);
}
}