org.eclipse.jdt.internal.core.SourceMapper.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.jdt.internal.core.SourceMapper.java

Source

/*******************************************************************************
 * Copyright (c) 2000, 2017 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *     Kelly Campbell <kellyc@google.com> - Hangs in SourceMapper during java proposals - https://bugs.eclipse.org/bugs/show_bug.cgi?id=281575
 *     Stephan Herrmann - Contribution for Bug 380048 - error popup when navigating to source files
 *******************************************************************************/
package org.eclipse.jdt.internal.core;

import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.jdt.core.Flags;
import org.eclipse.jdt.core.IField;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IMember;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IModuleDescription;
import org.eclipse.jdt.core.IOrdinaryClassFile;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.ISourceRange;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.ITypeParameter;
import org.eclipse.jdt.core.JavaConventions;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.Signature;
import org.eclipse.jdt.core.SourceRange;
import org.eclipse.jdt.core.compiler.CategorizedProblem;
import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.internal.compiler.IProblemFactory;
import org.eclipse.jdt.internal.compiler.ISourceElementRequestor;
import org.eclipse.jdt.internal.compiler.SourceElementParser;
import org.eclipse.jdt.internal.compiler.ast.Expression;
import org.eclipse.jdt.internal.compiler.ast.ImportReference;
import org.eclipse.jdt.internal.compiler.env.IBinaryType;
import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
import org.eclipse.jdt.internal.compiler.lookup.TypeConstants;
import org.eclipse.jdt.internal.compiler.problem.DefaultProblemFactory;
import org.eclipse.jdt.internal.compiler.util.JRTUtil;
import org.eclipse.jdt.internal.compiler.util.SuffixConstants;
import org.eclipse.jdt.internal.compiler.util.Util;
import org.eclipse.jdt.internal.core.util.ReferenceInfoAdapter;

/**
 * A SourceMapper maps source code in a ZIP file to binary types or
 * binary modules in a JAR. The SourceMapper uses the fuzzy parser 
 * to identify source fragments in a .java file, and attempts to match
 * the source code with children in a binary type.
 * Since a module has no children in the Java Model no such matching
 * happens in that case.
 * A SourceMapper is associated with a JarPackageFragment by an AttachSourceOperation.
 *
 * @see org.eclipse.jdt.internal.core.JarPackageFragment
 */
@SuppressWarnings({ "rawtypes", "unchecked" })
public class SourceMapper extends ReferenceInfoAdapter implements ISourceElementRequestor, SuffixConstants {

    public static class LocalVariableElementKey {
        String parent;
        String name;

        public LocalVariableElementKey(IJavaElement method, String name) {
            StringBuffer buffer = new StringBuffer();
            buffer.append(method.getParent().getHandleIdentifier()).append('#').append(method.getElementName())
                    .append('(');
            if (method.getElementType() == IJavaElement.METHOD) {
                String[] parameterTypes = ((IMethod) method).getParameterTypes();
                for (int i = 0, max = parameterTypes.length; i < max; i++) {
                    if (i > 0) {
                        buffer.append(',');
                    }
                    buffer.append(Signature.getSignatureSimpleName(parameterTypes[i]));
                }
            }
            buffer.append(')');
            this.parent = String.valueOf(buffer);
            this.name = name;
        }

        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + ((this.name == null) ? 0 : this.name.hashCode());
            result = prime * result + ((this.parent == null) ? 0 : this.parent.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;
            LocalVariableElementKey other = (LocalVariableElementKey) obj;
            if (this.name == null) {
                if (other.name != null)
                    return false;
            } else if (!this.name.equals(other.name))
                return false;
            if (this.parent == null) {
                if (other.parent != null)
                    return false;
            } else if (!this.parent.equals(other.parent))
                return false;
            return true;
        }

        @Override
        public String toString() {
            StringBuffer buffer = new StringBuffer();
            buffer.append('(').append(this.parent).append('.').append(this.name).append(')');
            return String.valueOf(buffer);
        }
    }

    public static boolean VERBOSE = false;
    /**
     * Specifies the location of the package fragment roots within
     * the zip (empty specifies the default root). <code>null</code> is
     * not a valid root path.
     */
    protected ArrayList rootPaths;

    /**
     * The binary type or module source is being mapped for
     */
    protected NamedMember binaryTypeOrModule;

    /**
     * The location of the zip file containing source.
     */
    protected IPath sourcePath;
    /**
     * Specifies the location of the package fragment root within
     * the zip (empty specifies the default root). <code>null</code> is
     * not a valid root path.
     */
    protected String rootPath = ""; //$NON-NLS-1$

    /**
     * Table that maps a binary method to its parameter names.
     * Keys are the method handles, entries are <code>char[][]</code>.
     */
    protected HashMap parameterNames;

    /**
     * Table that maps a binary element to its <code>SourceRange</code>s.
     * Keys are the element handles, entries are <code>SourceRange[]</code> which
     * is a two element array; the first being source range, the second
     * being name range.
     */
    protected HashMap sourceRanges;

    /*
     * A map from IJavaElement to String[]
     */
    protected HashMap categories;

    /**
     * Table that contains all source ranges for local variables.
     * Keys are the special local variable elements, entries are <code>char[][]</code>.
     */
    protected HashMap parametersRanges;

    /**
     * Set that contains all final local variables.
     */
    protected HashSet finalParameters;

    /**
     * The unknown source range {-1, 0}
     */
    public static final SourceRange UNKNOWN_RANGE = new SourceRange(-1, 0);

    /**
     * The position within the source of the start of the
     * current member element, or -1 if we are outside a member.
     */
    protected int[] memberDeclarationStart;
    /**
     * The <code>SourceRange</code> of the name of the current member element.
     */
    protected SourceRange[] memberNameRange;
    /**
     * The name of the current member element.
     */
    protected String[] memberName;

    /**
     * The parameter names for the current member method element.
     */
    protected char[][][] methodParameterNames;

    /**
     * The parameter types for the current member method element.
     */
    protected char[][][] methodParameterTypes;

    /**
     * The element searched for
     */
    protected IJavaElement searchedElement;

    /**
     * imports references (keyed by binaryTypeOrModule)
     */
    private HashMap importsTable;
    private HashMap importsCounterTable;

    /**
     * Enclosing type information
     */
    IType[] types;
    int[] typeDeclarationStarts;
    SourceRange[] typeNameRanges;
    int[] typeModifiers;
    int typeDepth;

    /**
     * Module information
     */
    SourceRange moduleNameRange;
    int moduleDeclarationStart;
    int moduleModifiers;

    /**
     *  Anonymous counter in case we want to map the source of an anonymous class.
     */
    int anonymousCounter;
    int anonymousClassName;

    String encoding;
    String defaultEncoding;
    /**
     *Options to be used
     */
    Map options;

    /**
     * Use to handle root paths inference
     */
    private boolean areRootPathsComputed;

    public SourceMapper() {
        this.areRootPathsComputed = false;
    }

    public SourceMapper(IPath sourcePath, String rootPath, Map options) {
        this(sourcePath, rootPath, options, null);
    }

    /**
     * Creates a <code>SourceMapper</code> that locates source in the zip file
     * at the given location in the specified package fragment root.
     */
    public SourceMapper(IPath sourcePath, String rootPath, Map options, String encoding) {
        this.areRootPathsComputed = false;
        this.options = options;
        this.encoding = encoding;
        try {
            this.defaultEncoding = ResourcesPlugin.getWorkspace().getRoot().getDefaultCharset();
        } catch (CoreException e) {
            // use no encoding
        }
        if (rootPath != null) {
            this.rootPath = rootPath;
            this.rootPaths = new ArrayList();
            this.rootPaths.add(rootPath);
        }
        this.sourcePath = sourcePath;
        this.sourceRanges = new HashMap();
        this.parametersRanges = new HashMap();
        this.parameterNames = new HashMap();
        this.importsTable = new HashMap();
        this.importsCounterTable = new HashMap();
    }

    /**
     * @see ISourceElementRequestor
     */
    @Override
    public void acceptImport(int declarationStart, int declarationEnd, int nameStart, int nameEnd, char[][] tokens,
            boolean onDemand, int modifiers) {
        char[][] imports = (char[][]) this.importsTable.get(this.binaryTypeOrModule);
        int importsCounter;
        if (imports == null) {
            imports = new char[5][];
            importsCounter = 0;
        } else {
            importsCounter = ((Integer) this.importsCounterTable.get(this.binaryTypeOrModule)).intValue();
        }
        if (imports.length == importsCounter) {
            System.arraycopy(imports, 0, (imports = new char[importsCounter * 2][]), 0, importsCounter);
        }
        char[] name = CharOperation.concatWith(tokens, '.');
        if (onDemand) {
            int nameLength = name.length;
            System.arraycopy(name, 0, (name = new char[nameLength + 2]), 0, nameLength);
            name[nameLength] = '.';
            name[nameLength + 1] = '*';
        }
        imports[importsCounter++] = name;
        this.importsTable.put(this.binaryTypeOrModule, imports);
        this.importsCounterTable.put(this.binaryTypeOrModule, Integer.valueOf(importsCounter));
    }

    /**
     * @see ISourceElementRequestor
     */
    @Override
    public void acceptLineSeparatorPositions(int[] positions) {
        //do nothing
    }

    /**
     * @see ISourceElementRequestor
     */
    @Override
    public void acceptPackage(ImportReference importReference) {
        //do nothing
    }

    /**
     * @see ISourceElementRequestor
     */
    @Override
    public void acceptProblem(CategorizedProblem problem) {
        //do nothing
    }

    private void addCategories(IJavaElement element, char[][] elementCategories) {
        if (elementCategories == null)
            return;
        if (this.categories == null)
            this.categories = new HashMap();
        this.categories.put(element, CharOperation.toStrings(elementCategories));
    }

    /**
     * Closes this <code>SourceMapper</code>'s zip file. Once this is done, this
     * <code>SourceMapper</code> cannot be used again.
     */
    public void close() {
        this.sourceRanges = null;
        this.parameterNames = null;
        this.parametersRanges = null;
        this.finalParameters = null;
    }

    /**
     * NOT API, public only for access by Unit tests.
     * Converts these type names to unqualified signatures. This needs to be done in order to be consistent
     * with the way the source range is retrieved.
     * @see SourceMapper#getUnqualifiedMethodHandle
     * @see Signature
     */
    public String[] convertTypeNamesToSigs(char[][] typeNames) {
        if (typeNames == null)
            return CharOperation.NO_STRINGS;
        int n = typeNames.length;
        if (n == 0)
            return CharOperation.NO_STRINGS;
        String[] typeSigs = new String[n];
        for (int i = 0; i < n; ++i) {
            char[] typeSig = Signature.createCharArrayTypeSignature(typeNames[i], false);

            // transforms signatures that contains a qualification into unqualified signatures
            // e.g. "QX<+QMap.Entry;>;" becomes "QX<+QEntry;>;"
            StringBuffer simpleTypeSig = null;
            int start = 0;
            int dot = -1;
            int length = typeSig.length;
            for (int j = 0; j < length; j++) {
                switch (typeSig[j]) {
                case Signature.C_UNRESOLVED:
                    if (simpleTypeSig != null)
                        simpleTypeSig.append(typeSig, start, j - start);
                    start = j;
                    break;
                case Signature.C_DOT:
                    dot = j;
                    break;
                case Signature.C_GENERIC_START:
                    int matchingEnd = findMatchingGenericEnd(typeSig, j + 1);
                    if (matchingEnd > 0 && matchingEnd + 1 < length
                            && typeSig[matchingEnd + 1] == Signature.C_DOT) {
                        // found Head<Param>.Tail -> discard everything except Tail
                        if (simpleTypeSig == null)
                            simpleTypeSig = new StringBuffer().append(typeSig, 0, start);
                        simpleTypeSig.append(Signature.C_UNRESOLVED);
                        start = j = matchingEnd + 2;
                        break;
                    }
                    //$FALL-THROUGH$
                case Signature.C_NAME_END:
                    if (dot > start) {
                        if (simpleTypeSig == null)
                            simpleTypeSig = new StringBuffer().append(typeSig, 0, start);
                        simpleTypeSig.append(Signature.C_UNRESOLVED);
                        simpleTypeSig.append(typeSig, dot + 1, j - dot - 1);
                        start = j;
                    }
                    break;
                }
            }
            if (simpleTypeSig == null) {
                typeSigs[i] = new String(typeSig);
            } else {
                simpleTypeSig.append(typeSig, start, length - start);
                typeSigs[i] = simpleTypeSig.toString();
            }
        }
        return typeSigs;
    }

    private int findMatchingGenericEnd(char[] sig, int start) {
        int nesting = 0;
        int length = sig.length;
        for (int i = start; i < length; i++) {
            switch (sig[i]) {
            case Signature.C_GENERIC_START:
                nesting++;
                break;
            case Signature.C_GENERIC_END:
                if (nesting == 0)
                    return i;
                nesting--;
                break;
            }
        }
        return -1;
    }

    class JrtPackageNamesAdderVisitor implements JRTUtil.JrtFileVisitor<java.nio.file.Path> {

        public final HashSet firstLevelPackageNames;
        final IPackageFragmentRoot root;
        public String sourceLevel = null;
        public String complianceLevel = null;
        public boolean containsADefaultPackage;
        public boolean containsJavaSource;

        JrtPackageNamesAdderVisitor(HashSet firstLevelPackageNames, String sourceLevel, String complianceLevel,
                boolean containsADefaultPackage, boolean containsJavaSource, IPackageFragmentRoot root) {
            this.firstLevelPackageNames = firstLevelPackageNames;
            this.root = root;
            this.sourceLevel = sourceLevel;
            this.complianceLevel = complianceLevel;
            this.containsADefaultPackage = containsADefaultPackage;
            this.containsJavaSource = containsJavaSource;
        }

        @Override
        public FileVisitResult visitPackage(java.nio.file.Path dir, java.nio.file.Path mod,
                BasicFileAttributes attrs) throws IOException {
            return FileVisitResult.CONTINUE;
        }

        @Override
        public FileVisitResult visitFile(java.nio.file.Path file, java.nio.file.Path mod, BasicFileAttributes attrs)
                throws IOException {
            String entryName = file.toString();
            if (Util.isClassFileName(entryName)) {
                int index = entryName.indexOf('/');
                if (index != -1) {
                    String firstLevelPackageName = entryName.substring(0, index);
                    if (!this.firstLevelPackageNames.contains(firstLevelPackageName)) {
                        if (this.sourceLevel == null) {
                            IJavaProject project = this.root.getJavaProject();
                            this.sourceLevel = project.getOption(JavaCore.COMPILER_SOURCE, true);
                            this.complianceLevel = project.getOption(JavaCore.COMPILER_COMPLIANCE, true);
                        }
                        IStatus status = JavaConventions.validatePackageName(firstLevelPackageName,
                                this.sourceLevel, this.complianceLevel);
                        if (status.isOK() || status.getSeverity() == IStatus.WARNING) {
                            this.firstLevelPackageNames.add(firstLevelPackageName);
                        }
                    }
                } else {
                    this.containsADefaultPackage = true;
                }
            } else if (!this.containsJavaSource
                    && org.eclipse.jdt.internal.core.util.Util.isJavaLikeFileName(entryName)) {
                this.containsJavaSource = true;
            }
            return FileVisitResult.CONTINUE;
        }

        @Override
        public FileVisitResult visitModule(java.nio.file.Path path, String name) throws IOException {
            return FileVisitResult.CONTINUE;
        }
    }

    private synchronized void computeAllRootPaths(IJavaElement typeOrModule) {
        if (this.areRootPathsComputed) {
            return;
        }
        IPackageFragmentRoot root = (IPackageFragmentRoot) typeOrModule
                .getAncestor(IJavaElement.PACKAGE_FRAGMENT_ROOT);
        IPath pkgFragmentRootPath = root.getPath();
        final HashSet tempRoots = new HashSet();
        long time = 0;
        if (VERBOSE) {
            System.out.println("compute all root paths for " + root.getElementName()); //$NON-NLS-1$
            time = System.currentTimeMillis();
        }
        final HashSet firstLevelPackageNames = new HashSet();
        boolean containsADefaultPackage = false;
        boolean containsJavaSource = !pkgFragmentRootPath.equals(this.sourcePath); // used to optimize zip file reading only if source path and root path are equals, otherwise assume that attachment contains Java source

        String sourceLevel = null;
        String complianceLevel = null;
        if (Util.isJrt(pkgFragmentRootPath.toOSString())) {
            try {
                JrtPackageNamesAdderVisitor jrtPackageNamesAdderVisitor = new JrtPackageNamesAdderVisitor(
                        firstLevelPackageNames, sourceLevel, complianceLevel, containsADefaultPackage,
                        containsJavaSource, root);
                org.eclipse.jdt.internal.compiler.util.JRTUtil.walkModuleImage(root.getPath().toFile(),
                        jrtPackageNamesAdderVisitor, JRTUtil.NOTIFY_FILES);
                sourceLevel = jrtPackageNamesAdderVisitor.sourceLevel;
                complianceLevel = jrtPackageNamesAdderVisitor.complianceLevel;
                containsADefaultPackage = jrtPackageNamesAdderVisitor.containsADefaultPackage;
                containsJavaSource = jrtPackageNamesAdderVisitor.containsJavaSource;
            } catch (IOException e) {
                // We are not reading any specific file, so, move on for now
                if (VERBOSE) {
                    e.printStackTrace();
                }
            }
        } else if (root.isArchive()) {
            JavaModelManager manager = JavaModelManager.getJavaModelManager();
            ZipFile zip = null;
            try {
                zip = manager.getZipFile(pkgFragmentRootPath);
                for (Enumeration entries = zip.entries(); entries.hasMoreElements();) {
                    ZipEntry entry = (ZipEntry) entries.nextElement();
                    String entryName = entry.getName();
                    if (!entry.isDirectory()) {
                        if (Util.isClassFileName(entryName)) {
                            int index = entryName.indexOf('/');
                            if (index != -1) {
                                String firstLevelPackageName = entryName.substring(0, index);
                                if (!firstLevelPackageNames.contains(firstLevelPackageName)) {
                                    if (sourceLevel == null) {
                                        IJavaProject project = root.getJavaProject();
                                        sourceLevel = project.getOption(JavaCore.COMPILER_SOURCE, true);
                                        complianceLevel = project.getOption(JavaCore.COMPILER_COMPLIANCE, true);
                                    }
                                    IStatus status = JavaConventions.validatePackageName(firstLevelPackageName,
                                            sourceLevel, complianceLevel);
                                    if (status.isOK() || status.getSeverity() == IStatus.WARNING) {
                                        firstLevelPackageNames.add(firstLevelPackageName);
                                    }
                                }
                            } else {
                                containsADefaultPackage = true;
                            }
                        } else if (!containsJavaSource
                                && org.eclipse.jdt.internal.core.util.Util.isJavaLikeFileName(entryName)) {
                            containsJavaSource = true;
                        }
                    }
                }
            } catch (CoreException e) {
                // ignore
            } finally {
                manager.closeZipFile(zip); // handle null case
            }
        } else {
            Object target = JavaModel.getTarget(root.getPath(), true);
            if (target instanceof IResource) {
                IResource resource = (IResource) target;
                if (resource instanceof IContainer) {
                    try {
                        IResource[] members = ((IContainer) resource).members();
                        for (int i = 0, max = members.length; i < max; i++) {
                            IResource member = members[i];
                            String resourceName = member.getName();
                            if (member.getType() == IResource.FOLDER) {
                                if (sourceLevel == null) {
                                    IJavaProject project = root.getJavaProject();
                                    sourceLevel = project.getOption(JavaCore.COMPILER_SOURCE, true);
                                    complianceLevel = project.getOption(JavaCore.COMPILER_COMPLIANCE, true);
                                }
                                IStatus status = JavaConventions.validatePackageName(resourceName, sourceLevel,
                                        complianceLevel);
                                if (status.isOK() || status.getSeverity() == IStatus.WARNING) {
                                    firstLevelPackageNames.add(resourceName);
                                }
                            } else if (Util.isClassFileName(resourceName)) {
                                containsADefaultPackage = true;
                            } else if (!containsJavaSource
                                    && org.eclipse.jdt.internal.core.util.Util.isJavaLikeFileName(resourceName)) {
                                containsJavaSource = true;
                            }
                        }
                    } catch (CoreException e) {
                        // ignore
                    }
                }
            }
        }

        if (containsJavaSource) { // no need to read source attachment if it contains no Java source (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=190840 )
            Object target = JavaModel.getTarget(this.sourcePath, true);
            if (target instanceof IContainer) {
                IContainer folder = (IContainer) target;
                computeRootPath(folder, firstLevelPackageNames, containsADefaultPackage, tempRoots,
                        folder.getFullPath().segmentCount()/*if external folder, this is the linked folder path*/);
            } else {
                JavaModelManager manager = JavaModelManager.getJavaModelManager();
                ZipFile zip = null;
                try {
                    zip = manager.getZipFile(this.sourcePath);
                    for (Enumeration entries = zip.entries(); entries.hasMoreElements();) {
                        ZipEntry entry = (ZipEntry) entries.nextElement();
                        String entryName;
                        if (!entry.isDirectory() && org.eclipse.jdt.internal.core.util.Util
                                .isJavaLikeFileName(entryName = entry.getName())) {
                            IPath path = new Path(entryName);
                            int segmentCount = path.segmentCount();
                            if (segmentCount > 1) {
                                for (int i = 0, max = path.segmentCount() - 1; i < max; i++) {
                                    if (firstLevelPackageNames.contains(path.segment(i))) {
                                        tempRoots.add(path.uptoSegment(i));
                                        // don't break here as this path could contain other first level package names (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=74014)
                                    }
                                    if (i == max - 1 && containsADefaultPackage) {
                                        tempRoots.add(path.uptoSegment(max));
                                    }
                                }
                            } else if (containsADefaultPackage) {
                                tempRoots.add(new Path("")); //$NON-NLS-1$
                            }
                        }
                    }
                } catch (CoreException e) {
                    // ignore
                } finally {
                    manager.closeZipFile(zip); // handle null case
                }
            }
        }
        int size = tempRoots.size();
        if (this.rootPaths != null) {
            for (Iterator iterator = this.rootPaths.iterator(); iterator.hasNext();) {
                tempRoots.add(new Path((String) iterator.next()));
            }
            this.rootPaths.clear();
        } else {
            this.rootPaths = new ArrayList(size);
        }
        size = tempRoots.size();
        if (size > 0) {
            ArrayList sortedRoots = new ArrayList(tempRoots);
            if (size > 1) {
                Collections.sort(sortedRoots, new Comparator() {
                    @Override
                    public int compare(Object o1, Object o2) {
                        IPath path1 = (IPath) o1;
                        IPath path2 = (IPath) o2;
                        return path1.segmentCount() - path2.segmentCount();
                    }
                });
            }
            for (Iterator iter = sortedRoots.iterator(); iter.hasNext();) {
                IPath path = (IPath) iter.next();
                this.rootPaths.add(path.toString());
            }
        }
        this.areRootPathsComputed = true;
        if (VERBOSE) {
            System.out.println("Spent " + (System.currentTimeMillis() - time) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$
            System.out.println("Found " + size + " root paths"); //$NON-NLS-1$ //$NON-NLS-2$
            int i = 0;
            for (Iterator iterator = this.rootPaths.iterator(); iterator.hasNext();) {
                System.out.println("root[" + i + "]=" + ((String) iterator.next()));//$NON-NLS-1$ //$NON-NLS-2$
                i++;
            }
        }
    }

    private void computeRootPath(IContainer container, HashSet firstLevelPackageNames, boolean hasDefaultPackage,
            Set set, int sourcePathSegmentCount) {
        try {
            IResource[] resources = container.members();
            for (int i = 0, max = resources.length; i < max; i++) {
                IResource resource = resources[i];
                if (resource.getType() == IResource.FOLDER) {
                    if (firstLevelPackageNames.contains(resource.getName())) {
                        IPath fullPath = container.getFullPath();
                        IPath rootPathEntry = fullPath.removeFirstSegments(sourcePathSegmentCount).setDevice(null);
                        if (rootPathEntry.segmentCount() >= 1) {
                            set.add(rootPathEntry);
                        }
                        computeRootPath((IFolder) resource, firstLevelPackageNames, hasDefaultPackage, set,
                                sourcePathSegmentCount);
                    } else {
                        computeRootPath((IFolder) resource, firstLevelPackageNames, hasDefaultPackage, set,
                                sourcePathSegmentCount);
                    }
                }
                if (i == max - 1 && hasDefaultPackage) {
                    // check if one member is a .java file
                    boolean hasJavaSourceFile = false;
                    for (int j = 0; j < max; j++) {
                        if (org.eclipse.jdt.internal.core.util.Util.isJavaLikeFileName(resources[i].getName())) {
                            hasJavaSourceFile = true;
                            break;
                        }
                    }
                    if (hasJavaSourceFile) {
                        IPath fullPath = container.getFullPath();
                        IPath rootPathEntry = fullPath.removeFirstSegments(sourcePathSegmentCount).setDevice(null);
                        set.add(rootPathEntry);
                    }
                }
            }
        } catch (CoreException e) {
            // ignore
            e.printStackTrace();
        }
    }

    /**
     * @see ISourceElementRequestor
     */
    @Override
    public void enterType(TypeInfo typeInfo) {

        this.typeDepth++;
        if (this.typeDepth == this.types.length) { // need to grow
            System.arraycopy(this.types, 0, this.types = new IType[this.typeDepth * 2], 0, this.typeDepth);
            System.arraycopy(this.typeNameRanges, 0, this.typeNameRanges = new SourceRange[this.typeDepth * 2], 0,
                    this.typeDepth);
            System.arraycopy(this.typeDeclarationStarts, 0,
                    this.typeDeclarationStarts = new int[this.typeDepth * 2], 0, this.typeDepth);
            System.arraycopy(this.memberName, 0, this.memberName = new String[this.typeDepth * 2], 0,
                    this.typeDepth);
            System.arraycopy(this.memberDeclarationStart, 0,
                    this.memberDeclarationStart = new int[this.typeDepth * 2], 0, this.typeDepth);
            System.arraycopy(this.memberNameRange, 0, this.memberNameRange = new SourceRange[this.typeDepth * 2], 0,
                    this.typeDepth);
            System.arraycopy(this.methodParameterTypes, 0,
                    this.methodParameterTypes = new char[this.typeDepth * 2][][], 0, this.typeDepth);
            System.arraycopy(this.methodParameterNames, 0,
                    this.methodParameterNames = new char[this.typeDepth * 2][][], 0, this.typeDepth);
            System.arraycopy(this.typeModifiers, 0, this.typeModifiers = new int[this.typeDepth * 2], 0,
                    this.typeDepth);
        }
        if (typeInfo.name.length == 0) {
            this.anonymousCounter++;
            if (this.anonymousCounter == this.anonymousClassName) {
                this.types[this.typeDepth] = getType(this.binaryTypeOrModule.getElementName());
            } else {
                this.types[this.typeDepth] = getType(new String(typeInfo.name));
            }
        } else {
            this.types[this.typeDepth] = getType(new String(typeInfo.name));
        }
        this.typeNameRanges[this.typeDepth] = new SourceRange(typeInfo.nameSourceStart,
                typeInfo.nameSourceEnd - typeInfo.nameSourceStart + 1);
        this.typeDeclarationStarts[this.typeDepth] = typeInfo.declarationStart;

        IType currentType = this.types[this.typeDepth];

        // type parameters
        if (typeInfo.typeParameters != null) {
            for (int i = 0, length = typeInfo.typeParameters.length; i < length; i++) {
                TypeParameterInfo typeParameterInfo = typeInfo.typeParameters[i];
                ITypeParameter typeParameter = currentType.getTypeParameter(new String(typeParameterInfo.name));
                setSourceRange(typeParameter,
                        new SourceRange(typeParameterInfo.declarationStart,
                                typeParameterInfo.declarationEnd - typeParameterInfo.declarationStart + 1),
                        new SourceRange(typeParameterInfo.nameSourceStart,
                                typeParameterInfo.nameSourceEnd - typeParameterInfo.nameSourceStart + 1));
            }
        }

        // type modifiers
        this.typeModifiers[this.typeDepth] = typeInfo.modifiers;

        // categories
        addCategories(currentType, typeInfo.categories);
    }

    @Override
    public void enterModule(ModuleInfo moduleInfo) {
        this.moduleNameRange = new SourceRange(moduleInfo.nameSourceStart,
                moduleInfo.nameSourceEnd - moduleInfo.nameSourceStart + 1);
        this.moduleDeclarationStart = moduleInfo.declarationStart;

        // module type modifiers
        this.moduleModifiers = moduleInfo.modifiers;

        if (this.binaryTypeOrModule instanceof IModuleDescription) {
            // categories
            addCategories(this.binaryTypeOrModule, moduleInfo.categories);
        }
    }

    @Override
    public void exitModule(int declarationEnd) {
        setSourceRange(this.binaryTypeOrModule,
                new SourceRange(this.moduleDeclarationStart, declarationEnd - this.moduleDeclarationStart + 1),
                this.moduleNameRange);
    }

    /**
     * @see ISourceElementRequestor
     */
    @Override
    public void enterCompilationUnit() {
        // do nothing
    }

    /**
     * @see ISourceElementRequestor
     */
    @Override
    public void enterConstructor(MethodInfo methodInfo) {
        enterAbstractMethod(methodInfo);
    }

    /**
     * @see ISourceElementRequestor
     */
    @Override
    public void enterField(FieldInfo fieldInfo) {
        if (this.typeDepth >= 0) {
            this.memberDeclarationStart[this.typeDepth] = fieldInfo.declarationStart;
            this.memberNameRange[this.typeDepth] = new SourceRange(fieldInfo.nameSourceStart,
                    fieldInfo.nameSourceEnd - fieldInfo.nameSourceStart + 1);
            String fieldName = new String(fieldInfo.name);
            this.memberName[this.typeDepth] = fieldName;

            // categories
            IType currentType = this.types[this.typeDepth];
            IField field = currentType.getField(fieldName);
            addCategories(field, fieldInfo.categories);
        }
    }

    /**
     * @see ISourceElementRequestor
     */
    @Override
    public void enterInitializer(int declarationSourceStart, int modifiers) {
        //do nothing
    }

    /**
     * @see ISourceElementRequestor
     */
    @Override
    public void enterMethod(MethodInfo methodInfo) {
        enterAbstractMethod(methodInfo);
    }

    private void enterAbstractMethod(MethodInfo methodInfo) {
        if (this.typeDepth >= 0) {
            this.memberName[this.typeDepth] = new String(methodInfo.name);
            this.memberNameRange[this.typeDepth] = new SourceRange(methodInfo.nameSourceStart,
                    methodInfo.nameSourceEnd - methodInfo.nameSourceStart + 1);
            this.memberDeclarationStart[this.typeDepth] = methodInfo.declarationStart;
            IType currentType = this.types[this.typeDepth];
            int currenTypeModifiers = this.typeModifiers[this.typeDepth];
            char[][] parameterTypes = methodInfo.parameterTypes;
            if (methodInfo.isConstructor && currentType.getDeclaringType() != null
                    && !Flags.isStatic(currenTypeModifiers)) {
                IType declaringType = currentType.getDeclaringType();
                String declaringTypeName = declaringType.getElementName();
                if (declaringTypeName.length() == 0) {
                    IOrdinaryClassFile classFile = declaringType.getClassFile();
                    int length = parameterTypes != null ? parameterTypes.length : 0;
                    char[][] newParameterTypes = new char[length + 1][];
                    declaringTypeName = classFile.getElementName();
                    declaringTypeName = declaringTypeName.substring(0, declaringTypeName.indexOf('.'));
                    newParameterTypes[0] = declaringTypeName.toCharArray();
                    if (length != 0) {
                        System.arraycopy(parameterTypes, 0, newParameterTypes, 1, length);
                    }
                    this.methodParameterTypes[this.typeDepth] = newParameterTypes;
                } else {
                    int length = parameterTypes != null ? parameterTypes.length : 0;
                    char[][] newParameterTypes = new char[length + 1][];
                    newParameterTypes[0] = declaringTypeName.toCharArray();
                    if (length != 0) {
                        System.arraycopy(parameterTypes, 0, newParameterTypes, 1, length);
                    }
                    this.methodParameterTypes[this.typeDepth] = newParameterTypes;
                }
            } else {
                this.methodParameterTypes[this.typeDepth] = parameterTypes;
            }
            this.methodParameterNames[this.typeDepth] = methodInfo.parameterNames;

            IMethod method = currentType.getMethod(this.memberName[this.typeDepth],
                    convertTypeNamesToSigs(this.methodParameterTypes[this.typeDepth]));

            // type parameters
            if (methodInfo.typeParameters != null) {
                for (int i = 0, length = methodInfo.typeParameters.length; i < length; i++) {
                    TypeParameterInfo typeParameterInfo = methodInfo.typeParameters[i];
                    ITypeParameter typeParameter = method.getTypeParameter(new String(typeParameterInfo.name));
                    setSourceRange(typeParameter,
                            new SourceRange(typeParameterInfo.declarationStart,
                                    typeParameterInfo.declarationEnd - typeParameterInfo.declarationStart + 1),
                            new SourceRange(typeParameterInfo.nameSourceStart,
                                    typeParameterInfo.nameSourceEnd - typeParameterInfo.nameSourceStart + 1));
                }
            }
            // parameters infos
            if (methodInfo.parameterInfos != null) {
                for (int i = 0, length = methodInfo.parameterInfos.length; i < length; i++) {
                    ParameterInfo parameterInfo = methodInfo.parameterInfos[i];
                    LocalVariableElementKey key = new LocalVariableElementKey(method,
                            new String(parameterInfo.name));
                    SourceRange[] allRanges = new SourceRange[] {
                            new SourceRange(parameterInfo.declarationStart,
                                    parameterInfo.declarationEnd - parameterInfo.declarationStart + 1),
                            new SourceRange(parameterInfo.nameSourceStart,
                                    parameterInfo.nameSourceEnd - parameterInfo.nameSourceStart + 1) };
                    this.parametersRanges.put(key, allRanges);
                    if (parameterInfo.modifiers != 0) {
                        if (this.finalParameters == null) {
                            this.finalParameters = new HashSet();
                        }
                        this.finalParameters.add(key);
                    }
                }
            }

            // categories
            addCategories(method, methodInfo.categories);
        }
    }

    /**
     * @see ISourceElementRequestor
     */
    @Override
    public void exitType(int declarationEnd) {
        if (this.typeDepth >= 0) {
            IType currentType = this.types[this.typeDepth];
            setSourceRange(currentType,
                    new SourceRange(this.typeDeclarationStarts[this.typeDepth],
                            declarationEnd - this.typeDeclarationStarts[this.typeDepth] + 1),
                    this.typeNameRanges[this.typeDepth]);
            this.typeDepth--;
        }
    }

    /**
     * @see ISourceElementRequestor
     */
    @Override
    public void exitCompilationUnit(int declarationEnd) {
        //do nothing
    }

    /**
     * @see ISourceElementRequestor
     */
    @Override
    public void exitConstructor(int declarationEnd) {
        exitAbstractMethod(declarationEnd);
    }

    /**
     * @see ISourceElementRequestor
     */
    @Override
    public void exitField(int initializationStart, int declarationEnd, int declarationSourceEnd) {
        if (this.typeDepth >= 0) {
            IType currentType = this.types[this.typeDepth];
            setSourceRange(currentType.getField(this.memberName[this.typeDepth]),
                    new SourceRange(this.memberDeclarationStart[this.typeDepth],
                            declarationEnd - this.memberDeclarationStart[this.typeDepth] + 1),
                    this.memberNameRange[this.typeDepth]);
        }
    }

    /**
     * @see ISourceElementRequestor
     */
    @Override
    public void exitInitializer(int declarationEnd) {
        // implements abstract method
    }

    /**
     * @see ISourceElementRequestor
     */
    @Override
    public void exitMethod(int declarationEnd, Expression defaultValue) {
        exitAbstractMethod(declarationEnd);
    }

    private void exitAbstractMethod(int declarationEnd) {
        if (this.typeDepth >= 0) {
            IType currentType = this.types[this.typeDepth];
            SourceRange sourceRange = new SourceRange(this.memberDeclarationStart[this.typeDepth],
                    declarationEnd - this.memberDeclarationStart[this.typeDepth] + 1);
            IMethod method = currentType.getMethod(this.memberName[this.typeDepth],
                    convertTypeNamesToSigs(this.methodParameterTypes[this.typeDepth]));
            setSourceRange(method, sourceRange, this.memberNameRange[this.typeDepth]);
            setMethodParameterNames(method, this.methodParameterNames[this.typeDepth]);
        }
    }

    /**
     * Locates and returns source code for the given (binary) type, in this
     * SourceMapper's ZIP file, or returns <code>null</code> if source
     * code cannot be found.
     */
    public char[] findSource(IType type, IBinaryType info) {
        if (!type.isBinary()) {
            return null;
        }
        String simpleSourceFileName = ((BinaryType) type).getSourceFileName(info);
        if (simpleSourceFileName == null) {
            return null;
        }
        return findSource(type, simpleSourceFileName);
    }

    /**
     * Locates and returns source code for the given (binary) type, in this
     * SourceMapper's ZIP file, or returns <code>null</code> if source
     * code cannot be found.
     * The given simpleSourceFileName is the .java file name (without the enclosing
     * folder) used to create the given type (e.g. "A.java" for x/y/A$Inner.class)
     */
    public char[] findSource(IType type, String simpleSourceFileName) {
        PackageFragment pkgFrag = (PackageFragment) type.getPackageFragment();
        String name = org.eclipse.jdt.internal.core.util.Util.concatWith(pkgFrag.names, simpleSourceFileName, '/');
        return internalFindSource((NamedMember) type, name);
    }

    /**
     * Locates and returns source code for the given (binary) module, in this
     * SourceMapper's ZIP file, or returns <code>null</code> if source
     * code cannot be found.
     */
    public char[] findSource(IModuleDescription module) {
        if (!module.isBinary()) {
            return null;
        }
        return internalFindSource((NamedMember) module, TypeConstants.MODULE_INFO_FILE_NAME_STRING);
    }

    private char[] internalFindSource(NamedMember typeOrModule, String name) {
        long time = 0;
        if (VERBOSE) {
            time = System.currentTimeMillis();
        }

        char[] source = null;

        JavaModelManager javaModelManager = JavaModelManager.getJavaModelManager();
        try {
            javaModelManager.cacheZipFiles(this); // Cache any zip files we open during this operation

            if (this.rootPath != null) {
                source = getSourceForRootPath(this.rootPath, name);
                if (source == null) {
                    source = getSourceForRootPath("", name); //$NON-NLS-1$
                }
            }

            if (source == null) { // proceed with automatic root path detection ...
                // ... but not for multi-module roots
                if (!(typeOrModule
                        .getAncestor(IJavaElement.PACKAGE_FRAGMENT_ROOT) instanceof JrtPackageFragmentRoot)) {
                    computeAllRootPaths(typeOrModule);
                    if (this.rootPaths != null) {
                        loop: for (Iterator iterator = this.rootPaths.iterator(); iterator.hasNext();) {
                            String currentRootPath = (String) iterator.next();
                            if (!currentRootPath.equals(this.rootPath)) {
                                source = getSourceForRootPath(currentRootPath, name);
                                if (source != null) {
                                    // remember right root path
                                    this.rootPath = currentRootPath;
                                    break loop;
                                }
                            }
                        }
                    }
                }
            }
        } finally {
            javaModelManager.flushZipFiles(this); // clean up cached zip files.
        }
        if (VERBOSE) {
            System.out.println(
                    "spent " + (System.currentTimeMillis() - time) + "ms for " + typeOrModule.getElementName()); //$NON-NLS-1$ //$NON-NLS-2$
        }
        return source;
    }

    private char[] getSourceForRootPath(String currentRootPath, String name) {
        String newFullName;
        if (!currentRootPath.equals(IPackageFragmentRoot.DEFAULT_PACKAGEROOT_PATH)) {
            if (currentRootPath.endsWith("/")) { //$NON-NLS-1$
                newFullName = currentRootPath + name;
            } else {
                newFullName = currentRootPath + '/' + name;
            }
        } else {
            newFullName = name;
        }
        return this.findSource(newFullName);
    }

    public char[] findSource(String fullName) {
        char[] source = null;
        Object target = JavaModel.getTarget(this.sourcePath, true);
        String charSet = null;
        if (target instanceof IContainer) {
            IResource res = ((IContainer) target).findMember(fullName);
            if (res instanceof IFile) {
                try {
                    // Order of preference: charSet supplied, this.encoding or this.defaultEncoding in that order
                    try {
                        // Use the implicit encoding only when the source attachment's encoding hasn't been explicitly set.
                        charSet = ((IFile) res).getCharset(this.encoding == null);
                    } catch (CoreException e) {
                        // Ignore
                    }
                    source = org.eclipse.jdt.internal.core.util.Util.getResourceContentsAsCharArray((IFile) res,
                            charSet == null ? (this.encoding == null ? this.defaultEncoding : this.encoding)
                                    : charSet);
                } catch (JavaModelException e) {
                    // Ignore
                }
            }
        } else {
            try {
                // https://bugs.eclipse.org/bugs/show_bug.cgi?id=303511
                // For a resource inside the workspace, use the encoding set on the resource
                if (target instanceof IFile)
                    charSet = ((IFile) target).getCharset(this.encoding == null);
            } catch (CoreException e) {
                // Ignore
            }

            // try to get the entry
            ZipEntry entry = null;
            ZipFile zip = null;
            JavaModelManager manager = JavaModelManager.getJavaModelManager();
            try {
                zip = manager.getZipFile(this.sourcePath);
                entry = zip.getEntry(fullName);
                if (entry != null) {
                    // now read the source code
                    source = readSource(entry, zip, charSet);
                }
            } catch (CoreException e) {
                return null;
            } finally {
                manager.closeZipFile(zip); // handle null case
            }
        }
        return source;
    }

    public int getFlags(IJavaElement element) {
        switch (element.getElementType()) {
        case IJavaElement.LOCAL_VARIABLE:
            LocalVariableElementKey key = new LocalVariableElementKey(element.getParent(),
                    element.getElementName());
            if (this.finalParameters != null && this.finalParameters.contains(key)) {
                return Flags.AccFinal;
            }
        }
        return 0;
    }

    /**
     * Returns the SourceRange for the name of the given element, or
     * {-1, -1} if no source range is known for the name of the element.
     */
    public SourceRange getNameRange(IJavaElement element) {
        switch (element.getElementType()) {
        case IJavaElement.METHOD:
            if (((IMember) element).isBinary()) {
                IJavaElement[] el = getUnqualifiedMethodHandle((IMethod) element, false);
                if (el[1] != null && this.sourceRanges.get(el[0]) == null) {
                    element = getUnqualifiedMethodHandle((IMethod) element, true)[0];
                } else {
                    element = el[0];
                }
            }
            break;
        case IJavaElement.TYPE_PARAMETER:
            IJavaElement parent = element.getParent();
            if (parent.getElementType() == IJavaElement.METHOD) {
                IMethod method = (IMethod) parent;
                if (method.isBinary()) {
                    IJavaElement[] el = getUnqualifiedMethodHandle(method, false);
                    if (el[1] != null && this.sourceRanges.get(el[0]) == null) {
                        method = (IMethod) getUnqualifiedMethodHandle(method, true)[0];
                    } else {
                        method = (IMethod) el[0];
                    }
                    element = method.getTypeParameter(element.getElementName());
                }
            }
            break;
        case IJavaElement.LOCAL_VARIABLE:
            LocalVariableElementKey key = new LocalVariableElementKey(element.getParent(),
                    element.getElementName());
            SourceRange[] ranges = (SourceRange[]) this.parametersRanges.get(key);
            if (ranges == null) {
                return UNKNOWN_RANGE;
            } else {
                return ranges[1];
            }
        }
        SourceRange[] ranges = (SourceRange[]) this.sourceRanges.get(element);
        if (ranges == null) {
            return UNKNOWN_RANGE;
        } else {
            return ranges[1];
        }
    }

    /**
     * Returns parameters names for the given method, or
     * null if no parameter names are known for the method.
     */
    public char[][] getMethodParameterNames(IMethod method) {
        if (method.isBinary()) {
            IJavaElement[] el = getUnqualifiedMethodHandle(method, false);
            if (el[1] != null && this.parameterNames.get(el[0]) == null) {
                method = (IMethod) getUnqualifiedMethodHandle(method, true)[0];
            } else {
                method = (IMethod) el[0];
            }
        }
        char[][] parameters = (char[][]) this.parameterNames.get(method);
        if (parameters == null) {
            return null;
        } else {
            return parameters;
        }
    }

    /**
     * Returns the <code>SourceRange</code> for the given element, or
     * {-1, -1} if no source range is known for the element.
     */
    public SourceRange getSourceRange(IJavaElement element) {
        switch (element.getElementType()) {
        case IJavaElement.METHOD:
            if (((IMember) element).isBinary()) {
                IJavaElement[] el = getUnqualifiedMethodHandle((IMethod) element, false);
                if (el[1] != null && this.sourceRanges.get(el[0]) == null) {
                    element = getUnqualifiedMethodHandle((IMethod) element, true)[0];
                } else {
                    element = el[0];
                }
            }
            break;
        case IJavaElement.TYPE_PARAMETER:
            IJavaElement parent = element.getParent();
            if (parent.getElementType() == IJavaElement.METHOD) {
                IMethod method = (IMethod) parent;
                if (method.isBinary()) {
                    IJavaElement[] el = getUnqualifiedMethodHandle(method, false);
                    if (el[1] != null && this.sourceRanges.get(el[0]) == null) {
                        method = (IMethod) getUnqualifiedMethodHandle(method, true)[0];
                    } else {
                        method = (IMethod) el[0];
                    }
                    element = method.getTypeParameter(element.getElementName());
                }
            }
            break;
        case IJavaElement.LOCAL_VARIABLE:
            LocalVariableElementKey key = new LocalVariableElementKey(element.getParent(),
                    element.getElementName());
            SourceRange[] ranges = (SourceRange[]) this.parametersRanges.get(key);
            if (ranges == null) {
                return UNKNOWN_RANGE;
            } else {
                return ranges[0];
            }
        }
        SourceRange[] ranges = (SourceRange[]) this.sourceRanges.get(element);
        if (ranges == null) {
            return UNKNOWN_RANGE;
        } else {
            return ranges[0];
        }
    }

    /**
     * Returns the type with the given <code>typeName</code>.  Returns inner classes
     * as well.
     */
    protected IType getType(String typeName) {
        if (!(this.binaryTypeOrModule instanceof IType))
            return null;
        IType type = (IType) this.binaryTypeOrModule;
        if (typeName.length() == 0) {
            IJavaElement classFile = type.getParent();
            String classFileName = classFile.getElementName();
            StringBuffer newClassFileName = new StringBuffer();
            int lastDollar = classFileName.lastIndexOf('$');
            for (int i = 0; i <= lastDollar; i++)
                newClassFileName.append(classFileName.charAt(i));
            newClassFileName.append(Integer.toString(this.anonymousCounter));
            PackageFragment pkg = (PackageFragment) classFile.getParent();
            return new BinaryType(new ClassFile(pkg, newClassFileName.toString()), typeName);
        } else if (type.getElementName().equals(typeName))
            return type;
        else
            return ((this.typeDepth <= 1) ? type : this.types[this.typeDepth - 1]).getType(typeName);
    }

    /**
     * Creates a handle that has parameter types that are not
     * fully qualified so that the correct source is found.
     */
    protected IJavaElement[] getUnqualifiedMethodHandle(IMethod method, boolean noDollar) {
        boolean hasDollar = false;
        String[] qualifiedParameterTypes = method.getParameterTypes();
        String[] unqualifiedParameterTypes = new String[qualifiedParameterTypes.length];
        for (int i = 0; i < qualifiedParameterTypes.length; i++) {
            StringBuffer unqualifiedTypeSig = new StringBuffer();
            getUnqualifiedTypeSignature(qualifiedParameterTypes[i], 0/*start*/, qualifiedParameterTypes[i].length(),
                    unqualifiedTypeSig, noDollar);
            unqualifiedParameterTypes[i] = unqualifiedTypeSig.toString();
            hasDollar |= unqualifiedParameterTypes[i].lastIndexOf('$') != -1;
        }

        IJavaElement[] result = new IJavaElement[2];
        result[0] = ((IType) method.getParent()).getMethod(method.getElementName(), unqualifiedParameterTypes);
        if (hasDollar) {
            result[1] = result[0];
        }
        return result;
    }

    private int getUnqualifiedTypeSignature(String qualifiedTypeSig, int start, int length,
            StringBuffer unqualifiedTypeSig, boolean noDollar) {
        char firstChar = qualifiedTypeSig.charAt(start);
        int end = start + 1;
        boolean sigStart = false;
        firstPass: for (int i = start; i < length; i++) {
            char current = qualifiedTypeSig.charAt(i);
            switch (current) {
            case Signature.C_ARRAY:
            case Signature.C_SUPER:
            case Signature.C_EXTENDS:
                unqualifiedTypeSig.append(current);
                start = i + 1;
                end = start + 1;
                firstChar = qualifiedTypeSig.charAt(start);
                break;
            case Signature.C_RESOLVED:
            case Signature.C_UNRESOLVED:
            case Signature.C_TYPE_VARIABLE:
                if (!sigStart) {
                    start = ++i;
                    sigStart = true;
                }
                break;
            case Signature.C_NAME_END:
            case Signature.C_GENERIC_START:
                end = i;
                break firstPass;
            case Signature.C_STAR:
                unqualifiedTypeSig.append(current);
                start = i + 1;
                end = start + 1;
                firstChar = qualifiedTypeSig.charAt(start);
                break;
            case Signature.C_GENERIC_END:
                return i;
            case Signature.C_DOT:
                start = ++i;
                break;
            case Signature.C_BOOLEAN:
            case Signature.C_BYTE:
            case Signature.C_CHAR:
            case Signature.C_DOUBLE:
            case Signature.C_FLOAT:
            case Signature.C_INT:
            case Signature.C_LONG:
            case Signature.C_SHORT:
                if (!sigStart) {
                    unqualifiedTypeSig.append(current);
                    return i + 1;
                }
            }
        }
        switch (firstChar) {
        case Signature.C_RESOLVED:
        case Signature.C_UNRESOLVED:
        case Signature.C_TYPE_VARIABLE:
            unqualifiedTypeSig.append(Signature.C_UNRESOLVED);
            if (noDollar) {
                int lastDollar = qualifiedTypeSig.lastIndexOf('$', end);
                if (lastDollar > start)
                    start = lastDollar + 1;
            }
            for (int i = start; i < length; i++) {
                char current = qualifiedTypeSig.charAt(i);
                switch (current) {
                case Signature.C_GENERIC_START:
                    unqualifiedTypeSig.append(current);
                    i++;
                    do {
                        i = getUnqualifiedTypeSignature(qualifiedTypeSig, i, length, unqualifiedTypeSig, noDollar);
                    } while (qualifiedTypeSig.charAt(i) != Signature.C_GENERIC_END);
                    unqualifiedTypeSig.append(Signature.C_GENERIC_END);
                    break;
                case Signature.C_NAME_END:
                    unqualifiedTypeSig.append(current);
                    return i + 1;
                default:
                    unqualifiedTypeSig.append(current);
                    break;
                }
            }
            return length;
        default:
            // primitive type or wildcard
            unqualifiedTypeSig.append(qualifiedTypeSig.substring(start, end));
            return end;
        }
    }

    /**
     * Maps the given source code to the given binary type or module and its children.
     */
    public void mapSource(NamedMember typeOrModule, char[] contents, IBinaryType info) {
        this.mapSource(typeOrModule, contents, info, null);
    }

    /**
     * Maps the given source code to the given binary type and its children.
     * If a non-null java element is passed, finds the name range for the
     * given java element without storing it.
     */
    public synchronized ISourceRange mapSource(NamedMember typeOrModule, char[] contents, IBinaryType info, // null for modules
            IJavaElement elementToFind) {

        this.binaryTypeOrModule = typeOrModule;

        // check whether it is already mapped
        if (this.sourceRanges.get(typeOrModule) != null)
            return (elementToFind != null) ? getNameRange(elementToFind) : null;

        this.importsTable.remove(this.binaryTypeOrModule);
        this.importsCounterTable.remove(this.binaryTypeOrModule);
        this.searchedElement = elementToFind;
        this.types = new IType[1];
        this.typeDeclarationStarts = new int[1];
        this.typeNameRanges = new SourceRange[1];
        this.typeModifiers = new int[1];
        this.typeDepth = -1;
        this.memberDeclarationStart = new int[1];
        this.memberName = new String[1];
        this.memberNameRange = new SourceRange[1];
        this.methodParameterTypes = new char[1][][];
        this.methodParameterNames = new char[1][][];
        this.anonymousCounter = 0;

        HashMap oldSourceRanges = null;
        if (elementToFind != null) {
            oldSourceRanges = (HashMap) this.sourceRanges.clone();
        }
        try {
            IProblemFactory factory = new DefaultProblemFactory();
            SourceElementParser parser = null;
            boolean doFullParse = false;
            this.anonymousClassName = 0;
            String sourceFileName;
            if (this.binaryTypeOrModule instanceof BinaryType) {
                if (info == null) {
                    try {
                        info = (IBinaryType) this.binaryTypeOrModule.getElementInfo();
                    } catch (JavaModelException e) {
                        return null;
                    }
                }
                sourceFileName = ((BinaryType) this.binaryTypeOrModule).sourceFileName(info);
                boolean isAnonymousClass = info.isAnonymous();

                char[] fullName = info.getName();
                if (isAnonymousClass) {
                    String eltName = this.binaryTypeOrModule.getParent().getElementName();
                    eltName = eltName.substring(eltName.lastIndexOf('$') + 1, eltName.length());
                    try {
                        this.anonymousClassName = Integer.parseInt(eltName);
                    } catch (NumberFormatException e) {
                        // ignore
                    }
                }
                doFullParse = hasToRetrieveSourceRangesForLocalClass(fullName);
            } else {
                sourceFileName = TypeConstants.MODULE_INFO_CLASS_NAME_STRING;
            }
            parser = new SourceElementParser(this, factory, new CompilerOptions(this.options), doFullParse,
                    true/*optimize string literals*/);
            parser.javadocParser.checkDocComment = false; // disable javadoc parsing
            IJavaElement javaElement = this.binaryTypeOrModule.getCompilationUnit();
            if (javaElement == null)
                javaElement = this.binaryTypeOrModule.getParent();
            parser.parseCompilationUnit(new BasicCompilationUnit(contents, null, sourceFileName, javaElement),
                    doFullParse, null/*no progress*/);
            if (elementToFind != null) {
                ISourceRange range = getNameRange(elementToFind);
                return range;
            } else {
                return null;
            }
        } finally {
            if (elementToFind != null) {
                this.sourceRanges = oldSourceRanges;
            }
            this.binaryTypeOrModule = null;
            this.searchedElement = null;
            this.types = null;
            this.typeDeclarationStarts = null;
            this.typeNameRanges = null;
            this.typeDepth = -1;
        }
    }

    private char[] readSource(ZipEntry entry, ZipFile zip, String charSet) {
        try {
            byte[] bytes = Util.getZipEntryByteContent(entry, zip);
            if (bytes != null) {
                // Order of preference: charSet supplied, this.encoding or this.defaultEncoding in that order
                return Util.bytesToChar(bytes,
                        charSet == null ? (this.encoding == null ? this.defaultEncoding : this.encoding) : charSet);
            }
        } catch (IOException e) {
            // ignore
        }
        return null;
    }

    /**
     * Sets the mapping for this method to its parameter names.
     *
     * @see #parameterNames
     */
    protected void setMethodParameterNames(IMethod method, char[][] parameterNames) {
        if (parameterNames == null) {
            parameterNames = CharOperation.NO_CHAR_CHAR;
        }
        this.parameterNames.put(method, parameterNames);
    }

    /**
     * Sets the mapping for this element to its source ranges for its source range
     * and name range.
     *
     * @see #sourceRanges
     */
    protected void setSourceRange(IJavaElement element, SourceRange sourceRange, SourceRange nameRange) {
        this.sourceRanges.put(element, new SourceRange[] { sourceRange, nameRange });
    }

    /**
     * Return a char[][] array containing the imports of the attached source for the binary type
     */
    public char[][] getImports(Member typeOrModule) {
        char[][] imports = (char[][]) this.importsTable.get(typeOrModule);
        if (imports != null) {
            int importsCounter = ((Integer) this.importsCounterTable.get(typeOrModule)).intValue();
            if (imports.length != importsCounter) {
                System.arraycopy(imports, 0, (imports = new char[importsCounter][]), 0, importsCounter);
            }
            this.importsTable.put(typeOrModule, imports);
        }
        return imports;
    }

    private boolean hasToRetrieveSourceRangesForLocalClass(char[] eltName) {
        /*
         * A$1$B$2 : true
         * A$B$B$2 : true
         * A$C$B$D : false
         * A$F$B$D$1$F : true
         * A$F$B$D$1F : true
         * A$1 : true
         * A$B : false
         */
        if (eltName == null)
            return false;
        int length = eltName.length;
        int dollarIndex = CharOperation.indexOf('$', eltName, 0);
        while (dollarIndex != -1) {
            int nameStart = dollarIndex + 1;
            if (nameStart == length)
                return false;
            if (Character.isDigit(eltName[nameStart]))
                return true;
            dollarIndex = CharOperation.indexOf('$', eltName, nameStart);
        }
        return false;
    }

}