org.eclipse.b3.backend.evaluator.JavaToB3Helper.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.b3.backend.evaluator.JavaToB3Helper.java

Source

/**
 * Copyright (c) 2010, Cloudsmith Inc.
 * The code, documentation and other materials contained herein have been
 * licensed under the Eclipse Public License - v 1.0 by the copyright holder
 * listed above, as the Initial Contributor under such license. The text of
 * such license is available at www.eclipse.org.
 */

package org.eclipse.b3.backend.evaluator;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.eclipse.b3.backend.core.B3Backend;
import org.eclipse.b3.backend.core.exceptions.B3EngineException;
import org.eclipse.b3.backend.core.exceptions.B3FunctionLoadException;
import org.eclipse.b3.backend.evaluator.b3backend.B3MetaClass;
import org.eclipse.b3.backend.evaluator.b3backend.B3ParameterizedType;
import org.eclipse.b3.backend.evaluator.b3backend.B3backendFactory;
import org.eclipse.b3.backend.evaluator.b3backend.BGuard;
import org.eclipse.b3.backend.evaluator.b3backend.BJavaCallType;
import org.eclipse.b3.backend.evaluator.b3backend.BJavaFunction;
import org.eclipse.b3.backend.evaluator.b3backend.BParameterDeclaration;
import org.eclipse.b3.backend.evaluator.b3backend.BTCPluggable;
import org.eclipse.b3.backend.evaluator.b3backend.BTypeCalculator;
import org.eclipse.b3.backend.evaluator.b3backend.IFunction;
import org.eclipse.b3.backend.evaluator.b3backend.impl.FunctionCandidateAdapterFactory;
import org.eclipse.b3.backend.evaluator.typesystem.TypeUtils;
import org.eclipse.b3.provisional.core.EMFPackage;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EFactory;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EcoreFactory;
import org.eclipse.emf.ecore.impl.EClassImpl;

import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
import com.google.inject.internal.Maps;

/**
 * Converts a class with (optional) B3 annotations into B3 Functions.
 * 
 */
public class JavaToB3Helper {
    private static Map<Class<? extends BTypeCalculator>, BTypeCalculator> calculatorMap = Maps.newHashMap();

    /**
     * <!-- begin-user-doc -->
     * Create and initialize a BJavaFunction to represent the method m.
     * 
     * @param m
     *            the method to create the BJavaFunction representation for
     * @param callType
     *            the intended call type of the BJavaFunction to be created
     * @return the BJavaFunction representation of the method m
     *         <!-- end-user-doc -->
     * @generated NOT
     */
    public static BJavaFunction createJavaFunction(Method m, BJavaCallType callType) {
        int modifiers = m.getModifiers();
        boolean isStatic;

        // only handle public methods and also require the methods to be static if the call type is different from
        // BJavaCallType.METHOD
        if (!(Modifier.isPublic(modifiers)
                && ((isStatic = Modifier.isStatic(modifiers)) || callType == BJavaCallType.METHOD)))
            return null;

        BJavaFunction f = B3backendFactory.eINSTANCE.createBJavaFunction();

        // TODO: f's Resource ?!?
        // String fileName = m.getDeclaringClass().getCanonicalName();
        // fileRef.setFileName(fileName == null ? "anonymous class" : fileName);

        f.setName(m.getName());
        f.setMethod(m); // add the thing to call
        f.setFinal(Modifier.isFinal(modifiers)); // make it final in b3 as well
        f.setCallType(callType);
        f.setVarArgs(m.isVarArgs());
        f.setTypeParameters(m.getTypeParameters());

        Type[] instanceParameterTypes;
        String[] instanceParameterNames;

        if (callType == BJavaCallType.METHOD) {
            // only set this for methods with call type == BJavaCallType.METHOD
            f.setClassFunction(isStatic); // invokable via class - i.e. "static" in java
            if (isStatic) {
                B3MetaClass metaClass = B3backendFactory.eINSTANCE.createB3MetaClass();
                metaClass.setInstanceClass(m.getDeclaringClass());
                instanceParameterTypes = new Type[] { metaClass };
                instanceParameterNames = new String[] { "class" };
            } else {
                instanceParameterTypes = new Type[] { m.getDeclaringClass() };
                instanceParameterNames = new String[] { "this" };
            }
        } else {
            instanceParameterTypes = null;
            instanceParameterNames = null;
        }

        TypeUtils.JavaCandidate javaFunctionCandidate = FunctionCandidateAdapterFactory.eINSTANCE.adapt(f,
                TypeUtils.JavaCandidate.class);
        // this eventually calls f.setParameterTypes()
        javaFunctionCandidate.processJavaParameterTypes(instanceParameterTypes);

        f.setParameterNames(getParameterNames(m.getParameterAnnotations(), instanceParameterNames));

        setParameterDeclarations(f);

        f.setReturnType(TypeUtils.objectify(m.getGenericReturnType()));
        f.setExceptionTypes(m.getGenericExceptionTypes());

        return f;
    }

    public static Iterable<IFunction> getNamedFunctions(Class<?> clazz, String name) {
        List<IFunction> result = new ArrayList<IFunction>();
        Method[] ms = clazz.getMethods();
        for (int i = 0; i < ms.length; i++) {
            if (!name.equals(ms[i].getName()))
                continue;
            result.add(createJavaFunction(ms[i], BJavaCallType.METHOD));
        }
        return result;
    }

    /**
     * Loads static functions from a java class. Using (optional) B3Backend annotations to
     * direct the translation into B3 functions.
     * Returns a Multimap of each defined function (as key), and for each function one or more names (aliases).
     * 
     */
    public static Multimap<IFunction, String> loadFunctions(Class<? extends Object> clazz)
            throws B3EngineException {
        Multimap<IFunction, String> result = Multimaps.newHashMultimap();
        Map<String, BJavaFunction> systemFunctions = new HashMap<String, BJavaFunction>();
        Map<String, BJavaFunction> guards = new HashMap<String, BJavaFunction>();
        Map<String, BJavaFunction> typeCalculators = new HashMap<String, BJavaFunction>();

        Map<String, List<BJavaFunction>> systemProxies = new HashMap<String, List<BJavaFunction>>();
        Map<String, List<BJavaFunction>> guardedFunctions = new HashMap<String, List<BJavaFunction>>();
        Map<String, List<BJavaFunction>> typeCalculatedFunctions = new HashMap<String, List<BJavaFunction>>();

        // create and initialize a BJavaFunction to represent each public static function
        for (Method m : clazz.getDeclaredMethods()) {
            // the call type of the function may later change to system call
            BJavaFunction f = createJavaFunction(m, BJavaCallType.FUNCTION);

            if (f == null)
                continue;

            // check for annotations
            B3Backend annotation = m.getAnnotation(B3Backend.class);

            // take a shortcut if there is no annotation
            if (annotation == null) {
                result.put(f, m.getName());
                continue;
            }

            String[] names = annotation.funcNames();

            if (annotation.hideOriginal()) {
                if (names == null || names.length == 0)
                    throw new B3FunctionLoadException(
                            "hideOriginal annotation specified but not funcNames annotation", m);
                f.setName(names[0]);
            }

            f.setExecutionMode(BackendHelper.getExecutionMode(annotation));
            f.setVisibility(BackendHelper.getVisibility(annotation));

            // If the function is a system function, set it aside and patch the functions proxying it later.
            if (annotation.system()) {
                systemFunctions.put(f.getName(), f);
                continue;
            }

            // If the function is a guard, set it aside and patch the functions using it later.
            if (annotation.guard()) {
                // guards are called using system calling convention
                f.setCallType(BJavaCallType.SYSTEM);
                guards.put(f.getName(), f);
                continue;
            }

            // If the function is a type calculator, set it aside and patch the functions using it later.
            if (annotation.typeCalculator()) {
                typeCalculators.put(f.getName(), f);
                continue;
            }

            // add defined function to the result
            if (!annotation.hideOriginal())
                result.put(f, m.getName());

            if (names != null) {
                for (String fname : names)
                    result.put(f, fname);

            }

            // if a function is a proxy for a system function, remember it
            {
                String systemFunctionName = annotation.systemFunction();
                if (systemFunctionName != null && systemFunctionName.length() > 0) {
                    List<BJavaFunction> fs = null;
                    if ((fs = systemProxies.get(systemFunctionName)) == null)
                        systemProxies.put(systemFunctionName, fs = new ArrayList<BJavaFunction>());
                    fs.add(f);
                }
            }

            // if a function is guarded, remember it
            {
                String guardFunctionName = annotation.guardFunction();
                if (guardFunctionName != null && guardFunctionName.length() > 0) {
                    List<BJavaFunction> gf = null;
                    if ((gf = guardedFunctions.get(guardFunctionName)) == null)
                        guardedFunctions.put(guardFunctionName, gf = new ArrayList<BJavaFunction>());
                    gf.add(f);
                }
            }

            // if a function has a type calculator, remember it
            {
                // if it has a typeClass, use that instead of type function
                final Class<? extends BTypeCalculator> typeClass = annotation.typeClass();
                if (typeClass != null && typeClass != BTypeCalculator.class) {
                    // Jump through EMF hoops to get an instance of the impl class.
                    // BTypeCalculator typeCalculator = calculatorMap.get(typeClass);
                    // if(typeCalculator == null) {
                    BTypeCalculator typeCalculator = createInstanceUsingPackageFactory(typeClass);
                    // calculatorMap.put(typeClass, typeCalculator);
                    // }
                    f.setTypeCalculator(typeCalculator);

                }
                String typeCalculatorFunctionName = annotation.typeFunction();
                if (typeCalculatorFunctionName != null && typeCalculatorFunctionName.length() > 0) {
                    List<BJavaFunction> tf = null;
                    if ((tf = typeCalculatedFunctions.get(typeCalculatorFunctionName)) == null)
                        typeCalculatedFunctions.put(typeCalculatorFunctionName,
                                tf = new ArrayList<BJavaFunction>());
                    tf.add(f);
                }
            }
        }

        // patch system functions
        // ---
        // revisit all functions that reference a system function and replace their method
        // with the system method, and also indicate that the call should be made as a system
        // call.
        for (Entry<String, List<BJavaFunction>> e : systemProxies.entrySet()) {
            for (BJavaFunction s : e.getValue()) {
                BJavaFunction sys = systemFunctions.get(e.getKey());
                if (sys == null)
                    throw new B3FunctionLoadException("reference to system function: " + e.getKey()
                            + " can not be satisfied - no such method found.", s.getMethod());
                // patch the method in the func store with values from the system function
                s.setCallType(BJavaCallType.SYSTEM);
                s.setMethod(sys.getMethod());
            }
        }

        // link guards
        // ---
        // revisit all functions that reference a guardFunction and set that function as a guard
        //
        for (Entry<String, List<BJavaFunction>> e : guardedFunctions.entrySet()) {
            for (BJavaFunction guarded : e.getValue()) {
                BJavaFunction g = guards.get(e.getKey());
                if (g == null)
                    throw new B3FunctionLoadException("reference to guard function: " + e.getKey()
                            + " can not be satisfied - no such guard found.", guarded.getMethod());
                // set the guard function, wrapped in a guard
                BGuard gf = B3backendFactory.eINSTANCE.createBGuard();
                gf.setFunc(g);
                guarded.setGuard(gf);
            }
        }

        // link type calculators
        // ---
        // revisit all functions that reference a typeFunction and set that function as a typeCalculator
        //
        for (Entry<String, List<BJavaFunction>> e : typeCalculatedFunctions.entrySet()) {
            for (BJavaFunction typed : e.getValue()) {
                BJavaFunction tc = typeCalculators.get(e.getKey());
                if (tc == null)
                    throw new B3FunctionLoadException("reference to type calculator function: " + e.getKey()
                            + " can not be satisfied - no such function found.", typed.getMethod());
                // set the type calculator function, wrapped in a BTypeCalculator
                BTCPluggable tcf = B3backendFactory.eINSTANCE.createBTCPluggable();
                tcf.setFunc(tc);
                typed.setTypeCalculator(tcf);
            }
        }
        return Multimaps.unmodifiableMultimap(result);
    }

    private static <T> T createInstanceUsingPackageFactory(Class<T> clazz) {
        // Jump through EMF hoops to get an instance of the impl class.

        if (clazz == null || !clazz.isInterface())
            throw new IllegalArgumentException("The class: " + clazz + " is not an interface");

        // Requires that the EMF URL can be obtained from the interface.
        // Which requires an annotation using @EMFPackage
        EMFPackage packageInfo = clazz.getPackage().getAnnotation(EMFPackage.class);
        if (packageInfo == null)
            throw new IllegalArgumentException(
                    "The interface: " + clazz + "is not in a package annotated with @EMFPackage");
        String emfPackageURI = packageInfo.value();
        if (emfPackageURI == null || emfPackageURI.length() < 1)
            throw new IllegalArgumentException(
                    "The @EMFPackage annotation did not define the package URI for:" + clazz);

        EFactory factory = EPackage.Registry.INSTANCE.getEFactory(emfPackageURI);
        if (factory == null)
            throw new IllegalStateException(
                    "Can not load type calculator class - no EFactory found for:" + emfPackageURI);
        EClassifier eTypeCalculatorClass = factory.getEPackage().getEClassifier(clazz.getSimpleName());
        if (eTypeCalculatorClass == null)
            throw new IllegalStateException(
                    "The EMF package for: " + clazz + "did not contain an EClass for it using its simple name");

        // Hoop jumping time - need an EClass instance to be able to create using the factory method create.
        // The EClass is not obtainable from the first lookup, it needs to be created and its ID set to
        // what is obtained from the classifier we got back when looking up the EClassifier via name.
        EClassImpl anEclass = (EClassImpl) EcoreFactory.eINSTANCE.createEClass();
        anEclass.setClassifierID(eTypeCalculatorClass.getClassifierID());

        // An instance can now be created
        Object x = factory.create(anEclass);
        if (!clazz.isInstance(x))
            throw new IllegalStateException("Could not create type class:" + clazz);
        return clazz.cast(x);

    }

    private static String[] getParameterNames(Annotation[][] allParametersAnnotations,
            String... instanceParameterNames) {
        int instanceParameterNamesCount = instanceParameterNames != null ? instanceParameterNames.length : 0;
        String parameterNames[] = new String[instanceParameterNamesCount + allParametersAnnotations.length];
        HashSet<String> usedNames = new HashSet<String>();
        // first process the instance parameter names - don't allow duplicates
        for (int i = 0; i < instanceParameterNamesCount; ++i) {
            String name = instanceParameterNames[i];
            if (!usedNames.contains(name)) {
                parameterNames[i] = name;
                usedNames.add(name);
            }
        }
        // now look for parameter names defined by annotations - watch for duplicates again
        for (int i = 0; i < allParametersAnnotations.length; ++i) {
            Annotation[] parameterAnnotations = allParametersAnnotations[i];
            if (parameterAnnotations != null) {
                for (int j = 0; j < parameterAnnotations.length; ++j) {
                    if (parameterAnnotations[j] instanceof B3Backend) {
                        String name = ((B3Backend) parameterAnnotations[j]).name();
                        if (!usedNames.contains(name)) {
                            parameterNames[instanceParameterNamesCount + i] = name;
                            usedNames.add(name);
                        }
                        break; // only use first name declared for the parameter
                    }
                }
            }
        }
        // finally supply default unique names for those parameters which have no name assigned by now
        for (int i = 0, j = 0; i < parameterNames.length; ++i) {
            if (parameterNames[i] == null) {
                String name;
                while (usedNames.contains(name = toAlphabetString(j++)))
                    ;
                parameterNames[i] = name;
            }
        }
        return parameterNames;
    }

    private static void setParameterDeclarations(BJavaFunction f) {
        // note: Makes use of BJavaFunction's parameter type/names as array.
        Type[] types = f.getParameterTypes();
        // Type[] types = FunctionUtils.getParameterTypes(f);
        String[] names = f.getParameterNames();
        if (types.length != names.length)
            throw new IllegalArgumentException("All types must have a name");
        EList<BParameterDeclaration> parameters = f.getParameters();
        for (int i = 0; i < types.length; i++) {
            BParameterDeclaration decl = B3backendFactory.eINSTANCE.createBParameterDeclaration();
            Type type = types[i];
            if (!(type instanceof EObject)) {
                B3ParameterizedType b3type = B3backendFactory.eINSTANCE.createB3ParameterizedType();
                b3type.setRawType(type);
                types[i] = type = b3type; // modify the parameterTypes array as well to keep it in sync
            }
            decl.setType(type);
            decl.setName(names[i]);
            parameters.add(decl);
        }
    }

    private static String toAlphabetString(long number) {
        if (number < 0)
            throw new IllegalArgumentException();

        StringBuilder buf = new StringBuilder();

        while (number >= ('z' - 'a' + 1)) {
            buf.append((char) ('a' + (int) (number % ('z' - 'a' + 1))));
            number = number / ('z' - 'a' + 1) - 1;
        }
        buf.append((char) ('a' + (int) number));

        return buf.reverse().toString();
    }
}