org.eclipse.objectteams.otdt.internal.core.MethodMapping.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.objectteams.otdt.internal.core.MethodMapping.java

Source

/*******************************************************************************
 * Copyright (c) 2000, 2012 IBM Corporation 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
 * 
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *     Fraunhofer FIRST - extended API and implementation
 *     Technical University Berlin - extended API and implementation
 *******************************************************************************/
package org.eclipse.objectteams.otdt.internal.core;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.jdt.core.IBuffer;
import org.eclipse.jdt.core.IClassFile;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IMember;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IOpenable;
import org.eclipse.jdt.core.ISourceRange;
import org.eclipse.jdt.core.ISourceReference;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.ITypeHierarchy;
import org.eclipse.jdt.core.ITypeParameter;
import org.eclipse.jdt.core.ITypeRoot;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.Signature;
import org.eclipse.jdt.core.SourceRange;
import org.eclipse.jdt.core.ToolFactory;
import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.core.compiler.IScanner;
import org.eclipse.jdt.core.compiler.ITerminalSymbols;
import org.eclipse.jdt.core.compiler.InvalidInputException;
import org.eclipse.jdt.internal.compiler.lookup.Binding;
import org.eclipse.jdt.internal.compiler.problem.AbortCompilation;
import org.eclipse.jdt.internal.core.JavaElement;
import org.eclipse.jdt.internal.core.SourceMethod;
import org.eclipse.jdt.internal.core.SourceType;
import org.eclipse.jdt.internal.core.SourceTypeElementInfo;
import org.eclipse.jdt.internal.core.TypeParameter;
import org.eclipse.jdt.internal.core.util.MementoTokenizer;
import org.eclipse.jdt.internal.core.util.Util;
import org.eclipse.objectteams.otdt.core.IMethodMapping;
import org.eclipse.objectteams.otdt.core.IMethodSpec;
import org.eclipse.objectteams.otdt.core.IOTJavaElement;
import org.eclipse.objectteams.otdt.core.IOTType;
import org.eclipse.objectteams.otdt.core.IRoleType;
import org.eclipse.objectteams.otdt.core.OTModelManager;
import org.eclipse.objectteams.otdt.core.TypeHelper;
import org.eclipse.objectteams.otdt.internal.core.util.MethodData;

/**
 * OT_COPY_PASTE from Member.getCategories()
 *               from Member.getJavadocRange()
 *               from SourceRefElement.getOpenableParent()
 *               
 * Generic Method Mapping, needs to be subclassed to add missing behaviour
 * for IMethodMapping.getMappingKind()
 *  
 * @author jwloka
 */
public abstract class MethodMapping extends OTJavaElement implements IMethodMapping {
    protected static final String[] EMPTY_STRING_ARRAY = new String[0];
    protected static final ITypeParameter[] NO_TYPE_PARAMETERS = new ITypeParameter[0];

    private int declarationSourceStart;
    private int sourceStart;
    private int sourceEnd;
    private int declarationSourceEnd;
    private IMethod roleMethod;
    protected MethodData roleMethodHandle;
    private boolean hasSignature;

    public MethodMapping(int declarationSourceStart, int sourceStart, int sourceEnd, int declarationSourceEnd,
            int type, IMethod correspondingJavaElem, IType parent, MethodData roleMethodHandle,
            boolean hasSignature) {
        super(type, correspondingJavaElem, parent);
        this.roleMethodHandle = roleMethodHandle;
        this.declarationSourceStart = declarationSourceStart;
        this.sourceStart = sourceStart;
        this.sourceEnd = sourceEnd;
        this.declarationSourceEnd = declarationSourceEnd;
        this.hasSignature = hasSignature;
    }

    public MethodMapping(int declarationSourceStart, int sourceStart, int sourceEnd, int declarationSourceEnd,
            int type, IMethod correspondingJavaElem, IType parent, MethodData roleMethodHandle,
            boolean hasSignature, boolean addAsChild) {
        super(type, correspondingJavaElem, parent, addAsChild);
        this.roleMethodHandle = roleMethodHandle;
        this.declarationSourceStart = declarationSourceStart;
        this.sourceStart = sourceStart;
        this.sourceEnd = sourceEnd;
        this.declarationSourceEnd = declarationSourceEnd;
        this.hasSignature = hasSignature;
    }

    // ==== memento generation: ====
    @Override
    public String getHandleIdentifier() {
        StringBuffer buff = new StringBuffer();
        IJavaElement myParent = getParent();
        if (myParent instanceof IOTJavaElement)
            myParent = ((IOTJavaElement) myParent).getCorrespondingJavaElement();
        // prefix
        buff.append(((JavaElement) myParent).getHandleMemento());
        char delimiter = OTJavaElement.OTEM_METHODMAPPING;
        // start:
        buff.append(delimiter);
        // mapping kind:
        buff.append(getMappingKindChar());
        // long or short?
        buff.append(this.hasSignature ? 'l' : 's');
        buff.append(delimiter);
        // mapping name (if any);
        getNameForHandle(buff);
        // role method:
        getMethodForHandle(this.roleMethodHandle, buff);
        // base methods:
        getBaseMethodsForHandle(buff);
        buff.append(delimiter);
        return buff.toString();
    }

    protected void getNameForHandle(StringBuffer buff) {
        /* default: mapping has no name. */ }

    /** 
     * Answer a char encoding the mapping kind with this information:
     * callin: a=after, b=before, r=replace; 
     * callout: o=regular, g=getter, s=setter [capital=isOverride].
     */
    abstract protected char getMappingKindChar();

    abstract protected void getBaseMethodsForHandle(StringBuffer buff);

    protected void getMethodForHandle(IMethodSpec method, StringBuffer buff) {
        escapeMementoName(buff, method.getSelector());
        if (this.hasSignature) {
            for (String argType : method.getArgumentTypes()) {
                buff.append(JavaElement.JEM_METHOD);
                escapeMementoName(buff, argType);
            }
            buff.append(JavaElement.JEM_METHOD);
            escapeMementoName(buff, method.getReturnType());
        }
        buff.append(OTJavaElement.OTEM_METHODMAPPING);
    }

    // ==== retreive method spec from memento: === 
    public static MethodData createMethodData(MementoTokenizer memento, String selector) {
        String cur = memento.nextToken();
        if (cur.charAt(0) == JavaElement.JEM_METHOD)
            cur = memento.nextToken(); // skip initial separator
        List<String> argTypes = new ArrayList<String>();
        while (cur.charAt(0) != OTJavaElement.OTEM_METHODMAPPING) {
            StringBuffer buffer = new StringBuffer();
            while (cur.length() == 1 && Signature.C_ARRAY == cur.charAt(0)) { // backward compatible with 3.0 mementos
                buffer.append(Signature.C_ARRAY);
                if (!memento.hasMoreTokens())
                    break;
                cur = memento.nextToken();
            }
            buffer.append(cur);
            argTypes.add(buffer.toString());
            if (memento.nextToken().charAt(0) != JavaElement.JEM_METHOD)
                break;
            cur = memento.nextToken();
        }
        String returnType = null;
        if (argTypes.size() > 0)
            returnType = argTypes.remove(argTypes.size() - 1);
        return new MethodData(selector, argTypes.toArray(new String[argTypes.size()]), null, returnType, false);
    }
    // ====

    public IMethod getRoleMethod() {
        if (this.roleMethod == null) {
            try {
                this.roleMethod = findRoleMethod();
                assert (this.roleMethod != null);
            } catch (JavaModelException ex) {
                Util.log(ex, "Failed to lookup original role method element!"); //$NON-NLS-1$
            }
        }

        return this.roleMethod;
    }

    // added for the SourceTypeConverter
    public MethodData getRoleMethodHandle() {
        return this.roleMethodHandle;
    }

    public IMethod getRoleMethodThrowingException() throws JavaModelException {
        if (this.roleMethod == null) {
            this.roleMethod = findRoleMethod();
        }

        return this.roleMethod;
    }

    public void setRoleMethod(IMethod meth) {
        this.roleMethod = meth;
    }

    /**
     * Only returns the role-methods part -- subclasses must override and 
     * construct the whole element name!
     */
    public String getElementName() {
        if (this.hasSignature) {
            return this.roleMethodHandle.toString();
        }

        return this.roleMethodHandle.getSelector();
    }

    public int getDeclarationSourceStart() {
        return this.declarationSourceStart;
    }

    public int getSourceStart() {
        return this.sourceStart;
    }

    public int getSourceEnd() {
        return this.sourceEnd;
    }

    public int getDeclarationSourceEnd() {
        return this.declarationSourceEnd;
    }

    public boolean equals(Object obj) {
        MethodMapping other = (MethodMapping) obj;

        return super.equals(other)
                //            && declarationSourceStart == other.getDeclarationSourceStart()
                //            && _declarationSourceEnd == other.getDeclarationSourceEnd()
                && getElementName().equals(other.getElementName());
    }

    @SuppressWarnings("nls")
    public String toString() {
        return "methodmapping: " + getElementName();
    }

    /**
    * Tries to find JavaElement method on demand for a given method from
    * current binding. Lookup is using role hierarchy (implicit and explicit).
    */
    protected IMethod findRoleMethod() throws JavaModelException {
        IType[] implicitParents = TypeHelper.getImplicitSuperTypes(getDeclaringRole());
        HashSet<IType> allParents = new HashSet<IType>();

        // collect all parents in role type hierarchy
        for (int idx = 0; idx < implicitParents.length; idx++) {
            IType elem = implicitParents[idx];

            // build super class hierarchy for element
            ITypeHierarchy hierarchy = elem.newSupertypeHierarchy(new NullProgressMonitor());
            IType[] superTypes = hierarchy.getAllSuperclasses(elem);

            // add implicit parent...
            allParents.add(elem);
            // ...and all "extends" parents
            if (superTypes.length > 0) {
                allParents.addAll(Arrays.asList(superTypes));
            }
        }
        return findMethod(allParents.toArray(new IType[allParents.size()]), this.roleMethodHandle);
    }

    protected abstract IRoleType getDeclaringRole();

    /**
     * Tries to find an IMethod matching the given methodHandle in a set
     * of types.
     * @return the first matching IMethod in the set of types or null if
     *          nothing found
     */
    protected IMethod findMethod(IType[] types, IMethodSpec methodHandle) throws JavaModelException {
        // cycle through types...
        for (int parIdx = 0; parIdx < types.length; parIdx++) {
            IMethod[] methods = types[parIdx].getMethods();
            // ... and compare with each method defined in current type
            for (int methIdx = 0; methIdx < methods.length; methIdx++) {
                IMethod tmpMethod = methods[methIdx];
                // check for equal method name and signature               
                String selector = tmpMethod.getElementName();
                if (isEqualMethod(methodHandle, tmpMethod, selector))
                    // return immediately on first match
                    return tmpMethod;
            }
            IOTType otType = OTModelManager.getOTElement(types[parIdx]);
            if (otType != null && otType.isRole()) {
                for (IMethodMapping mapping : ((IRoleType) otType).getMethodMappings(IRoleType.CALLOUTS)) {
                    AbstractCalloutMapping tmpMethod = (AbstractCalloutMapping) mapping;
                    if (tmpMethod == this)
                        continue; // callout fakes its own role method, but don't take it for real here!
                    // check for equal method name and signature               
                    String selector = tmpMethod.getCorrespondingJavaElement().getElementName();
                    if (isEqualMethod(methodHandle, tmpMethod, selector))
                        // return immediately on first match
                        return tmpMethod;
                }
            }
        }
        IMethod methodReference = SourceMethod.createHandle((JavaElement) types[0], methodHandle);
        // failure might be due to mismatching qualified/simple types
        // this variant only uses the simple types:
        for (int parIdx = 0; parIdx < types.length; parIdx++) {
            IMethod[] methods = types[parIdx].findMethods(methodReference);
            if (methods != null && methods.length == 1)
                return methods[0];
        }
        return null;
    }

    // helper for above to generalize over real methods and callouts:
    private boolean isEqualMethod(IMethodSpec baseMethodHandle, IMethod foundMethodOrCallout,
            String foundSelector) {
        if (!foundSelector.equals(baseMethodHandle.getSelector()))
            return false;
        if (!baseMethodHandle.hasSignature())
            return true;
        return Util.equalArraysOrNull(foundMethodOrCallout.getParameterTypes(),
                baseMethodHandle.getArgumentTypes());
    }

    //{OT_COPY_PASTE: SourceRefElement, STATE: 3.4 M7
    /**
     * Return the first instance of IOpenable in the hierarchy of this
     * type (going up the hierarchy from this type);
     */
    public IOpenable getOpenableParent() {
        IJavaElement current = getParent();
        while (current != null) {
            if (current instanceof IOpenable) {
                return (IOpenable) current;
            }
            //{ObjectTeams : Teams have no parents in the ot-hierarchy.
            if (current.getElementType() == IOTJavaElement.TEAM) {
                IOTType otElement = (IOTType) current;
                current = otElement.getCorrespondingJavaElement();
            }
            //haebor}         
            current = current.getParent();
        }
        return null;
    }

    //{OTModelUpdate : many of this methods shouldn't be delegated
    //                 to the corresponding method. Started with these three.
    //   public String getSource() throws JavaModelException
    //   {
    //       return getIMethod().getSource();
    //   }
    //   
    //   public ISourceRange getSourceRange() throws JavaModelException
    //   {
    //       return getIMethod().getSourceRange();
    //   }
    //   public ISourceRange getNameRange() throws JavaModelException
    //   {
    //       return getIMethod().getNameRange();
    //   }

    //haebor}
    /**
     * @see ISourceReference
     */
    public String getSource() throws JavaModelException {
        IOpenable openable = getOpenableParent();
        IBuffer buffer = openable.getBuffer();
        if (buffer == null) {
            return null;
        }
        ISourceRange range = getSourceRange();
        int offset = range.getOffset();
        int length = range.getLength();
        if (offset == -1 || length == 0) {
            return null;
        }
        try {
            return buffer.getText(offset, length);
        } catch (RuntimeException ex) {
            return null;
        }
    }

    /**
     * @see ISourceReference
     */
    public ISourceRange getSourceRange() throws JavaModelException {
        //{ObjectTeams: we don't have an ElementInfo but we know sourcestart, sourceend
        return new SourceRange(this.declarationSourceStart,
                this.declarationSourceEnd - this.declarationSourceStart + 1);
        //haebor}      
        //orig:      
        //      SourceRefElementInfo info = (SourceRefElementInfo) getElementInfo();
        //      return info.getSourceRange();
    }

    //haebor}
    public ISourceRange getNameRange() throws JavaModelException {
        ISourceRange range = new SourceRange(this.sourceStart, this.sourceEnd - this.sourceStart + 1);
        return range;
    }

    /** Answer the name that represents this mapping. */
    protected String getSourceName() {
        return super.getElementName();
    }

    //delegates   
    IMethod getIMethod() {
        return (IMethod) getCorrespondingJavaElement();
    }

    public String[] getExceptionTypes() throws JavaModelException {
        return getIMethod().getExceptionTypes();
    }

    /**
     * @deprecated (cf. IMethod.getTypeParameterSignatures())
     */
    public String[] getTypeParameterSignatures() throws JavaModelException {
        return getIMethod().getTypeParameterSignatures();
    }

    public int getNumberOfParameters() {
        return getIMethod().getNumberOfParameters();
    }

    public String[] getParameterNames() throws JavaModelException {
        if (this.roleMethodHandle != null && this.roleMethodHandle.hasSignature())
            return this.roleMethodHandle.getArgumentNames();
        return getIMethod().getParameterNames();
    }

    public String[] getParameterTypes() {
        return getIMethod().getParameterTypes();
    }

    public String getReturnType() throws JavaModelException {
        if (this.roleMethodHandle != null && this.roleMethodHandle.hasSignature())
            return this.roleMethodHandle.getReturnType();
        return getIMethod().getReturnType();
    }

    public String getSignature() throws JavaModelException {
        return getIMethod().getSignature();
    }

    public boolean isConstructor() throws JavaModelException {
        return getIMethod().isConstructor();
    }

    public boolean isMainMethod() throws JavaModelException {
        return getIMethod().isMainMethod();
    }

    public boolean isLambdaMethod() {
        return false;
    }

    public boolean isSimilar(IMethod method) {
        return getIMethod().isSimilar(method);
    }

    public IClassFile getClassFile() {
        return getIMethod().getClassFile();
    }

    public ICompilationUnit getCompilationUnit() {
        return getIMethod().getCompilationUnit();
    }

    public IType getDeclaringType() {
        return getIMethod().getDeclaringType();
    }

    public int getFlags() throws JavaModelException {
        return 0; // SH: method mappings have no regular flags. orig: getIMethod().getFlags();
    }

    public IType getType(String name, int count) {
        return getIMethod().getType(name, count);
    }

    public boolean isBinary() {
        return getIMethod().isBinary();
    }

    public void copy(IJavaElement container, IJavaElement sibling, String rename, boolean replace,
            IProgressMonitor monitor) throws JavaModelException {
        getIMethod().copy(container, sibling, rename, replace, monitor);
    }

    public void delete(boolean force, IProgressMonitor monitor) throws JavaModelException {
        getIMethod().delete(force, monitor);
    }

    public void move(IJavaElement container, IJavaElement sibling, String rename, boolean replace,
            IProgressMonitor monitor) throws JavaModelException {
        getIMethod().move(container, sibling, rename, replace, monitor);
    }

    public void rename(String name, boolean replace, IProgressMonitor monitor) throws JavaModelException {
        getIMethod().rename(name, replace, monitor);
    }

    public boolean hasSignature() {
        return this.hasSignature;
    }

    public boolean exists() {
        IJavaElement myParent = getParent();
        if (!myParent.exists())
            return false;
        try {
            for (IJavaElement child : ((IType) myParent).getChildren())
                if (this.equals(child)) {
                    // side-effect: fetch source range:
                    if (this != child && this.declarationSourceStart == 0) {
                        MethodMapping other = (MethodMapping) child;
                        this.declarationSourceStart = other.declarationSourceStart;
                        this.declarationSourceEnd = other.declarationSourceEnd;
                        this.sourceStart = other.sourceStart;
                        this.sourceEnd = other.sourceEnd;
                    }
                    return true;
                }
        } catch (JavaModelException e) {
            /* nop, will return false */ }
        return false;
    }

    public boolean isStructureKnown() throws JavaModelException {
        // See exists()
        return getParent().isStructureKnown();
    }

    public String getKey() {
        // km: perhaps: calculating own key would be better
        return getIMethod().getKey();
    }

    public ITypeParameter[] getTypeParameters() throws JavaModelException {
        return NO_TYPE_PARAMETERS; // must not return null!
    }

    public String[] getRawParameterNames() throws JavaModelException {
        return EMPTY_STRING_ARRAY;
    }

    public ITypeParameter getTypeParameter(String name) {
        return new TypeParameter((JavaElement) getCorrespondingJavaElement(), name);
    }

    public boolean isResolved() {
        return false;
    }

    public String getAttachedJavadoc(IProgressMonitor monitor) throws JavaModelException {
        return null;
    }

    //OT_COPY_PASTE from Member.getCategories(). STATE: 3.4 M7, checked at 3.5 M7
    @SuppressWarnings("rawtypes")
    public String[] getCategories() throws JavaModelException {
        IType type = (IType) getAncestor(IJavaElement.TYPE);
        if (type == null)
            return CharOperation.NO_STRINGS;
        if (type.isBinary()) {
            return CharOperation.NO_STRINGS;
        } else {
            SourceTypeElementInfo info = (SourceTypeElementInfo) ((SourceType) type).getElementInfo();
            HashMap map = info.getCategories();
            if (map == null)
                return CharOperation.NO_STRINGS;
            String[] categories = (String[]) map.get(this);
            if (categories == null)
                return CharOperation.NO_STRINGS;
            return categories;
        }
    }

    //OT_COPY_PASTE from Member.getJavadocRange(). STATE: 3.4 M7
    public ISourceRange getJavadocRange() throws JavaModelException {
        ISourceRange range = this.getSourceRange();
        if (range == null)
            return null;
        IBuffer buf = null;
        if (this.isBinary()) {
            buf = this.getClassFile().getBuffer();
        } else {
            ICompilationUnit compilationUnit = this.getCompilationUnit();
            if (!compilationUnit.isConsistent()) {
                return null;
            }
            buf = compilationUnit.getBuffer();
        }
        final int start = range.getOffset();
        final int length = range.getLength();
        if (length > 0 && buf.getChar(start) == '/') {
            IScanner scanner = ToolFactory.createScanner(true, false, false, false);
            scanner.setSource(buf.getText(start, length).toCharArray());
            try {
                int docOffset = -1;
                int docEnd = -1;

                int terminal = scanner.getNextToken();
                loop: while (true) {
                    switch (terminal) {
                    case ITerminalSymbols.TokenNameCOMMENT_JAVADOC:
                        docOffset = scanner.getCurrentTokenStartPosition();
                        docEnd = scanner.getCurrentTokenEndPosition() + 1;
                        terminal = scanner.getNextToken();
                        break;
                    case ITerminalSymbols.TokenNameCOMMENT_LINE:
                    case ITerminalSymbols.TokenNameCOMMENT_BLOCK:
                        terminal = scanner.getNextToken();
                        continue loop;
                    default:
                        break loop;
                    }
                }
                if (docOffset != -1) {
                    return new SourceRange(docOffset + start, docEnd - docOffset + 1);
                }
            } catch (InvalidInputException ex) {
                // try if there is inherited Javadoc
            }
        }
        return null;
    }

    //{CRIPPLE:
    public int getOccurrenceCount() {
        // TODO Auto-generated method stub
        return 0;
    }
    // km}

    /**
     * Copied from Member.
     * @see IMember#getTypeRoot()
     */
    public ITypeRoot getTypeRoot() {
        IJavaElement element = getParent();
        while (element instanceof IMember) {
            element = element.getParent();
        }
        return (ITypeRoot) element;
    }

    public OTJavaElement resolved(Binding binding) {
        char[] uniqueKey = binding.computeUniqueKey();
        if (uniqueKey == null)
            throw new AbortCompilation(); // better than NPE below
        return resolved(uniqueKey);
    }

    public abstract OTJavaElement resolved(char[] uniqueKey);

    @Override
    protected char getHandleMementoDelimiter() {
        return OTEM_METHODMAPPING;
    }
}