/* * 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. */ /* * JavaBindingGenerator.java * Creation date: Jul 12, 2006 * By: RCypher */ package org.openquark.cal.internal.javamodel; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.SortedSet; import java.util.TreeMap; import java.util.TreeSet; import org.openquark.cal.caldoc.CALDocToJavaDocUtilities; import org.openquark.cal.compiler.CALDocComment; import org.openquark.cal.compiler.ClassMethod; import org.openquark.cal.compiler.DataConstructor; import org.openquark.cal.compiler.ForeignTypeInfo; import org.openquark.cal.compiler.Function; import org.openquark.cal.compiler.FunctionalAgent; import org.openquark.cal.compiler.ModuleName; import org.openquark.cal.compiler.ModuleTypeInfo; import org.openquark.cal.compiler.QualifiedName; import org.openquark.cal.compiler.SourceModel; import org.openquark.cal.compiler.TypeClass; 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.internal.javamodel.JavaExpression.JavaField; import org.openquark.cal.internal.javamodel.JavaExpression.LiteralWrapper; import org.openquark.cal.internal.javamodel.JavaExpression.MethodInvocation; import org.openquark.cal.internal.javamodel.JavaExpression.MethodVariable; import org.openquark.cal.internal.javamodel.JavaStatement.JavaDocComment; import org.openquark.cal.internal.javamodel.JavaStatement.MultiLineComment; import org.openquark.cal.internal.javamodel.JavaStatement.ReturnStatement; import org.openquark.cal.machine.Module; import org.openquark.cal.module.Cal.Core.CAL_Prelude; /** * This class is used to generate the Java source code for a binding class * for a CAL module. * * This class is the preferred means for client code to refer to CAL entities. * For example rather than referring to List.sort by using literal strings the * client code would use the field 'QualifiedName sort = new QualifiedName("List", "sort");' * If at some point the name of the function is changed in the CAL code the * binding class will be regenerated and compilation errors will show anywhere that * client code was referring to this function. * * A binding class contains a field with the name of the corresponding CAL module. * It contains four inner classes: Functions, DataConstructors, TypeConstructors, and TypeClasses. * * The inner classes contain a QualifiedName field corresponding to each entity of that type. * In addition the Functions and DataConstructors classes contain helper methods which make * it easier to generate SourceModel for applications of the function/data constructor. * * * @author RCypher * */ public class JavaBindingGenerator { static final JavaTypeName[] EMPTY_TYPE_NAME_ARRAY = new JavaTypeName[0]; /** * The suffix to append to the binding class name when it is the binding class for * non public entities. */ private static final String PRIVATE_BINDING_SUFFIX = "_internal"; /** * The prefix applied to the name of the generated java binding class. */ private static final String BINDING_CLASS_PREFIX = "CAL_"; /** The name of the inner class for data constructors. */ private static final String DATA_CONSTRUCTOR_CLASS_NAME = "DataConstructors"; /** The name of the inner class for functions. */ private static final String FUNCTION_CLASS_NAME = "Functions"; /** The name of the inner class for type classes. */ private static final String TYPECLASS_CLASS_NAME = "TypeClasses"; /** The name of the inner classes for type constructors (i.e. data types). */ private static final String TYPECONSTRUCTOR_CLASS_NAME = "TypeConstructors"; /** The name of the field which will hold the module name. */ private static final String MODULE_NAME_FIELD_NAME = "MODULE_NAME"; /** Constant corresponding to Modifier.PUBLIC | Modifier.STATIC | Modifier.FINAL. */ private static final int PUBLIC_STATIC_FINAL = Modifier.PUBLIC | Modifier.STATIC | Modifier.FINAL; /** The JavaTypeName for QualifiedName. */ private static final JavaTypeName QUALIFIED_NAME_TYPE_NAME = JavaTypeName.make(QualifiedName.class); /** The JavaTypeName for SourceModel.Expr. */ private static final JavaTypeName SOURCE_MODEL_EXPR_TYPE_NAME = JavaTypeName.make(SourceModel.Expr.class); /** The JavaTypeName for SourceModel.Expr.Var. */ private static final JavaTypeName SOURCE_MODEL_EXPR_VAR_TYPE_NAME = JavaTypeName.make(SourceModel.Expr.Var.class); /** The JavaTypeName for sourceModel.Expr.DataCons. */ private static final JavaTypeName SOURCE_MODEL_EXPR_DATA_CONS_TYPE_NAME = JavaTypeName.make(SourceModel.Expr.DataCons.class); /** The JavaTypeName for SourceModel.Expr.Application. */ private static final JavaTypeName SOURCE_MODEL_EXPR_APPLICATION_TYPE_NAME = JavaTypeName.make(SourceModel.Expr.Application.class); /** The name of the field used to store the hash value for the concatenated JavaDoc. */ private static final String JAVADOC_HASH_FIELD_NAME = "javaDocHash"; /** Set this flag to true if debugging output is desired. */ private static final boolean SHOW_DEBUGGING_OUTPUT = false; /** The module for which a binding class is being generated. */ private final Module module; /** The module type info for the module for which a binding class is being generated. */ private final ModuleTypeInfo moduleTypeInfo; /** Flag indicating that the binding class is for public (vs. non public) entities. */ private final boolean publicEntities; /** JavaField for referring to the module name field in the generated binding class. */ private final JavaField moduleNameField; /** The JavaTypeName for the generated binding class. */ private final JavaTypeName bindingClassTypeName; /** The unqualified name of the Java binding class. */ private final String bindingClassName; /** The package in which generated binding classes are placed. */ private final String bindingClassPackage; /** * Generate the source code for the Java binding class for the named module. * @param moduleTypeInfo * @param targetPackage * @param publicEntities * @return The source of the generated class. */ public static final String getJavaBinding (ModuleTypeInfo moduleTypeInfo, String targetPackage, boolean publicEntities) throws UnableToResolveForeignEntityException { JavaBindingGenerator instance = new JavaBindingGenerator (moduleTypeInfo, targetPackage, publicEntities); return instance.buildJavaBinding(); } /** * For the given module check that a binding class exists and that it * is current with the content of the CAL module. * @param moduleTypeInfo * @param targetPackage * @param publicEntities * @param showFailureInfo if true, extra debug info will be output for any failed binding check * @return true if the binding class exists and is up-to-date, false otherwise. */ public static final boolean checkJavaBindingClass (ModuleTypeInfo moduleTypeInfo, String targetPackage, boolean publicEntities, boolean showFailureInfo) throws UnableToResolveForeignEntityException { boolean showDebugOrFailureInfo = showFailureInfo || SHOW_DEBUGGING_OUTPUT; try { // Generate the Java model for a Java binding class based on the current CAL // module. JavaBindingGenerator instance = new JavaBindingGenerator (moduleTypeInfo, targetPackage, publicEntities); JavaClassRep bindingClassModel = instance.buildJavaBindingModel(); // Check to see if the Java binding class already exists. Class<?> existingBindingClass = Class.forName(bindingClassModel.getClassName().getFullJavaSourceName(), true, moduleTypeInfo.getModule().getForeignClassLoader()); if (bindingClassModel.getNInnerClasses() != existingBindingClass.getDeclaredClasses().length) { if (showDebugOrFailureInfo) { System.out.println("bindingClassModel.getNInnerClasses() : " + bindingClassModel.getNInnerClasses() + " != existingBindingClass.getDeclaredClasses().length : " + existingBindingClass.getDeclaredClasses().length); } return false; } // Walk the Java model building up information about the QualifiedName // fields in the class. JavaBindingClassChecker bindingClassChecker = new JavaBindingClassChecker(); bindingClassModel.accept(bindingClassChecker, null); if (!checkInnerBindingClass( existingBindingClass, FUNCTION_CLASS_NAME, bindingClassChecker.functionFields, bindingClassChecker.functions, null, showDebugOrFailureInfo)) { if (showDebugOrFailureInfo) { System.out.println("Inner function binding class mismatch."); } return false; } if (!checkInnerBindingClass( existingBindingClass, DATA_CONSTRUCTOR_CLASS_NAME, bindingClassChecker.dataConsFields, bindingClassChecker.dataConsFunctions, bindingClassChecker.dataConsOrdinalFields, showDebugOrFailureInfo)) { if (showDebugOrFailureInfo) { System.out.println("Inner data constructor binding class mismatch."); } return false; } if (!checkInnerBindingClass( existingBindingClass, TYPECLASS_CLASS_NAME, bindingClassChecker.typeClassFields, null, null, showDebugOrFailureInfo)) { if (showDebugOrFailureInfo) { System.out.println("Inner type class binding class mismatch."); } return false; } if (!checkInnerBindingClass( existingBindingClass, TYPECONSTRUCTOR_CLASS_NAME, bindingClassChecker.typeConstructorFields, null, null, showDebugOrFailureInfo)) { if (showDebugOrFailureInfo) { System.out.println("Inner type constructor binding class mismatch."); } return false; } // Check the JavaDoc has value computed for the generated class. int newJavaDocHash = 0; for (int i = 0, n = bindingClassModel.getNFieldDeclarations(); i < n; ++i) { JavaFieldDeclaration jfd = bindingClassModel.getFieldDeclaration(i); if (jfd.getFieldName().equals(JavaBindingGenerator.JAVADOC_HASH_FIELD_NAME)) { newJavaDocHash = ((Integer)((LiteralWrapper)jfd.getInitializer()).getLiteralObject()).intValue(); break; } } try { Field existingJavaDocHashField = existingBindingClass.getDeclaredField(JavaBindingGenerator.JAVADOC_HASH_FIELD_NAME); int existingJavaDocHash = existingJavaDocHashField.getInt(null); if (newJavaDocHash != existingJavaDocHash) { if (showDebugOrFailureInfo) { System.out.println("newJavaDocHash : " + newJavaDocHash + " != existingJavaDocHash : " + existingJavaDocHash); } return false; } } catch (NoSuchFieldException e) { if (showDebugOrFailureInfo) { System.out.println("Exception: " + e); } return false; } catch (IllegalAccessException e) { if (showDebugOrFailureInfo) { System.out.println("Exception: " + e); } return false; } } catch (ClassNotFoundException e) { if (showDebugOrFailureInfo) { System.out.println("Exception: " + e); } return false; } return true; } /** * Compare the existing inner binding class to what it should actually be. * @param bindingClass - the existing binding class. * @param innerClassName - the name of the inner class to check. * @param expectedQualifiedNameFields - Map of String -> JavaFieldDeclaration. (i.e. field name to field declaration) * @param expectedFunctions - Set of JavaMethod. (i.e. method name to method) * @param expectedIntFields - Map of String -> JavaFieldDeclaration. (i.e. field name to field declaration) * @param showDebugOrFailureInfo if true, extra debug info will be output for any failed binding check * @return true if the existing and expected inner classes match. */ private static final boolean checkInnerBindingClass ( Class<?> bindingClass, String innerClassName, Map<String, JavaFieldDeclaration> expectedQualifiedNameFields, Set<JavaMethod> expectedFunctions, Map<String, JavaFieldDeclaration> expectedIntFields, boolean showDebugOrFailureInfo) { // Get the inner classes from the existing binding class. Class<?> declaredClasses[] = bindingClass.getDeclaredClasses(); Class<?> innerClass = null; for (int i = 0; i < declaredClasses.length; ++i) { String dcName = declaredClasses[i].getName(); dcName = dcName.substring(dcName.lastIndexOf("$") + 1); if (dcName.equals(innerClassName)) { innerClass = declaredClasses[i]; break; } } Map<String, Field> declaredQualifiedNameFields = new HashMap<String, Field>(); SortedSet<String> declaredQualifiedNameFieldsNames = new TreeSet<String>(); Map<String, Field> declaredIntFields = new HashMap<String, Field>(); if (innerClass != null) { Field declaredFields[] = innerClass.getDeclaredFields(); for (int i = 0, n = declaredFields.length; i < n; ++i) { Field declaredField = declaredFields[i]; if (declaredField.getType().equals(QualifiedName.class)) { declaredQualifiedNameFields.put(declaredField.getName(), declaredField); declaredQualifiedNameFieldsNames.add(declaredField.getName()); } if (declaredField.getType().equals(int.class)) { declaredIntFields.put(declaredField.getName(), declaredField); } } } SortedSet<String> expectedQualifiedNameFieldsNames = new TreeSet<String>(); for (final String expectedQualifiedNameFieldName : expectedQualifiedNameFields.keySet()) { expectedQualifiedNameFieldsNames.add(expectedQualifiedNameFieldName); } // Check the qualified name fields. if (!expectedQualifiedNameFieldsNames.equals(declaredQualifiedNameFieldsNames)) { // Different number of entities in the existing class and // the current module. if (showDebugOrFailureInfo) { System.out.println("expectedQualifiedNameFieldsNames: " + expectedQualifiedNameFieldsNames); System.out.println("... does not equal ..."); System.out.println("declaredQualifiedNameFieldsNames: " + declaredQualifiedNameFieldsNames); } return false; } // check the ordinal fields. if (expectedIntFields == null) { expectedIntFields = new HashMap<String, JavaFieldDeclaration>(); } if (expectedIntFields.size() != declaredIntFields.size()) { if (showDebugOrFailureInfo) { System.out.println("expectedIntFields.size() : " + expectedIntFields.size() + " != declaredIntFields.size() : " + declaredIntFields.size()); } return false; } // Check the field names and values. for (final String fieldName : expectedIntFields.keySet()) { JavaFieldDeclaration jfd = expectedIntFields.get(fieldName); Field existingField = declaredIntFields.get(fieldName); if (existingField == null) { if (showDebugOrFailureInfo) { System.out.println("Missing field named: " + fieldName); } return false; } try { int existingValue = existingField.getInt(null); JavaExpression init = jfd.getInitializer(); if (!(init instanceof LiteralWrapper)) { if (showDebugOrFailureInfo) { System.out.println("Field " + fieldName + " does not equal " + existingValue); } return false; } Object value = ((LiteralWrapper)init).getLiteralObject(); if (!(value instanceof Integer)) { if (showDebugOrFailureInfo) { System.out.println("Field " + fieldName + " does not equal " + existingValue); } return false; } if (existingValue != ((Integer)value).intValue()) { if (showDebugOrFailureInfo) { System.out.println("Field " + fieldName + " does not equal " + existingValue); } return false; } } catch (IllegalAccessException e) { if (showDebugOrFailureInfo) { System.out.println("Exception: " + e); } return false; } } if (expectedFunctions == null) { expectedFunctions = new HashSet<JavaMethod>(); } // Now check the functions. int nDeclaredMethods = 0; if (innerClass != null) { nDeclaredMethods = innerClass.getDeclaredMethods().length; } if (nDeclaredMethods != expectedFunctions.size()) { if (showDebugOrFailureInfo) { System.out.println("nDeclaredMethods : " + nDeclaredMethods + " != expectedFunctions.size() : " + expectedFunctions.size()); } return false; } // Now we need to confirm that the functions are the same. // i.e. same name and type. for (final JavaMethod bindingClassMethod : expectedFunctions) { String bindingMethodName = bindingClassMethod.getMethodName(); int nParams = bindingClassMethod.getNParams(); Class<?> bindingMethodParamTypes[] = new Class<?>[nParams]; for (int iParam = 0; iParam < nParams; ++iParam) { JavaTypeName paramType = bindingClassMethod.getParamType(iParam); if (paramType instanceof JavaTypeName.Primitive) { if (paramType instanceof JavaTypeName.Primitive.Boolean) { bindingMethodParamTypes[iParam] = boolean.class; } else if (paramType instanceof JavaTypeName.Primitive.Byte) { bindingMethodParamTypes[iParam] = byte.class; } else if (paramType instanceof JavaTypeName.Primitive.Char) { bindingMethodParamTypes[iParam] = char.class; } else if (paramType instanceof JavaTypeName.Primitive.Double) { bindingMethodParamTypes[iParam] = double.class; } else if (paramType instanceof JavaTypeName.Primitive.Float) { bindingMethodParamTypes[iParam] = float.class; } else if (paramType instanceof JavaTypeName.Primitive.Int) { bindingMethodParamTypes[iParam] = int.class; } else if (paramType instanceof JavaTypeName.Primitive.Long) { bindingMethodParamTypes[iParam] = long.class; } else if (paramType instanceof JavaTypeName.Primitive.Short) { bindingMethodParamTypes[iParam] = short.class; } else { if (showDebugOrFailureInfo) { System.out.println("Unrecognized primitive param type: " + paramType); } return false; } } else if (paramType.equals(JavaBindingGenerator.SOURCE_MODEL_EXPR_TYPE_NAME)) { bindingMethodParamTypes[iParam] = SourceModel.Expr.class; } else if (paramType.equals(JavaTypeName.STRING)) { bindingMethodParamTypes[iParam] = java.lang.String.class; } else { if (showDebugOrFailureInfo) { System.out.println("Unrecognized param type: " + paramType); } return false; } } if (innerClass != null) { try { Method declaredMethod = innerClass.getDeclaredMethod(bindingMethodName, bindingMethodParamTypes); if (declaredMethod == null) { if (showDebugOrFailureInfo) { System.out.println("Missing method: " + bindingMethodName + ", params: " + Arrays.asList(bindingMethodParamTypes)); } return false; } } catch (NoSuchMethodException e) { // Mismatch so return false. if (showDebugOrFailureInfo) { System.out.println("Exception: " + e); } return false; } } } return true; } /** * Returns the full name of the package for the class generated for the given module. * For example, if the target package is foo.bar and the module's name is A.B.C, the returned * package name would be foo.bar.A.B (into which the class CAL_C would go). * * @param targetPackage the name of the target package. * @param moduleName the module name. * @return the full name of the package for the class generated for the given module. */ public static String getPackageName(String targetPackage, ModuleName moduleName) { String basePackage = targetPackage == null ? "" : targetPackage; StringBuilder packageName = new StringBuilder(basePackage); // to the base package we add all the components but the last to the package name // the last component is to be transformed to a class name for (int i = 0, nMinusOne = moduleName.getNComponents() - 1; i < nMinusOne; i++) { if (packageName.length() > 0) { packageName.append('.'); } packageName.append(moduleName.getNthComponent(i)); } return packageName.toString(); } /** * Returns the name of the class generated for the given module. * @param moduleName the module name. * @param publicEntities whether only references to public entities are to be included in the generated class. * @return the name of the class generated for the given module. */ public static final String getClassName (ModuleName moduleName, boolean publicEntities) { String unqualifiedModuleName = moduleName.getLastComponent(); return JavaSourceGenerator.fixupClassName(publicEntities ? (BINDING_CLASS_PREFIX + unqualifiedModuleName) : (BINDING_CLASS_PREFIX + unqualifiedModuleName + PRIVATE_BINDING_SUFFIX)); } /** * Create an instance of JavaBindingGenerator. * @param moduleTypeInfo * @param targetPackage * @param publicEntities */ private JavaBindingGenerator (ModuleTypeInfo moduleTypeInfo, String targetPackage, boolean publicEntities) { this.moduleTypeInfo = moduleTypeInfo; this.module = moduleTypeInfo.getModule(); this.publicEntities = publicEntities; // First create the top level class. // This will be in the package org.openquark.cal.module. // The binding class for public members will have the same name as the module. // The binding class for non-public members will be named with the name of the // module with "_internal" appended. this.bindingClassName = JavaBindingGenerator.getClassName(moduleTypeInfo.getModuleName(), publicEntities); this.bindingClassPackage = getPackageName(targetPackage, moduleTypeInfo.getModuleName()); this.bindingClassTypeName = JavaTypeName.make(bindingClassPackage + "." + bindingClassName, false); this.moduleNameField = new JavaExpression.JavaField.Static (this.bindingClassTypeName, MODULE_NAME_FIELD_NAME, JavaTypeName.MODULE_NAME); } /** * Generate the Java model for the Java binding class for the named module. * @return The model of the generated class. */ private final JavaClassRep buildJavaBindingModel () throws UnableToResolveForeignEntityException { // We want to generate a Java class that makes it easier to bind // client Java code to the Java code generated from the CAL source // in a safe fashion. // First create the top level class. // This will be in the package org.openquark.cal.module. // The binding class for public members will have the same name as the module. // The binding class for non-public members will be named with the name of the // module with "_internal" appended. JavaClassRep bindingClass = new JavaClassRep (bindingClassTypeName, JavaTypeName.OBJECT, Modifier.PUBLIC | Modifier.FINAL, EMPTY_TYPE_NAME_ARRAY); // We want to insert the CALDoc comment for the module as a comment for the class. JavaDocComment jdc; CALDocComment moduleComment = moduleTypeInfo.getCALDocComment(); if (moduleComment != null) { String convertedComment = calDocCommentToJavaComment (moduleComment, null, true); jdc = new JavaDocComment (convertedComment); } else { // If there is no module comment from the CAL source we should just // create a generic one. jdc = new JavaDocComment ("This class (" + bindingClassName + ") provides Java constants, methods, etc."); jdc.addLine("which make it easier to bind client Java code to the Java code generated"); jdc.addLine("from CAL source in a safe fashion"); } bindingClass.setJavaDoc(jdc); // 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 (" + bindingClass.getClassName().getUnqualifiedJavaSourceName() + ".java)"); mlc.addLine("was generated from CAL module: " + module.getName() + "."); mlc.addLine("The constants and methods provided are intended to facilitate accessing the"); mlc.addLine(module.getName() + " module from Java code."); mlc.addLine(" "); mlc.addLine("Creation date: " + new Date()); mlc.addLine("--!>"); mlc.addLine(" "); bindingClass.setComment(mlc); // Add the module name field. JavaFieldDeclaration moduleNameDeclaration = new JavaFieldDeclaration (PUBLIC_STATIC_FINAL, JavaTypeName.MODULE_NAME, MODULE_NAME_FIELD_NAME, new JavaExpression.MethodInvocation.Static( JavaTypeName.MODULE_NAME, "make", new JavaExpression[]{JavaExpression.LiteralWrapper.make(moduleTypeInfo.getModuleName().toSourceText())}, new JavaTypeName[]{JavaTypeName.STRING}, JavaTypeName.MODULE_NAME)); bindingClass.addFieldDeclaration(moduleNameDeclaration); // Add the inner class for type classes. buildTypeClassInnerClass(bindingClass, moduleTypeInfo, bindingClassName); // Add the inner class for type constructors. buildTypeConstructorsInnerClass(bindingClass, moduleTypeInfo, bindingClassName); // Add the inner class for data constructors. buildDataConstructorsInnerClass(bindingClass, moduleTypeInfo, bindingClassName); // Add the inner class for functions. buildFunctionsInnerClass(bindingClass, moduleTypeInfo, bindingClassName); int javaDocHash = computeJavaDocHash(bindingClass); JavaFieldDeclaration javaDocHashField = new JavaFieldDeclaration(PUBLIC_STATIC_FINAL, JavaTypeName.INT, JavaBindingGenerator.JAVADOC_HASH_FIELD_NAME, LiteralWrapper.make(Integer.valueOf(javaDocHash))); JavaDocComment javaDocHashFieldComment = new JavaDocComment("A hash of the concatenated JavaDoc for this class (including inner classes)."); javaDocHashFieldComment.addLine("This value is used when checking for changes to generated binding classes."); javaDocHashField.setJavaDoc(javaDocHashFieldComment); bindingClass.addFieldDeclaration(javaDocHashField); return bindingClass; } /** * Generate the source code for the Java binding class for the named module. * @return The source of the generated class. */ private final String buildJavaBinding () throws UnableToResolveForeignEntityException { JavaClassRep bindingClass = buildJavaBindingModel(); // Generate the source. try { String source = JavaSourceGenerator.generateSourceCode(bindingClass); return source; } catch (JavaGenerationException e) { throw new NullPointerException(e.getLocalizedMessage()); } } /** * Generate the inner class for constants and helper methods relating to CAL functions. * @param bindingClass * @param moduleTypeInfo * @param bindingClassName */ private final void buildFunctionsInnerClass ( JavaClassRep bindingClass, ModuleTypeInfo moduleTypeInfo, String bindingClassName) throws UnableToResolveForeignEntityException { if (moduleTypeInfo.getNFunctions() + moduleTypeInfo.getNTypeClasses() == 0) { return; } // Create the Functions class. JavaTypeName functionsClassTypeName = JavaTypeName.make (bindingClassPackage + "." + bindingClassName + "." + FUNCTION_CLASS_NAME, false); JavaClassRep functionsClass = new JavaClassRep ( functionsClassTypeName, JavaTypeName.OBJECT, PUBLIC_STATIC_FINAL, EMPTY_TYPE_NAME_ARRAY); // Add the class JavaDoc JavaDocComment jdc = new JavaDocComment("This inner class (" + FUNCTION_CLASS_NAME + ") contains constants"); jdc.addLine("and methods related to binding to CAL functions in the " + moduleTypeInfo.getModuleName() + " module."); functionsClass.setJavaDoc(jdc); // Build up and sort the list of function names. Map<String, FunctionalAgent> nameToFuncInfo = new TreeMap<String, FunctionalAgent>(); for (int i = 0, n = moduleTypeInfo.getNFunctions(); i < n; ++i) { Function fe = moduleTypeInfo.getNthFunction(i); if (fe.getScope().isPublic() != publicEntities) { continue; } nameToFuncInfo.put(fe.getName().getUnqualifiedName(), fe); } for (int i = 0, n = moduleTypeInfo.getNTypeClasses(); i < n; ++i) { TypeClass tc = moduleTypeInfo.getNthTypeClass(i); for (int j = 0, k = tc.getNClassMethods(); j < k; ++j) { ClassMethod cm = tc.getNthClassMethod(j); if (cm.getScope().isPublic() == publicEntities) { nameToFuncInfo.put(cm.getName().getUnqualifiedName(), cm); } } } if (nameToFuncInfo.size() == 0) { return; } bindingClass.addInnerClass(functionsClass); // Now go over the function names and generate the helper functions and // QualifiedName constant. for (final Map.Entry<String, FunctionalAgent> entry : nameToFuncInfo.entrySet()) { String calFuncName = entry.getKey(); FunctionalAgent functionalAgent = entry.getValue(); CALDocComment cdc = functionalAgent.getCALDocComment(); // We need to make sure the name for the helper function/qualified name field is // a valid java name. String javaFuncName = fixupVarName(calFuncName); // Add a binding method for the CAL function. // Essentially this method builds an application // of the function to the supplied arguments using our // source model. // Build up the argument names and types. TypeExpr te = functionalAgent.getTypeExpr(); int nArgs = te.getArity(); int nNamedArgs = functionalAgent.getNArgumentNames(); String paramNames[] = CALDocToJavaDocUtilities.getArgumentNamesFromCALDocComment(cdc, functionalAgent); if (paramNames.length != nArgs) { throw new NullPointerException ("Mismatch between arity and number of arguments for " + calFuncName); } String origParamNames[] = new String[paramNames.length]; System.arraycopy(paramNames, 0, origParamNames, 0, origParamNames.length); JavaTypeName paramTypes[] = new JavaTypeName [paramNames.length]; // If we have named arguments we should use those names. Set<String> paramNamesSet = new HashSet<String>(); for (int i = 0; i < paramNames.length; ++i) { String name = paramNames[i]; if (name == null) { if (i < nNamedArgs) { name = functionalAgent.getArgumentName(i); } if (name == null || name.equals("")) { name = null; // There is no name for this argument in the functional entity. This // usually occurs with foreign functions and class methods because // you don't have to explicitly name the arguments. // Check to see if there was a CALDoc comment that named the arguments. if (cdc != null && cdc.getNArgBlocks() == paramNames.length) { name = cdc.getNthArgBlock(i).getArgName().getCalSourceForm(); } // If we still don't have an argument name just build one. if (name == null) { name = "arg_" + (i+1); } } // Make sure name is unique. String baseName = name; int suffix = 0; while (paramNamesSet.contains(name)) { name = baseName + "_" + suffix++; } } origParamNames[i] = name; paramNames[i] = fixupVarName(name); paramTypes[i] = SOURCE_MODEL_EXPR_TYPE_NAME; } // Get the JavaDoc for the helper function. If there is CALDoc // for the function we translate it to JavaDoc. JavaDocComment funcComment; if (cdc != null) { funcComment = new JavaDocComment(calDocCommentToJavaComment(cdc, functionalAgent, false)); funcComment = fixupJavaDoc(funcComment, origParamNames, paramNames); } else { funcComment = new JavaDocComment("Helper binding method for function: " + calFuncName + ". "); for (int iName = 0; iName < paramNames.length; ++iName) { funcComment.addLine("@param " + paramNames[iName]); } funcComment.addLine("@return the SourceModule.expr representing an application of " + calFuncName); } // Create the method. JavaMethod bindingMethod = new JavaMethod(PUBLIC_STATIC_FINAL, SOURCE_MODEL_EXPR_TYPE_NAME, paramNames, paramTypes, null, javaFuncName); functionsClass.addMethod(bindingMethod); // Add the JavaDoc bindingMethod.setJavaDocComment(funcComment); // Build up the body of the method. // We want to us the QualifiedName field. JavaField nameField = new JavaField.Static(functionsClassTypeName, javaFuncName, QUALIFIED_NAME_TYPE_NAME); // Create an instance of SourceModel.Expr.Var for the CAL function. JavaExpression sourceModelVarCreation = new MethodInvocation.Static( SOURCE_MODEL_EXPR_VAR_TYPE_NAME, "make", nameField, QUALIFIED_NAME_TYPE_NAME, SOURCE_MODEL_EXPR_VAR_TYPE_NAME); // For the comment for this method we want an @see referring to the previous method. String atSee = "@see #" + javaFuncName + "("; for (int iArg = 0; iArg < paramNames.length; ++iArg) { if (iArg == 0) { atSee = atSee + paramTypes[iArg].getFullJavaSourceName(); } else { atSee = atSee + ", " + paramTypes[iArg].getFullJavaSourceName(); } } atSee = atSee + ")"; if (paramNames.length == 0) { // Simply return the Expr.Var bindingMethod.addStatement(new ReturnStatement(sourceModelVarCreation)); } else { // Need to create an application. // Create an array of SourceModel.Expr. The first element is the SourceModel.Expr.Var // for the CAL function the remaining elements are the function arguments. JavaExpression arrayElements[] = new JavaExpression[paramNames.length + 1]; arrayElements[0] = sourceModelVarCreation; for (int i = 1; i <= paramNames.length; ++i) { arrayElements[i] = new JavaExpression.MethodVariable(paramNames[i-1]); } // Build the array creation expression. JavaExpression arrayCreation = new JavaExpression.ArrayCreationExpression(SOURCE_MODEL_EXPR_TYPE_NAME, arrayElements); // Invoke SourceModel.Expr.Application.make() MethodInvocation makeApply = new MethodInvocation.Static( SOURCE_MODEL_EXPR_APPLICATION_TYPE_NAME, "make", arrayCreation, JavaTypeName.makeArrayType(SOURCE_MODEL_EXPR_TYPE_NAME), SOURCE_MODEL_EXPR_APPLICATION_TYPE_NAME); bindingMethod.addStatement(new ReturnStatement (makeApply)); // If any of the argument types correspond to a Java type. // (eg. Prelude.Int, Prelude.Long, etc. we can generate a version // of the binding function that takes these argument types. boolean primArgs = false; TypeExpr[] pieces = te.getTypePieces(); for (int i = 0; i < paramNames.length; ++i) { if (canTypeBeUnboxed(pieces[i])) { primArgs = true; // Change the param type. paramTypes[i] = typeExprToTypeName(pieces[i]); // Since the parameter will now be an int, boolean, long, etc. we // need to create the appropriate SourceModel wrapper around the // value. arrayElements[i+1] = wrapArgument(paramNames[i], pieces[i]); } } if (primArgs) { // Create the alternate helper method. bindingMethod = new JavaMethod(PUBLIC_STATIC_FINAL, SOURCE_MODEL_EXPR_TYPE_NAME, paramNames, paramTypes, null, javaFuncName); functionsClass.addMethod(bindingMethod); JavaStatement.JavaDocComment comment = new JavaStatement.JavaDocComment(atSee); for (int iArg = 0; iArg < paramNames.length; ++iArg) { comment.addLine("@param " + paramNames[iArg]); } comment.addLine("@return the SourceModel.Expr representing an application of " + calFuncName); bindingMethod.setJavaDocComment(comment); arrayCreation = new JavaExpression.ArrayCreationExpression(SOURCE_MODEL_EXPR_TYPE_NAME, arrayElements); makeApply = new MethodInvocation.Static( SOURCE_MODEL_EXPR_APPLICATION_TYPE_NAME, "make", arrayCreation, JavaTypeName.makeArrayType(SOURCE_MODEL_EXPR_TYPE_NAME), SOURCE_MODEL_EXPR_APPLICATION_TYPE_NAME); bindingMethod.addStatement(new ReturnStatement (makeApply)); } } // Add a field for the function name. // 'static final QualifiedName functionName = "functionName";' // We need to check for conflict between the functionName // and java keywords. It is valid to have a CAL // function called assert but not valid to have java code // 'static final Qualifiedname assert = ... JavaFieldDeclaration jfd = makeQualifiedNameDeclaration(javaFuncName, calFuncName); JavaDocComment qfComment = new JavaDocComment("Name binding for function: " + calFuncName + "."); qfComment.addLine(atSee); jfd.setJavaDoc(qfComment); functionsClass.addFieldDeclaration(jfd); } } /** * Fixup the @param tags in a JavaDoc comment. This is sometimes necessary when the original * param name in CAL conflicts with a reserved/key word in java, resulting in the actual * param name being updated. ex. char would be changed to char_. * @param comment * @param originalNames * @param updatedNames * @return the updated JavaDocComment */ private final JavaDocComment fixupJavaDoc (JavaDocComment comment, String[] originalNames, String[] updatedNames) { List<String> lines = new ArrayList<String> (); lines.addAll(comment.getCommentLines()); boolean hasReturn = false; boolean explicitComment = false; for (int i = 0; i < lines.size(); ++i) { String line = lines.get(i); if (i == 0 && line.trim().startsWith("/*")) { explicitComment = true; } if (line.indexOf("@return") >= 0) { hasReturn = true; } for (int iName = 0; iName < originalNames.length; ++iName) { String originalName = originalNames[iName]; String updatedName = updatedNames[iName]; if (originalName.equals(updatedName)) { continue; } String find = "@param " + originalName + " "; String replace = "@param " + updatedName + " "; lines.set(i, line.replaceFirst(find, replace)); } } if (!hasReturn) { if (explicitComment) { lines.add(lines.size()-1, " * @return SourceModel.Expr"); } else { lines.add("@return SourceModel.Expr"); } } return new JavaDocComment(lines); } /** * Build the TypeConstructors inner class. * @param bindingClass * @param moduleTypeInfo * @param bindingClassName */ private final void buildTypeConstructorsInnerClass ( JavaClassRep bindingClass, ModuleTypeInfo moduleTypeInfo, String bindingClassName) { if (moduleTypeInfo.getNTypeConstructors() == 0) { return; } // Create the inner class. JavaTypeName typeConstructorsClassTypeName = JavaTypeName.make (bindingClassPackage + "." + bindingClassName + "." + TYPECONSTRUCTOR_CLASS_NAME, false); JavaClassRep typeConstructorsClass = new JavaClassRep ( typeConstructorsClassTypeName, JavaTypeName.OBJECT, PUBLIC_STATIC_FINAL, EMPTY_TYPE_NAME_ARRAY); // Create the JavaDoc for the inner class. JavaDocComment jdc = new JavaDocComment("This inner class (" + TYPECONSTRUCTOR_CLASS_NAME + ") contains constants"); jdc.addLine("and methods related to binding to CAL TypeConstructors in the " + moduleTypeInfo.getModuleName() + " module."); typeConstructorsClass.setJavaDoc(jdc); // Build up a list of type constructors and sort by name. List<String> typeConstructorNames = new ArrayList<String>(); for (int i = 0, n = moduleTypeInfo.getNTypeConstructors(); i < n; ++i) { TypeConstructor typeCons = moduleTypeInfo.getNthTypeConstructor(i); if (typeCons.getScope().isPublic() != publicEntities) { continue; } typeConstructorNames.add(typeCons.getName().getUnqualifiedName()); } Collections.sort(typeConstructorNames); if (typeConstructorNames.size() == 0) { return; } bindingClass.addInnerClass(typeConstructorsClass); // For each type constructor create a QualifiedName field. // The field will have the same name as the type constructor. for (final String typeConstructorName : typeConstructorNames) { TypeConstructor typeCons = moduleTypeInfo.getTypeConstructor(typeConstructorName); // Add a field for the typeConstructor name. // 'static final QualifiedName typeConstructorName = QualifiedName.make(moduleName, typeConstructorName); // We need to check for conflict between the functionName // and java keywords. It is valid to have a CAL // function called assert but not valid to have java code // 'static final String assert = "assert"; String fieldName = fixupVarName(typeConstructorName); // Since TypeConsApp names are capitalized it is possible to have // a conflict between the name of the field and the name of the top // level class. if (fieldName.equals(this.bindingClassName)) { fieldName = fieldName + "_"; } JavaFieldDeclaration jfd = makeQualifiedNameDeclaration(fieldName, typeConstructorName); // Add JavaDoc for the field declaration. We use the CALDoc if there is any. JavaDocComment typeConstructorComment; CALDocComment cdc = typeCons.getCALDocComment(); if (cdc != null) { typeConstructorComment = new JavaDocComment(calDocCommentToJavaComment(cdc, null, false)); } else { typeConstructorComment = new JavaDocComment("/** Name binding for TypeConsApp: " + typeConstructorName + ". */"); } jfd.setJavaDoc(typeConstructorComment); typeConstructorsClass.addFieldDeclaration(jfd); } } /** * Create the inner class for data constructors. * @param bindingClass * @param moduleTypeInfo * @param bindingClassName */ private final void buildDataConstructorsInnerClass ( JavaClassRep bindingClass, ModuleTypeInfo moduleTypeInfo, String bindingClassName) throws UnableToResolveForeignEntityException { if (moduleTypeInfo.getNTypeConstructors() == 0) { return; } // Create the class. JavaTypeName dataConstructorsClassTypeName = JavaTypeName.make (bindingClassPackage + "." + bindingClassName + "." + DATA_CONSTRUCTOR_CLASS_NAME, false); JavaClassRep dataConstructorsClass = new JavaClassRep ( dataConstructorsClassTypeName, JavaTypeName.OBJECT, PUBLIC_STATIC_FINAL, EMPTY_TYPE_NAME_ARRAY); // Add the class JavaDoc. JavaDocComment jdc = new JavaDocComment("/**"); jdc.addLine(" * This inner class (" + DATA_CONSTRUCTOR_CLASS_NAME + ") contains constants"); jdc.addLine(" * and methods related to binding to CAL DataConstructors in the " + moduleTypeInfo.getModuleName() + " module."); jdc.addLine(" */"); dataConstructorsClass.setJavaDoc(jdc); // We want to build up and sort a list of type constructor names. List<String> typeConstructorNames = new ArrayList<String>(); for (int i = 0, n = moduleTypeInfo.getNTypeConstructors(); i < n; ++i) { TypeConstructor typeCons = moduleTypeInfo.getNthTypeConstructor(i); typeConstructorNames.add(typeCons.getName().getUnqualifiedName()); } Collections.sort(typeConstructorNames); /* * Create a method to generate a SourceModel expression which will result * in the creation of an instance of the DC. * Create a QualifiedName field for the name of the DC. * * The ordering will be by name of the type constructor and then by dc ordinal. */ boolean addClass = false; // Set of String used to avoid naming conflicts in the generated fields. // We want to build up a set of all the names that will be used for the methods and // QualifiedName fields. This will allow us to avoid naming conflicts when creating // the ordinal fields. Set<String> javaNames = new HashSet<String>(); for (final String typeConstructorName : typeConstructorNames) { TypeConstructor typeCons = moduleTypeInfo.getTypeConstructor(typeConstructorName); for (int i = 0, n = typeCons.getNDataConstructors(); i < n; ++i) { DataConstructor dc = typeCons.getNthDataConstructor(i); // If we are showing public entities we only car about public data constructors. if (dc.getScope().isPublic() != publicEntities) { continue; } // This is the name used to name the java function and the QualfiedName field // associated with this data constructor. String javaFuncName = fixupVarName(dc.getName().getUnqualifiedName()); javaNames.add(javaFuncName); } } for (final String typeConstructorName : typeConstructorNames) { TypeConstructor typeCons = moduleTypeInfo.getTypeConstructor(typeConstructorName); boolean commentAdded = false; for (int i = 0, n = typeCons.getNDataConstructors(); i < n; ++i) { DataConstructor dc = typeCons.getNthDataConstructor(i); // If we are showing public entities we only car about public data constructors. if (dc.getScope().isPublic() != publicEntities) { continue; } addClass = true; // If this is the first DC for the data type we want a non JavaDoc comment // indicating the data type. if (!commentAdded) { MultiLineComment mlc = new MultiLineComment("DataConstructors for the " + typeCons.getName().getQualifiedName() + " data type."); dataConstructorsClass.addComment(mlc); commentAdded = true; } // Get the types of the data constructor fields. TypeExpr[] fieldTypes = getFieldTypesForDC(dc); String javaFuncName = fixupVarName(dc.getName().getUnqualifiedName()); // Since data constructors are capitalized its possible to have a conflict between the name // of our field/helper function and the name of the containing class. if (javaFuncName.equals(this.bindingClassName)) { javaFuncName = javaFuncName + "_"; } // First generate a method that takes SourceModel.Expr instances for // each field value. // Build up the argument names and types. int nArgs = dc.getArity(); JavaTypeName argTypes[] = new JavaTypeName[nArgs]; Arrays.fill(argTypes, SOURCE_MODEL_EXPR_TYPE_NAME); String argNames[] = new String[nArgs]; String origArgNames[] = new String[nArgs]; for (int j = 0, k = argNames.length; j < k; ++j) { argNames[j] = fixupVarName(dc.getArgumentName(j)); origArgNames[j] = dc.getArgumentName(j); } // Create the method. JavaMethod bindingFunction = new JavaMethod(PUBLIC_STATIC_FINAL, SOURCE_MODEL_EXPR_TYPE_NAME, argNames, argTypes, null, javaFuncName); dataConstructorsClass.addMethod(bindingFunction); // Add JavaDoc for the method. JavaDocComment funcComment; CALDocComment cdc = dc.getCALDocComment(); if (cdc != null) { funcComment = new JavaDocComment(calDocCommentToJavaComment(cdc, dc, false, argNames)); funcComment = fixupJavaDoc(funcComment, origArgNames, argNames); } else { funcComment = new JavaDocComment("Binding for DataConstructor: " + dc.getName().getQualifiedName() + "."); for (int iName = 0; iName < argNames.length; ++iName) { funcComment.addLine("@param " + argNames[iName]); } funcComment.addLine("@return the SourceModule.Expr representing an application of " + dc.getName().getQualifiedName()); } bindingFunction.setJavaDocComment(funcComment); // Now we need to fill in the body. // Create an instance of SourceModel.Expr.DataCons for the data constructor. JavaField nameField = new JavaField.Static(dataConstructorsClassTypeName, javaFuncName, QUALIFIED_NAME_TYPE_NAME); JavaExpression sourceModelDataConsCreation = new MethodInvocation.Static( SOURCE_MODEL_EXPR_DATA_CONS_TYPE_NAME, "make", nameField, QUALIFIED_NAME_TYPE_NAME, SOURCE_MODEL_EXPR_DATA_CONS_TYPE_NAME); // Build up an @see tag for the function just created. String atSee = "@see #" + javaFuncName + "("; for (int iArg = 0; iArg < argNames.length; ++iArg) { if (iArg == 0) { atSee = atSee + argTypes[iArg].getFullJavaSourceName(); } else { atSee = atSee + ", " + argTypes[iArg].getFullJavaSourceName(); } } atSee = atSee + ")"; if (dc.getArity() == 0) { // Simply return the Expr.DataCons. bindingFunction.addStatement(new ReturnStatement(sourceModelDataConsCreation)); } else { // Need to build up an application. // Create an array of SourceModel.Expr where the first element is the // SourceModel.Expr.DataCons instance and the following elements are // the function arguments. JavaExpression arrayElements[] = new JavaExpression[dc.getArity() + 1]; arrayElements[0] = sourceModelDataConsCreation; for (int j = 1; j <= argNames.length; ++j) { arrayElements[j] = new JavaExpression.MethodVariable(argNames[j-1]); } JavaExpression arrayCreation = new JavaExpression.ArrayCreationExpression(SOURCE_MODEL_EXPR_TYPE_NAME, arrayElements); // Invoke SourceModle.Expr.Application.make() MethodInvocation makeApply = new MethodInvocation.Static( SOURCE_MODEL_EXPR_APPLICATION_TYPE_NAME, "make", arrayCreation, JavaTypeName.makeArrayType(SOURCE_MODEL_EXPR_TYPE_NAME), SOURCE_MODEL_EXPR_APPLICATION_TYPE_NAME); bindingFunction.addStatement(new ReturnStatement (makeApply)); // If any of the argument types correspond to a Java type. // (eg. Prelude.Int, Prelude.Long, etc. we can generate a version // of the binding function that takes these argument types. boolean primArgs = false; for (int j = 0, k = fieldTypes.length; j < k; ++j) { if (canTypeBeUnboxed(fieldTypes[j])) { primArgs = true; // Update the type of the argument. argTypes[j] = typeExprToTypeName(fieldTypes[j]); // The argument to Application.make needs to be updated. // We need to wrap the raw value (i.e. int, boolean, etc.) // in the appropriate SourceModel construct. arrayElements[j+1] = wrapArgument(argNames[j], fieldTypes[j]); } } if (primArgs) { bindingFunction = new JavaMethod(PUBLIC_STATIC_FINAL, SOURCE_MODEL_EXPR_TYPE_NAME, argNames, argTypes, null, javaFuncName); dataConstructorsClass.addMethod(bindingFunction); // For the comment for this method we want an @see referring to the previous method. JavaStatement.JavaDocComment comment = new JavaStatement.JavaDocComment(atSee); for (int iArg = 0; iArg < argNames.length; ++iArg) { comment.addLine("@param " + argNames[iArg]); } comment.addLine("@return " + SOURCE_MODEL_EXPR_TYPE_NAME.getFullJavaSourceName()); bindingFunction.setJavaDocComment(comment); arrayCreation = new JavaExpression.ArrayCreationExpression(SOURCE_MODEL_EXPR_TYPE_NAME, arrayElements); makeApply = new MethodInvocation.Static( SOURCE_MODEL_EXPR_APPLICATION_TYPE_NAME, "make", arrayCreation, JavaTypeName.makeArrayType(SOURCE_MODEL_EXPR_TYPE_NAME), SOURCE_MODEL_EXPR_APPLICATION_TYPE_NAME); bindingFunction.addStatement(new ReturnStatement (makeApply)); } } // Create a field declaration for the QualifiedName field. JavaFieldDeclaration jfd = makeQualifiedNameDeclaration(javaFuncName, dc.getName().getUnqualifiedName()); // If there is CALDoc for the DC add it as JavaDoc. JavaDocComment comment = new JavaDocComment("Name binding for DataConstructor: " + dc.getName().getQualifiedName() + "."); comment.addLine(atSee); jfd.setJavaDoc(comment); dataConstructorsClass.addFieldDeclaration(jfd); // Now add an int field which is the ordinal of the data constructor. String ordinalFieldName = javaFuncName + "_ordinal"; while (javaNames.contains(ordinalFieldName)) { ordinalFieldName = ordinalFieldName + "_"; } JavaFieldDeclaration ordinalFieldDec = new JavaFieldDeclaration(PUBLIC_STATIC_FINAL, JavaTypeName.INT, ordinalFieldName, JavaExpression.LiteralWrapper.make(Integer.valueOf(dc.getOrdinal()))); javaNames.add(ordinalFieldName); JavaDocComment ordinalComment = new JavaDocComment("Ordinal of DataConstructor " + dc.getName().getQualifiedName() + "."); ordinalComment.addLine(atSee); ordinalFieldDec.setJavaDoc(ordinalComment); dataConstructorsClass.addFieldDeclaration(ordinalFieldDec); } } if (addClass) { bindingClass.addInnerClass(dataConstructorsClass); } } /** * Build the inner class for TypeClasses * @param bindingClass * @param moduleTypeInfo * @param bindingClassName */ private final void buildTypeClassInnerClass ( JavaClassRep bindingClass, ModuleTypeInfo moduleTypeInfo, String bindingClassName) { if (moduleTypeInfo.getNTypeClasses() == 0) { return; } // Create the inner class. JavaTypeName typeClassClassTypeName = JavaTypeName.make (bindingClassPackage + "." + bindingClassName + "." + TYPECLASS_CLASS_NAME, false); JavaClassRep typeClassClass = new JavaClassRep ( typeClassClassTypeName, JavaTypeName.OBJECT, PUBLIC_STATIC_FINAL, EMPTY_TYPE_NAME_ARRAY); // Set the JavaDoc for the class. JavaDocComment jdc = new JavaDocComment("This inner class (" + TYPECLASS_CLASS_NAME + ") contains constants"); jdc.addLine("and methods related to binding to CAL TypeClasses in the " + moduleTypeInfo.getModuleName() + " module."); typeClassClass.setJavaDoc(jdc); // Build up a list of TypeClass names and sort. List<String> typeClassNames = new ArrayList<String>(); for (int i = 0, n = moduleTypeInfo.getNTypeClasses(); i < n; ++i) { TypeClass tc = moduleTypeInfo.getNthTypeClass(i); if (tc.getScope().isPublic() != publicEntities) { continue; } typeClassNames.add(tc.getName().getUnqualifiedName()); } Collections.sort(typeClassNames); if (typeClassNames.size() == 0) { return; } bindingClass.addInnerClass(typeClassClass); // Generate a QualifiedName field for each type class. for (final String typeClassName : typeClassNames) { TypeClass tc = moduleTypeInfo.getTypeClass(typeClassName); // Add a field for the TypeClass name. // 'static final String typeClassName = "typeClassName";' // We need to check for conflict between the functionName // and java keywords. It is valid to have a CAL // function called assert but not valid to have java code // 'static final QualifiedName assert = ... String fieldName = fixupVarName(typeClassName); // Since TypeClass names are capitalized it's possible to have a conflict // between the name of the field and the name of the top level class. if (fieldName.equals(this.bindingClassName)) { fieldName = fieldName + "_"; } JavaFieldDeclaration jfd = makeQualifiedNameDeclaration(fieldName, typeClassName); // Add JavaDoc. We use any CALDoc for the type class if available. JavaDocComment comment; CALDocComment cdc = tc.getCALDocComment(); if (cdc != null) { comment = new JavaDocComment(calDocCommentToJavaComment(cdc, null, false)); } else { comment = new JavaDocComment("/** Name binding for TypeClass: " + tc.getName().getQualifiedName() + ". */"); } jfd.setJavaDoc(comment); typeClassClass.addFieldDeclaration(jfd); } } /** * 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; } /** * Generate a JavaFieldDeclaration for a QualifiedName field. * @param fieldName * @param entityName * @return the field declaration. */ private final JavaFieldDeclaration makeQualifiedNameDeclaration (String fieldName, String entityName) { JavaExpression initializer = new JavaExpression.MethodInvocation.Static ( QUALIFIED_NAME_TYPE_NAME, "make", new JavaExpression[]{moduleNameField, LiteralWrapper.make(entityName)}, new JavaTypeName[]{JavaTypeName.MODULE_NAME, JavaTypeName.STRING}, QUALIFIED_NAME_TYPE_NAME); return new JavaFieldDeclaration(PUBLIC_STATIC_FINAL, QUALIFIED_NAME_TYPE_NAME, fieldName, initializer); } /** * Returns true if the provided type expression corresponds * to an unboxable type. Currently these types consist of * the CAL types corresponding to Java primitive types, excluding * void, and java.lang.String. * @param typeExpr * @return true if the type can be unboxed. */ static private boolean canTypeBeUnboxed (TypeExpr typeExpr) throws UnableToResolveForeignEntityException { if (typeExpr == null) { return false; } TypeConsApp typeConsApp = typeExpr.rootTypeConsApp(); if (typeConsApp != null && typeConsApp.getNArgs() == 0) { if (typeConsApp.isNonParametricType(CAL_Prelude.TypeConstructors.Boolean)) { return true; } if(typeConsApp.getForeignTypeInfo() != null) { ForeignTypeInfo fti = typeConsApp.getForeignTypeInfo(); Class<?> foreignClass = fti.getForeignType(); if(foreignClass.isPrimitive() && foreignClass != void.class) { return true; } if (foreignClass == java.lang.String.class) { return true; } } } return false; } /** * Generate the JavaModel which will wrap an unboxed value in the * appropriate SourceModel construct. * @param argName * @param argType * @return the JavaExpression which wraps the unboxed value. */ static private JavaExpression wrapArgument (String argName, TypeExpr argType) throws UnableToResolveForeignEntityException { TypeConsApp typeConsApp = argType.rootTypeConsApp(); if (typeConsApp != null && typeConsApp.getNArgs() == 0) { // boolean if (typeConsApp.isNonParametricType(CAL_Prelude.TypeConstructors.Boolean)) { JavaExpression makeBoolean = new MethodInvocation.Static( SOURCE_MODEL_EXPR_TYPE_NAME, "makeBooleanValue", new MethodVariable(argName), JavaTypeName.BOOLEAN, SOURCE_MODEL_EXPR_TYPE_NAME); return makeBoolean; } if(typeConsApp.getForeignTypeInfo() != null) { ForeignTypeInfo fti = typeConsApp.getForeignTypeInfo(); Class<?> foreignClass = fti.getForeignType(); // We handle the primitive Java types, excluding void. if(foreignClass.isPrimitive() && foreignClass != void.class) { MethodVariable mv = new MethodVariable(argName); String funcName; JavaTypeName valueType; if (foreignClass == boolean.class) { funcName = "makeBooleanValue"; valueType = JavaTypeName.BOOLEAN; } else if (foreignClass == byte.class) { funcName = "makeByteValue"; valueType = JavaTypeName.BYTE; } else if (foreignClass == char.class) { funcName = "makeCharValue"; valueType = JavaTypeName.CHAR; } else if (foreignClass == double.class) { funcName = "makeDoubleValue"; valueType = JavaTypeName.DOUBLE; } else if (foreignClass == float.class) { funcName = "makeFloatValue"; valueType = JavaTypeName.FLOAT; } else if (foreignClass == int.class) { funcName = "makeIntValue"; valueType = JavaTypeName.INT; } else if (foreignClass == long.class) { funcName = "makeLongValue"; valueType = JavaTypeName.LONG; } else if (foreignClass == short.class) { funcName = "makeShortValue"; valueType = JavaTypeName.SHORT; } else { throw new IllegalStateException("Unhandled primitive type " + foreignClass.getName()); } JavaExpression makeValue = new MethodInvocation.Static( SOURCE_MODEL_EXPR_TYPE_NAME, funcName, mv, valueType, SOURCE_MODEL_EXPR_TYPE_NAME); return makeValue; } // Handle java.lang.String. if (foreignClass == java.lang.String.class) { return new MethodInvocation.Static( SOURCE_MODEL_EXPR_TYPE_NAME, "makeStringValue", new MethodVariable(argName), JavaTypeName.STRING, SOURCE_MODEL_EXPR_TYPE_NAME); } } } // Not a type we handle. throw new IllegalStateException("Unable to wrap argument of type: " + argType.toString()); } /** * Translate a TypeExpr to the corresponding JavaTypeName. * @param argType * @return the JavaTypeName. */ private final static JavaTypeName typeExprToTypeName (TypeExpr argType) throws UnableToResolveForeignEntityException { TypeConsApp typeConsApp = argType.rootTypeConsApp(); if (typeConsApp != null && typeConsApp.getNArgs() == 0) { if (typeConsApp.isNonParametricType(CAL_Prelude.TypeConstructors.Boolean)) { return JavaTypeName.BOOLEAN; } if(typeConsApp.getForeignTypeInfo() != null) { ForeignTypeInfo fti = typeConsApp.getForeignTypeInfo(); Class<?> foreignClass = fti.getForeignType(); if(foreignClass.isPrimitive() && foreignClass != void.class) { if (foreignClass == boolean.class) { return JavaTypeName.BOOLEAN; } else if (foreignClass == byte.class) { return JavaTypeName.BYTE; } else if (foreignClass == char.class) { return JavaTypeName.CHAR; } else if (foreignClass == double.class) { return JavaTypeName.DOUBLE; } else if (foreignClass == float.class) { return JavaTypeName.FLOAT; } else if (foreignClass == int.class) { return JavaTypeName.INT; } else if (foreignClass == long.class) { return JavaTypeName.LONG; } else if (foreignClass == short.class) { return JavaTypeName.SHORT; } else { throw new IllegalStateException("Unhandled primitive type " + foreignClass.getName()); } } if (foreignClass == java.lang.String.class) { return JavaTypeName.STRING; } } } throw new IllegalStateException("Unable to convert type: " + argType.toString()); } /** * Use this function to get the field types for a data constructor. * Appropriate conversions are done based on whether or not unboxed DC fields are being used * and whether enums are treated as ints. * @param dc * @return an array of TypeExpr */ private static 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; } /** * 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 static String fixupVarName(String varName) { if (JavaReservedWords.javaLanguageKeywords.contains(varName)) { return varName + "_"; } if (varName.equals("QualifiedName")) { varName = varName + "_"; } varName = varName.replace ('.', '_'); varName = varName.replace ('#', '_'); return varName; } /** * @param classRep * @return a has code for the concatenated JavaDoc in the classRep. */ private int computeJavaDocHash (JavaClassRep classRep){ // We want to traverse the class collecting all the // JavaDoc comments. JavaDocCollector jdc = new JavaDocCollector(); List<JavaDocComment> comments = new ArrayList<JavaDocComment>(); classRep.accept(jdc, comments); StringBuilder sb = new StringBuilder(); for (final JavaDocComment comment : comments) { for (final String line : comment.getCommentLines()) { sb.append(line); } } return sb.toString().hashCode(); } private static class JavaBindingClassChecker extends JavaModelTraverser<Void, Void> { /** Map of String -> JavaFieldDeclaration. Data constructor QualifiedName binding fields.*/ Map<String, JavaFieldDeclaration> dataConsFields = new HashMap<String, JavaFieldDeclaration>(); /** Map of String -> JavaFieldDeclaration. DataConstructor ordinal value fields. */ Map<String, JavaFieldDeclaration> dataConsOrdinalFields = new HashMap<String, JavaFieldDeclaration>(); /** Set of JavaMethod. Methods in the DC inner class. */ Set<JavaMethod> dataConsFunctions = new HashSet<JavaMethod>(); /** Map of String -> JavaFieldDeclaration. Function QualifiedName binding fields. */ Map<String, JavaFieldDeclaration> functionFields = new HashMap<String, JavaFieldDeclaration>(); /** Set of JavaMethod. Helper methods in the Functions inner class. */ Set<JavaMethod> functions = new HashSet<JavaMethod>(); /** Map of String -> JavaFieldDeclaration. TypeClass QualifiedName binding fields. */ Map<String, JavaFieldDeclaration> typeClassFields = new HashMap<String, JavaFieldDeclaration>(); /** Map of String -> JavaFieldDeclaration. TypeConstructor QualfiedName binding Fields. */ Map<String, JavaFieldDeclaration> typeConstructorFields = new HashMap<String, JavaFieldDeclaration>(); Map<String, JavaFieldDeclaration> currentFieldSet; Set<JavaMethod> currentFunctionSet; Map<String, JavaFieldDeclaration> currentIntFieldSet; /** * @param fieldDeclaration * the java model element to be visited * @param arg * additional argument for the visitation * @return the result from visiting the java model element */ @Override public Void visitJavaFieldDeclaration ( JavaFieldDeclaration fieldDeclaration, Void arg) { if (currentFieldSet != null && fieldDeclaration.getFieldType().equals(JavaTypeName.QUALIFIED_NAME)) { currentFieldSet.put(fieldDeclaration.getFieldName(), fieldDeclaration); } if (currentIntFieldSet != null && fieldDeclaration.getFieldType().equals(JavaTypeName.INT)) { currentIntFieldSet.put(fieldDeclaration.getFieldName(), fieldDeclaration); } return null; } /** * @param javaMethod * the java model element to be visited * @param arg * additional argument for the visitation * @return the result from visiting the java model element */ @Override public Void visitJavaMethod ( JavaMethod javaMethod, Void arg) { if (currentFunctionSet != null) { currentFunctionSet.add(javaMethod); } return super.visitJavaMethod(javaMethod, arg); } /** * @param classRep * the java model element to be visited * @param arg * additional argument for the visitation * @return the result from visiting the java model element */ @Override public Void visitJavaClassRep ( JavaClassRep classRep, Void arg) { String className = classRep.getClassName().getUnqualifiedJavaSourceName(); if (className.indexOf("$") >= 0) { className = className.substring(className.lastIndexOf("$") + 1); } if (className.equals(FUNCTION_CLASS_NAME)) { currentFieldSet = functionFields; currentFunctionSet = functions; currentIntFieldSet = null; } else if (className.equals(DATA_CONSTRUCTOR_CLASS_NAME)) { currentFieldSet = dataConsFields; currentFunctionSet = dataConsFunctions; currentIntFieldSet = dataConsOrdinalFields; } else if (className.equals(TYPECLASS_CLASS_NAME)) { currentFieldSet = typeClassFields; currentFunctionSet = null; currentIntFieldSet = null; } else if (className.equals(TYPECONSTRUCTOR_CLASS_NAME)) { currentFieldSet = typeConstructorFields; currentFunctionSet = null; currentIntFieldSet = null; } else { currentFieldSet = null; currentFunctionSet = null; currentIntFieldSet = null; } return super.visitJavaClassRep(classRep, arg); } } private static class JavaDocCollector extends JavaModelTraverser<List<JavaDocComment>, Void> { /* (non-Javadoc) * @see org.openquark.cal.internal.runtime.lecc.JavaModelVisitor#visitJavaDocCommentStatement(org.openquark.cal.internal.runtime.lecc.JavaStatement.JavaDocComment, java.lang.Object) */ @Override public Void visitJavaDocCommentStatement( JavaStatement.JavaDocComment comment, List<JavaDocComment> arg) { arg.add(comment); return null; } } }