/* * 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. */ /* * IO_Source_Generator.java * * Creation date: May 17, 2007 * By: Raymond Cypher */ package org.openquark.cal.tools.iosourcegenerator; import java.lang.reflect.Modifier; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Date; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.logging.Level; import java.util.logging.LogRecord; import org.openquark.cal.compiler.ClassInstance; import org.openquark.cal.compiler.DataConstructor; import org.openquark.cal.compiler.FieldName; import org.openquark.cal.compiler.ForeignTypeInfo; import org.openquark.cal.compiler.Function; import org.openquark.cal.compiler.ModuleName; import org.openquark.cal.compiler.ModuleTypeInfo; import org.openquark.cal.compiler.QualifiedName; import org.openquark.cal.compiler.RecordType; import org.openquark.cal.compiler.Scope; import org.openquark.cal.compiler.SourceModel; import org.openquark.cal.compiler.SourceModelCopier; import org.openquark.cal.compiler.TypeApp; import org.openquark.cal.compiler.TypeConsApp; import org.openquark.cal.compiler.TypeConstructor; import org.openquark.cal.compiler.TypeExpr; import org.openquark.cal.compiler.UnableToResolveForeignEntityException; import org.openquark.cal.compiler.SourceModel.CALDoc; import org.openquark.cal.compiler.SourceModel.Expr; import org.openquark.cal.compiler.SourceModel.Friend; import org.openquark.cal.compiler.SourceModel.FunctionDefn; import org.openquark.cal.compiler.SourceModel.FunctionTypeDeclaration; import org.openquark.cal.compiler.SourceModel.Import; import org.openquark.cal.compiler.SourceModel.InstanceDefn; import org.openquark.cal.compiler.SourceModel.LocalDefn; import org.openquark.cal.compiler.SourceModel.ModuleDefn; import org.openquark.cal.compiler.SourceModel.Name; import org.openquark.cal.compiler.SourceModel.Parameter; import org.openquark.cal.compiler.SourceModel.TopLevelSourceElement; import org.openquark.cal.compiler.SourceModel.TypeConstructorDefn; import org.openquark.cal.compiler.SourceModel.TypeExprDefn; import org.openquark.cal.compiler.SourceModel.TypeSignature; 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.JavaMethod; import org.openquark.cal.internal.javamodel.JavaOperator; import org.openquark.cal.internal.javamodel.JavaReservedWords; import org.openquark.cal.internal.javamodel.JavaStatement; import org.openquark.cal.internal.javamodel.JavaTypeName; import org.openquark.cal.internal.javamodel.JavaExpression.Assignment; import org.openquark.cal.internal.javamodel.JavaExpression.CastExpression; import org.openquark.cal.internal.javamodel.JavaExpression.JavaField; import org.openquark.cal.internal.javamodel.JavaExpression.LiteralWrapper; import org.openquark.cal.internal.javamodel.JavaExpression.LocalVariable; import org.openquark.cal.internal.javamodel.JavaExpression.MethodInvocation; import org.openquark.cal.internal.javamodel.JavaExpression.MethodVariable; import org.openquark.cal.internal.javamodel.JavaExpression.OperatorExpression; import org.openquark.cal.internal.javamodel.JavaStatement.Block; import org.openquark.cal.internal.javamodel.JavaStatement.ExpressionStatement; import org.openquark.cal.internal.javamodel.JavaStatement.IfThenElseStatement; import org.openquark.cal.internal.javamodel.JavaStatement.JavaDocComment; import org.openquark.cal.internal.javamodel.JavaStatement.LocalVariableDeclaration; import org.openquark.cal.internal.javamodel.JavaStatement.MultiLineComment; import org.openquark.cal.internal.javamodel.JavaStatement.ReturnStatement; import org.openquark.cal.internal.javamodel.JavaStatement.SwitchStatement; import org.openquark.cal.module.Cal.Core.CAL_Prelude; import org.openquark.cal.runtime.CalValue; /** * This class generates I/O source code in the form of a set of Java classes and a CAL * module. The Java classes are in the form of instances of JavaClassRep and the CAL module * is produced in the form of an instance of SourceModel.Module. Client code is responsible for * converting these forms into source code, byte code, etc. * * When working on a project which combines CAL and Java code there is often a need to * marshal data types between CAL and Java. The mechanism which is generally used for * this is to make a CAL data type an instance of the Inputable and Outputable type classes. * * To make a user defined type inputable and outputable, you would first decide what the * Java representation of the Quark type will be. The input and output methods will convert * between the Quark type and an appropriate instance of this Java class. * A common approach is to define a foreign type for the target Java class, write CAL * functions that convert to and from that type, using foreign functions, then convert * to or from JObject using a foreign function that merely casts between the target Java * class and Object. For more information please see the two documents �CAL User�s Guide� * and �Java Meets Quark�. * * The Input/Output code generation tool seeks to streamline the process of making a CAL * data type an instance of the Inputable and Outputable type classes by generating the * majority of the necessary CAL and Java code. * * The code generation tool takes an existing CAL data type and will generate a corresponding * Java class. It will then generate the necessary CAL code to refer to the generated Java * class as a CAL foreign type, generate the CAL functions needed to convert between the original * CAL data type and the foreign type, and generate the type class instance declarations to make the * original data type an instance of the Inputable and Outputable type classes. * * @author rcypher * */ public class IO_Source_Generator { private static final JavaTypeName CAL_VALUE = JavaTypeName.make(CalValue.class); /** * These are not valid file names in Windows. In addition there is the com1, com2, ..., lpt1, lpt2, ... family of names that are * parameterized by an integer. Note that Windows is case-insensitive, so check that the lower case of your string is not in this * set. */ static private final Set<String> windowsReservedWords = new LinkedHashSet<String>(); static { windowsReservedWords.add ("clock$"); windowsReservedWords.add ("con"); windowsReservedWords.add ("prn"); windowsReservedWords.add ("nul"); windowsReservedWords.add ("config$"); windowsReservedWords.add ("aux"); } private static final JavaTypeName[] EMPTY_TYPE_NAME_ARRAY = new JavaTypeName[0]; private static final JavaTypeName UNSUPPORTED_OPERATION_TYPE_NAME = JavaTypeName.make(UnsupportedOperationException.class); private static final String GET_DC_ORDINAL_METHOD_NAME = "getDCOrdinal"; private static final String GET_DC_NAME_METHOD_NAME = "getDCName"; private static final String ORDINAL_FIELD_NAME = "ordinal$"; private static final String DC_NAME_FIELD_NAME = "dcName$"; private static final String HASH_CODE_FIELD_NAME = "hashCode"; private static final TypeExprDefn.TypeCons INT_TYPE_EXPR_DEFN = TypeExprDefn.TypeCons.make(Name.TypeCons.make(CAL_Prelude.TypeConstructors.Int)); private static final TypeExprDefn.TypeCons JOBJECT_TYPE_EXPR_DEFN = TypeExprDefn.TypeCons.make(Name.TypeCons.make(CAL_Prelude.TypeConstructors.JObject)); public static final class GeneratedIO { /** QualifiedName -> JavaClassRep */ private final Map<QualifiedName, JavaClassRep> javaClasses; /** QualifiedName -> (List of LogRecord) */ private final Map<QualifiedName, List<LogRecord>> javaClassLogMessages; /** SourceModel.ModuleDefn for CAL I/O code. */ private final SourceModel.ModuleDefn ioModule; /** List of LogRecord for the module definition. */ private final List<LogRecord> moduleLogMessages; private final List<LogRecord> ioLogMessages; GeneratedIO (Map<QualifiedName, JavaClassRep> javaClasses, Map<QualifiedName, List<LogRecord>> javaClassLogMessages, ModuleDefn ioModule, List<LogRecord> moduleLogMessages, List<LogRecord> ioLogMessages) { this.javaClasses = javaClasses; this.javaClassLogMessages = javaClassLogMessages; this.ioModule = ioModule; this.moduleLogMessages = moduleLogMessages; this.ioLogMessages = ioLogMessages; } /** * @return the ioModule */ public SourceModel.ModuleDefn getIoModule() { return ioModule; } /** * @return the javaClasses */ public Map<QualifiedName, JavaClassRep> getJavaClasses() { return javaClasses; } /** * @return the ioLogMessages */ public List<LogRecord> getIoLogMessages() { return ioLogMessages; } /** * @return the javaClassLogMessages */ public Map<QualifiedName, List<LogRecord>> getJavaClassLogMessages() { return javaClassLogMessages; } /** * @return the moduleLogMessages */ public List<LogRecord> getModuleLogMessages() { return moduleLogMessages; } } final GeneratedIO generateIO ( ModuleTypeInfo module, String targetPackage, Set<QualifiedName> typeConstructorsToIgnore, Map<QualifiedName, JavaTypeName> typeToClassMappings, Map<ModuleName, String>moduleToPackageMappings) throws UnableToResolveForeignEntityException { /** Maps names of CAL types to the corresponding generated Java class. */ Map<QualifiedName, JavaClassRep> classReps = new LinkedHashMap<QualifiedName, JavaClassRep>(); Map<QualifiedName, List<LogRecord>> classLogMessages = new LinkedHashMap<QualifiedName, List<LogRecord>>(); List<TopLevelSourceElement> allTopLevelElements = new ArrayList<TopLevelSourceElement>(); List<LogRecord> calLogMessages = new ArrayList<LogRecord>(); Set<ModuleName> mustHaveImports = new LinkedHashSet<ModuleName>(); Map<String, TypeSignature> castFunctions = new LinkedHashMap<String, TypeSignature>(); Map<QualifiedName, TypeConstructor> additionalForeignTypeDeclarations = new LinkedHashMap<QualifiedName, TypeConstructor>(); for (int i = 0, n = module.getNTypeConstructors(); i < n; ++i) { TypeConstructor tc = module.getNthTypeConstructor(i); if (typeConstructorsToIgnore.contains(tc.getName()) || tc.getForeignTypeInfo() != null) { logMessage(Level.INFO, "Skipped type constructor " + tc.getName().getUnqualifiedName() + " because of ignore directive."); continue; } // If the type constructor is declared as inputable/outputable in the containing module // we skip it because generating inputable/outputable instances would conflict. boolean alreadyInputableOutputable = false; for (int j = 0, k = module.getNClassInstances(); j < k; ++j) { ClassInstance ci = module.getNthClassInstance(j); if (ci.getTypeClass().getName().equals(CAL_Prelude.TypeClasses.Inputable) || ci.getTypeClass().getName().equals(CAL_Prelude.TypeClasses.Outputable)) { // Check the instance type TypeExpr instanceTypeExpr = ci.getType(); TypeConsApp tca = instanceTypeExpr.rootTypeConsApp(); if (tca != null) { if (tca.getRoot().getName().equals(tc.getName())) { alreadyInputableOutputable = true; } } } } if (alreadyInputableOutputable) { logMessage(Level.INFO, "Skipped type constructor " + tc.getName().getUnqualifiedName() + " because it is alreayd an instance of Inputable or Outputable."); continue; } // Build up info about the type constructor and data constructors. TypeConstructorInfo typeConstructorInfo = getTypeConstructorInfo (tc, typeToClassMappings, moduleToPackageMappings, module, targetPackage); // Now we want to generate the Java class. JavaDataClassGenerator javaDataClassGenerator = new JavaDataClassGenerator( targetPackage, typeConstructorInfo); JavaClassRep javaClass = javaDataClassGenerator.generateClassForType(); classReps.put(tc.getName(), javaClass); classLogMessages.put(tc.getName(), javaDataClassGenerator.logMessages); // Generate the CAL I/O CAL_IO_Generator ioGenerator = new CAL_IO_Generator ( typeConstructorInfo, module, javaClass); Collection<TopLevelSourceElement> topLevelElements = ioGenerator.generateCAL_IO (); allTopLevelElements.addAll(topLevelElements); calLogMessages.addAll(ioGenerator.logMessages); mustHaveImports.addAll(ioGenerator.getInputableImports()); castFunctions.putAll(ioGenerator.castFunctionNameToTypeSignature); additionalForeignTypeDeclarations.putAll(ioGenerator.additionalForeignTypeDeclarations); } SourceModel.ModuleDefn moduleDefn = null; if (allTopLevelElements.size() > 0) { ModuleName newModuleName = ModuleName.make(module.getModuleName().toString() + "_JavaIO"); for (Map.Entry<String, TypeSignature> entry : castFunctions.entrySet()) { String castFunctionName = (String)entry.getKey(); SourceModel.TypeSignature typeSignature = (TypeSignature)entry.getValue(); TypeExprDefn domain = ((TypeExprDefn.Function)typeSignature.getTypeExprDefn()).getDomain(); TypeExprDefn coDomain = ((TypeExprDefn.Function)typeSignature.getTypeExprDefn()).getCodomain(); SourceModel.CALDoc.TextSegment.Plain textSegment = SourceModel.CALDoc.TextSegment.Plain.make( "Cast an instance of " + domain.toString() + " to " + coDomain.toString()); SourceModel.CALDoc.TextBlock textBlock = SourceModel.CALDoc.TextBlock.make(new SourceModel.CALDoc.TextSegment.TopLevel[]{textSegment}); SourceModel.CALDoc.Comment.Function functionComment = SourceModel.CALDoc.Comment.Function.make( textBlock, null); SourceModel.FunctionDefn castFunction = FunctionDefn.Foreign.make( functionComment, castFunctionName, Scope.PRIVATE, true, "cast", typeSignature); allTopLevelElements.add(castFunction); } for (Map.Entry<QualifiedName, TypeConstructor>entry : additionalForeignTypeDeclarations.entrySet()) { QualifiedName typeConstructorName = (QualifiedName)entry.getKey(); TypeConstructor typeConstructor = (TypeConstructor)entry.getValue(); // Declare the java class as a foreign type. // ex. data foreign unsafe import jvm "org.olap.CubeType" public JCubeType; SourceModel.TypeConstructorDefn.ForeignType foreignType = TypeConstructorDefn.ForeignType.make( typeConstructorName.getUnqualifiedName(), Scope.PRIVATE, ForeignTypeInfo.getCalSourceName(typeConstructor.getForeignTypeInfo().getForeignType()), Scope.PRIVATE, null); allTopLevelElements.add(foreignType); } SourceModel.CALDoc.TextSegment.Plain textSegment = SourceModel.CALDoc.TextSegment.Plain.make( "\nThis module, " + newModuleName + ", implements input and output\n" + "functions for the type constructors contained in the " + module.getModuleName() + " module.\n" + "\n" + "***************************************************************************************\n\n" + "NOTE: The code in this module is automatically generated.\n" + " MODIFICATIONS TO THIS SOURCE MAY BE OVERWRITTEN - DO NOT MODIFY THIS FILE\n\n" + "***************************************************************************************\n\n" + "The Java classes corresponding to the type constructors are also automatically generated.\n" ); SourceModel.CALDoc.TextBlock textBlock = SourceModel.CALDoc.TextBlock.make(new SourceModel.CALDoc.TextSegment.TopLevel[]{textSegment}); SourceModel.CALDoc.Comment.Module moduleComment = SourceModel.CALDoc.Comment.Module.make( textBlock, null); moduleDefn = SourceModel.ModuleDefn.make( moduleComment, newModuleName, new SourceModel.Import[0], (SourceModel.TopLevelSourceElement[])allTopLevelElements.toArray(new SourceModel.TopLevelSourceElement[0])); CAL_Module_ImportGenerator<Void> importGenerator = new CAL_Module_ImportGenerator<Void>(moduleDefn, mustHaveImports); moduleDefn = importGenerator.generateImports(moduleDefn); } GeneratedIO generatedIO = new GeneratedIO( classReps, classLogMessages, moduleDefn, calLogMessages, ioLogMessages); return generatedIO; } /** * Build up information about a type constructor that will be * used for generating Java and CAL sources. * * @param typeConstructor * @param typeToClassMappings * @param moduleToPackageMappings * @param module * @param targetPackage * @return the info about the type constructor * @throws UnableToResolveForeignEntityException */ private TypeConstructorInfo getTypeConstructorInfo ( TypeConstructor typeConstructor, Map<QualifiedName, JavaTypeName> typeToClassMappings, Map<ModuleName, String> moduleToPackageMappings, ModuleTypeInfo module, String targetPackage) throws UnableToResolveForeignEntityException { Set<FieldName> allFieldNames = new LinkedHashSet<FieldName>(); Map<FieldName, Set<JavaTypeName>> allFieldTypes = new LinkedHashMap<FieldName, Set<JavaTypeName>>(); int nEnumDCs = 0; for (int i = 0, n = typeConstructor.getNDataConstructors(); i < n; ++i) { DataConstructor dc = typeConstructor.getNthDataConstructor(i); if (dc.getArity() == 0) { nEnumDCs++; } TypeExpr[] fieldTypes = getFieldTypesForDC(dc); for (int j = 0, k = dc.getArity(); j < k; ++j) { FieldName fn = dc.getNthFieldName(j); allFieldNames.add(fn); JavaTypeName jtn = typeExprToTypeName(fieldTypes[j], typeToClassMappings, moduleToPackageMappings, targetPackage); Set<JavaTypeName> javaTypeNames = (Set<JavaTypeName>)allFieldTypes.get(fn); if (javaTypeNames == null) { javaTypeNames = new LinkedHashSet<JavaTypeName>(); allFieldTypes.put(fn, javaTypeNames); } javaTypeNames.add(jtn); } } boolean enumDataType = nEnumDCs == typeConstructor.getNDataConstructors(); Map<FieldName, String> fieldJavaNames = new LinkedHashMap<FieldName, String>(); Map<FieldName, String> fieldAccessorMethodNames = new LinkedHashMap<FieldName, String>(); Map<FieldName, Map<JavaTypeName, String>> calFieldForeignTypes = new LinkedHashMap<FieldName, Map<JavaTypeName, String>>(); for (FieldName fn : allFieldNames) { String javaName = getJavaFieldNameFromFieldName(fn); fieldJavaNames.put(fn, javaName); Set<JavaTypeName> javaTypes = (Set<JavaTypeName>)allFieldTypes.get(fn); String updatedFieldName = javaName.substring(1); char[] ln = updatedFieldName.toCharArray(); ln[0] = Character.toUpperCase(ln[0]); updatedFieldName = new String (ln); String methodName = "get" + updatedFieldName; fieldAccessorMethodNames.put(fn, methodName); Map<JavaTypeName, String> calForeignTypes = new LinkedHashMap<JavaTypeName, String>(); calFieldForeignTypes.put(fn, calForeignTypes); for (JavaTypeName jtn : javaTypes) { String calForeignTypeName = getNameOfCALForeignType(jtn, module); calForeignTypes.put(jtn, calForeignTypeName); } } // We want to avoid a situation where the outer class and inner class have the same name // since in Java a nested type can't hide an enclosing type. To avoid this we // append an '_' to the outer class name. String javaClassName = getClassName(typeConstructor); if (!enumDataType) { for (int i = 0, n = typeConstructor.getNDataConstructors(); i < n; ++i) { DataConstructor dc = typeConstructor.getNthDataConstructor(i); String innerClassName = fixupClassName(dc.getName().getUnqualifiedName()); if (innerClassName.equals(javaClassName)) { javaClassName = javaClassName + "_"; break; } } } Set<FieldName> commonFieldNames = getCommonFieldNames(typeConstructor); Set<FieldName> finalCommonFieldNames = new LinkedHashSet<FieldName>(); for (FieldName fn : commonFieldNames) { Set<JavaTypeName> fieldTypes = (Set<JavaTypeName>)allFieldTypes.get(fn); if (fieldTypes.size() == 1) { finalCommonFieldNames.add(fn); } } TypeConstructorInfo tci = new TypeConstructorInfo( typeConstructor, javaClassName, enumDataType, allFieldNames, allFieldTypes, finalCommonFieldNames, fieldJavaNames, fieldAccessorMethodNames, calFieldForeignTypes, typeToClassMappings); for (int i = 0, n = typeConstructor.getNDataConstructors(); i < n; ++i) { DataConstructor dc = typeConstructor.getNthDataConstructor(i); DataConstructorInfo dci = getDataConstructorInfo (dc, tci, typeToClassMappings, moduleToPackageMappings, targetPackage); tci.dataConstructorInfo.put(dc, dci); } return tci; } private final DataConstructorInfo getDataConstructorInfo( DataConstructor dataConstructor, TypeConstructorInfo typeConstructorInfo, Map<QualifiedName, JavaTypeName> typeToClassMappings, Map<ModuleName, String> moduleToPackageMappings, String targetPackage) throws UnableToResolveForeignEntityException { Map<FieldName, String> fieldJavaAccessorMethodNames = new LinkedHashMap<FieldName, String>(); Map<FieldName, JavaTypeName> fieldTypeNames = new LinkedHashMap<FieldName, JavaTypeName>(); TypeExpr fieldTypes[] = getFieldTypesForDC(dataConstructor); boolean containsFields = false; for (int i = 0, n = dataConstructor.getArity(); i < n; ++i) { FieldName fn = dataConstructor.getNthFieldName(i); if (!typeConstructorInfo.commonFieldNames.contains(fn)) { containsFields = true; } JavaTypeName javaType = typeExprToTypeName(fieldTypes[i], typeToClassMappings, moduleToPackageMappings, targetPackage); fieldTypeNames.put(fn, javaType); String accessorName = (String)typeConstructorInfo.fieldJavaAccessorMethodNames.get(fn); fieldJavaAccessorMethodNames.put(fn, accessorName); } return new DataConstructorInfo( dataConstructor, fieldJavaAccessorMethodNames, fieldTypeNames, containsFields); } private String getNameOfCALForeignType (JavaTypeName javaType, ModuleTypeInfo module) throws UnableToResolveForeignEntityException { // First try to find a type constructor for each java type; TypeConstructor typeConstructor = findTypeConstructorForJavaClass(javaType, module); if (typeConstructor != null) { if (typeConstructor.getScope() == Scope.PUBLIC) { return typeConstructor.getName().toString(); } else { return typeConstructor.getName().getUnqualifiedName(); } } else if (javaType.equals(JavaTypeName.BOOLEAN)){ return CAL_Prelude.TypeConstructors.Boolean.toString(); } else { // Couldn't find a type constructor for the Java class. // Build up a Name.TypeCons using our naming pattern. // try to find a module containing the base CAL type. ModuleName moduleName = findModuleContainingType(javaType.getUnqualifiedJavaSourceName(), module); if (moduleName == null) { return "J" + javaType.getUnqualifiedJavaSourceName(); } else { moduleName = ModuleName.make(moduleName.toString() + "_JavaIO"); return moduleName.toString() + ".J" + javaType.getUnqualifiedJavaSourceName(); } } } private final ModuleName findModuleContainingType (String typeName, ModuleTypeInfo module) { return findModuleContainingType (new LinkedHashSet<ModuleName>(), typeName, module); } private final ModuleName findModuleContainingType(Set<ModuleName> visitedModules, String typeName, ModuleTypeInfo module) { if (visitedModules.contains(module.getModuleName())) { return null; } visitedModules.add(module.getModuleName()); for (int i = 0, n = module.getNTypeConstructors(); i < n; ++i) { if (module.getNthTypeConstructor(i).getName().getUnqualifiedName().equals(typeName)) { return module.getModuleName(); } } for (int i = 0, n = module.getNImportedModules(); i < n; ++i) { ModuleTypeInfo importedModule = module.getNthImportedModule(i); ModuleName mn = findModuleContainingType(visitedModules, typeName, importedModule); if (mn != null) { return mn; } } return null; } /** * Try to find a CAL type constructor corresponding to the named java type. * @param javaType * @param module * @return TypeConstructor of foreign type, null if not found. */ private final TypeConstructor findTypeConstructorForJavaClass (JavaTypeName javaType, ModuleTypeInfo module) throws UnableToResolveForeignEntityException { return findTypeConstructorForJavaClass (javaType, module, new LinkedHashSet<ModuleTypeInfo>()); } /** * Try to find a CAL type constructor corresponding to the named java type. * @param javaType * @param module * @param visitedModules * @return TypeConstructor of foreign type, null if not found. */ private final TypeConstructor findTypeConstructorForJavaClass (JavaTypeName javaType, ModuleTypeInfo module, Set<ModuleTypeInfo> visitedModules) throws UnableToResolveForeignEntityException { if (visitedModules.contains(module)) { return null; } visitedModules.add(module); for (int i = 0, n = module.getNTypeConstructors(); i < n; ++i) { TypeConstructor tc = module.getNthTypeConstructor(i); ForeignTypeInfo fti = tc.getForeignTypeInfo(); if (fti == null) { continue; } JavaTypeName foreignType = JavaTypeName.make(fti.getForeignType()); if (javaType.equals(foreignType)) { return tc; } } for (int i = 0, n = module.getNImportedModules(); i < n; ++i) { ModuleTypeInfo importedModule = module.getNthImportedModule(i); TypeConstructor tc = findTypeConstructorForJavaClass(javaType, importedModule, visitedModules); if (tc != null) { return tc; } } return null; } private final class CAL_IO_Generator { private ModuleTypeInfo module; private final TypeConstructorInfo typeConstructorInfo; private final TypeConstructor typeConstructor; private final JavaClassRep javaClass; private final Set<ModuleName> inputableImports = new LinkedHashSet<ModuleName>(); /* String -> TypeSignature */ private final Map<String, TypeSignature> castFunctionNameToTypeSignature = new LinkedHashMap<String, TypeSignature>(); /* QualifiedName -> TypeConstructor */ private final Map<QualifiedName, TypeConstructor> additionalForeignTypeDeclarations = new LinkedHashMap<QualifiedName, TypeConstructor>(); /** List of LogRecord */ private final List<LogRecord> logMessages = new ArrayList<LogRecord>(); private CAL_IO_Generator ( TypeConstructorInfo typeConstructorInfo, ModuleTypeInfo module, JavaClassRep javaClass) { assert (module != null && typeConstructorInfo != null && javaClass != null) : "Null argument to IO_Source_Generator.generateJavaType()."; this.module = module; this.typeConstructorInfo = typeConstructorInfo; this.javaClass = javaClass; this.typeConstructor = typeConstructorInfo.typeConstructor; } private void logMessage (Level level, String text) { logMessages.add(new LogRecord(level, text)); } /** * * @return a Collection of SourceModel.TopLevelSourceElement */ Collection<TopLevelSourceElement> generateCAL_IO () throws UnableToResolveForeignEntityException { List<TopLevelSourceElement> topLevelDefnsList = new ArrayList<TopLevelSourceElement>(); // We have three different scenarios to deal with: // 1) The CAL type is an enum // 2) The CAL type has only one data constructor and is not an enum and the dat constructor has the same name as the type constructor. // 3) The CAL type has more than one data constructor and is not an enum. if (typeConstructorInfo.isEnumerationType) { generateEnumerationIO (topLevelDefnsList); } else { generateMultiClassIO (topLevelDefnsList); } return topLevelDefnsList; } /** * * @param dc * @return QualifiedName of the make function, null if none found. */ private QualifiedName findMakeDCName (DataConstructor dc) { TypeExpr dcTypeExpr = dc.getTypeExpr(); String probableFunctionName = "make" + dc.getName().getUnqualifiedName(); for (int i = 0; i < module.getNFunctions(); ++i) { Function function = module.getNthFunction(i); if (function.getName().getUnqualifiedName().equals(probableFunctionName)) { if (dcTypeExpr.sameType(function.getTypeExpr())) { return QualifiedName.make(module.getModuleName(), probableFunctionName); } } } return null; } private void generateFieldAccessor ( String instanceForeignTypeName, FieldName fn, JavaTypeName fieldType, String foreignFieldTypeString, DataConstructor dc, List<TopLevelSourceElement> topLevelDefns) { TypeExprDefn jInstanceClassTypeExpr = TypeExprDefn.TypeCons.make(Name.TypeCons.makeUnqualified(instanceForeignTypeName)); Name.TypeCons typeConsName; if (foreignFieldTypeString.indexOf('.') > -1) { typeConsName = Name.TypeCons.make(QualifiedName.makeFromCompoundName(foreignFieldTypeString)); } else { typeConsName = Name.TypeCons.makeUnqualified(foreignFieldTypeString); } TypeExprDefn fieldTypeForGetter = TypeExprDefn.TypeCons.make(typeConsName); String functionName = (String)(typeConstructorInfo.calFieldAccessorFunctionNames.get(fn)).get(dc); String methodName = (String)typeConstructorInfo.fieldJavaAccessorMethodNames.get(fn); SourceModel.CALDoc.TextSegment.Plain textSegment = SourceModel.CALDoc.TextSegment.Plain.make( "Retrieve the " + fn.toString() + " field from an instance of " + instanceForeignTypeName + "."); SourceModel.CALDoc.TextBlock textBlock = SourceModel.CALDoc.TextBlock.make(new SourceModel.CALDoc.TextSegment.TopLevel[]{textSegment}); SourceModel.CALDoc.Comment.Function functionComment = SourceModel.CALDoc.Comment.Function.make( textBlock, null); SourceModel.FunctionDefn getter = FunctionDefn.Foreign.make( functionComment, functionName, Scope.PRIVATE, true, "method " + methodName, TypeSignature.make(TypeExprDefn.Function.make(jInstanceClassTypeExpr, fieldTypeForGetter))); topLevelDefns.add(getter); } private void generateMultiClassIO ( List<TopLevelSourceElement> topLevelDefnsList) throws UnableToResolveForeignEntityException { QualifiedName typeConstructorName = typeConstructorInfo.typeConstructor.getName(); // Import the top level class as a foreign type. // ex. data foreign unsafe import jvm "org.olap.CubeType" public JCubeType; makeForeignClassDeclaration(topLevelDefnsList); for (int i = 0, n = typeConstructor.getNDataConstructors(); i < n; ++i) { DataConstructor dc = typeConstructor.getNthDataConstructor(i); makeForeignClassDeclaration(dc, topLevelDefnsList); } TypeExprDefn.TypeCons jClassTypeExpr = TypeExprDefn.TypeCons.make(Name.TypeCons.makeUnqualified(typeConstructorInfo.calForeignTypeName)); // Import the constructors for each inner class (i.e. each data constructor) // Pull in the constructor for the Java class. for (int i = 0, n = javaClass.getNInnerClasses(); i < n; ++i) { JavaClassRep innerClass = javaClass.getInnerClass(i); assert (innerClass.getNConstructors() == 1); JavaConstructor constructor = innerClass.getConstructor(0); topLevelDefnsList.add(importJavaConstructor(constructor, true)); } // Create accessors for fields in the top level class. for (FieldName fn : typeConstructorInfo.commonFieldNames) { Set<JavaTypeName> fieldTypes = typeConstructorInfo.allFieldTypeNames.get(fn); // There will only be one type for common fields. Iterator<JavaTypeName> types = fieldTypes.iterator(); JavaTypeName fieldType = types.next(); String foreignFieldTypeSting = typeConstructorInfo.calFieldForeignTypes.get(fn).get(fieldType); generateFieldAccessor ( typeConstructorInfo.calForeignTypeName, fn, fieldType, foreignFieldTypeSting, typeConstructor.getNthDataConstructor(0), topLevelDefnsList); } // Create accessors for the fields in the inner classes. for (int i = 0, n = typeConstructor.getNDataConstructors(); i < n; ++i) { DataConstructor dc = typeConstructor.getNthDataConstructor(i); DataConstructorInfo dcInfo = (DataConstructorInfo)typeConstructorInfo.dataConstructorInfo.get(dc); for (FieldName fn : dcInfo.allFieldNames) { if (typeConstructorInfo.commonFieldNames.contains(fn)) { continue; } JavaTypeName fieldType = (JavaTypeName)dcInfo.fieldTypeNames.get(fn); String foreignFieldTypeSting = typeConstructorInfo.calFieldForeignTypes.get(fn).get(fieldType); generateFieldAccessor ( dcInfo.calForeignTypeName, fn, fieldType, foreignFieldTypeSting, dc, topLevelDefnsList); } } // Bring in the 'getDCOrdinal()' method of the Java class. String getDCOrdinalName = "j" + javaClass.getClassName().getUnqualifiedJavaSourceName() + "_getDCOrdinal"; SourceModel.TypeSignature getDCOrdinalTypeSignature = TypeSignature.make( TypeExprDefn.Function.make( jClassTypeExpr, INT_TYPE_EXPR_DEFN)); SourceModel.CALDoc.Comment.Function getDCOrdinalComment; { SourceModel.CALDoc.TextSegment.Plain textSegment = SourceModel.CALDoc.TextSegment.Plain.make( "\nRetrieve the ordinal value from an instance of " + typeConstructorInfo.calForeignTypeName + ".\n" + "The ordinal can be used to determine which data constructor the " + typeConstructorInfo.calForeignTypeName + "\n" + "instance corresponds to."); SourceModel.CALDoc.TextBlock textBlock = SourceModel.CALDoc.TextBlock.make(new SourceModel.CALDoc.TextSegment.TopLevel[]{textSegment}); getDCOrdinalComment = SourceModel.CALDoc.Comment.Function.make( textBlock, null); } SourceModel.FunctionDefn getDCOrdinal = FunctionDefn.Foreign.make( getDCOrdinalComment, getDCOrdinalName, Scope.PRIVATE, true, "method getDCOrdinal", getDCOrdinalTypeSignature); topLevelDefnsList.add(getDCOrdinal); // Create input function. // inputCube :: JCube -> Cube; // inputCube jCube = // case (jCube_getDCOrdinal JCube) of // 0 -> ...; // 1 -> ...; String inputFunctionName = "input" + typeConstructor.getName().getUnqualifiedName(); String inputFunctionArgName = "j" + typeConstructorInfo.calForeignTypeName.substring(1); Expr.Var inputFunctionArg = Expr.Var.make(Name.Function.makeUnqualified(inputFunctionArgName)); // Do the function type declaration. // JCube -> Cube TypeExprDefn inputFunctionType = TypeExprDefn.Function.make( TypeExprDefn.TypeCons.make(Name.TypeCons.makeUnqualified(typeConstructorInfo.calForeignTypeName)), TypeExprDefn.TypeCons.make(Name.TypeCons.makeUnqualified(typeConstructorName.getUnqualifiedName()))); SourceModel.CALDoc.Comment.Function inputFunctionComment; { SourceModel.CALDoc.TextSegment.Plain textSegment = SourceModel.CALDoc.TextSegment.Plain.make( "\nInput an instance of " + typeConstructor.getName().getUnqualifiedName() + ".\n" + "Translates an instance of " + typeConstructorInfo.calForeignTypeName + " to\n" + "an instance of "+ typeConstructor.getName().getUnqualifiedName() + "."); SourceModel.CALDoc.TextBlock textBlock = SourceModel.CALDoc.TextBlock.make(new SourceModel.CALDoc.TextSegment.TopLevel[]{textSegment}); inputFunctionComment = SourceModel.CALDoc.Comment.Function.make( textBlock, null); } SourceModel.FunctionTypeDeclaration inputFunctionTypeDecl = FunctionTypeDeclaration.make( inputFunctionComment, inputFunctionName, TypeSignature.make(inputFunctionType)); topLevelDefnsList.add(inputFunctionTypeDecl); // build up the function body SourceModel.Expr condition = Expr.Application.make( new Expr[]{Expr.Var.make(Name.Function.makeUnqualified(getDCOrdinalName)), inputFunctionArg}); SourceModel.Expr.Case.Alt caseAlts[] = new SourceModel.Expr.Case.Alt[typeConstructor.getNDataConstructors()]; for (int i = 0, n = caseAlts.length; i < n; ++i) { DataConstructor dc = typeConstructor.getNthDataConstructor(i); // Build up an application of the data constructor. SourceModel.Expr dcApplication = makeDCCall(dc, inputFunctionArg); caseAlts[i] = Expr.Case.Alt.UnpackInt.make( new BigInteger[]{BigInteger.valueOf(dc.getOrdinal())}, dcApplication); } SourceModel.Expr body = Expr.Case.make(condition, caseAlts); SourceModel.FunctionDefn.Algebraic inputFunction = FunctionDefn.Algebraic.make( inputFunctionName, Scope.PUBLIC, new SourceModel.Parameter[]{Parameter.make(inputFunctionArgName, false)}, body); topLevelDefnsList.add(inputFunction); // Create an inputCubeFromJObject function. String inputFromJObjectFunctionName = inputFunctionName + "FromJObject"; createInputFromJObjectFunction(inputFromJObjectFunctionName, inputFunctionName, topLevelDefnsList); // Create Inputable instance. SourceModel.InstanceDefn instanceDefn = InstanceDefn.make( Name.TypeClass.make(CAL_Prelude.TypeClasses.Inputable), InstanceDefn.InstanceTypeCons.TypeCons.make(Name.TypeCons.make(typeConstructorName), null), null, new InstanceDefn.InstanceMethod[]{ InstanceDefn.InstanceMethod.make(CAL_Prelude.Functions.input.getUnqualifiedName(), Name.Function.makeUnqualified(inputFromJObjectFunctionName)) }); topLevelDefnsList.add(instanceDefn); // Now we want to make the outputable instance. // Build up a function body that converts // from 'Cube' to 'JCube'. Expr.Var dcInstance = Expr.Var.makeUnqualified("dcInstance"); caseAlts = new SourceModel.Expr.Case.Alt[typeConstructor.getNDataConstructors()]; for (int i = 0, n = caseAlts.length; i < n; ++i) { DataConstructor dc = typeConstructor.getNthDataConstructor(i); SourceModel.Pattern patterns[] = new SourceModel.Pattern[dc.getArity()]; Arrays.fill(patterns, SourceModel.Pattern.Wildcard.make()); caseAlts[i] = Expr.Case.Alt.UnpackDataCons.make( Name.DataCons.make(dc.getName()), patterns, makeConstructorCall(dc, dcInstance, true)); } SourceModel.Expr.Case conversionFunctionBody = Expr.Case.make(dcInstance, caseAlts); makeOutputableInstance(typeConstructor, conversionFunctionBody, topLevelDefnsList); } private void makeOutputableInstance ( TypeConstructor typeConstructor, SourceModel.Expr outputFunctionBody, List<TopLevelSourceElement> topLevelDefnsList) { // Create an output function. // outputCube :: Cube -> JCube; // public outputCube dcInstance = // case dcInstanceOf // DC1 -> jDC1_new dcInstance.DC1.field1 (jObjectToJ... (output (dcInstance.DC1.field2))); // ... // // outputCubeToJObject :: Cube -> JObject; // private outputCubeToJObject dcInstance = // output (outputCube dcInstance); String outputFunctionName = "output" + typeConstructor.getName().getUnqualifiedName(); // Do the function type declaration. TypeExprDefn outputFunctionType = TypeExprDefn.Function.make( TypeExprDefn.TypeCons.make(Name.TypeCons.makeUnqualified(typeConstructorInfo.typeConstructor.getName().getUnqualifiedName())), TypeExprDefn.TypeCons.make(Name.TypeCons.makeUnqualified(typeConstructorInfo.calForeignTypeName))); SourceModel.CALDoc.Comment.Function outputFunctionComment; { SourceModel.CALDoc.TextSegment.Plain textSegment = SourceModel.CALDoc.TextSegment.Plain.make( "\nOutput an instance of " + typeConstructor.getName().getUnqualifiedName() + ".\n" + "Translates an instance of " + typeConstructor.getName().getUnqualifiedName() + " to\n" + "an instance of "+ typeConstructorInfo.calForeignTypeName + "."); SourceModel.CALDoc.TextBlock textBlock = SourceModel.CALDoc.TextBlock.make(new SourceModel.CALDoc.TextSegment.TopLevel[]{textSegment}); outputFunctionComment = SourceModel.CALDoc.Comment.Function.make( textBlock, null); } SourceModel.FunctionTypeDeclaration outputFunctionTypeDecl = FunctionTypeDeclaration.make( outputFunctionComment, outputFunctionName, TypeSignature.make(outputFunctionType)); topLevelDefnsList.add(outputFunctionTypeDecl); SourceModel.FunctionDefn.Algebraic outputFunction = FunctionDefn.Algebraic.make( outputFunctionName, Scope.PUBLIC, new SourceModel.Parameter[]{Parameter.make("dcInstance", true)}, outputFunctionBody); topLevelDefnsList.add(outputFunction); // Now create the conversion function. i.e. a private function that outputs to a JObject. String conversionFunctionName = outputFunctionName + "ToJObject"; // Do the function type declaration. TypeExprDefn conversionFunctionType = TypeExprDefn.Function.make( TypeExprDefn.TypeCons.make(Name.TypeCons.makeUnqualified(typeConstructorInfo.typeConstructor.getName().getUnqualifiedName())), JOBJECT_TYPE_EXPR_DEFN); SourceModel.CALDoc.Comment.Function conversionFunctionComment; { SourceModel.CALDoc.TextSegment.Plain textSegment = SourceModel.CALDoc.TextSegment.Plain.make( "\nOutput an instance of " + typeConstructor.getName().getUnqualifiedName() + ".\n" + "Translates an instance of " + typeConstructor.getName().getUnqualifiedName() + " to\n" + "an instance of JObject."); SourceModel.CALDoc.TextBlock textBlock = SourceModel.CALDoc.TextBlock.make(new SourceModel.CALDoc.TextSegment.TopLevel[]{textSegment}); conversionFunctionComment = SourceModel.CALDoc.Comment.Function.make( textBlock, null); } SourceModel.FunctionTypeDeclaration conversionFunctionTypeDecl = FunctionTypeDeclaration.make( conversionFunctionComment, conversionFunctionName, TypeSignature.make(conversionFunctionType)); topLevelDefnsList.add(conversionFunctionTypeDecl); SourceModel.Expr conversionFunctionBody = SourceModel.Expr.Application.make( new SourceModel.Expr[]{ SourceModel.Expr.Var.make(Name.Function.makeUnqualified(outputFunctionName)), SourceModel.Expr.Var.make(Name.Function.makeUnqualified("dcInstance")) }); conversionFunctionBody = SourceModel.Expr.Application.make( new SourceModel.Expr[]{ SourceModel.Expr.Var.make(Name.Function.make(CAL_Prelude.Functions.output)), conversionFunctionBody }); // Now do the body of the conversion function. SourceModel.FunctionDefn.Algebraic conversionFunction = FunctionDefn.Algebraic.make( conversionFunctionName, Scope.PRIVATE, new SourceModel.Parameter[]{Parameter.make("dcInstance", true)}, conversionFunctionBody); topLevelDefnsList.add(conversionFunction); // Create Outputable instance. SourceModel.InstanceDefn outputInstanceDefn = InstanceDefn.make( Name.TypeClass.make(CAL_Prelude.TypeClasses.Outputable), InstanceDefn.InstanceTypeCons.TypeCons.make(Name.TypeCons.make(typeConstructorInfo.typeConstructor.getName()), null), null, new InstanceDefn.InstanceMethod[]{ InstanceDefn.InstanceMethod.make(CAL_Prelude.Functions.output.getUnqualifiedName(), Name.Function.makeUnqualified(conversionFunctionName)) }); topLevelDefnsList.add(outputInstanceDefn); } private SourceModel.Expr makeConstructorCall ( DataConstructor dc, SourceModel.Expr.Var dcVar, boolean isInnerClass) throws UnableToResolveForeignEntityException { SourceModel.Name.DataCons dataConsSourceModelName = Name.DataCons.make(dc.getName()); String javaConstructorName = fixupClassName(dc.getName().getUnqualifiedName()); String calConstructorName = "j" + javaConstructorName + "_new"; TypeExpr[] dcFieldTypeExprs = getFieldTypesForDC(dc); JavaConstructor javaConstructor = null; if (isInnerClass) { for (int i = 0, n = javaClass.getNInnerClasses(); i < n; ++i) { JavaClassRep innerClass = javaClass.getInnerClass(i); if (innerClass.getClassName().getUnqualifiedJavaSourceName().endsWith("." + javaConstructorName)) { javaConstructor = innerClass.getConstructor(0); break; } } } else { javaConstructor = javaClass.getConstructor(0); } if (javaConstructor == null) { throw new NullPointerException ("Unable to find Java constructor for data constructor " + dc.getName()); } int nArgs = javaConstructor.getNParams(); if (nArgs == 0) { return SourceModel.Expr.Var.makeUnqualified(calConstructorName); } TypeExprDefn argTypes[] = new TypeExprDefn[nArgs]; for (int i = 0; i < nArgs; ++i) { argTypes[i] = getTypeExprDefn(new JavaTypeName[]{javaConstructor.getParamType(i)}); } SourceModel.Expr applicationExpressions[] = new SourceModel.Expr[nArgs+ 1]; SourceModel.Expr.Var output = Expr.Var.make(CAL_Prelude.Functions.output); applicationExpressions[0] = SourceModel.Expr.Var.makeUnqualified(calConstructorName); Expr.Var unsafeCoerce = Expr.Var.make(CAL_Prelude.Functions.unsafeCoerce); for (int i = 0, n = dc.getArity(); i < n; ++i) { FieldName fn = dc.getNthFieldName(i); SourceModel.Expr argValue = Expr.SelectDataConsField.make(dcVar, dataConsSourceModelName, SourceModel.Name.Field.make(fn)); // If the type of the dc field matches the type of the constructor argument // we can simply use the field accessor. Otherwise we need to apply output to // resulting value. TypeExpr fieldTypeExpr = dcFieldTypeExprs[i]; updateInputableImports(fieldTypeExpr); String dcFieldTypeConsName = ""; TypeConsApp typeConsApp = fieldTypeExpr.rootTypeConsApp(); if (typeConsApp != null) { TypeConstructor tc = typeConsApp.getRoot(); dcFieldTypeConsName = tc.getName().toString(); } String constructorArgTypeConsName = ((TypeExprDefn.TypeCons)argTypes[i]).getTypeConsName().toString(); if (!constructorArgTypeConsName.equals(dcFieldTypeConsName)) { // We don't need to apply 'output' if the result type of the field accessor is CALValue if (!constructorArgTypeConsName.equals(CAL_Prelude.TypeConstructors.CalValue.getQualifiedName())) { argValue = Expr.Application.make(new Expr[]{output, argValue}); // We want to cast the result String castFunctionName = makeCastFunctionFromJObject(((TypeExprDefn.TypeCons)argTypes[i]).getTypeConsName().getUnqualifiedName()); argValue = Expr.Application.make(new Expr[]{Expr.Var.make(Name.Function.makeUnqualified(castFunctionName)), argValue}); } else { argValue = Expr.Application.make(new Expr[]{unsafeCoerce, argValue}); } } applicationExpressions[i+1] = argValue; } return SourceModel.Expr.Application.make(applicationExpressions); } private SourceModel.Expr makeDCCall ( DataConstructor dc, Expr.Var argCastToTopLevelJType ) throws UnableToResolveForeignEntityException { // We want to generate something along the lines of: // let // jInnerClassName = jObjectToJInnerClassName jObject; // in // DataConstructorName // input field 1... DataConstructorInfo dcInfo = (DataConstructorInfo)typeConstructorInfo.dataConstructorInfo.get(dc); // Build up the values for each field in the data constructor. SourceModel.Expr dcApplicationExpressions[] = new SourceModel.Expr[dc.getArity() + 1]; QualifiedName makeFunctionName = findMakeDCName(dc); if (makeFunctionName != null) { dcApplicationExpressions[0] = SourceModel.Expr.Var.make (Name.Function.make(makeFunctionName)); logMessage(Level.INFO, "Using function " + makeFunctionName.getUnqualifiedName() + " instead of data constructor " + dc.getName().getUnqualifiedName()); } else { dcApplicationExpressions[0] = SourceModel.Expr.DataCons.make(dc.getName()); } TypeExpr[] dcFieldTypes = getFieldTypesForDC(dc); SourceModel.Expr.Var input = Expr.Var.make(CAL_Prelude.Functions.input); Expr unsafeCoerce = Expr.Var.make(Name.Function.make(CAL_Prelude.Functions.unsafeCoerce)); // We need a cast function from JObject to the foreign inner class if there are fields // in the inner class. SourceModel.LocalDefn localDefns[] = null; Expr.Var argCastToInnerJType = null; if (dcInfo.containsFields) { // We want to create a let var that is the cast of the // JObject parameter to the J... foreign type. String letVarName = "j" + dcInfo.calForeignTypeName.substring(1); localDefns = makeOuterForeignTypeToInnerForeignTypeLetVar( argCastToTopLevelJType.getVarName().getUnqualifiedName(), typeConstructorInfo.calForeignTypeName, letVarName, dcInfo.calForeignTypeName); argCastToInnerJType = Expr.Var.makeUnqualified(letVarName); } for (int i = 0, n = dc.getArity(); i < n; ++i) { FieldName fn = dc.getNthFieldName(i); JavaTypeName fieldType = (JavaTypeName)dcInfo.fieldTypeNames.get(fn); String fieldAccessorName = typeConstructorInfo.calFieldAccessorFunctionNames.get(fn).get(dc); // If the type of the retrieved field matches the type of the dc field // we can simply use the field accessor. Otherwise we need to apply input to // resulting value. SourceModel.Expr.Var accessor = Expr.Var.make(Name.Function.makeUnqualified(fieldAccessorName)); SourceModel.Expr accessApplication; if (typeConstructorInfo.commonFieldNames.contains(fn)) { accessApplication = Expr.Application.make(new SourceModel.Expr[]{accessor, argCastToTopLevelJType}); } else { accessApplication = Expr.Application.make(new SourceModel.Expr[]{accessor, argCastToInnerJType}); } TypeExpr fieldTypeExpr = dcFieldTypes[i]; updateInputableImports(fieldTypeExpr); TypeExprDefn fieldTypeForGetter = getTypeExprDefn(new JavaTypeName[]{fieldType}); String fieldTypeForGetterTypeConsName = ((TypeExprDefn.TypeCons)fieldTypeForGetter).getTypeConsName().toString(); String dcFieldTypeConsName = ""; TypeConsApp typeConsApp = fieldTypeExpr.rootTypeConsApp(); if (typeConsApp != null) { TypeConstructor tc = typeConsApp.getRoot(); dcFieldTypeConsName = tc.getName().toString(); } if (!fieldTypeForGetterTypeConsName.equals(dcFieldTypeConsName)) { // apply input and usafe coerce. // We don't need to apply 'input' if the result type of the field accessor is CALValue if (!fieldTypeForGetterTypeConsName.equals(CAL_Prelude.TypeConstructors.CalValue.getQualifiedName())) { String castFunctionName = makeCastFunctionToJobject(((TypeExprDefn.TypeCons)fieldTypeForGetter).getTypeConsName().getUnqualifiedName()); accessApplication = Expr.Application.make(new Expr[]{input, Expr.Application.make(new Expr[]{Expr.Var.make(Name.Function.makeUnqualified(castFunctionName)), accessApplication})}); } else { // Inputing to a function type. Use unsafeCoerce. accessApplication = Expr.Application.make(new Expr[]{unsafeCoerce, accessApplication}); } } accessApplication = Expr.Application.make(new Expr[]{Expr.Var.make(Name.Function.make(CAL_Prelude.Functions.eager)), accessApplication}); dcApplicationExpressions[i+1] = accessApplication; } // Now apply the data constructor to each of the arguments. SourceModel.Expr dcApplication; if (dcApplicationExpressions.length >= 2) { dcApplication = Expr.Application.make(dcApplicationExpressions); } else { dcApplication = dcApplicationExpressions[0]; } if (localDefns != null) { dcApplication = SourceModel.Expr.Let.make(localDefns, dcApplication); } return dcApplication; } private SourceModel.FunctionDefn importJavaConstructor (JavaConstructor constructor, boolean innerClass) throws UnableToResolveForeignEntityException { int nArgs = constructor.getNParams(); JavaTypeName argTypes[] = new JavaTypeName[nArgs + 1]; for (int i = 0; i < nArgs; ++i) { argTypes[i] = constructor.getParamType(i); } argTypes[nArgs] = javaClass.getClassName(); TypeExprDefn argTypeDef = getTypeExprDefn(argTypes); TypeSignature declaredType = TypeSignature.make(argTypeDef); SourceModel.CALDoc.Comment.Function constructorFunctionComment; { SourceModel.CALDoc.TextSegment.Plain textSegment = SourceModel.CALDoc.TextSegment.Plain.make( "Constructor for " + constructor.getConstructorName() + "."); SourceModel.CALDoc.TextBlock textBlock = SourceModel.CALDoc.TextBlock.make(new SourceModel.CALDoc.TextSegment.TopLevel[]{textSegment}); constructorFunctionComment = SourceModel.CALDoc.Comment.Function.make( textBlock, null); } SourceModel.FunctionDefn jConstructor = FunctionDefn.Foreign.make( constructorFunctionComment, "j" + constructor.getConstructorName() + "_new", Scope.PUBLIC, true, "constructor " + javaClass.getClassName().getFullJavaSourceName() + (innerClass ? "$" + constructor.getConstructorName() : ""), declaredType); return jConstructor; } private String makeCastFunctionToJobject (String foreignTypeName) { String castFunctionName = Character.toLowerCase(foreignTypeName.charAt(0)) + foreignTypeName.substring(1) + "ToJObject"; if (castFunctionNameToTypeSignature.get(castFunctionName) == null) { SourceModel.TypeSignature castFunctionTypeSignature = TypeSignature.make( TypeExprDefn.Function.make( TypeExprDefn.TypeCons.make( Name.TypeCons.makeUnqualified(foreignTypeName)), JOBJECT_TYPE_EXPR_DEFN)); castFunctionNameToTypeSignature.put(castFunctionName, castFunctionTypeSignature); } return castFunctionName; } private String makeCastFunction (String fromForeignTypeName, String toForeignTypeName) { String castFunctionName = "cast" + fromForeignTypeName + "To" + toForeignTypeName; if (castFunctionNameToTypeSignature.get(castFunctionName) == null) { SourceModel.TypeSignature castFunctionTypeSignature = TypeSignature.make( TypeExprDefn.Function.make( TypeExprDefn.TypeCons.make( Name.TypeCons.makeUnqualified(fromForeignTypeName)), TypeExprDefn.TypeCons.make( Name.TypeCons.makeUnqualified(toForeignTypeName)))); castFunctionNameToTypeSignature.put(castFunctionName, castFunctionTypeSignature); } return castFunctionName; } private String makeCastFunctionFromJObject (String foreignTypeName) { String castFunctionName = "jObjectTo" + foreignTypeName; if (castFunctionNameToTypeSignature.get(castFunctionName) == null) { SourceModel.TypeSignature castFunctionTypeSignature = TypeSignature.make( TypeExprDefn.Function.make( JOBJECT_TYPE_EXPR_DEFN, TypeExprDefn.TypeCons.make( Name.TypeCons.makeUnqualified(foreignTypeName)))); castFunctionNameToTypeSignature.put(castFunctionName, castFunctionTypeSignature); } return castFunctionName; } /** * Create a LocalDefn for a let var cast from an existing var. * @param outerTypeVarName * @param outerForeignTypeName * @param letVarName * @param innerForeignTypeName * @return the local definition */ private SourceModel.LocalDefn[] makeOuterForeignTypeToInnerForeignTypeLetVar ( String outerTypeVarName, String outerForeignTypeName, String letVarName, String innerForeignTypeName) { SourceModel.LocalDefn[] localDefns = new SourceModel.LocalDefn[2]; TypeExprDefn foreignTypeExprDefn = TypeExprDefn.TypeCons.make(Name.TypeCons.makeUnqualified(innerForeignTypeName)); SourceModel.CALDoc.TextSegment.Plain textSegment = SourceModel.CALDoc.TextSegment.Plain.make( "Cast the " + outerForeignTypeName + " value to a " + innerForeignTypeName); SourceModel.CALDoc.TextBlock description = SourceModel.CALDoc.TextBlock.make(new SourceModel.CALDoc.TextSegment.TopLevel[]{textSegment}); CALDoc.Comment.Function caldocComment = CALDoc.Comment.Function.make( description, null); SourceModel.LocalDefn.Function.TypeDeclaration localTypeDecl = LocalDefn.Function.TypeDeclaration.make( caldocComment, letVarName, TypeSignature.make(foreignTypeExprDefn)); String castFunctionName = makeCastFunction(outerForeignTypeName, innerForeignTypeName); Expr letVarDef = Expr.Application.make( new Expr[]{ Expr.Var.make(Name.Function.makeUnqualified(castFunctionName)), Expr.Var.makeUnqualified(outerTypeVarName)}); letVarDef = Expr.Application.make( new Expr[]{ Expr.Var.make(CAL_Prelude.Functions.eager), letVarDef}); SourceModel.LocalDefn.Function.Definition localDefnFctn = LocalDefn.Function.Definition.make(letVarName, null, letVarDef); localDefns[0] = localTypeDecl; localDefns[1] = localDefnFctn; return localDefns; } /** * Build up function type. a -> b -> c ... * @param javaTypes * @return a TypeExprDefn */ private SourceModel.TypeExprDefn getTypeExprDefn (JavaTypeName[] javaTypes) throws UnableToResolveForeignEntityException { // First try to find a type constructor for each java type; TypeConstructor typeConstructors[] = new TypeConstructor[javaTypes.length]; findTypeConstructorForJavaClass(javaTypes, typeConstructors); Name.TypeCons typeConstructorNames[] = new Name.TypeCons[javaTypes.length]; for (int i = 0, n = javaTypes.length; i < n; ++i) { if (typeConstructors[i] != null) { if (typeConstructors[i].getScope().equals(Scope.PUBLIC)) { typeConstructorNames[i] = Name.TypeCons.make(typeConstructors[i].getName()); } else { // Need to reproduce this foreign type declaration // in this module. additionalForeignTypeDeclarations.put(typeConstructors[i].getName(), typeConstructors[i]); typeConstructorNames[i] = Name.TypeCons.makeUnqualified(typeConstructors[i].getName().getUnqualifiedName()); } } else if (javaTypes[i].equals(JavaTypeName.BOOLEAN)){ typeConstructorNames[i] = Name.TypeCons.make(CAL_Prelude.TypeConstructors.Boolean); } else { // Couldn't find a type constructor for the Java class. // Build up a Name.TypeCons using our naming pattern. // try to find a module containing the base CAL type. String baseCALTypeName = javaTypes[i].getUnqualifiedJavaSourceName(); if (baseCALTypeName.endsWith("_")) { baseCALTypeName = baseCALTypeName.substring(0, baseCALTypeName.length()-1); } ModuleName moduleName = findModuleContainingType(baseCALTypeName); if (moduleName == null) { typeConstructorNames[i] = Name.TypeCons.makeUnqualified("J" + javaTypes[i].getUnqualifiedJavaSourceName()); } else { moduleName = ModuleName.make(moduleName.toString() + "_JavaIO"); typeConstructorNames[i] = Name.TypeCons.make(moduleName, "J" + javaTypes[i].getUnqualifiedJavaSourceName()); } } } TypeExprDefn codomain = TypeExprDefn.TypeCons.make(typeConstructorNames[typeConstructorNames.length - 1]); for (int i = typeConstructorNames.length - 2; i >= 0; i--) { TypeExprDefn domain = TypeExprDefn.TypeCons.make(typeConstructorNames[i]); codomain = TypeExprDefn.Function.make(domain, codomain); } return codomain; } private final void updateInputableImports (TypeExpr typeExpr) { TypeConsApp tca = typeExpr.rootTypeConsApp(); if (tca != null) { for (int i = 0, n = tca.getNArgs(); i < n; ++i) { updateInputableImports(tca.getArg(i)); } } if (typeExpr.isListType()) { if (typeExpr.rootTypeConsApp() == null) { ((TypeApp)typeExpr).getOperandType(); } } else if (typeExpr.isTupleType()) { RecordType rt = (RecordType)typeExpr; Map<FieldName, TypeExpr> hasFieldsMap = rt.getHasFieldsMap(); for (Iterator<TypeExpr> it = hasFieldsMap.values().iterator(); it.hasNext();) { updateInputableImports(it.next()); } } else if (!typeExpr.isFunctionType()) { if (tca != null) { TypeConstructor tc = tca.getRoot(); // Ignore functions ModuleTypeInfo m = findModuleWithClassInstance(CAL_Prelude.TypeClasses.Inputable, tc.getName()); if (m != null) { inputableImports.add(m.getModuleName()); } else if (!tc.getName().getModuleName().equals(module.getModuleName())){ // Create a module name based on our naming. ModuleName mn = ModuleName.make( tc.getName().getModuleName().toString() + "_JavaIO"); inputableImports.add(mn); } } } } private final ModuleTypeInfo findModuleWithClassInstance (QualifiedName className, QualifiedName typeConstructorName) { return findModuleWithClassInstance (className, typeConstructorName, module, new LinkedHashSet<ModuleTypeInfo>()); } private final ModuleTypeInfo findModuleWithClassInstance (QualifiedName className, QualifiedName typeConstructorName, ModuleTypeInfo module, Set<ModuleTypeInfo> visitedModules) { if (visitedModules.contains(module)) { return null; } visitedModules.add(module); for (int i = 0, n = module.getNClassInstances(); i < n; ++i) { ClassInstance ci = module.getNthClassInstance(i); if (ci.getTypeClass().getName().equals(className)) { // Check the instance type TypeExpr instanceTypeExpr = ci.getType(); TypeConsApp tca = instanceTypeExpr.rootTypeConsApp(); if (tca != null) { if (tca.getRoot().getName().equals(typeConstructorName)) { return module; } } } } for (int i = 0, n = module.getNImportedModules(); i < n; ++i) { ModuleTypeInfo mti = module.getNthImportedModule(i); ModuleTypeInfo ci = findModuleWithClassInstance(className, typeConstructorName, mti, visitedModules); if (ci != null) { return ci; } } return null; } private final ModuleName findModuleContainingType (String typeName) { return findModuleContainingType (new LinkedHashSet<ModuleName>(), typeName, module); } private final ModuleName findModuleContainingType(Set<ModuleName> visitedModules, String typeName, ModuleTypeInfo module) { if (visitedModules.contains(module.getModuleName())) { return null; } visitedModules.add(module.getModuleName()); for (int i = 0, n = module.getNTypeConstructors(); i < n; ++i) { if (module.getNthTypeConstructor(i).getName().getUnqualifiedName().equals(typeName)) { return module.getModuleName(); } } for (int i = 0, n = module.getNImportedModules(); i < n; ++i) { ModuleTypeInfo importedModule = module.getNthImportedModule(i); ModuleName mn = findModuleContainingType(visitedModules, typeName, importedModule); if (mn != null) { return mn; } } return null; } /** * Try to find a CAL type constructor corresponding to the named java type. * @param javaTypes * @param typeConstructors */ private void findTypeConstructorForJavaClass (JavaTypeName[] javaTypes, TypeConstructor[] typeConstructors) throws UnableToResolveForeignEntityException { findTypeConstructorForJavaClass (javaTypes, typeConstructors, module, new LinkedHashSet<ModuleTypeInfo>()); } /** * Try to find a CAL type constructor corresponding to the named java type. * @param javaTypes * @param typeConstructors * @param module * @param visitedModules */ private void findTypeConstructorForJavaClass (JavaTypeName[] javaTypes, TypeConstructor[] typeConstructors, ModuleTypeInfo module, Set<ModuleTypeInfo> visitedModules) throws UnableToResolveForeignEntityException { if (visitedModules.contains(module)) { return; } visitedModules.add(module); for (int i = 0, n = module.getNTypeConstructors(); i < n; ++i) { TypeConstructor tc = module.getNthTypeConstructor(i); ForeignTypeInfo fti = tc.getForeignTypeInfo(); if (fti == null) { continue; } JavaTypeName foreignType = JavaTypeName.make(fti.getForeignType()); for (int j = 0, k = javaTypes.length; j < k; ++j) { if (typeConstructors[j] != null) { continue; } if (foreignType.equals(javaTypes[j])) { typeConstructors[j] = tc; } } } for (int i = 0, n = module.getNImportedModules(); i < n; ++i) { ModuleTypeInfo importedModule = module.getNthImportedModule(i); findTypeConstructorForJavaClass(javaTypes, typeConstructors, importedModule, visitedModules); } } private void makeForeignClassDeclaration (List<TopLevelSourceElement> topLevelDefnsList) { // Declare the java class as a foreign type. // ex. data foreign unsafe import jvm "org.olap.CubeType" public JCubeType; SourceModel.CALDoc.Comment.TypeCons foreignTypeComment; { SourceModel.CALDoc.TextSegment.Plain textSegment = SourceModel.CALDoc.TextSegment.Plain.make( "\nForeign type for " + javaClass.getClassName().getFullJavaSourceName() + ".\n" + typeConstructor.getName() + " outputs to and inputs from this type.\n"); SourceModel.CALDoc.TextBlock textBlock = SourceModel.CALDoc.TextBlock.make(new SourceModel.CALDoc.TextSegment.TopLevel[]{textSegment}); foreignTypeComment = SourceModel.CALDoc.Comment.TypeCons.make( textBlock, null); } SourceModel.TypeConstructorDefn.ForeignType foreignType = TypeConstructorDefn.ForeignType.make( foreignTypeComment, typeConstructorInfo.calForeignTypeName, Scope.PUBLIC, true, javaClass.getClassName().getFullJavaSourceName(), Scope.PUBLIC, true, new Name.TypeClass[]{Name.TypeClass.make(CAL_Prelude.TypeClasses.Inputable), Name.TypeClass.make(CAL_Prelude.TypeClasses.Outputable)}); topLevelDefnsList.add (foreignType); } private void makeForeignClassDeclaration (DataConstructor dc, List<TopLevelSourceElement> topLevelDefnsList) { DataConstructorInfo dcInfo = (DataConstructorInfo)typeConstructorInfo.dataConstructorInfo.get(dc); SourceModel.CALDoc.Comment.TypeCons foreignTypeComment; { SourceModel.CALDoc.TextSegment.Plain textSegment = SourceModel.CALDoc.TextSegment.Plain.make( "\nForeign type for " + javaClass.getClassName().getFullJavaSourceName() + "." + dcInfo.innerClassName + ".\n" + "This Java class corresponds to the " + dc.getName().getUnqualifiedName() + " data constructor.\n"); SourceModel.CALDoc.TextBlock textBlock = SourceModel.CALDoc.TextBlock.make(new SourceModel.CALDoc.TextSegment.TopLevel[]{textSegment}); foreignTypeComment = SourceModel.CALDoc.Comment.TypeCons.make( textBlock, null); } // Declare the java class as a foreign type. // ex. data foreign unsafe import jvm "org.olap.CubeType" public JCubeType; SourceModel.TypeConstructorDefn.ForeignType foreignType = TypeConstructorDefn.ForeignType.make( foreignTypeComment, dcInfo.calForeignTypeName, Scope.PUBLIC, true, javaClass.getClassName().getFullJavaSourceName() + "$" + dcInfo.innerClassName, Scope.PUBLIC, true, new Name.TypeClass[]{Name.TypeClass.make(CAL_Prelude.TypeClasses.Inputable), Name.TypeClass.make(CAL_Prelude.TypeClasses.Outputable)}); topLevelDefnsList.add (foreignType); } private void importGetDCOrdinal (List<TopLevelSourceElement> topLevelDefnsList) { // Bring in the 'getDCOrdinal()' method of the Java class. SourceModel.TypeSignature getDCOrdinalTypeSignature = TypeSignature.make( TypeExprDefn.Function.make( TypeExprDefn.TypeCons.make(Name.TypeCons.makeUnqualified(typeConstructorInfo.calForeignTypeName)), INT_TYPE_EXPR_DEFN)); SourceModel.CALDoc.Comment.Function getDCOrdinalComment; { SourceModel.CALDoc.TextSegment.Plain textSegment = SourceModel.CALDoc.TextSegment.Plain.make( "\nRetrieve the ordinal value from an instance of " + typeConstructorInfo.calForeignTypeName + ".\n" + "The ordinal can be used to determine which data constructor the " + typeConstructorInfo.calForeignTypeName + "\n" + "instance corresponds to."); SourceModel.CALDoc.TextBlock textBlock = SourceModel.CALDoc.TextBlock.make(new SourceModel.CALDoc.TextSegment.TopLevel[]{textSegment}); getDCOrdinalComment = SourceModel.CALDoc.Comment.Function.make( textBlock, null); } SourceModel.FunctionDefn getDCOrdinal = FunctionDefn.Foreign.make( getDCOrdinalComment, "j" + typeConstructorInfo.calForeignTypeName.substring(1) + "_getDCOrdinal", Scope.PRIVATE, true, "method getDCOrdinal", getDCOrdinalTypeSignature); topLevelDefnsList.add(getDCOrdinal); } private void generateEnumerationIO (List<TopLevelSourceElement> topLevelDefnsList) { // Declare the java class as a foreign type. // ex. data foreign unsafe import jvm "org.olap.CubeType" public JCubeType; makeForeignClassDeclaration(topLevelDefnsList); TypeExprDefn.TypeCons jClassTypeExpr = TypeExprDefn.TypeCons.make(Name.TypeCons.makeUnqualified(typeConstructorInfo.calForeignTypeName)); // String -> String; Map<String, String> enumFieldFunctionNames = new LinkedHashMap<String, String>(); // Now we need to bring in each instance of the enumeration class. // These will be public static fields of the class. for (int i = 0, n = typeConstructor.getNDataConstructors(); i < n; ++i) { DataConstructor dc = typeConstructor.getNthDataConstructor(i); String enumFieldName = createEnumFieldName(dc.getName().getUnqualifiedName()); // Example: // foreign unsafe import jvm "static field org.olap.CubeType.TYPE" // public jCubeType_TYPE :: JCubeType; SourceModel.TypeSignature typeSignature = TypeSignature.make(jClassTypeExpr); String functionName = "j" + javaClass.getClassName().getUnqualifiedJavaSourceName() + "_" + enumFieldName; SourceModel.CALDoc.Comment.Function foreignFieldComment; { SourceModel.CALDoc.TextSegment.Plain textSegment = SourceModel.CALDoc.TextSegment.Plain.make( "\nThe Java static field " + javaClass.getClassName().getUnqualifiedJavaSourceName() + "." + enumFieldName + ".\n" + "This field is used to represent instances of the data constructor " + dc.getName().getUnqualifiedName() + ".\n"); SourceModel.CALDoc.TextBlock textBlock = SourceModel.CALDoc.TextBlock.make(new SourceModel.CALDoc.TextSegment.TopLevel[]{textSegment}); foreignFieldComment = SourceModel.CALDoc.Comment.Function.make( textBlock, null); } SourceModel.FunctionDefn enumInstance = FunctionDefn.Foreign.make( foreignFieldComment, functionName, Scope.PRIVATE, true, "static field " + javaClass.getClassName().getFullJavaSourceName() + "." + enumFieldName, typeSignature); topLevelDefnsList.add (enumInstance); enumFieldFunctionNames.put(dc.getName().getUnqualifiedName(), functionName); } // Bring in the getDCOrdinal() method. importGetDCOrdinal(topLevelDefnsList); // Create an function that converts a JCubeType_ to a CubeType // inputCubeType :: JCubeType_ -> CubeType; // public inputCubeType jCubeType_ = // case (jCubeType_getDCOrdinal jCubeType_) of // 0 -> ...; // 1 -> ...; String inputArgumentName = "j" + javaClass.getClassName().getUnqualifiedJavaSourceName(); String inputFunctionName = "input" + typeConstructor.getName().getUnqualifiedName(); Expr.Var inputArgument = Expr.Var.make(Name.Function.makeUnqualified(inputArgumentName)); SourceModel.Expr condition = Expr.Application.make( new Expr[]{Expr.Var.make(Name.Function.makeUnqualified("j" + javaClass.getClassName().getUnqualifiedJavaSourceName() + "_getDCOrdinal")), inputArgument}); SourceModel.Expr.Case.Alt caseAlts[] = new SourceModel.Expr.Case.Alt[typeConstructor.getNDataConstructors()]; for (int i = 0, n = caseAlts.length; i < n; ++i) { DataConstructor dc = typeConstructor.getNthDataConstructor(i); caseAlts[i] = Expr.Case.Alt.UnpackInt.make( new BigInteger[]{BigInteger.valueOf(dc.getOrdinal())}, Expr.DataCons.make(Name.DataCons.make(dc.getName()))); } SourceModel.Expr.Case body = Expr.Case.make(condition, caseAlts); SourceModel.FunctionDefn.Algebraic inputFunction = FunctionDefn.Algebraic.make( inputFunctionName, Scope.PUBLIC, new SourceModel.Parameter[]{Parameter.make(inputArgumentName, false)}, body); TypeExprDefn inputFunctionType = TypeExprDefn.Function.make( TypeExprDefn.TypeCons.make(Name.TypeCons.makeUnqualified(typeConstructorInfo.calForeignTypeName)), TypeExprDefn.TypeCons.make(Name.TypeCons.makeUnqualified(typeConstructor.getName().getUnqualifiedName()))); SourceModel.CALDoc.Comment.Function inputFunctionComment; { SourceModel.CALDoc.TextSegment.Plain textSegment = SourceModel.CALDoc.TextSegment.Plain.make( "\nInput an instance of " + typeConstructor.getName().getUnqualifiedName() + ".\n" + "Translates an instance of " + typeConstructorInfo.calForeignTypeName + " to\n" + "an instance of "+ typeConstructor.getName().getUnqualifiedName() + "."); SourceModel.CALDoc.TextBlock textBlock = SourceModel.CALDoc.TextBlock.make(new SourceModel.CALDoc.TextSegment.TopLevel[]{textSegment}); inputFunctionComment = SourceModel.CALDoc.Comment.Function.make( textBlock, null); } SourceModel.FunctionTypeDeclaration inputFunctionTypeDecl = FunctionTypeDeclaration.make( inputFunctionComment, inputFunctionName, TypeSignature.make(inputFunctionType)); topLevelDefnsList.add(inputFunctionTypeDecl); topLevelDefnsList.add(inputFunction); // Create a function which creates a CubeType from a JObject. String inputFromJObjectFunctionName = inputFunctionName + "FromJObject"; createInputFromJObjectFunction(inputFromJObjectFunctionName, inputFunctionName, topLevelDefnsList); // Now we need to declare an instance of inputable. // instance Inputable CubeType where // input = jObjectToCubeType; // ; SourceModel.InstanceDefn instanceDefn = InstanceDefn.make( Name.TypeClass.make(CAL_Prelude.TypeClasses.Inputable), InstanceDefn.InstanceTypeCons.TypeCons.make(Name.TypeCons.make(typeConstructor.getName()), null), null, new InstanceDefn.InstanceMethod[]{ InstanceDefn.InstanceMethod.make(CAL_Prelude.Functions.input.getUnqualifiedName(), Name.Function.makeUnqualified(inputFromJObjectFunctionName)) }); topLevelDefnsList.add(instanceDefn); // Create a conversion function for CAL type to corresponding foreign type. // cubeTypeToJCubeType :: CubeType -> JCubeType_; // cubeTypeToJCubeType dcInstance = // case dcInstance of // DC1 -> unsafeCoerce jCubeType_DC1; // ... // ; condition = Expr.Var.makeUnqualified("dcInstance"); caseAlts = new SourceModel.Expr.Case.Alt[typeConstructor.getNDataConstructors()]; for (int i = 0, n = caseAlts.length; i < n; ++i) { DataConstructor dc = typeConstructor.getNthDataConstructor(i); Expr enumFieldFunction = Expr.Var.make( Name.Function.makeUnqualified((String)enumFieldFunctionNames.get(dc.getName().getUnqualifiedName()))); caseAlts[i] = Expr.Case.Alt.UnpackDataCons.make( Name.DataCons.makeUnqualified(dc.getName().getUnqualifiedName()), enumFieldFunction); } body = Expr.Case.make(condition, caseAlts); makeOutputableInstance(typeConstructor, body, topLevelDefnsList); } /** * @return the inputableImports */ private Set<ModuleName> getInputableImports() { return inputableImports; } /** * Create a wrapper function around the input... function. * The wrapper function will take a JObject as argument rather than * the foreign type corresponding to the CAL type. * This new function will be used for the Inputable class method input. * @param inputFromJObjectFunctionName * @param inputFunctionName * @param topLevelDefnsList */ private void createInputFromJObjectFunction ( String inputFromJObjectFunctionName, String inputFunctionName, List<TopLevelSourceElement> topLevelDefnsList) { // Now create a function that inputs from a JObject. // inputCubeTypeFromJObject :: JObject -> CubeType; // inputCubeTypeFromJObject jObject = inputCubeType (input jObject); String inputArgumentName = "jObject"; Expr.Var inputArgument = Expr.Var.make(Name.Function.makeUnqualified(inputArgumentName)); SourceModel.Expr body = CAL_Prelude.Functions.input(inputArgument); body = Expr.Application.make( new Expr[]{Expr.Var.make(Name.Function.makeUnqualified(inputFunctionName)), body}); SourceModel.FunctionDefn.Algebraic inputFunction = FunctionDefn.Algebraic.make( inputFromJObjectFunctionName, Scope.PRIVATE, new SourceModel.Parameter[]{Parameter.make("jObject", false)}, body); TypeExprDefn inputFunctionType = TypeExprDefn.Function.make( JOBJECT_TYPE_EXPR_DEFN, TypeExprDefn.TypeCons.make(Name.TypeCons.makeUnqualified(typeConstructor.getName().getUnqualifiedName()))); SourceModel.CALDoc.Comment.Function inputFunctionComment; { SourceModel.CALDoc.TextSegment.Plain textSegment = SourceModel.CALDoc.TextSegment.Plain.make( "\nInput an instance of " + typeConstructor.getName().getUnqualifiedName() + ".\n" + "Translates an instance of JObject to\n" + "an instance of "+ typeConstructor.getName().getUnqualifiedName() + "."); SourceModel.CALDoc.TextBlock textBlock = SourceModel.CALDoc.TextBlock.make(new SourceModel.CALDoc.TextSegment.TopLevel[]{textSegment}); inputFunctionComment = SourceModel.CALDoc.Comment.Function.make( textBlock, null); } SourceModel.FunctionTypeDeclaration inputFunctionTypeDecl = FunctionTypeDeclaration.make( inputFunctionComment, inputFromJObjectFunctionName, TypeSignature.make(inputFunctionType)); topLevelDefnsList.add(inputFunctionTypeDecl); topLevelDefnsList.add(inputFunction); } } private final class JavaDataClassGenerator { private final String targetPackage; private final TypeConstructorInfo typeConstructorInfo; /** List of LogRecord */ private final List<LogRecord> logMessages = new ArrayList<LogRecord>(); /** * JavaDataClassGenerator constructor. * @param targetPackage * @param typeConstructorInfo */ private JavaDataClassGenerator ( String targetPackage, TypeConstructorInfo typeConstructorInfo) { this.typeConstructorInfo = typeConstructorInfo; this.targetPackage = targetPackage; } JavaClassRep generateClassForType () { JavaTypeName typeConstructorClassName = JavaTypeName.make(targetPackage + "." + typeConstructorInfo.javaClassName, false); TypeConstructor typeConstructor = typeConstructorInfo.typeConstructor; JavaClassRep outerTypeDefinition = new OuterTypeDefinitionGenerator().generateOuterTypeDefinition(typeConstructorClassName); // Now we need to generate inner classes for each data constructor. // This is only necessary if the data type is not an enumeration. if (!typeConstructorInfo.isEnumerationType) { for (int i = 0, n = typeConstructor.getNDataConstructors(); i < n; ++i) { DataConstructor dc = typeConstructor.getNthDataConstructor(i); JavaClassRep dcClass = new InnerDCClassGenerator().generateInnerDCClass(dc, typeConstructorInfo, typeConstructorClassName); outerTypeDefinition.addInnerClass(dcClass); } } return outerTypeDefinition; } private final class InnerDCClassGenerator { private final JavaClassRep generateInnerDCClass ( DataConstructor dc, TypeConstructorInfo typeConstructorInfo, JavaTypeName typeConstructorClassName) { int classModifiers = Modifier.PUBLIC | Modifier.STATIC | Modifier.FINAL; DataConstructorInfo dcInfo = (DataConstructorInfo)typeConstructorInfo.dataConstructorInfo.get(dc); JavaTypeName dcClassTypeName = JavaTypeName.make( typeConstructorClassName.getFullJavaSourceName() + "$" + dcInfo.innerClassName, false); JavaClassRep dcClass = new JavaClassRep( dcClassTypeName, typeConstructorClassName, classModifiers, IO_Source_Generator.EMPTY_TYPE_NAME_ARRAY); JavaDocComment jdc = new JavaDocComment ("This class represents instances of the CAL data constructor " + dc.getName() + "."); dcClass.setJavaDoc(jdc); createFields (dcClass, dc, typeConstructorInfo); dcClass.addConstructor(createConstructor(dc, typeConstructorInfo, dcClassTypeName)); dcClass.addMethod(createMethod_getDCName(dc.getName().getUnqualifiedName())); for (FieldName fieldName : dcInfo.fieldTypeNames.keySet()) { if (typeConstructorInfo.commonFieldNames.contains(fieldName)) { continue; } JavaTypeName fieldType = (JavaTypeName)dcInfo.fieldTypeNames.get(fieldName); String javaFieldName = (String)typeConstructorInfo.fieldJavaNames.get(fieldName); String accessorName = (String)typeConstructorInfo.fieldJavaAccessorMethodNames.get(fieldName); JavaMethod getter = createMethod_getField(accessorName, javaFieldName, fieldType, true); dcClass.addMethod(getter); } dcClass.addMethod(createMethod_getDCOrdinal(dc.getOrdinal())); dcClass.addMethod (JavaDataClassGenerator.this.createMethod_toString(dcInfo.allFieldNames, dcInfo.fieldTypeNames)); dcClass.addMethod ( JavaDataClassGenerator.this.createMethod_equals( dcClassTypeName, dcInfo.allFieldNames, dcInfo.fieldTypeNames)); dcClass.addMethod ( JavaDataClassGenerator.this.createMethod_hashCode( dcInfo.allFieldNames, dcInfo.fieldTypeNames)); return dcClass; } private final JavaConstructor createConstructor( DataConstructor dc, TypeConstructorInfo typeConstructorInfo, JavaTypeName dcClassTypeName) { DataConstructorInfo dci = (DataConstructorInfo)typeConstructorInfo.dataConstructorInfo.get(dc); String[] argNames = new String [dc.getArity()]; JavaTypeName[] argTypes = new JavaTypeName [argNames.length]; Block constructorBody = new Block(); for (int i = 0, n = dc.getArity(); i < n; ++i) { FieldName fn = (FieldName)dc.getNthFieldName(i); JavaTypeName type = (JavaTypeName)dci.fieldTypeNames.get(fn); String fieldName = (String)typeConstructorInfo.fieldJavaNames.get(fn); String argName = fieldName+"$"; argNames[i] = argName; argTypes[i] = type; if (!typeConstructorInfo.commonFieldNames.contains(fn)) { JavaExpression.JavaField.Instance field = new JavaExpression.JavaField.Instance(null, fieldName, type); JavaExpression assign = new Assignment (field, new MethodVariable(argName)); constructorBody.addStatement(new ExpressionStatement(assign)); } } String constructorName = dcClassTypeName.getUnqualifiedJavaSourceName(); int index = constructorName.lastIndexOf('.'); if (index > -1) { constructorName = constructorName.substring(index + 1); } JavaConstructor constructor; if (typeConstructorInfo.commonFieldNames.size() > 0) { JavaExpression superArgValues[] = new JavaExpression[typeConstructorInfo.commonFieldNames.size()]; JavaTypeName superArgTypes[] = new JavaTypeName[superArgValues.length]; int i = 0; for (FieldName superFieldName : typeConstructorInfo.commonFieldNames) { JavaTypeName fieldType = (JavaTypeName)dci.fieldTypeNames.get(superFieldName); String fieldName = (String)typeConstructorInfo.fieldJavaNames.get(superFieldName); String argName = fieldName+"$"; JavaExpression superArgValue = new MethodVariable(argName); superArgValues[i] = superArgValue; superArgTypes[i] = fieldType; ++i; } constructor = new JavaConstructor (Modifier.PUBLIC, argNames, argTypes, constructorName, superArgValues, superArgTypes); } else { constructor = new JavaConstructor (Modifier.PUBLIC, argNames, argTypes, constructorName); } constructor.addStatement(constructorBody); return constructor; } private final void createFields (JavaClassRep dcClass, DataConstructor dc, TypeConstructorInfo typeConstructorInfo) { DataConstructorInfo dci = (DataConstructorInfo)typeConstructorInfo.dataConstructorInfo.get(dc); // Add a volatile int field for the hashcode. JavaFieldDeclaration hashCodeFieldDecl = new JavaFieldDeclaration( Modifier.PRIVATE | Modifier.VOLATILE, JavaTypeName.INT, HASH_CODE_FIELD_NAME, LiteralWrapper.make(new Integer(0))); hashCodeFieldDecl.setJavaDoc(new JavaDocComment("Lazily initialized, cached hashCode.")); dcClass.addFieldDeclaration(hashCodeFieldDecl); for (int i = 0, n = dc.getArity(); i < n; ++i) { FieldName fieldName = dc.getNthFieldName(i); if (typeConstructorInfo.commonFieldNames.contains(fieldName)) { continue; } JavaTypeName type = (JavaTypeName)dci.fieldTypeNames.get(fieldName); String javaFieldName = (String)typeConstructorInfo.fieldJavaNames.get(fieldName); int modifiers = Modifier.FINAL | Modifier.PRIVATE; JavaFieldDeclaration fieldDec = new JavaFieldDeclaration (modifiers, type, javaFieldName, null); dcClass.addFieldDeclaration(fieldDec); } } private final JavaMethod createMethod_getDCOrdinal(int ordinal) { JavaMethod javaMethod = new JavaMethod(Modifier.PUBLIC, JavaTypeName.INT, GET_DC_ORDINAL_METHOD_NAME); // return dc ordinal. javaMethod.addStatement (new ReturnStatement(LiteralWrapper.make (new Integer (ordinal)))); JavaDocComment jdc = new JavaDocComment ("@return the ordinal of this instance of " + typeConstructorInfo.javaClassName); javaMethod.setJavaDocComment(jdc); return javaMethod; } private final JavaMethod createMethod_getDCName(String dcName) { int modifiers = Modifier.PUBLIC | Modifier.FINAL; JavaMethod javaMethod = new JavaMethod(modifiers, JavaTypeName.STRING, GET_DC_NAME_METHOD_NAME); javaMethod.addStatement(new ReturnStatement(LiteralWrapper.make(dcName))); JavaDocComment jdc = new JavaDocComment ("@return the name of the data constructor corresponding to this instance of " + typeConstructorInfo.javaClassName); javaMethod.setJavaDocComment(jdc); return javaMethod; } } private final class OuterTypeDefinitionGenerator { /** * Generate the java class representation of the data type for which this builder is responsible. * The generated representation will be the outermost class definition only -- no inner classes will have been generated. * @param typeConstructorClassName * @return class rep */ private JavaClassRep generateOuterTypeDefinition(JavaTypeName typeConstructorClassName) { int classModifiers = Modifier.PUBLIC; if (typeConstructorInfo.isEnumerationType) { classModifiers |= Modifier.FINAL; } else { classModifiers |= Modifier.ABSTRACT; } JavaTypeName superClassTypeName = JavaTypeName.OBJECT; // No interfaces are implemented JavaTypeName[] interfaces = IO_Source_Generator.EMPTY_TYPE_NAME_ARRAY; JavaClassRep javaClassRep = new JavaClassRep( typeConstructorClassName, superClassTypeName, classModifiers, interfaces); // Add a comment for the top of the source file. MultiLineComment mlc = new MultiLineComment("<!--"); mlc.addLine(" "); mlc.addLine("**************************************************************"); mlc.addLine("This Java source has been automatically generated."); mlc.addLine("MODIFICATIONS TO THIS SOURCE MAY BE OVERWRITTEN - DO NOT MODIFY THIS FILE"); mlc.addLine("**************************************************************"); mlc.addLine(" "); mlc.addLine(" "); mlc.addLine("This file (" + javaClassRep.getClassName().getUnqualifiedJavaSourceName() + ".java)"); mlc.addLine("was generated from CAL type constructor: " + typeConstructorInfo.typeConstructor.getName() + "."); mlc.addLine(" "); mlc.addLine("Creation date: " + new Date()); mlc.addLine("--!>"); mlc.addLine(" "); javaClassRep.setComment(mlc); // We want to insert the CALDoc comment for the type as a JavaDoc comment for the class. JavaDocComment jdc = new JavaDocComment ("This class (" + javaClassRep.getClassName().getUnqualifiedJavaSourceName() + ") provides a Java data class corresponding to"); jdc.addLine("the CAL type constructor " + typeConstructorInfo.typeConstructor.getName() + "."); jdc.addLine(""); if (typeConstructorInfo.isEnumerationType) { jdc.addLine("This type constructor is an enumeration. (i.e. all data constructors have no fields)"); jdc.addLine("The individual data constructors are represented by instances of " + javaClassRep.getClassName().getUnqualifiedJavaSourceName() + " held "); jdc.addLine("in static final fields."); } else { jdc.addLine("Because the type constructor has only one data constructor, with the same name"); jdc.addLine("as the type constructor this class also represents instances of the data constructor."); } javaClassRep.setJavaDoc(jdc); // Generate fields in this class for any fields which are common to all data constructors. createFields(javaClassRep, typeConstructorInfo); if (typeConstructorInfo.isEnumerationType) { // Add instance fields for name and ordinal // Add static final fields for each data constructor createFields_Enumeration(typeConstructorInfo.typeConstructor, javaClassRep, typeConstructorClassName); // Add a function which takes an int value for the // ordinal and returns the corresponding enum instance. javaClassRep.addMethod(createMethod_fromOrdinal (typeConstructorClassName)); } if (typeConstructorInfo.commonFieldNames.size() > 0) { javaClassRep.addConstructor(createConstructor(typeConstructorInfo, typeConstructorClassName)); } else { javaClassRep.addConstructor(createConstructor_default(typeConstructorClassName, typeConstructorInfo.isEnumerationType)); } // Create a get_FieldName method for each unique field name in the set of // data constructors. // If the field is common to all DCs it will be implemented at this // level. Otherwise the implementation at this level throws an error // and the DC classes are expected to override it. for (FieldName fieldName : typeConstructorInfo.commonFieldNames) { String javaFieldName = (String)typeConstructorInfo.fieldJavaNames.get(fieldName); Set<JavaTypeName> fieldTypes = typeConstructorInfo.allFieldTypeNames.get(fieldName); for (JavaTypeName type : fieldTypes) { String accessorMethodName = (String)typeConstructorInfo.fieldJavaAccessorMethodNames.get(fieldName); JavaMethod getter = createMethod_getField ( accessorMethodName, javaFieldName, type, true); javaClassRep.addMethod(getter); getter.setJavaDocComment(new JavaDocComment ("@return " + fieldName)); } } javaClassRep.addMethod(createMethod_getDCName(typeConstructorInfo)); javaClassRep.addMethod(createMethod_getDCOrdinal(typeConstructorInfo)); JavaMethod toString = createMethod_toString(); if (toString != null) { javaClassRep.addMethod (toString); } if (typeConstructorInfo.isEnumerationType) { JavaMethod equals = JavaDataClassGenerator.this.createMethod_equals( typeConstructorClassName, typeConstructorInfo.commonFieldNames, typeConstructorInfo.commonFieldTypes); javaClassRep.addMethod (equals); } createMethod_hashCode (javaClassRep); return javaClassRep; } private void createMethod_hashCode (JavaClassRep javaClassRep) { if (typeConstructorInfo.isEnumerationType) { // We can just return the ordinal. JavaMethod hashCode = new JavaMethod( Modifier.PUBLIC | Modifier.FINAL, JavaTypeName.INT, "hashCode"); JavaExpression call_getOrdinal = new MethodInvocation.Instance( null, GET_DC_ORDINAL_METHOD_NAME, JavaTypeName.INT, MethodInvocation.InvocationType.VIRTUAL); hashCode.addStatement(new ReturnStatement (call_getOrdinal)); javaClassRep.addMethod(hashCode); } } private JavaMethod createMethod_toString() { JavaMethod toString; if (typeConstructorInfo.isEnumerationType) { toString = new JavaMethod( Modifier.PUBLIC | Modifier.FINAL, JavaTypeName.STRING, "toString"); JavaExpression getDCName = new JavaExpression.MethodInvocation.Instance( null, GET_DC_NAME_METHOD_NAME, JavaTypeName.STRING, MethodInvocation.InvocationType.VIRTUAL); toString.addStatement(new ReturnStatement (getDCName)); return toString; } else { toString = null; } if (toString != null) { JavaDocComment jdc = new JavaDocComment ("@return a String representing this instance of " + typeConstructorInfo.javaClassName); toString.setJavaDocComment(jdc); } return toString; } private JavaMethod createMethod_fromOrdinal (JavaTypeName dataType_TypeName) { JavaMethod fromOrdinal = new JavaMethod( Modifier.PUBLIC | Modifier.STATIC | Modifier.FINAL, dataType_TypeName, "ordinal", JavaTypeName.INT, true, "fromOrdinal"); // build up a switch. JavaStatement.SwitchStatement switchStatement = new JavaStatement.SwitchStatement(new MethodVariable("ordinal")); for (int i = 0, n = typeConstructorInfo.typeConstructor.getNDataConstructors(); i < n; ++i) { DataConstructor dc = typeConstructorInfo.typeConstructor.getNthDataConstructor(i); String enumFieldName = createEnumFieldName(dc.getName().getUnqualifiedName()); JavaField field = new JavaField.Static(dataType_TypeName, enumFieldName, dataType_TypeName); JavaStatement.SwitchStatement.IntCaseGroup intCase = new SwitchStatement.IntCaseGroup( dc.getOrdinal(), new ReturnStatement(field)); switchStatement.addCase(intCase); } // Throw an error if ordinal is outside accepted range. JavaExpression message = new JavaExpression.OperatorExpression.Binary( JavaOperator.STRING_CONCATENATION, LiteralWrapper.make ("Invalid ordinal " ), new MethodVariable("ordinal")); message = new JavaExpression.OperatorExpression.Binary( JavaOperator.STRING_CONCATENATION, message, LiteralWrapper.make(" for data type " + typeConstructorInfo.typeConstructor.getName().getUnqualifiedName())); JavaExpression createException = new JavaExpression.ClassInstanceCreationExpression ( UNSUPPORTED_OPERATION_TYPE_NAME, message, JavaTypeName.STRING); JavaStatement throwStatement = new JavaStatement.ThrowStatement(createException); SwitchStatement.DefaultCase defaultCase = new SwitchStatement.DefaultCase(throwStatement); switchStatement.addCase(defaultCase); fromOrdinal.addStatement(switchStatement); JavaDocComment jdc = new JavaDocComment ("@param ordinal"); jdc.addLine("@return the instance of " + typeConstructorInfo.javaClassName + " corresponding to the given ordinal."); fromOrdinal.setJavaDocComment(jdc); return fromOrdinal; } /** * Create fields for this class. * @param javaClassRep * @param typeConstructorInfo */ private void createFields ( JavaClassRep javaClassRep, TypeConstructorInfo typeConstructorInfo) { for (FieldName fieldName : typeConstructorInfo.commonFieldNames) { Set<JavaTypeName> javaTypeNames = typeConstructorInfo.allFieldTypeNames.get(fieldName); assert (javaTypeNames.size() == 1); Iterator<JavaTypeName> types = javaTypeNames.iterator(); JavaTypeName type = types.next(); String javaFieldName = typeConstructorInfo.fieldJavaNames.get(fieldName); int modifiers = Modifier.FINAL; JavaFieldDeclaration fieldDec = new JavaFieldDeclaration (modifiers, type, javaFieldName, null); javaClassRep.addFieldDeclaration(fieldDec); } } private final JavaMethod createMethod_getDCOrdinal(TypeConstructorInfo typeConstructorInfo) { int modifiers = Modifier.PUBLIC; JavaMethod javaMethod = new JavaMethod(modifiers, JavaTypeName.INT, GET_DC_ORDINAL_METHOD_NAME); if (typeConstructorInfo.isEnumerationType) { // return instance field. javaMethod.addStatement (new ReturnStatement (new JavaField.Instance (null, ORDINAL_FIELD_NAME, JavaTypeName.INT))); } else { javaMethod.addStatement (new ReturnStatement(LiteralWrapper.make (new Integer(-1)))); } JavaDocComment jdc = new JavaDocComment ("@return the ordinal of this instance of " + typeConstructorInfo.javaClassName); javaMethod.setJavaDocComment(jdc); return javaMethod; } private final JavaMethod createMethod_getDCName(TypeConstructorInfo typeConstructorInfo) { int modifiers = Modifier.PUBLIC; JavaMethod javaMethod = new JavaMethod(modifiers, JavaTypeName.STRING, GET_DC_NAME_METHOD_NAME); if (typeConstructorInfo.isEnumerationType) { // return the name$ field javaMethod.addStatement (new ReturnStatement (new JavaField.Instance (null, DC_NAME_FIELD_NAME, JavaTypeName.STRING))); } else { javaMethod.addStatement (new ReturnStatement(LiteralWrapper.NULL)); } JavaDocComment jdc = new JavaDocComment ("@return the name of the data constructor corresponding to this instance of " + typeConstructorInfo.javaClassName); javaMethod.setJavaDocComment(jdc); return javaMethod; } /** * Create a constructor for the data type class. * @param typeConstructorInfo * @param className * @return the constructor for the data type class. */ private JavaConstructor createConstructor ( TypeConstructorInfo typeConstructorInfo, JavaTypeName className) { String[] argNames = new String [typeConstructorInfo.commonFieldNames.size()]; JavaTypeName[] argTypes = new JavaTypeName [argNames.length]; Block constructorBody = new Block(); int i = 0; for (FieldName fn : typeConstructorInfo.commonFieldNames) { Set<JavaTypeName> javaTypeNames = typeConstructorInfo.allFieldTypeNames.get(fn); assert(javaTypeNames.size() == 1); Iterator<JavaTypeName> types = javaTypeNames.iterator(); JavaTypeName type = types.next(); String fieldName = (String)typeConstructorInfo.fieldJavaNames.get(fn); String argName = fieldName+"$"; argNames[i] = argName; argTypes[i] = type; JavaExpression.JavaField.Instance field = new JavaExpression.JavaField.Instance(null, fieldName, type); JavaExpression assign = new Assignment (field, new MethodVariable(argName)); constructorBody.addStatement(new ExpressionStatement(assign)); i++; } String constructorName = className.getUnqualifiedJavaSourceName(); int index = constructorName.lastIndexOf('.'); if (index > -1) { constructorName = constructorName.substring(index + 1); } JavaConstructor constructor; if (!typeConstructorInfo.isEnumerationType) { constructor = new JavaConstructor (Modifier.PRIVATE, argNames, argTypes, constructorName); } else { constructor = new JavaConstructor (Modifier.PUBLIC, argNames, argTypes, constructorName); } constructor.addStatement(constructorBody); return constructor; } /** * Create the default constructor for the data type class. * @param className * @param isEnumeration * @return the constructor. */ private final JavaConstructor createConstructor_default ( JavaTypeName className, boolean isEnumeration) { JavaConstructor constructor; if (isEnumeration) { // We want a private constructor that initializes the name$ and ordinal$ fields. constructor = new JavaConstructor ( Modifier.PRIVATE, new String[]{"ordinal", "name"}, new JavaTypeName[]{JavaTypeName.INT, JavaTypeName.STRING}, className.getUnqualifiedJavaSourceName()); JavaExpression assignOrdinal = new JavaExpression.Assignment ( new JavaField.Instance(null, ORDINAL_FIELD_NAME, JavaTypeName.INT), new MethodVariable("ordinal")); constructor.addStatement(new ExpressionStatement(assignOrdinal)); JavaExpression assignName = new JavaExpression.Assignment ( new JavaField.Instance(null, DC_NAME_FIELD_NAME, JavaTypeName.STRING), new MethodVariable("name")); constructor.addStatement(new ExpressionStatement(assignName)); } else { constructor = new JavaConstructor (Modifier.PUBLIC, className.getUnqualifiedJavaSourceName()); } return constructor; } private void createFields_Enumeration ( TypeConstructor typeConstructor, JavaClassRep dataTypeClass, JavaTypeName dataType_TypeName) { JavaTypeName[] constructorArgTypes = new JavaTypeName[]{JavaTypeName.INT, JavaTypeName.STRING}; // For each data constructor create a constant int field for the ordinal. for (int i = 0, n = typeConstructor.getNDataConstructors(); i < n; ++i) { DataConstructor dc = typeConstructor.getNthDataConstructor(i); String ordinalFieldName = createEnumFieldName(dc.getName().getUnqualifiedName()) + "_ORDINAL"; JavaExpression initializer = LiteralWrapper.make(new Integer(dc.getOrdinal())); JavaFieldDeclaration fieldDec = new JavaFieldDeclaration ( Modifier.PUBLIC | Modifier.STATIC | Modifier.FINAL, JavaTypeName.INT, ordinalFieldName, initializer); dataTypeClass.addFieldDeclaration(fieldDec); JavaDocComment jdc = new JavaDocComment ("Ordinal value corresponding to the " + dc.getName().getUnqualifiedName() + " data constructor."); fieldDec.setJavaDoc(jdc); } // For each data constructor create a static field for (int i = 0, n = typeConstructor.getNDataConstructors(); i < n; ++i) { DataConstructor dc = typeConstructor.getNthDataConstructor(i); String staticFieldName = createEnumFieldName(dc.getName().getUnqualifiedName()); JavaExpression initializer = new JavaExpression.ClassInstanceCreationExpression( dataType_TypeName, new JavaExpression[]{ new JavaField.Static( dataType_TypeName, staticFieldName + "_ORDINAL", JavaTypeName.INT), LiteralWrapper.make(dc.getName().getUnqualifiedName())}, constructorArgTypes); JavaFieldDeclaration fieldDec = new JavaFieldDeclaration ( Modifier.PUBLIC | Modifier.STATIC | Modifier.FINAL, dataType_TypeName, staticFieldName, initializer); JavaDocComment jdc = new JavaDocComment ("This instance of " + typeConstructorInfo.javaClassName + " representing the " + dc.getName().getUnqualifiedName() + " data constructor."); fieldDec.setJavaDoc(jdc); dataTypeClass.addFieldDeclaration(fieldDec); } // Add two instance fields. // int ordinal$; // String name$; JavaFieldDeclaration fieldDec = new JavaFieldDeclaration (Modifier.PRIVATE, JavaTypeName.INT, ORDINAL_FIELD_NAME, null); dataTypeClass.addFieldDeclaration(fieldDec); fieldDec = new JavaFieldDeclaration (Modifier.PRIVATE, JavaTypeName.STRING, DC_NAME_FIELD_NAME, null); dataTypeClass.addFieldDeclaration(fieldDec); } } /** * Generate an accessor function of the form RTValue get_FieldName() {} * @param methodName * @param fieldName * @param fieldType * @param implementAtThisLevel * @return JavaMethod */ private final JavaMethod createMethod_getField ( String methodName, String fieldName, JavaTypeName fieldType, boolean implementAtThisLevel) { int modifiers = Modifier.PUBLIC; if (implementAtThisLevel) { modifiers = modifiers | Modifier.FINAL; } // Create the method. JavaMethod javaMethod = new JavaMethod(modifiers, fieldType, methodName); if (implementAtThisLevel) { JavaField field = new JavaField.Instance (null, fieldName, fieldType); javaMethod.addStatement(new ReturnStatement(field)); } else { // This class should throw an error for any access. The methods will // be overridden by derived classes for each data constructor. JavaExpression getDCName = new MethodInvocation.Instance(null, GET_DC_NAME_METHOD_NAME, JavaTypeName.STRING, MethodInvocation.InvocationType.VIRTUAL); JavaExpression message = new JavaExpression.OperatorExpression.Binary( JavaOperator.STRING_CONCATENATION, LiteralWrapper.make ("Cannot access field " + fieldName + " on an instance of "), getDCName); JavaExpression createException = new JavaExpression.ClassInstanceCreationExpression ( UNSUPPORTED_OPERATION_TYPE_NAME, message, JavaTypeName.STRING); javaMethod.addStatement(new JavaStatement.ThrowStatement(createException)); } return javaMethod; } private final JavaMethod createMethod_hashCode ( Set<FieldName> fieldNames, Map<FieldName, JavaTypeName> fieldNameToType) { JavaMethod hashCode = new JavaMethod ( Modifier.PUBLIC | Modifier.FINAL, JavaTypeName.INT, "hashCode"); JavaField hashCodeField = new JavaField.Instance ( null, HASH_CODE_FIELD_NAME, JavaTypeName.INT); JavaExpression condition = new OperatorExpression.Binary ( JavaOperator.EQUALS_INT, hashCodeField, LiteralWrapper.make(new Integer(0))); JavaStatement.Block thenBlock = new JavaStatement.Block(); IfThenElseStatement ifThen = new IfThenElseStatement (condition, thenBlock); hashCode.addStatement (ifThen); // build up the hashcode value. JavaExpression.LocalVariable result = new LocalVariable ("result", JavaTypeName.INT); LocalVariableDeclaration localVarDecl = new LocalVariableDeclaration(result, LiteralWrapper.make(new Integer(17))); thenBlock.addStatement(localVarDecl); LiteralWrapper thirtySeven = LiteralWrapper.make (new Integer(37)); JavaExpression thirtySevenTimesResult = new OperatorExpression.Binary(JavaOperator.MULTIPLY_INT, thirtySeven, result); LiteralWrapper zero = LiteralWrapper.make (new Integer (0)); LiteralWrapper one = LiteralWrapper.make (new Integer (1)); // Start by including dc name in the hashcode. // get objects hashcode JavaExpression nameExpression = new MethodInvocation.Instance ( new MethodInvocation.Instance( null, GET_DC_NAME_METHOD_NAME, JavaTypeName.STRING, MethodInvocation.InvocationType.VIRTUAL), "hashCode", JavaTypeName.INT, MethodInvocation.InvocationType.VIRTUAL); JavaExpression newResult = new OperatorExpression.Binary (JavaOperator.ADD_INT, thirtySevenTimesResult, nameExpression); JavaExpression assignResult = new Assignment (result, newResult); thenBlock.addStatement(new ExpressionStatement (assignResult)); // Now get the contribution from each dc field. for (FieldName fn : fieldNames) { String javaFieldName = typeConstructorInfo.fieldJavaNames.get (fn); JavaTypeName fieldType = fieldNameToType.get (fn); JavaField field = new JavaField.Instance(null, javaFieldName, fieldType); JavaExpression fieldExpression; if (fieldType instanceof JavaTypeName.Primitive) { if (fieldType instanceof JavaTypeName.Primitive.Boolean) { fieldExpression = new OperatorExpression.Ternary (field, zero, one); } else if (fieldType instanceof JavaTypeName.Primitive.Byte || fieldType instanceof JavaTypeName.Primitive.Char || fieldType instanceof JavaTypeName.Primitive.Short) { fieldExpression = new CastExpression(JavaTypeName.INT, field); }else if (fieldType instanceof JavaTypeName.Primitive.Double) { // long f = Double.doubleToLongBits(f); // result = (int) (f ^ (f >>> 32)); JavaExpression.LocalVariable f = new LocalVariable ("f", JavaTypeName.LONG); JavaExpression initializeF = new MethodInvocation.Static ( JavaTypeName.DOUBLE_OBJECT, "doubleToLongBits", field, JavaTypeName.DOUBLE, JavaTypeName.LONG); LocalVariableDeclaration fVarDecl = new LocalVariableDeclaration(f, initializeF); thenBlock.addStatement(fVarDecl); fieldExpression = new OperatorExpression.Binary ( JavaOperator.SHIFTR_UNSIGNED_LONG, f, LiteralWrapper.make(new Integer (32))); fieldExpression = new OperatorExpression.Binary ( JavaOperator.BITWISE_XOR_LONG, f, fieldExpression); fieldExpression = new CastExpression (JavaTypeName.INT, fieldExpression); } else if (fieldType instanceof JavaTypeName.Primitive.Float) { fieldExpression = new MethodInvocation.Static ( JavaTypeName.FLOAT_OBJECT, "floatToIntBits", field, JavaTypeName.FLOAT, JavaTypeName.INT); } else if (fieldType instanceof JavaTypeName.Primitive.Int) { fieldExpression = field; } else if (fieldType instanceof JavaTypeName.Primitive.Long) { fieldExpression = new OperatorExpression.Binary ( JavaOperator.SHIFTR_UNSIGNED_LONG, field, LiteralWrapper.make(new Integer (32))); fieldExpression = new OperatorExpression.Binary ( JavaOperator.BITWISE_XOR_LONG, field, fieldExpression); fieldExpression = new CastExpression (JavaTypeName.INT, fieldExpression); } else { fieldExpression = new MethodInvocation.Instance (field, "hashCode", JavaTypeName.INT, MethodInvocation.InvocationType.VIRTUAL); } } else { // get objects hashcode fieldExpression = new MethodInvocation.Instance (field, "hashCode", JavaTypeName.INT, MethodInvocation.InvocationType.VIRTUAL); } newResult = new OperatorExpression.Binary (JavaOperator.ADD_INT, thirtySevenTimesResult, fieldExpression); assignResult = new Assignment (result, newResult); thenBlock.addStatement(new ExpressionStatement (assignResult)); } // Assign the hash code value to the hashCode field. JavaExpression assign = new JavaExpression.Assignment(hashCodeField, result); thenBlock.addStatement(new ExpressionStatement (assign)); // Return the initialized hashCode field. hashCode.addStatement (new ReturnStatement (hashCodeField)); return hashCode; } private final JavaMethod createMethod_equals ( JavaTypeName typeName, Set<FieldName> fieldNames, Map<FieldName, JavaTypeName> fieldNameToType) { String argName = "object"; JavaMethod equals = new JavaMethod( Modifier.PUBLIC | Modifier.FINAL, JavaTypeName.BOOLEAN, argName, JavaTypeName.OBJECT, false, "equals"); MethodVariable objectArg = new MethodVariable (argName); JavaField thisField = new JavaField.This(typeName); // if (object == this) { // return true; // } JavaExpression condition = new JavaExpression.OperatorExpression.Binary( JavaOperator.EQUALS_OBJECT, thisField, objectArg); JavaStatement.IfThenElseStatement ifThen = new IfThenElseStatement( condition, new ReturnStatement(LiteralWrapper.TRUE)); equals.addStatement(ifThen); // if (object == null) { // return false; // } condition = new JavaExpression.OperatorExpression.Binary(JavaOperator.EQUALS_OBJECT, objectArg, LiteralWrapper.NULL); ifThen = new IfThenElseStatement (condition, new ReturnStatement(LiteralWrapper.FALSE)); equals.addStatement(ifThen); // if (!(object instanceOf ThisType)) { // return false; // } condition = new JavaExpression.InstanceOf(objectArg, typeName); condition = new OperatorExpression.Unary(JavaOperator.LOGICAL_NEGATE, condition); ifThen = new IfThenElseStatement ( condition, new ReturnStatement(LiteralWrapper.FALSE)); equals.addStatement(ifThen); // ThisType castObject = (ThisType)object; LocalVariable localVar = new LocalVariable ("cast" + argName, typeName); JavaStatement localVarDecl = new JavaStatement.LocalVariableDeclaration ( localVar, new JavaExpression.CastExpression(typeName, objectArg)); equals.addStatement (localVarDecl); // Check DC name // if (!getDCName().equals(castObject.getDCName())) { // return false; // } JavaExpression thisGetDCName = new MethodInvocation.Instance(null, GET_DC_NAME_METHOD_NAME, JavaTypeName.STRING, MethodInvocation.InvocationType.VIRTUAL); JavaExpression otherGetDCName = new MethodInvocation.Instance(localVar, GET_DC_NAME_METHOD_NAME, JavaTypeName.STRING, MethodInvocation.InvocationType.VIRTUAL); JavaExpression compareDCNames = new MethodInvocation.Instance(thisGetDCName, "equals", otherGetDCName, JavaTypeName.OBJECT, JavaTypeName.BOOLEAN, MethodInvocation.InvocationType.VIRTUAL); compareDCNames = new OperatorExpression.Unary(JavaOperator.LOGICAL_NEGATE, compareDCNames); ifThen = new IfThenElseStatement(compareDCNames, new ReturnStatement(LiteralWrapper.FALSE)); equals.addStatement(ifThen); if (fieldNames != null && fieldNames.size() > 0) { // Now we need to compare equality for the various fields. Iterator<FieldName> fields = fieldNames.iterator(); FieldName fn = fields.next (); JavaTypeName fieldType = (JavaTypeName)fieldNameToType.get(fn); JavaExpression compare = makeFieldComparison (fn, fieldType, localVar); while (fields.hasNext()) { fn = fields.next (); fieldType = (JavaTypeName)fieldNameToType.get(fn); JavaExpression nextCompare = makeFieldComparison (fn, fieldType, localVar); compare = new OperatorExpression.Binary (JavaOperator.CONDITIONAL_AND, compare, nextCompare); } equals.addStatement(new ReturnStatement(compare)); } else { equals.addStatement(new ReturnStatement(LiteralWrapper.TRUE)); } return equals; } private final JavaExpression makeFieldComparison ( FieldName fieldName, JavaTypeName fieldType, JavaExpression other) { String javaFieldName = (String)typeConstructorInfo.fieldJavaNames.get(fieldName); JavaExpression thisField = new JavaField.Instance (null, javaFieldName, fieldType); JavaExpression otherField = new JavaField.Instance (other, javaFieldName, fieldType); if (fieldType instanceof JavaTypeName.Primitive) { // We need to use the correct version of the // == operator. // this.field == other.field JavaOperator operator; if (fieldType instanceof JavaTypeName.Primitive.Boolean) { operator = JavaOperator.CONDITIONAL_AND; } else if (fieldType instanceof JavaTypeName.Primitive.Byte) { operator = JavaOperator.EQUALS_BYTE; } else if (fieldType instanceof JavaTypeName.Primitive.Char) { operator = JavaOperator.EQUALS_CHAR; } else if (fieldType instanceof JavaTypeName.Primitive.Double) { operator = JavaOperator.EQUALS_BYTE; } else if (fieldType instanceof JavaTypeName.Primitive.Float) { operator = JavaOperator.EQUALS_FLOAT; } else if (fieldType instanceof JavaTypeName.Primitive.Int) { operator = JavaOperator.EQUALS_INT; } else if (fieldType instanceof JavaTypeName.Primitive.Long) { operator = JavaOperator.EQUALS_LONG; } else if (fieldType instanceof JavaTypeName.Primitive.Short) { operator = JavaOperator.EQUALS_SHORT; }else { operator = JavaOperator.EQUALS_OBJECT; } JavaExpression compare = new OperatorExpression.Binary (operator, thisField, otherField); return compare; } else { // Invoke the equals method. // (this.field == null) ? (other.field == null) | this.field.equals(other.field) JavaExpression condition = new OperatorExpression.Binary ( JavaOperator.EQUALS_OBJECT, thisField, LiteralWrapper.NULL); JavaExpression then = new OperatorExpression.Binary ( JavaOperator.EQUALS_OBJECT, otherField, LiteralWrapper.NULL); JavaExpression elsePart = new MethodInvocation.Instance ( thisField, "equals", otherField, JavaTypeName.OBJECT, JavaTypeName.BOOLEAN, MethodInvocation.InvocationType.VIRTUAL); JavaExpression ternary = new OperatorExpression.Ternary ( condition, then, elsePart); return ternary; } } private final JavaMethod createMethod_toString (Set<FieldName> fieldNames, Map<FieldName, JavaTypeName> fieldTypes) { JavaMethod toString = new JavaMethod( Modifier.PUBLIC | Modifier.FINAL, JavaTypeName.STRING, "toString"); JavaExpression getDCName = new JavaExpression.MethodInvocation.Instance( null, GET_DC_NAME_METHOD_NAME, JavaTypeName.STRING, MethodInvocation.InvocationType.VIRTUAL); JavaExpression message = new JavaExpression.OperatorExpression.Binary( JavaOperator.STRING_CONCATENATION, getDCName, LiteralWrapper.make("\n")); for (FieldName fn : fieldNames) { // There will only be one field type. JavaTypeName fieldType = (JavaTypeName)fieldTypes.get(fn); String javaFieldName = (String)typeConstructorInfo.fieldJavaNames.get(fn); JavaExpression field = new JavaField.Instance(null, javaFieldName, fieldType); JavaExpression fieldString; if (fieldType instanceof JavaTypeName.Primitive || fieldType.equals(JavaTypeName.STRING)) { fieldString = field; } else { fieldString = new MethodInvocation.Instance(field, "toString", JavaTypeName.STRING, MethodInvocation.InvocationType.VIRTUAL); } message = new JavaExpression.OperatorExpression.Binary( JavaOperator.STRING_CONCATENATION, message, LiteralWrapper.make(" " + fn.toString() + " = ")); message = new JavaExpression.OperatorExpression.Binary( JavaOperator.STRING_CONCATENATION, message, fieldString); message = new JavaExpression.OperatorExpression.Binary( JavaOperator.STRING_CONCATENATION, message, LiteralWrapper.make("\n")); } toString.addStatement (new ReturnStatement (message)); return toString; } // /** // * Convert a CALDocComment instance into JavaDoc. // * @param comment // * @param bindingEntity // * @param isClassComment whether the Javadoc comment is a class comment. // * @return the text of the JavaDoc comment. // */ // private final String calDocCommentToJavaComment (CALDocComment comment, FunctionalAgent bindingEntity, boolean isClassComment) { // return calDocCommentToJavaComment(comment, bindingEntity, isClassComment, null); // } // /** // * Convert a CALDocComment instance into JavaDoc. // * @param comment // * @param bindingEntity // * @param isClassComment whether the Javadoc comment is a class comment. // * @param argNames the names of arguments to use. Can be null if the default mechanism for obtaining argument names is to be used. // * @return the text of the JavaDoc comment. // */ // private final String calDocCommentToJavaComment (CALDocComment comment, FunctionalAgent bindingEntity, boolean isClassComment, String[] argNames) { // String text = CALDocToJavaDocUtilities.getJavadocFromCALDocComment(comment, isClassComment, bindingEntity, argNames); // return text; // } } /** * Use this function to get the field types for a data constructor. * @param dc * @return an array of TypeExpr */ private final TypeExpr[] getFieldTypesForDC (DataConstructor dc) { TypeExpr[] fieldTypes = new TypeExpr[dc.getArity()]; TypeExpr dcType = dc.getTypeExpr(); TypeExpr[] tft = dcType.getTypePieces(dc.getArity()); System.arraycopy(tft, 0, fieldTypes, 0, fieldTypes.length); return fieldTypes; } private final String createEnumFieldName(String dcName) { //Substitute _ for # //Substitute _ for . //Substitute _ for $ StringBuffer sb = new StringBuffer(); char c = dcName.charAt(0); //for the first character, don't worry about case or the underscore since the CAL language guarantees that functions start //with a lower case letter. switch (c) { case '#': case '$': case '.': sb.append('_'); break; default: if (Character.isUpperCase(c)) { sb.append(c); } else { sb.append(Character.toUpperCase(c)); } break; } for (int i = 1, n = dcName.length(); i < n; ++i) { c = dcName.charAt(i); switch (c) { case '#': case '$': case '.': case '_': sb.append("__"); //escape special characters past the first with 2 underscores. break; default: { if (Character.isUpperCase(c)) { sb.append('_'); sb.append(c); } else { sb.append(Character.toUpperCase(c)); } break; } } } dcName = sb.toString(); if (isReservedWord(dcName)) { //there are no reserved words that start with an underscore. dcName = dcName + "_"; } return dcName; } /** * @param name a simple name * @return whether the given name is a reserved word. * ie. either a Java language keyword, or a name of a file which cannot be created in the Windows file system. */ private final boolean isReservedWord (String name) { if (JavaReservedWords.javaLanguageKeywords.contains(name)) { return true; } // Check for names that will conflict with reserved words in the Windows file system. // In this case we're looking for COM1, COM2, ... or LPT1, LPT2, ... // Also: CLOCK$, CON, AUX, PRN, NUL, CONFIG$ // Note: we want to look at these in a case insensitive fashion, since in the windows file // system con and CON are the same. name = name.toLowerCase(); // check the windows reserved words with all lower case. if (windowsReservedWords.contains(name)) { return true; } if (name.startsWith("com") || name.startsWith("lpt")) { String rest = name.substring(3); boolean isInt = true; for (int i = 0; i < rest.length(); ++i) { if (!Character.isDigit(rest.charAt(i))) { isInt = false; break; } } if (isInt) { return true; } } return false; } /** * Go from a TypeExpr to the corresponding JavaTypeName. * @param typeExpr * @param typeToClassMappings * @param moduleToPackageMappings * @param targetPackage * @return a JavaTypeName */ private final JavaTypeName typeExprToTypeName ( TypeExpr typeExpr, Map<QualifiedName, JavaTypeName> typeToClassMappings, Map<ModuleName, String> moduleToPackageMappings, String targetPackage) throws UnableToResolveForeignEntityException { if (typeExpr != null) { if (typeExpr instanceof RecordType) { return JavaTypeName.make(java.util.List.class); } TypeConsApp typeConsApp = typeExpr.rootTypeConsApp(); if (typeConsApp != null) { if (typeConsApp.isNonParametricType(CAL_Prelude.TypeConstructors.Boolean)) { return JavaTypeName.BOOLEAN; } if(typeConsApp.getForeignTypeInfo() != null) { ForeignTypeInfo fti = typeConsApp.getForeignTypeInfo(); return JavaTypeName.make (fti.getForeignType()); } TypeConstructor typeConstructor = typeConsApp.getRoot(); JavaTypeName typeName = (JavaTypeName)typeToClassMappings.get(typeConstructor.getName()); if (typeName != null) { return typeName; } String unqualifiedTypeConstructorName = typeConstructor.getName().getUnqualifiedName(); if (unqualifiedTypeConstructorName.equals("Function")) { return CAL_VALUE; } // We are going to build up a JavaTypeName based on our naming convention for generated // I/O classes. // Check to see if the CAL module is mapped to a Java package. String referencedPackage = moduleToPackageMappings.get(typeConstructor.getName().getModuleName()); if (referencedPackage == null) { // If not use the target package. referencedPackage = targetPackage; } if (referencedPackage.endsWith(".")) { referencedPackage = referencedPackage.substring(0, referencedPackage.lastIndexOf('.')); } if (referencedPackage.lastIndexOf('.') > 0) { referencedPackage = referencedPackage.substring(0, referencedPackage.lastIndexOf('.')+1); } else { referencedPackage = referencedPackage + "."; } // Create a Java class name. JavaTypeName typeConstructorClassName = JavaTypeName.make(referencedPackage + typeConstructor.getName().getModuleName().getLastComponent() + "." + getClassName(typeConstructor), false); return typeConstructorClassName; } } return JavaTypeName.OBJECT; } private final class CAL_Module_ImportGenerator<T> extends SourceModelCopier <T> { private final SourceModel.ModuleDefn moduleDefn; private final Map<String, Name.DataCons> shortNameToFullNameDataCons = new LinkedHashMap<String, Name.DataCons>(); private final Map<String, Name.Function> shortNameToFullNameFunction = new LinkedHashMap<String, Name.Function>(); private final Map<String, Name.TypeClass> shortNameToFullNameTypeClass = new LinkedHashMap<String, Name.TypeClass>(); private final Map<String, Name.TypeCons> shortNameToFullNameTypeCons = new LinkedHashMap<String, Name.TypeCons>(); private final Set<ModuleName> mustImportModules; /** * * @param moduleDefn * @param mustImportModules - Set of ModuleName */ CAL_Module_ImportGenerator (ModuleDefn moduleDefn, Set<ModuleName> mustImportModules) { this.moduleDefn = moduleDefn; this.mustImportModules = mustImportModules; } /** * @param cons the source model element to be traversed * @param arg unused argument * @return null */ public Name.DataCons visit_Name_DataCons( Name.DataCons cons, T arg) { if (cons.isQualified()) { if (!cons.getModuleName().toString().equals(moduleDefn.getModuleName().toString())) { Name.DataCons existingMapping = (Name.DataCons)shortNameToFullNameDataCons.get(cons.getUnqualifiedName()); if (existingMapping != null) { // Mapping exists for short name. Is it for this data cons. if (existingMapping.getModuleName().toString().equals(cons.getModuleName().toString())) { // Mapping is valid, return unqualified name. return Name.DataCons.makeUnqualified(cons.getUnqualifiedName()); } } else { // Mapping doesn't exist. Create one. shortNameToFullNameDataCons.put(cons.getUnqualifiedName(), cons); return Name.DataCons.makeUnqualified(cons.getUnqualifiedName()); } } else { return Name.DataCons.makeUnqualified(cons.getUnqualifiedName()); } } return super.visit_Name_DataCons(cons, arg); } /** * @param function the source model element to be traversed * @param arg unused argument * @return null */ public Name.Function visit_Name_Function( Name.Function function, T arg) { if (function.isQualified()) { if (!function.getModuleName().toString().equals(moduleDefn.getModuleName().toString())) { Name.Function existingMapping = (Name.Function)shortNameToFullNameFunction.get(function.getUnqualifiedName()); if (existingMapping != null) { // Mapping exists for short name. Is it for this data cons. if (existingMapping.getModuleName().toString().equals(function.getModuleName().toString())) { // Mapping is valid, return unqualified name. return Name.Function.makeUnqualified(function.getUnqualifiedName()); } } else { // Mapping doesn't exist. Create one. shortNameToFullNameFunction.put(function.getUnqualifiedName(), function); return Name.Function.makeUnqualified(function.getUnqualifiedName()); } } else { return Name.Function.makeUnqualified(function.getUnqualifiedName()); } } return super.visit_Name_Function(function, arg); } /** * @param typeClass the source model element to be traversed * @param arg unused argument * @return null */ public Name.TypeClass visit_Name_TypeClass( Name.TypeClass typeClass, T arg) { if (typeClass.isQualified()) { if (!typeClass.getModuleName().toString().equals(moduleDefn.getModuleName().toString())) { Name.TypeClass existingMapping = (Name.TypeClass)shortNameToFullNameTypeClass.get(typeClass.getUnqualifiedName()); if (existingMapping != null) { // Mapping exists for short name. Is it for this data cons. if (existingMapping.getModuleName().toString().equals(typeClass.getModuleName().toString())) { // Mapping is valid, return unqualified name. return Name.TypeClass.makeUnqualified(typeClass.getUnqualifiedName()); } } else { // Mapping doesn't exist. Create one. shortNameToFullNameTypeClass.put(typeClass.getUnqualifiedName(), typeClass); return Name.TypeClass.makeUnqualified(typeClass.getUnqualifiedName()); } } else { return Name.TypeClass.makeUnqualified(typeClass.getUnqualifiedName()); } } return super.visit_Name_TypeClass(typeClass, arg); } /** * @param cons the source model element to be traversed * @param arg unused argument * @return null */ public Name.TypeCons visit_Name_TypeCons( Name.TypeCons cons, T arg) { if (cons.isQualified()) { if (!cons.getModuleName().toString().equals(moduleDefn.getModuleName().toString())) { Name.TypeCons existingMapping = (Name.TypeCons)shortNameToFullNameTypeCons.get(cons.getUnqualifiedName()); if (existingMapping != null) { // Mapping exists for short name. Is it for this data cons. if (existingMapping.getModuleName().toString().equals(cons.getModuleName().toString())) { // Mapping is valid, return unqualified name. return Name.TypeCons.makeUnqualified(cons.getUnqualifiedName()); } } else { // Mapping doesn't exist. Create one. shortNameToFullNameTypeCons.put(cons.getUnqualifiedName(), cons); return Name.TypeCons.makeUnqualified(cons.getUnqualifiedName()); } } else { return Name.TypeCons.makeUnqualified(cons.getUnqualifiedName()); } } return super.visit_Name_TypeCons(cons, arg); } /** * @param defn the source model element to be copied * @param arg unused argument * @return a deep copy of the source model element */ public ModuleDefn visit_ModuleDefn( ModuleDefn defn, T arg) { CALDoc.Comment.Module newCALDocComment = null; if (defn.getCALDocComment() != null) { newCALDocComment = (CALDoc.Comment.Module)defn.getCALDocComment().accept(this, arg); } final int nFriendModules = defn.getNFriendModules(); Friend[] newFriendModules = new Friend[nFriendModules]; for (int i = 0; i < nFriendModules; i++) { newFriendModules[i] = (Friend)defn.getNthFriendModule(i).accept(this, arg); } final int nTopLevelDefns = defn.getNTopLevelDefns(); TopLevelSourceElement[] newTopLevelDefns = new TopLevelSourceElement[nTopLevelDefns]; for (int i = 0; i < nTopLevelDefns; i++) { newTopLevelDefns[i] = (TopLevelSourceElement)defn.getNthTopLevelDefn(i).accept(this, arg); } SourceModel.Import newImportedModules[] = buildImports(); return ModuleDefn.make( newCALDocComment, (Name.Module)defn.getModuleName().accept(this, arg), newImportedModules, newFriendModules, newTopLevelDefns); } private SourceModel.Import[] buildImports () { Set<String> moduleNames = new LinkedHashSet<String>(); // Note: SourceModel.Name, and derived classes, do not implement 'equals' or 'hashCode' // so we can't use them in hash sets, hash maps, etc. // Build up the set of modules to import. for (Name.Qualifiable name : shortNameToFullNameDataCons.values()) { String moduleName = name.getModuleName().toString(); moduleNames.add(moduleName); } for (Name.Qualifiable name : shortNameToFullNameFunction.values()) { String moduleName = name.getModuleName().toString(); moduleNames.add(moduleName); } for (Name.Qualifiable name : shortNameToFullNameTypeClass.values()) { String moduleName = name.getModuleName().toString(); moduleNames.add(moduleName); } for (Name.Qualifiable name : shortNameToFullNameTypeCons.values()) { String moduleName = name.getModuleName().toString(); moduleNames.add(moduleName); } List<SourceModel.Import> allImports = new ArrayList<SourceModel.Import>(); SourceModel.Import preludeImport = null; String preludeModuleName = CAL_Prelude.MODULE_NAME.toString(); // Now build up imports for each module. for (String moduleNameString : moduleNames) { List<String> dataConsNameStringList = new ArrayList<String>(); List<String> functionNameStringList = new ArrayList<String>(); List<String> typeConsNameStringList = new ArrayList<String>(); List<String> typeClassNameStringList = new ArrayList<String>(); for (String shortName :shortNameToFullNameDataCons.keySet()) { Name.Qualifiable qn = shortNameToFullNameDataCons.get(shortName); if (qn.getModuleName().toString().equals(moduleNameString)) { dataConsNameStringList.add(shortName); } } for (String shortName : shortNameToFullNameFunction.keySet()) { Name.Qualifiable qn = shortNameToFullNameFunction.get(shortName); if (qn.getModuleName().toString().equals(moduleNameString)) { functionNameStringList.add(shortName); } } for (String shortName : shortNameToFullNameTypeClass.keySet()) { Name.Qualifiable qn = shortNameToFullNameTypeClass.get(shortName); if (qn.getModuleName().toString().equals(moduleNameString)) { typeClassNameStringList.add(shortName); } } for (String shortName : shortNameToFullNameTypeCons.keySet()) { Name.Qualifiable qn = shortNameToFullNameTypeCons.get(shortName); if (qn.getModuleName().toString().equals(moduleNameString)) { typeConsNameStringList.add(shortName); } } SourceModel.Import importUsing = Import.make ( ModuleName.make(moduleNameString), (String[])functionNameStringList.toArray(new String[]{}), (String[])dataConsNameStringList.toArray(new String[]{}), (String[])typeConsNameStringList.toArray(new String[]{}), (String[])typeClassNameStringList.toArray(new String[]{}) ); if (moduleNameString.equals(preludeModuleName)) { preludeImport = importUsing; } else { allImports.add(importUsing); } } if (preludeImport == null) { preludeImport = SourceModel.Import.make(CAL_Prelude.MODULE_NAME); } allImports.add(0, preludeImport); for (ModuleName moduleName : mustImportModules) { String moduleNameString = moduleName.toString(); boolean found = false; for (Iterator<String> moduleNamesIterator = moduleNames.iterator(); moduleNamesIterator.hasNext(); ) { if (moduleNameString.equals((String)moduleNamesIterator.next())) { found = true; } } if (!found) { Import imp = Import.make(moduleName); allImports.add(imp); } } return (Import[])allImports.toArray(new Import[]{}); } ModuleDefn generateImports (SourceModel.ModuleDefn moduleDefn) { return (ModuleDefn)moduleDefn.accept(this, null); } } private static final class DataConstructorInfo { /** DataConstructor associated with this information. */ final DataConstructor dataConstructor; /** All the FieldName for this data constructor. */ final Set<FieldName> allFieldNames; /** * FieldName -> String * Map FieldName to the name of the Java accessor method for * that field in the generated Java class. */ final Map<FieldName, String> fieldJavaAccessorMethodNames; /** * FieldName -> JavaTypeName * Map FieldName to the corresponding Java type. */ final Map<FieldName, JavaTypeName> fieldTypeNames; /** * Name of the inner class for the data constructor. */ final String innerClassName; /** Foreign type name for the inner class. */ final String calForeignTypeName; /** * True if the data constructor has fields which are not * common with the other data constructors for the type. */ final boolean containsFields; DataConstructorInfo ( DataConstructor dataConstructor, Map<FieldName, String> fieldAccessorMethodNames, Map<FieldName, JavaTypeName> fieldTypeNames, boolean containsFields) { this.dataConstructor = dataConstructor; this.fieldJavaAccessorMethodNames = fieldAccessorMethodNames; this.fieldTypeNames = fieldTypeNames; this.innerClassName = fixupClassName(dataConstructor.getName().getUnqualifiedName()); this.calForeignTypeName = "J" + innerClassName; this.containsFields = containsFields; allFieldNames = new LinkedHashSet<FieldName>(); for (int i = 0, n = dataConstructor.getNArgumentNames(); i < n; ++i) { allFieldNames.add (dataConstructor.getNthFieldName (i)); } } } /** * Class used to hold information about a TypeConstructor. * This information is used in generating the Java and CAL code * necessary to use input/output to move values between Java and * CAL. * @author rcypher * */ private static final class TypeConstructorInfo { /** The TypeConstructor this information applies to. */ final TypeConstructor typeConstructor; /** The name of the generated Java class corresponding to the type constructor. */ final String javaClassName; /** The CAL name used for the foreign type declartion of the generated Java class. */ String calForeignTypeName; /** Is the type constructor an enumeration. (i.e. all data constructors have zero fields) */ final boolean isEnumerationType; /** Set of FieldName. All field names from all data constructors in the type. */ final Set<FieldName> allFieldNames; /** * FieldName -> (Set of JavaTypeName). * Maps a FieldName to the corresponding Java types. * Fields with the same names in different data constructors * can have different types. */ final Map<FieldName, Set<JavaTypeName>> allFieldTypeNames; /** * Set of FieldName. * Fields which appear with same name/type in all data constructors. */ final Set<FieldName> commonFieldNames; /** FieldName -> JavaTypeName */ final Map<FieldName, JavaTypeName> commonFieldTypes; /** * FieldName -> String * The names of the Java fields corresponding to the CAL fields. */ final Map<FieldName, String> fieldJavaNames; /** * FieldName -> String * Map FieldName to the name of the Java accessor method * in the generated Java class. */ final Map<FieldName, String> fieldJavaAccessorMethodNames; /** * FieldName -> (Map DataConstructor -> String). * The Java field accessor methods are declared as * foreign functions in the generated CAL. * From a FieldName you can access a Map of the * data constructors containing the field to the name of * the foreign function in the generated CAL. */ final Map<FieldName, Map<DataConstructor, String>> calFieldAccessorFunctionNames; /** * FieldName -> (Map JavaTypeName -> String) * For a FieldName a Map of the corresponding Java types * to the CAL foreign name for that type. */ final Map<FieldName, Map<JavaTypeName, String>> calFieldForeignTypes; /** * DataConstructor -> DataConstructorInfo * Information for each data constructor. */ final Map<DataConstructor, DataConstructorInfo> dataConstructorInfo = new LinkedHashMap<DataConstructor, DataConstructorInfo>(); TypeConstructorInfo ( TypeConstructor typeConstructor, String javaClassName, boolean isEnumerationType, Set<FieldName> allFieldNames, Map<FieldName, Set<JavaTypeName>> allFieldTypeNames, Set<FieldName> commonFieldNames, Map<FieldName, String> fieldJavaNames, Map<FieldName, String> fieldJavaAccessorMethodNames, Map<FieldName, Map<JavaTypeName, String>> calFieldForeignTypes, Map<QualifiedName, JavaTypeName> typeToClassMappings ) { this.typeConstructor = typeConstructor; this.javaClassName = javaClassName; this.isEnumerationType = isEnumerationType; this.allFieldNames = allFieldNames; this.allFieldTypeNames = allFieldTypeNames; this.commonFieldNames = commonFieldNames; this.fieldJavaNames = fieldJavaNames; this.fieldJavaAccessorMethodNames = fieldJavaAccessorMethodNames; this.calFieldForeignTypes = calFieldForeignTypes; this.calForeignTypeName = "J" + javaClassName; this.calFieldAccessorFunctionNames = new LinkedHashMap<FieldName, Map<DataConstructor, String>>(); this.commonFieldTypes = new LinkedHashMap<FieldName, JavaTypeName>(); for (FieldName fn : commonFieldNames) { Set<JavaTypeName> fieldTypes = this.allFieldTypeNames.get(fn); assert (fieldTypes.size() == 1); commonFieldTypes.put(fn, fieldTypes.iterator().next()); } for (int i = 0, n = typeConstructor.getNDataConstructors(); i < n; ++i) { DataConstructor dc = typeConstructor.getNthDataConstructor(i); for (int j = 0, k = dc.getArity(); j < k; ++j) { FieldName fn = dc.getNthFieldName(j); Map<DataConstructor, String> calAccessorNames = calFieldAccessorFunctionNames.get(fn); if (calAccessorNames == null) { calAccessorNames = new LinkedHashMap<DataConstructor, String>(); calFieldAccessorFunctionNames.put(fn, calAccessorNames); } String javaAccessorName = (String)fieldJavaAccessorMethodNames.get(fn); String className; if (commonFieldNames.contains(fn)) { className = javaClassName; } else { className = fixupClassName(dc.getName().getUnqualifiedName()); } String calAccessorName = "j" + className + "_" + javaAccessorName; calAccessorNames.put(dc, calAccessorName); } } } } /** List of LogRecord */ private final List<LogRecord> ioLogMessages = new ArrayList<LogRecord>(); IO_Source_Generator () { } private void logMessage (Level level, String message) { ioLogMessages.add(new LogRecord(level, message)); } private final String getClassName (TypeConstructor typeConstructor) { String unqualifiedName = typeConstructor.getName().getUnqualifiedName() + "_"; return fixupClassName(unqualifiedName); } /** * Return the name of the class, in a form that can be used in source code. * eg. [[B ==> byte[][]. * CALExecutor$ForeignFunctionException ==> CALExecutor.ForiegnFunctionException. * @param name * @return String */ private static final String fixupClassName (String name) { // Count the number of array dimensions (if any). int i = 0; while (name.startsWith("[")) { i++; name = name.substring(1); } if (name.startsWith ("L") && name.endsWith(";")) { // This is a fully qualified class name. name = name.substring (1, name.length() - 1); } else if (name.equals ("Z")) { // boolean name = "boolean"; } else if (name.equals ("B")) { name = "byte"; } else if (name.equals ("C")) { name = "char"; } else if (name.equals("S")) { name = "short"; } else if (name.equals ("I")) { name = "integer"; } else if (name.equals ("J")) { name = "long"; } else if (name.equals ("F")) { name = "float"; } else if (name.equals("D")) { name = "double"; } for (int j = 0; j < i; ++j) { name = name + "[]"; } // Substitute . for $ name = name.replace ('$', '.'); return name; } private final Set<FieldName> getCommonFieldNames (TypeConstructor typeConstructor) { Set<FieldName> commonFieldNames = new LinkedHashSet<FieldName>(); int nDCs = typeConstructor.getNDataConstructors(); DataConstructor firstDC = typeConstructor.getNthDataConstructor(0); for (int i = 0; i < firstDC.getArity(); ++i) { FieldName fn = firstDC.getNthFieldName(i); commonFieldNames.add(fn); } for (int i = 1; i < nDCs; ++i) { DataConstructor dc = typeConstructor.getNthDataConstructor(i); Set<FieldName> commonFieldNamesClone = new LinkedHashSet<FieldName>(commonFieldNames); for (int j = 0, dcArity = dc.getArity(); j < dcArity; ++j) { commonFieldNamesClone.remove(dc.getNthFieldName(j)); } commonFieldNames.removeAll(commonFieldNamesClone); } return commonFieldNames; } private final String getJavaFieldNameFromFieldName (FieldName fn) { String fieldName = fixupFieldName(fn.toString()); if (!fieldName.startsWith("_")) { fieldName = "_" + fieldName; } return fieldName; } /** * When deconstructing a data type the names assigned to the members * can have conflicts with java reserved words. We fix this by * prepending a '$'. The '$' is used to avoid creating a new conflict * with another CAL variable. * @param varName * @return String */ private final String fixupFieldName(String varName) { if (JavaReservedWords.javaLanguageKeywords.contains(varName)) { return varName + "_"; } // The optimizer will generate symbols sometimes where the first character // of the name between the last two '$' is a digit if (Character.isDigit(varName.charAt(0))){ return "_" + varName; } if (varName.equals("QualifiedName")) { varName = varName + "_"; } varName = varName.replace ('.', '_'); varName = varName.replace ('#', '_'); return varName; } }