org.eclipse.xtend.core.jvmmodel.DispatchHelper.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.xtend.core.jvmmodel.DispatchHelper.java

Source

/*******************************************************************************
 * Copyright (c) 2011 itemis AG (http://www.itemis.eu) and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *******************************************************************************/
package org.eclipse.xtend.core.jvmmodel;

import static com.google.common.collect.Iterables.*;

import java.util.Collections;
import java.util.Comparator;
import java.util.List;

import org.eclipse.emf.common.notify.impl.AdapterImpl;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.xtend.core.xtend.XtendFunction;
import org.eclipse.xtext.common.types.JvmDeclaredType;
import org.eclipse.xtext.common.types.JvmFeature;
import org.eclipse.xtext.common.types.JvmMember;
import org.eclipse.xtext.common.types.JvmOperation;
import org.eclipse.xtext.common.types.JvmType;
import org.eclipse.xtext.common.types.JvmTypeReference;
import org.eclipse.xtext.common.types.util.Primitives;
import org.eclipse.xtext.common.types.util.TypeReferences;
import org.eclipse.xtext.util.Strings;
import org.eclipse.xtext.xbase.jvmmodel.IJvmModelAssociations;
import org.eclipse.xtext.xbase.typesystem.references.ITypeReferenceOwner;
import org.eclipse.xtext.xbase.typesystem.references.StandardTypeReferenceOwner;
import org.eclipse.xtext.xbase.typesystem.util.CommonTypeComputationServices;
import org.eclipse.xtext.xbase.typesystem.util.ContextualVisibilityHelper;
import org.eclipse.xtext.xbase.typesystem.util.IVisibilityHelper;
import org.eclipse.xtext.xbase.typesystem.util.Multimaps2;

import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Lists;
import com.google.inject.Inject;

/*
 * TODO In case we deal with a Java class: should we check for an associated
 * dispatcher?
 */
/**
 * Helpers to deal with {@link XtendFunction#isDispatch() dispatch methods}.
 * Methods are considered to be dispatch methods, if they 
 * are derived from an {@link XtendFunction} that is marked as such
 * or if they are defined in a Java type and their name starts with an
 * underscore.
 * 
 * @author Jan Koehnlein - Initial contribution and API
 */
public class DispatchHelper {

    /**
     * A dispatch signature is a pair of a simple name
     * and an arity. In that sense it's different from a common understanding
     * of a method signature, since the dispatch signature does not take
     * the parameter types into account but only the number of parameters.
     */
    public static class DispatchSignature {
        private final String simpleName;
        private final int arity;

        /**
         * Creates a new {@link DispatchSignature} from a method name and parameter count.
         * The method name may not include the synthesized underscore even though an explicit underscore is ok.
         * @param simpleName the simple name of the associated dispatcher function.
         * @param arity the number of parameters (zero or more)
         */
        public DispatchSignature(String simpleName, int arity) {
            if (arity < 0) {
                throw new IllegalArgumentException(
                        "The number of parameters may not be smaller than zero, was: " + arity);
            }
            this.simpleName = "_" + simpleName;
            this.arity = arity;
        }

        /**
         * Returns the number of parameters for this dispatch signature.
         */
        public int getArity() {
            return arity;
        }

        /**
         * Returns the name of the dispatcher method. That is, the name without a synthetic underscore.
         */
        public String getSimpleName() {
            return simpleName.substring(1);
        }

        /**
         * Returns the name of the dispatch cases as they would be compiled to Java. The name includes the
         * synthetic underscore.
         */
        public String getDispatchCaseName() {
            return simpleName;
        }

        /**
         * Returns true, if the given operation matches this dispatch signature and is therefore a case
         * for this dispatcher.
         */
        public boolean isDispatchCase(JvmOperation operation) {
            return arity == operation.getParameters().size() && simpleName.equals(operation.getSimpleName());
        }

        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + arity;
            result = prime * result + ((simpleName == null) ? 0 : simpleName.hashCode());
            return result;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (getClass() != obj.getClass())
                return false;
            DispatchSignature other = (DispatchSignature) obj;
            if (arity != other.arity)
                return false;
            if (simpleName == null) {
                if (other.simpleName != null)
                    return false;
            } else if (!simpleName.equals(other.simpleName))
                return false;
            return true;
        }

        @Override
        public String toString() {
            StringBuilder builder = new StringBuilder();
            builder.append("DispatchSignature [simpleName=");
            builder.append(simpleName);
            builder.append(", arity=");
            builder.append(arity);
            builder.append("]");
            return builder.toString();
        }

    }

    @Inject
    private IJvmModelAssociations associations;

    @Inject
    private Primitives primitives;

    @Inject
    private TypeReferences typeRefs;

    @Inject
    private IVisibilityHelper visibilityHelper;

    @Inject
    private CommonTypeComputationServices services;

    private static class DispatcherMarker extends AdapterImpl {
    }

    public void markAsDispatcherFunction(JvmOperation inferredOperation) {
        inferredOperation.eAdapters().add(new DispatcherMarker());
    }

    public boolean isDispatcherFunction(JvmOperation inferredOperation) {
        return Iterables.any(inferredOperation.eAdapters(), new Predicate<Object>() {
            @Override
            public boolean apply(Object input) {
                return input instanceof DispatcherMarker;
            }
        });
    }

    public boolean isDispatchFunction(JvmOperation inferredOperation) {
        if (inferredOperation.getSimpleName() != null && inferredOperation.getSimpleName().startsWith("_")) {
            EObject sourceElement = associations.getPrimarySourceElement(inferredOperation);
            if (sourceElement instanceof XtendFunction) {
                XtendFunction function = (XtendFunction) sourceElement;
                if (function.isDispatch() && inferredOperation.getSimpleName().equals("_" + function.getName()))
                    return true;
                return false;
            }
        }
        return false;
    }

    /* @Nullable */
    public JvmOperation getDispatcherOperation(JvmOperation dispatchCase) {
        EObject sourceElement = associations.getPrimarySourceElement(dispatchCase);
        if (sourceElement instanceof XtendFunction) {
            XtendFunction function = (XtendFunction) sourceElement;
            if (function.isDispatch()) {
                Iterable<JvmOperation> operations = filter(associations.getJvmElements(sourceElement),
                        JvmOperation.class);
                for (JvmOperation operation : operations) {
                    if (Strings.equal(operation.getSimpleName(), function.getName())) {
                        return operation;
                    }
                }
            }
        } else {
            DispatchSignature signature = new DispatchSignature(dispatchCase.getSimpleName().substring(1),
                    dispatchCase.getParameters().size());
            JvmOperation result = getDispatcherOperation(dispatchCase.getDeclaringType(), signature);
            return result;
        }
        return null;
    }

    /* @Nullable */
    public JvmOperation getDispatcherOperation(JvmDeclaredType type, DispatchSignature signature) {
        Iterable<JvmFeature> allByName = type.findAllFeaturesByName(signature.getSimpleName());
        for (JvmFeature feature : allByName) {
            if (feature instanceof JvmOperation
                    && signature.getArity() == ((JvmOperation) feature).getParameters().size()) {
                return (JvmOperation) feature;
            }
        }
        return null;
    }

    /**
     * Return the local cases that match the given signature (in no particular order, usually as defined in the file).
     */
    public List<JvmOperation> getLocalDispatchCases(JvmDeclaredType type, DispatchSignature signature) {
        List<JvmOperation> result = Lists.newArrayListWithExpectedSize(5);
        for (JvmMember feature : type.getMembers()) {
            if (feature instanceof JvmOperation && signature.isDispatchCase((JvmOperation) feature)) {
                result.add((JvmOperation) feature);
            }
        }
        return result;
    }

    /**
     * Return the local cases that contribute to the given dispatch operation (in no particular order, but usually as defined in the file).
     */
    public List<JvmOperation> getLocalDispatchCases(JvmOperation dispatcherOperation) {
        DispatchSignature dispatchSignature = new DispatchSignature(dispatcherOperation.getSimpleName(),
                dispatcherOperation.getParameters().size());
        JvmDeclaredType type = dispatcherOperation.getDeclaringType();
        return getLocalDispatchCases(type, dispatchSignature);
    }

    /**
     * Return all the cases that are associated with the given dispatch operation.
     */
    public List<JvmOperation> getAllDispatchCases(JvmOperation dispatcherOperation) {
        DispatchSignature dispatchSignature = new DispatchSignature(dispatcherOperation.getSimpleName(),
                dispatcherOperation.getParameters().size());
        JvmDeclaredType type = dispatcherOperation.getDeclaringType();
        ITypeReferenceOwner owner = new StandardTypeReferenceOwner(services, type);
        ContextualVisibilityHelper contextualVisibilityHelper = new ContextualVisibilityHelper(visibilityHelper,
                owner.newParameterizedTypeReference(type));
        return getAllDispatchMethods(dispatchSignature, type, contextualVisibilityHelper);
    }

    /**
     * Computes all the dispatch methods that are declared in the given type or altered
     * by additional cases in this type. The associated operations are sorted by according their parameter types
     * from left to right where the most special types occur before more common types. Ambiguous
     * ordering is resolved alphabetically.
     * 
      * An exemplary order would look like this
     * <pre>
     *   method(String)
     *   method(Serializable)
     *   method(CharSequence)
     *   method(Object)
     * </pre>
     * 
     * @return a mapping from {@link DispatchSignature signature} to sorted operations.
     */
    public ListMultimap<DispatchSignature, JvmOperation> getDeclaredOrEnhancedDispatchMethods(
            JvmDeclaredType type) {
        ListMultimap<DispatchSignature, JvmOperation> result = Multimaps2.newLinkedHashListMultimap(2, 4);
        Iterable<JvmOperation> operations = type.getDeclaredOperations();
        ITypeReferenceOwner owner = new StandardTypeReferenceOwner(services, type);
        ContextualVisibilityHelper contextualVisibilityHelper = new ContextualVisibilityHelper(visibilityHelper,
                owner.newParameterizedTypeReference(type));
        for (JvmOperation operation : operations) {
            if (isDispatchFunction(operation)) {
                DispatchSignature signature = new DispatchSignature(operation.getSimpleName().substring(1),
                        operation.getParameters().size());
                if (!result.containsKey(signature)) {
                    List<JvmOperation> allOperations = getAllDispatchMethods(signature, type,
                            contextualVisibilityHelper);
                    result.putAll(signature, allOperations);
                }
            }
        }
        return result;
    }

    protected List<JvmOperation> getAllDispatchMethods(DispatchSignature signature, JvmDeclaredType type,
            ContextualVisibilityHelper contextualVisibilityHelper) {
        List<JvmOperation> allOperations = Lists.newArrayListWithExpectedSize(5);
        Iterable<JvmFeature> allFeatures = type.findAllFeaturesByName(signature.getDispatchCaseName());
        for (JvmFeature feature : allFeatures) {
            if (feature instanceof JvmOperation) {
                JvmOperation operationByName = (JvmOperation) feature;
                if (signature.isDispatchCase(operationByName)
                        && contextualVisibilityHelper.isVisible(operationByName)) {
                    allOperations.add(operationByName);
                }
            }
        }
        sort(allOperations);
        return allOperations;
    }

    protected void sort(List<JvmOperation> operations) {
        Collections.sort(operations, new Comparator<JvmOperation>() {
            @Override
            public int compare(JvmOperation o1, JvmOperation o2) {
                return DispatchHelper.this.compare(o1, o2);
            }
        });
    }

    protected int compare(JvmOperation o1, JvmOperation o2) {
        int params = o1.getParameters().size();
        for (int i = 0; i < params; i++) {
            final JvmTypeReference p1 = o1.getParameters().get(i).getParameterType();
            final JvmTypeReference p2 = o2.getParameters().get(i).getParameterType();
            int distance1 = p1 == null ? Integer.MAX_VALUE : getMaxDistanceToObject(p1);
            int distance2 = p2 == null ? Integer.MAX_VALUE : getMaxDistanceToObject(p2);
            if (distance1 != distance2) {
                return distance2 - distance1;
            }
        }
        String identifier1 = o1.getIdentifier();
        String parameterTypes1 = identifier1.substring(identifier1.indexOf('('));
        String identifier2 = o2.getIdentifier();
        String parameterTypes2 = identifier2.substring(identifier2.indexOf('('));
        return parameterTypes1.compareTo(parameterTypes2);
    }

    protected int getMaxDistanceToObject(JvmTypeReference type) {
        type = primitives.asWrapperTypeIfPrimitive(type);
        if (typeRefs.is(type, Object.class))
            return 0;
        JvmType jvmType = type.getType();
        int maxDistance = 1;
        if (jvmType instanceof JvmDeclaredType) {
            EList<JvmTypeReference> list = ((JvmDeclaredType) jvmType).getSuperTypes();
            for (JvmTypeReference jvmTypeReference : list) {
                int result = 1 + getMaxDistanceToObject(jvmTypeReference);
                if (result > maxDistance)
                    maxDistance = result;
            }
        }
        return maxDistance;
    }
}