/* * 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. */ /* * ForeignImportGenerator.java * Creation date: Sep 14, 2005. * By: Edward Lam */ package org.openquark.gems.client.jfit; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Member; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.text.DateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; 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.TreeMap; import org.openquark.cal.compiler.ForeignTypeInfo; import org.openquark.cal.compiler.LanguageInfo; import org.openquark.cal.compiler.ModuleName; import org.openquark.cal.compiler.ModuleTypeInfo; import org.openquark.cal.compiler.QualifiedName; import org.openquark.cal.compiler.Scope; import org.openquark.cal.compiler.SourceModel; import org.openquark.cal.compiler.TypeConstructor; import org.openquark.cal.compiler.UnableToResolveForeignEntityException; import org.openquark.cal.compiler.SourceModel.CALDoc; import org.openquark.cal.compiler.SourceModel.ModuleDefn; import org.openquark.cal.compiler.SourceModel.TopLevelSourceElement; import org.openquark.cal.compiler.SourceModel.TypeExprDefn; import org.openquark.cal.compiler.SourceModel.TypeSignature; import org.openquark.cal.compiler.SourceModel.FunctionDefn.Foreign; import org.openquark.cal.compiler.SourceModel.Name.TypeClass; import org.openquark.cal.compiler.SourceModel.TypeConstructorDefn.ForeignType; import org.openquark.cal.compiler.SourceModelUtilities.ImportAugmenter; import org.openquark.cal.module.Cal.Core.CAL_Debug; import org.openquark.cal.module.Cal.Core.CAL_Prelude; import org.openquark.cal.services.CALWorkspace; import org.openquark.cal.services.IdentifierUtils; import org.openquark.cal.services.MetaModule; import org.openquark.util.Pair; /** * This class generates CAL modules containing foreign functions and data types, when presented with desired Java classes and Java class members. * * @author Edward Lam */ public final class ForeignImportGenerator { /** A DateFormat for emitting time in the current locale. */ private static final DateFormat LOCALE_DATE_FORMAT = DateFormat.getDateInstance(DateFormat.MEDIUM); /** The type classes in the auto-generated using clause for the Prelude module. */ private static final String[] preludeUsingTypeClasses = { CAL_Prelude.TypeClasses.Inputable.getUnqualifiedName(), CAL_Prelude.TypeClasses.Outputable.getUnqualifiedName(), CAL_Prelude.TypeClasses.Eq.getUnqualifiedName(), CAL_Prelude.TypeClasses.Ord.getUnqualifiedName() }; /** The type classes in the auto-generated using clause for the Debug module. */ private static final String[] debugUsingTypeClasses = { CAL_Debug.TypeClasses.Show.getUnqualifiedName() }; /** * Generation scope enum pattern. * @author Edward Lam */ public static final class GenerationScope { private String typeString; private GenerationScope(String s) { typeString = s; } @Override public String toString() { return typeString; } /** All public. */ public static final GenerationScope ALL_PUBLIC = new GenerationScope("ALL_PUBLIC"); /** Functions and types public. Type implementations private. */ public static final GenerationScope PARTIAL_PUBLIC = new GenerationScope("PARTIAL_PUBLIC"); /** All private. */ public static final GenerationScope ALL_PRIVATE = new GenerationScope("ALL_PRIVATE"); } /** * A class to hold info about the source as it is being generated.. * @author Edward Lam */ private static final class GenerationInfo { /** The default name for generated types. */ private static final String DEFAULT_BASE_NAME = "ForeignType"; /** The name of the module for which the imports are being generated. */ private final ModuleName currentModuleName; /** Map from Class to the type constructor name given to that class, * for types previously existing in the workspace. */ private final Map<Class<?>, QualifiedName> existingClassToTypeConsMap; /** Map from Class to the type constructor name given to that class. */ private final Map<Class<?>, QualifiedName> classToTypeConsNameMap; /** The set of unqualified names that have been used for functions in the module being generated. */ private final Set<String> existingUnqualifiedFunctionNamesSet; /** The set of unqualified names that have been used for types in the module being generated. */ private final Set<String> existingUnqualifiedTypeConsNamesSet; /** Map from (java member, java class pair) to the unqualified function name assigned to that member, * for members for which names have been generated. */ private final Map<Pair<Member, Class<?>>, String> memberToUnqualifiedFunctionNameMap; /** the unqualified names of the types in the generated module. */ private final Set<String> preludeUsingTypes = new HashSet<String>(); /** The scope for generated functions and types. */ private final GenerationScope generationScope; GenerationInfo(ModuleName currentModuleName, CALWorkspace workspace, GenerationScope generationScope) throws UnableToResolveForeignEntityException { this.currentModuleName = currentModuleName; this.generationScope = generationScope; this.existingClassToTypeConsMap = getExistingTypeMap(workspace); this.classToTypeConsNameMap = new HashMap<Class<?>, QualifiedName>(existingClassToTypeConsMap); this.existingUnqualifiedFunctionNamesSet = new HashSet<String>(); this.existingUnqualifiedTypeConsNamesSet = new HashSet<String>(); this.memberToUnqualifiedFunctionNameMap = new HashMap<Pair<Member, Class<?>>, String>(); } /** * Mark a name as being the name of a type in the generated module. * @param unqualifiedTypeName the name of the type. * @return false if the name is already assigned to a type, true otherwise. */ boolean addTypeToPreludeUsingSet(String unqualifiedTypeName) { // If a type with the same unqualified name also exists in the current module, we must // qualify for the type in the Prelude. if (existingUnqualifiedTypeConsNamesSet.contains(unqualifiedTypeName)) { return false; } preludeUsingTypes.add(unqualifiedTypeName); return true; } /** * @return the unqualified names of the types in the generated module. */ String[] getPreludeUsingTypes() { return preludeUsingTypes.toArray(new String[preludeUsingTypes.size()]); } /** * @return the name of the module being generated. */ public ModuleName getCurrentModuleName() { return currentModuleName; } /** * @return the scope for generated functions and types. */ public GenerationScope getGenerationScope() { return generationScope; } /** * @param javaClass a java class. * @return the name of the generated type constructor mapped to the java class, or null if this has not happened. */ QualifiedName getForeignTypeName(Class<?> javaClass) { return classToTypeConsNameMap.get(javaClass); } /** * Create a new valid function name * @param javaMember the Java member for which to generate the function. * @param javaClass the class containing the member. * @return a valid function name based on the provided name. This will be added to the set of existing function names. */ String getNewFunctionName(Member javaMember, Class<?> javaClass) { generateFunctionNames(new Member[]{javaMember}, javaClass); return memberToUnqualifiedFunctionNameMap.get(new Pair<Member, Class<?>>(javaMember, javaClass)); } /** * Populates the internal map from java member to unqualified name of the foreign function referencing that member. * The generated names will not collide with any previously-generated names. * @param members the Java members for which to generate the function. * @param javaClass the class containing the member. */ public void generateFunctionNames(Member[] members, Class<?> javaClass) { // (String->Set of Member) Map from base name to the members with that name. Sorted by base name. // Only contains mappings for members for which names have not already been generated. Map<String, Set<Member>> baseNameToMembersMap = new TreeMap<String, Set<Member>>(); // Map member to base name, for any members for which names have not already been generated. for (final Member javaMember : members) { Class<?> memberClass = javaMember instanceof Constructor<?> ? javaMember.getDeclaringClass() : javaClass; // Check that a name hasn't already been generated for this member. if (memberToUnqualifiedFunctionNameMap.containsKey(new Pair<Member, Class<?>>(javaMember, javaClass))) { continue; } // Get the type constructor name for the class. String classTypeConsName = getTypeConsName(memberClass); // Get the name to convert to a suggested name. // Constructor.getName() is fully qualified, but Method.getName() and Field.getName() are not. String nameToConvert = javaMember instanceof Constructor<?> ? classTypeConsName + "_new" : javaMember instanceof Field ? /*"get." + */ classTypeConsName + "_" + javaMember.getName(): classTypeConsName + "_" + javaMember.getName(); // Method // If the class is generated, consume the first letter, which is the prepended "J". if (!existingClassToTypeConsMap.containsKey(memberClass)) { nameToConvert = nameToConvert.substring(1); } String baseFunctionName = IdentifierUtils.makeIdentifierName(nameToConvert, false); // Check for no valid CAL characters. This should be rare. if (baseFunctionName.length() == 0) { baseFunctionName = "generatedFunction"; } // Add to the members map. Set<Member> baseNameMembers = baseNameToMembersMap.get(baseFunctionName); if (baseNameMembers == null) { baseNameMembers = new HashSet<Member>(); baseNameToMembersMap.put(baseFunctionName, baseNameMembers); } baseNameMembers.add(javaMember); } // Iterate over the previously-constructed map. for (final Map.Entry<String, Set<Member>> mapEntry : baseNameToMembersMap.entrySet()) { String baseName = mapEntry.getKey(); Set<Member> baseNameMembers = mapEntry.getValue(); int nMembers = baseNameMembers.size(); // Check for the case of only one member with the given base name. if (nMembers == 1) { // Disambiguate the name. String functionName = baseName; int index = 2; while (!existingUnqualifiedFunctionNamesSet.add(functionName)) { functionName = baseName + "_" + index; index++; } memberToUnqualifiedFunctionNameMap.put(new Pair<Member, Class<?>>(baseNameMembers.iterator().next(), javaClass), functionName); continue; } // If here, more than one member with the same base name. // Disambiguate with respect to parameters if possible. for (final Member member : baseNameMembers) { StringBuilder newBaseNameSB = new StringBuilder(baseName); Class<?>[] paramTypes; if (member instanceof Field) { // Can't do much about this.. paramTypes = new Class<?>[0]; } else if (member instanceof Constructor<?>) { paramTypes = ((Constructor<?>)member).getParameterTypes(); } else if (member instanceof Method) { paramTypes = ((Method)member).getParameterTypes(); } else { throw new IllegalArgumentException("Unknown member type: " + member.getClass()); } // Append (underscore + param type) for each param. for (final Class<?> paramType : paramTypes) { newBaseNameSB.append("_" + getTypeConsName(paramType)); } String newBaseName = newBaseNameSB.toString(); // Disambiguate the name. String functionName = newBaseName; int index = 2; while (!existingUnqualifiedFunctionNamesSet.add(functionName)) { functionName = newBaseName + "_" + index; index++; } memberToUnqualifiedFunctionNameMap.put(new Pair<Member, Class<?>>(member, javaClass), functionName); } } } /** * Get a map from class to type constructor name, for classes for which foreign types are known to exist. * @param workspace the workspace * @return (Class->QualifiedName) map from class to type cons name. */ private static Map<Class<?>, QualifiedName> getExistingTypeMap(CALWorkspace workspace) throws UnableToResolveForeignEntityException { // (Class->QualifiedName) the return value. Map<Class<?>, QualifiedName> classToTypeConsNameMap = new HashMap<Class<?>, QualifiedName>(); // Iterate over the modules. for (int i = 0, nMetaModules = workspace.getNMetaModules(); i < nMetaModules; i++) { MetaModule metaModule = workspace.getNthMetaModule(i); // We're only interested in modules which are compiled. ModuleTypeInfo moduleTypeInfo = metaModule.getTypeInfo(); if (moduleTypeInfo == null) { // Not compiled. continue; } int nImports = moduleTypeInfo.getNImportedModules(); // Iterate over the type constructors in the module. int nTypeConstructors = moduleTypeInfo.getNTypeConstructors(); for (int j = 0; j < nTypeConstructors; j++) { TypeConstructor typeCons = moduleTypeInfo.getNthTypeConstructor(j); QualifiedName calName = typeCons.getName(); //Prelude.Unit and Prelude.Boolean are special cases in that they are algebraic types that however correspond //to foreign types for the purposes of declaring foreign functions. if (calName.equals(CAL_Prelude.TypeConstructors.Unit)) { classToTypeConsNameMap.put(void.class, calName); continue; } if (calName.equals(CAL_Prelude.TypeConstructors.Boolean)) { classToTypeConsNameMap.put(boolean.class, calName); continue; } // Check if the type is foreign. ForeignTypeInfo fti = typeCons.getForeignTypeInfo(); if (fti != null && fti.getImplementationVisibility() == Scope.PUBLIC) { Class<?> foreignType = fti.getForeignType(); // In cases where a single class maps to multiple type constructors, // we choose the type in the module which is "closest" to the Prelude. // For now, this distance is approximated by the number of imports in the module. // TODOEL: Later, we can figure out the size of the import graph. QualifiedName existingMappedName = classToTypeConsNameMap.get(foreignType); if (existingMappedName == null) { // Add a new mapping. classToTypeConsNameMap.put(foreignType, calName); continue; } // a mapping already exists. ModuleTypeInfo existingMappingModuleTypeInfo = workspace.getMetaModule(existingMappedName.getModuleName()).getTypeInfo(); if (existingMappingModuleTypeInfo == null) { // Existing mapping's type info not compiled. continue; } int existingMappingNImports = existingMappingModuleTypeInfo.getNImportedModules(); if (nImports < existingMappingNImports) { // override the existing mapping. classToTypeConsNameMap.put(foreignType, calName); continue; } } } } return classToTypeConsNameMap; } /** * Get a "reasonable" type constructor name for a foreign class. * If the class was previously mapped using calculateRequiredClassNames(), the previously-calculated name will be returned. * Otherwise a new one will be calculated and mapped. * * @param foreignClass the class for which a type constructor name will be generated. * This class' mapping will be augmented with a mapping for the generated name. * @return the new generated name. */ String getTypeConsName(Class<?> foreignClass) { { QualifiedName previouslyCalculatedName = classToTypeConsNameMap.get(foreignClass); if (previouslyCalculatedName != null) { return previouslyCalculatedName.getUnqualifiedName(); } } String baseName = makeTypeConsName(foreignClass, -1); // Disambiguate. int index = 2; String typeConsName = baseName; while (!existingUnqualifiedTypeConsNamesSet.add(typeConsName)) { typeConsName = baseName + index; index++; } // Add a mapping for the generated name. QualifiedName qualifiedTypeConsName = QualifiedName.make(currentModuleName, typeConsName); classToTypeConsNameMap.put(foreignClass, qualifiedTypeConsName); return typeConsName; } /** * Generate a type constructor name for the provided foreign class, with characters from the suggested number * of qualifications from the class' fully-qualified name. * * Examples: * ("java.lang.String", -1) -> JavaLangString * ("java.lang.String", 0) -> String * ("java.lang.String", 1) -> LangString * ("java.lang.String", 2) -> JavaLangString * ("java.lang.String", 3) -> (null) * * ("[Ljava.lang.String;", -1) -> JavaLangString_Array * * Note: for Java class names with no valid cal characters, * "ForeignType" is returned if numQualifications < 0, otherwise null is returned. * * Examples: * ("$", -1) -> ForeignType * ("$", 0) -> (null) * * * @param foreignClass the class for which the name should be generated. * @param numQualifications the number of qualifications. 0 for base name only, -1 for fully qualified.. * @return the suggested name, or null if the class does not have the suggested number of qualifications. */ private static String makeTypeConsName(Class<?> foreignClass, int numQualifications) { // If we have an array, reduce to the innermost component type and calculate the dimension int arrayDim = 0; Class<?> componentClass = foreignClass; while (componentClass.isArray()) { componentClass = componentClass.getComponentType(); arrayDim++; } // Get the full name, use this to calculate the class name (without the package). String componentClassFullName = componentClass.getName(); String componentClassName; if (numQualifications < 0) { // Use the fully qualified name. componentClassName = componentClassFullName; } else { // Split at the dots. String[] componentClassComponents = componentClassFullName.split("[.]"); // Check if there are too many qualifications. if (numQualifications > componentClassComponents.length - 1) { return null; } // Start off with the last component, which is the base name. componentClassName = componentClassComponents[componentClassComponents.length - 1]; // For each qualification, prepend the (i + 1)th last component plus a dot. for (int i = 0; i < numQualifications; i++) { componentClassName = componentClassComponents[componentClassComponents.length - 1 - i] + "." + componentClassName; } } char[] classNameChars = componentClassName.toCharArray(); int firstOkCharIndex = 0; // To make sure the first char is upper case, iterate over the array, // finding the first letter which is ok when upper cased. Set that char to upper case while (firstOkCharIndex < classNameChars.length) { char c = Character.toUpperCase(classNameChars[0]); if (LanguageInfo.isCALConsPart(c)) { classNameChars[firstOkCharIndex] = c; break; } firstOkCharIndex++; } // Iterate over the remaining array, replacing invalid chars with '_' for (int i = firstOkCharIndex + 1; i < classNameChars.length; i++) { char c = classNameChars[i]; if (!LanguageInfo.isCALConsPart(c)) { classNameChars[i] = '_'; } } // Create the base name. If there are no valid chars (should be rare.., eg. class name is "$"), just use a default. // Note: Prepend a "J" to follow convention for foreign types. String baseName = firstOkCharIndex < classNameChars.length ? "J" + new String(classNameChars, firstOkCharIndex, classNameChars.length - firstOkCharIndex) : DEFAULT_BASE_NAME; // Tweak name for arrays. if (arrayDim > 0) { baseName += "Array"; if (arrayDim > 1) { baseName += arrayDim; } } return baseName; } /** * Populates the internal map from required class to type name. * @param requiredClasses (Set of java.lang.Class) the class objects representing the required classes. */ public void calculateRequiredClassNames(Set<Class<?>> requiredClasses) { // (String->Set of Class) map from unqualified java name to the classes encountered with that base name. Map<String, Set<Class<?>>> baseNameToClassesMap = new HashMap<String, Set<Class<?>>>(); // Steps: // Find base names, classes with conflicting base name. // Disambiguate classes with conflicting base names. // Iterate over required classes, calculating which of these would have a suggested base name conflict with // other required classes. for (final Class<?> javaClass : requiredClasses) { // Skip generating a type for this class if a mapping already exists. if (classToTypeConsNameMap.containsKey(javaClass)) { continue; } // Note: this can return null. // It's ok to have a null key though. We just have to handle this later on. String baseTypeConsName = makeTypeConsName(javaClass, 0); Set<Class<?>> baseNameClasses = baseNameToClassesMap.get(baseTypeConsName); if (baseNameClasses == null) { baseNameClasses = new HashSet<Class<?>>(); baseNameToClassesMap.put(baseTypeConsName, baseNameClasses); } baseNameClasses.add(javaClass); } // Iterate over the sets of classes with the given base names. for (final Map.Entry<String, Set<Class<?>>> mapEntry : baseNameToClassesMap.entrySet()) { String baseName = mapEntry.getKey(); // can be null. Set<Class<?>> classSet = mapEntry.getValue(); // Default if no valid cal chars.. if (baseName == null) { baseName = DEFAULT_BASE_NAME; } if (classSet.size() == 1) { Class<?> conflictingClass = classSet.iterator().next(); // Disambiguate. int index = 2; String typeConsName = baseName; while (!existingUnqualifiedTypeConsNamesSet.add(typeConsName)) { typeConsName = baseName + index; index++; } // Only one class with this base name. QualifiedName qualifiedTypeConsName = QualifiedName.make(currentModuleName, baseName); classToTypeConsNameMap.put(conflictingClass, qualifiedTypeConsName); } else { // More than one class with the same base name. // Iterate over the classes in the set, figuring out how many qualifications are necessary to disambiguate. // Names are qualified in reverse order. // eg. for org.openquark.gems.client.jfit.ForeignImportGenerator, // base name : ForeignImportGenerator // 1 qualification : jfit.ForeignImportGenerator // 2 qualifications: client.jfit.ForeignImportGenerator // etc. int numQualifications = 1; while (numQualifications > 0) { // (Set of String) names of classes, with the given number of qualifications. Set<String> namesWithQualification = new HashSet<String>(); boolean foundConflict = false; for (final Class<?> conflictingClass : classSet) { String nameWithQualification = makeTypeConsName(conflictingClass, numQualifications); if (nameWithQualification == null) { // Not enough qualifications exist in the name. // foundConflict is false, so we will break out of the while.. loop. numQualifications = -1; break; } if (!namesWithQualification.add(nameWithQualification)) { // This number of qualifications is not enough to disambiguate with respect to the other classes. foundConflict = true; break; } else { // Didn't find a conflict. Try the next name. } } if (foundConflict) { // Try more qualifications. numQualifications++; } else { // Found a sufficient number of qualifications. break; } } // Now populate the classToTypeConsNameMap for (final Class<?> conflictingClass : classSet) { String nameWithQualification = makeTypeConsName(conflictingClass, numQualifications); // Disambiguate. int index = 2; String typeConsName = nameWithQualification; while (!existingUnqualifiedTypeConsNamesSet.add(typeConsName)) { typeConsName = baseName + index; index++; } // Add a mapping for the generated name. QualifiedName qualifiedTypeConsName = QualifiedName.make(currentModuleName, typeConsName); classToTypeConsNameMap.put(conflictingClass, qualifiedTypeConsName); } } } } } /** * A comparator which compares classes by generated foreign type name. * * @author Edward Lam */ private static class ClassTypeNameComparator implements Comparator<Class<?>> { private final GenerationInfo generationInfo; /** * Constructor for a ClassTypeNameComparator. * @param generationInfo */ ClassTypeNameComparator(GenerationInfo generationInfo) { this.generationInfo = generationInfo; } /** * {@inheritDoc} */ public int compare(Class<?> o1, Class<?> o2) { QualifiedName foreignTypeName1 = generationInfo.getForeignTypeName(o1); QualifiedName foreignTypeName2 = generationInfo.getForeignTypeName(o2); if (foreignTypeName1 == null) { throw new IllegalStateException("No name generated for foreignType: " + o1); } if (foreignTypeName2 == null) { throw new IllegalStateException("No name generated for foreignType: " + o2); } return foreignTypeName1.compareTo(foreignTypeName2); } } /** * A comparator which compares implementors of java.lang.reflect.Member. * * The primary sort is by member.getClass(). Constructors first, then fields then methods. * The secondary sort is by member name. * Note: the name returned by Constructor.getName() is fully-qualified, while for methods and fields it is unqualified. * The tertiary sort is by member parameter type, for members which have param types. * * @author Edward Lam */ private static class MemberComparator implements Comparator<Member> { private final GenerationInfo generationInfo; /** * Constructor for a MemberComparator. * @param generationInfo */ MemberComparator(GenerationInfo generationInfo) { this.generationInfo = generationInfo; } /** * {@inheritDoc} */ public int compare(Member member1, Member member2) { if (member1 == member2) { return 0; } // Primary sort = member class. if (member1.getClass() != member2.getClass()) { if (member1 instanceof Constructor<?>) { return -1; } if (member1 instanceof Field) { // If member2 not a constructor, must be a method. return member2 instanceof Constructor<?> ? 1 : -1; } // member1 must be a method. return 1; } // Secondary sort: member name. // Note: fully qualified for constructors, unqualified for methods and fields int compareResult = member1.getName().compareTo(member2.getName()); if (compareResult == 0) { Class<?>[] parameterTypes1 = null; Class<?>[] parameterTypes2 = null; if (member1 instanceof Constructor<?>) { parameterTypes1 = ((Constructor<?>)member1).getParameterTypes(); parameterTypes2 = ((Constructor<?>)member2).getParameterTypes(); } if (member1 instanceof Method) { parameterTypes1 = ((Method)member1).getParameterTypes(); parameterTypes2 = ((Method)member2).getParameterTypes(); } if (parameterTypes1 == null || parameterTypes2 == null) { // If here, logically member1 and member2 are both fields with the same name. // This can happen if they are from different classes. return 0; } // Tertiary sort: member param types. // The member with fewer params comes first. int nParams1 = parameterTypes1.length; int nParams2 = parameterTypes2.length; if (nParams1 < nParams2) { return -1; } else if (nParams1 > nParams2) { return 1; } // If here, the same number of params. // Sort according to the types of the params. for (int i = 0; i < nParams1; i++) { Class<?> paramType1 = parameterTypes1[i]; Class<?> paramType2 = parameterTypes2[i]; String typeConsName1 = generationInfo.getTypeConsName(paramType1); String typeConsName2 = generationInfo.getTypeConsName(paramType2); compareResult = typeConsName1.compareTo(typeConsName2); if (compareResult != 0) { return compareResult; } } } return compareResult; } } /** * Get the source model for a default module definition. * * @param moduleName the name of the module. * @param classNames (Set of String) the names of the classes for which functions will be generated. * @param requiredClasses (Set of Class) the classes for which types will be generated. * These should include any classes required by generated functions. * @param generationScope The scope for generated functions and types. * @param excludeMap (Class->Set of String) map from class to method names for methods to exclude. * @param workspace the cal workspace. * @return the source model for a default module definition using the above. */ public static SourceModel.ModuleDefn makeDefaultModuleDefn(ModuleName moduleName, Set<String> classNames, Set<Class<?>> requiredClasses, GenerationScope generationScope, Map<Class<?>, Set<String>> excludeMap, CALWorkspace workspace) throws UnableToResolveForeignEntityException { // Generate map (String->Class) from class name to class for required classes. Map<String, Class<?>> requiredClassNameToClassMap = new HashMap<String, Class<?>>(); for (final Class<?> requiredClass : requiredClasses) { requiredClassNameToClassMap.put(requiredClass.getName(), requiredClass); } GenerationInfo generationInfo = new GenerationInfo(moduleName, workspace, generationScope); // (List of SourceModel.TopLevelSourceElement) List<TopLevelSourceElement> topLevelSourceElementList = new ArrayList<TopLevelSourceElement>(); // Calculated the required class names. generationInfo.calculateRequiredClassNames(requiredClasses); // Create a sorted list of classes by name. List<Class<?>> requiredClassList = new ArrayList<Class<?>>(requiredClasses); Collections.sort(requiredClassList, new ClassTypeNameComparator(generationInfo)); // Generate the foreign types corresponding to the required classes. for (final Class<?> javaClass : requiredClassList) { // Skip generating a type for this class if a mapping already exists. if (!generationInfo.getForeignTypeName(javaClass).getModuleName().equals(generationInfo.currentModuleName)) { continue; } // Generate the foreign type. ForeignType foreignType = generateForeignType(javaClass, generationInfo); topLevelSourceElementList.add(foreignType); } // Create a comparator to compare the class members. MemberComparator memberComparator = new MemberComparator(generationInfo); // For each of the required classes, generate fields, constructors, methods. for (final String className : classNames) { Class<?> javaClass = requiredClassNameToClassMap.get(className); if (javaClass == null) { throw new IllegalStateException("Error: can't find class: " + className); } Field[] fields = javaClass.getFields(); // This can throw a NoClassDefFoundError Arrays.sort(fields, memberComparator); generationInfo.generateFunctionNames(fields, javaClass); for (final Field field : fields) { topLevelSourceElementList.add(generateForeignFunction(javaClass, field, generationInfo)); } Constructor<?>[] constructors = javaClass.getConstructors(); // This can throw a NoClassDefFoundError Arrays.sort(constructors, memberComparator); generationInfo.generateFunctionNames(constructors, javaClass); for (final Constructor<?> constructor : constructors) { topLevelSourceElementList.add(generateForeignFunction(javaClass, constructor, generationInfo)); } // Calculate the names of the methods to exclude for this class. Set<String> methodExcludeNames = new HashSet<String>(); for (final Map.Entry<Class<?>, Set<String>> mapEntry : excludeMap.entrySet()) { Class<?> excludeMapClass = mapEntry.getKey(); // Check if (javaClass instanceof excludeMapClass). if (excludeMapClass.isAssignableFrom(javaClass)) { methodExcludeNames.addAll(mapEntry.getValue()); } } Method[] methods = javaClass.getMethods(); // This can throw a NoClassDefFoundError Arrays.sort(methods, memberComparator); generationInfo.generateFunctionNames(methods, javaClass); for (final Method method : methods) { String methodName = method.getName(); // Check if this is a method we should exclude. if (methodExcludeNames.contains(methodName)) { continue; } // Only generate clone() if Cloneable. if (method.getName().equals("clone") && method.getParameterTypes().length == 0) { if (!Cloneable.class.isAssignableFrom(javaClass)) { continue; } } topLevelSourceElementList.add(generateForeignFunction(javaClass, method, generationInfo)); } } // Create the array of top-level source elements. SourceModel.TopLevelSourceElement[] topLevelSourceElements = topLevelSourceElementList.toArray(new SourceModel.TopLevelSourceElement[topLevelSourceElementList.size()]); // Generate a comment. SourceModel.CALDoc.Comment.Module moduleComment = getModuleCALDoc(moduleName); // Calculate the imports. SourceModel.Import[] imports = new SourceModel.Import[] { SourceModel.Import.make(CAL_Prelude.MODULE_NAME, null, null, generationInfo.getPreludeUsingTypes(), preludeUsingTypeClasses), SourceModel.Import.make(CAL_Debug.MODULE_NAME, null, null, null, debugUsingTypeClasses) }; // Create the source model for the module. ModuleDefn moduleDefn = SourceModel.ModuleDefn.make(moduleComment, moduleName, imports, topLevelSourceElements); // Add imports moduleDefn = ImportAugmenter.augmentWithImports(moduleDefn); return moduleDefn; } /** * Generate a boilerplate module comment. * @param moduleName the name of the module */ private static SourceModel.CALDoc.TextBlock getModuleCommentBoilerplate(ModuleName moduleName) { String commentText1 = "\n" + " " + moduleName + ".cal.\n" + " \n" + " "; SourceModel.CALDoc.TextSegment.InlineTag.Summary commentSegment2 = SourceModel.CALDoc.TextSegment.InlineTag.Summary.make( SourceModel.CALDoc.TextBlock.make( new SourceModel.CALDoc.TextSegment.TopLevel[] { SourceModel.CALDoc.TextSegment.Plain.make("This module was automatically generated by JFit.") })); String commentText3 = "\n" + " \n" + " Creation Date: " + LOCALE_DATE_FORMAT.format(new Date()) + "\n"; return SourceModel.CALDoc.TextBlock.make( new SourceModel.CALDoc.TextSegment.TopLevel[] { SourceModel.CALDoc.TextSegment.Plain.make(commentText1), commentSegment2, SourceModel.CALDoc.TextSegment.Plain.make(commentText3) }); } /** * Generate the CALDoc comment for a module. * @param moduleName the name of the module. * @return the source model for the auto-generated comment. */ private static SourceModel.CALDoc.Comment.Module getModuleCALDoc(ModuleName moduleName) { // Get the boilerplate strings. SourceModel.CALDoc.TextBlock moduleText = getModuleCommentBoilerplate(moduleName); // Add a tagged block for the author tag, if the "user.name" property is defined. SourceModel.CALDoc.TaggedBlock taggedBlocks[] = null; String userNameProperty = System.getProperty("user.name"); if (userNameProperty != null) { SourceModel.CALDoc.TextBlock authorBlock = SourceModel.CALDoc.TextBlock.make( new SourceModel.CALDoc.TextSegment.TopLevel[] { SourceModel.CALDoc.TextSegment.Plain.make(userNameProperty) }); taggedBlocks = new SourceModel.CALDoc.TaggedBlock[] { SourceModel.CALDoc.TaggedBlock.Author.make(authorBlock) }; } return SourceModel.CALDoc.Comment.Module.make(moduleText, taggedBlocks); } /** * Generate the source for a foreign type. * * @param foreignClass the class for which to generate a type. * @param generationInfo * @return the source for the type. */ public static ForeignType generateForeignType(Class<?> foreignClass, GenerationInfo generationInfo) { // Convert the java name to a valid type cons name. // For now, use the fully qualified name. This will be ugly though. String typeConsName = generationInfo.getTypeConsName(foreignClass); // Determine the generated scopes. GenerationScope generationScope = generationInfo.getGenerationScope(); Scope typeScope = generationScope == GenerationScope.ALL_PRIVATE ? Scope.PRIVATE : Scope.PUBLIC; Scope implementationScope = generationScope == GenerationScope.ALL_PUBLIC ? Scope.PUBLIC : Scope.PRIVATE; /* * Possible type classes: * * Always ok: * Debug.Show * Prelude.Inputable * Prelude.Outputable * Prelude.Eq * * If Comparable: * Prelude.Ord * * Only for enumerated types (not automatically generated for foreign types): * Prelude.Enum * Prelude.IntEnum * Prelude.Bounded */ // // NOTE: if changing the below, change the static XXXUsingTypeClasses String arrays in this class. // List<TypeClass> derivingClauseTypeClassNameList = new ArrayList<TypeClass>(Arrays.asList(new TypeClass[]{ TypeClass.makeUnqualified(CAL_Debug.TypeClasses.Show.getUnqualifiedName()), TypeClass.makeUnqualified(CAL_Prelude.TypeClasses.Inputable.getUnqualifiedName()), TypeClass.makeUnqualified(CAL_Prelude.TypeClasses.Outputable.getUnqualifiedName()), TypeClass.makeUnqualified(CAL_Prelude.TypeClasses.Eq.getUnqualifiedName()) })); if (foreignClass.isPrimitive() || Comparable.class.isAssignableFrom(foreignClass)) { derivingClauseTypeClassNameList.add(TypeClass.makeUnqualified(CAL_Prelude.TypeClasses.Ord.getUnqualifiedName())); } TypeClass[] derivingClauseTypeClassNames = derivingClauseTypeClassNameList.toArray(new TypeClass[derivingClauseTypeClassNameList.size()]); // // Create the cal doc comment. // final String calSourceNameOfJavaType = ForeignTypeInfo.getCalSourceName(foreignClass); return ForeignType.make((CALDoc.Comment.TypeCons)null, typeConsName, typeScope, true, calSourceNameOfJavaType, implementationScope, true, derivingClauseTypeClassNames); } /** * Generate the source for a foreign function representing a method. * * @param javaClass the class containing the method. * @param javaMethod the method. * @param generationInfo * @return the foreign function definition to call the method. */ public static SourceModel.FunctionDefn.Foreign generateForeignFunction(Class<?> javaClass, Method javaMethod, GenerationInfo generationInfo) { return generateForeignFunction( javaMethod, "method", javaClass, javaMethod.getReturnType(), javaMethod.getParameterTypes(), generationInfo); } /** * Generate the source for a foreign function representing a field. * * @param javaClass the class containing the field. * @param javaField the field. * @param generationInfo * @return the foreign function definition to get the field value. */ public static SourceModel.FunctionDefn.Foreign generateForeignFunction(Class<?> javaClass, Field javaField, GenerationInfo generationInfo) { return generateForeignFunction( javaField, "field", javaClass, javaField.getType(), null, generationInfo); } /** * Generate the source for a foreign function representing a constructor. * * @param javaClass the class containing the method. * @param javaConstructor the constructor. * @param generationInfo * @return the foreign function definition invoking the constructor. */ public static SourceModel.FunctionDefn.Foreign generateForeignFunction(Class<?> javaClass, Constructor<?> javaConstructor, GenerationInfo generationInfo) { return generateForeignFunction( javaConstructor, "constructor", javaClass, javaConstructor.getDeclaringClass(), javaConstructor.getParameterTypes(), generationInfo); } /** * Generate a foreign function for a member of a Java class. * * Note: it would be convenient to be able to make javaClass the same as the member's declaring class. * However, because of type signatures, we need to generate separate foreign functions for everywhere a function is defined. * * eg. for the hashCode method, we want to end up with type signatures something like: * JObject_hashCode :: JObject -> Int; * JFoo_hashCode :: JFoo -> Int; * * @param javaMember the member. * @param memberKindString the string describing the member. "method", "field", or "constructor" * @param javaClass the class containing the member. * @param returnType * @param paramTypes * @param generationInfo * * @return the source model for the foreign function definition. */ private static SourceModel.FunctionDefn.Foreign generateForeignFunction( final Member javaMember, final String memberKindString, final Class<?> javaClass, final Class<?> returnType, final Class<?>[] paramTypes, final GenerationInfo generationInfo) { final boolean isStatic = Modifier.isStatic(javaMember.getModifiers()); // Create a valid function name. final String functionName = generationInfo.getNewFunctionName(javaMember, javaClass); // Determine scope. final Scope scope = (generationInfo.getGenerationScope() == GenerationScope.ALL_PRIVATE) ? Scope.PRIVATE : Scope.PUBLIC; // Create the external name. final String externalName; { final StringBuilder externalNameSB = new StringBuilder(); if (isStatic) { externalNameSB.append("static "); } externalNameSB.append(memberKindString); if (!(javaMember instanceof Constructor<?>)) { final String memberName = javaMember.getName(); // Constructor.getName() is fully qualified, but Method.getName() and Field.getName() are not. final String qualifiedMemberName = javaClass.getName() + "." + memberName; externalNameSB.append(" "); if (isStatic) { externalNameSB.append(qualifiedMemberName); } else { externalNameSB.append(memberName); } } externalName = externalNameSB.toString(); } // Create the type signature. final Class<?> instanceMemberClass = isStatic || javaMember instanceof Constructor<?> ? null : javaClass; final TypeSignature typeSignature = getTypeSignature(paramTypes, returnType, instanceMemberClass, generationInfo); return Foreign.make((SourceModel.CALDoc.Comment.Function)null, functionName, scope, true, externalName, typeSignature); } /** * Generate a type signature. * * @param paramTypes if not null, the types of any parameters to a function or constructor. * @param returnType the return type for a function or constructor. The field type for a field. * @param instanceMemberClass if the member is a non-static method or field, the the class containing the member. Otherwise null. * @param generationInfo * @return the type signature representing the provided args. */ private static TypeSignature getTypeSignature(Class<?>[] paramTypes, Class<?> returnType, Class<?> instanceMemberClass, GenerationInfo generationInfo) { // Build the type expr from right to left.. // The typeExpr defn ends with the return type. TypeExprDefn typeExprDefn = getTypeExprForClass(returnType, generationInfo); // Add the argument types in reverse order. if (paramTypes != null) { Class<?>[] methodArgTypes = paramTypes; for (int i = methodArgTypes.length - 1; i >= 0; i--) { Class<?> methodArgType = methodArgTypes[i]; TypeExprDefn argTypeCalNameTypeExprDefn = getTypeExprForClass(methodArgType, generationInfo); typeExprDefn = TypeExprDefn.Function.make(argTypeCalNameTypeExprDefn, typeExprDefn); } } // If a method is not static, the class CAL type has to precede the argument list. if (instanceMemberClass != null) { TypeExprDefn argTypeCalNameTypeExprDefn = getTypeExprForClass(instanceMemberClass, generationInfo); typeExprDefn = TypeExprDefn.Function.make(argTypeCalNameTypeExprDefn, typeExprDefn); } // Finally, create and return the type signature. return TypeSignature.make(typeExprDefn); } /** * Generate a type expr to represent a class. * * @param javaClass the class for which a type expr should be calculated. * @param generationInfo * @return the source model for a type expr defn for the class. */ private static TypeExprDefn getTypeExprForClass(Class<?> javaClass, GenerationInfo generationInfo) { // If this is an array, it would be nice to make an array or list from the component types... // if (clazz.isArray()) { // Class componentType = clazz.getComponentType(); // return TypeExprDefn.List.make(getTypeExprForClass(componentType, generationInfo)); // } QualifiedName typeConsName = generationInfo.getForeignTypeName(javaClass); // For the unit, make a Unit type. if (typeConsName.equals(CAL_Prelude.TypeConstructors.Unit)) { return TypeExprDefn.Unit.make(); } // Just a regular type cons. // If in the current module, we can use in unqualified form. ModuleName typeConsModuleName = typeConsName.getModuleName(); if (typeConsModuleName.equals(generationInfo.getCurrentModuleName())) { return TypeExprDefn.TypeCons.make(null, typeConsName.getUnqualifiedName()); } else if (typeConsModuleName.equals(CAL_Prelude.MODULE_NAME)) { // If in the Prelude, may also be able to use the unqualified form, but this means we need to add to the using clause. // We cannot use in an unqualifed way if a type with the same unqualifed name exists in the current module. String unqualifiedTypeName = typeConsName.getUnqualifiedName(); if (generationInfo.addTypeToPreludeUsingSet(unqualifiedTypeName)) { return TypeExprDefn.TypeCons.make(null, unqualifiedTypeName); } } // Use the qualified form. return TypeExprDefn.TypeCons.make(typeConsName); } }