Package org.openquark.gems.client.jfit

Source Code of org.openquark.gems.client.jfit.ForeignImportGenerator$GenerationScope

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

Related Classes of org.openquark.gems.client.jfit.ForeignImportGenerator$GenerationScope

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