org.eclipse.jdt.internal.core.util.Util.java Source code

Java tutorial

Introduction

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

Source

/*******************************************************************************
 * Copyright (c) 2000, 2019 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
 *     Stephan Herrmann - Contribution for
 *                        Bug 458577 - IClassFile.getWorkingCopy() may lead to NPE in BecomeWorkingCopyOperation
 *******************************************************************************/
package org.eclipse.jdt.internal.core.util;

import java.io.*;
import java.net.URI;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

import org.eclipse.core.filesystem.EFS;
import org.eclipse.core.filesystem.IFileStore;
import org.eclipse.core.resources.*;
import org.eclipse.core.runtime.*;
import org.eclipse.core.runtime.content.IContentType;
import org.eclipse.core.runtime.preferences.IScopeContext;
import org.eclipse.core.runtime.preferences.InstanceScope;
import org.eclipse.jdt.core.*;
import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ArrayType;
import org.eclipse.jdt.core.dom.ParameterizedType;
import org.eclipse.jdt.core.dom.PrimitiveType;
import org.eclipse.jdt.core.dom.QualifiedType;
import org.eclipse.jdt.core.dom.SimpleType;
import org.eclipse.jdt.core.dom.Type;
import org.eclipse.jdt.core.dom.WildcardType;
import org.eclipse.jdt.core.util.IClassFileAttribute;
import org.eclipse.jdt.core.util.IClassFileReader;
import org.eclipse.jdt.core.util.ICodeAttribute;
import org.eclipse.jdt.core.util.IFieldInfo;
import org.eclipse.jdt.core.util.IMethodInfo;
import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration;
import org.eclipse.jdt.internal.compiler.ast.AnnotationMethodDeclaration;
import org.eclipse.jdt.internal.compiler.ast.Argument;
import org.eclipse.jdt.internal.compiler.ast.IntersectionCastTypeReference;
import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration;
import org.eclipse.jdt.internal.compiler.ast.TypeReference;
import org.eclipse.jdt.internal.compiler.ast.UnionTypeReference;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFormatException;
import org.eclipse.jdt.internal.compiler.env.ClassSignature;
import org.eclipse.jdt.internal.compiler.env.EnumConstantSignature;
import org.eclipse.jdt.internal.compiler.env.IBinaryAnnotation;
import org.eclipse.jdt.internal.compiler.env.IDependent;
import org.eclipse.jdt.internal.compiler.impl.Constant;
import org.eclipse.jdt.internal.compiler.lookup.Binding;
import org.eclipse.jdt.internal.compiler.lookup.FieldBinding;
import org.eclipse.jdt.internal.compiler.lookup.MethodBinding;
import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding;
import org.eclipse.jdt.internal.compiler.lookup.TypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.TypeIds;
import org.eclipse.jdt.internal.compiler.parser.ScannerHelper;
import org.eclipse.jdt.internal.compiler.util.SuffixConstants;
import org.eclipse.jdt.internal.core.Annotation;
import org.eclipse.jdt.internal.core.ClassFile;
import org.eclipse.jdt.internal.core.JavaElement;
import org.eclipse.jdt.internal.core.JavaModelManager;
import org.eclipse.jdt.internal.core.Member;
import org.eclipse.jdt.internal.core.MemberValuePair;
import org.eclipse.jdt.internal.core.PackageFragment;
import org.eclipse.jdt.internal.core.PackageFragmentRoot;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.text.edits.MalformedTreeException;
import org.eclipse.text.edits.TextEdit;

/**
 * Provides convenient utility methods to other types in this package.
 */
@SuppressWarnings({ "rawtypes", "unchecked" })
public class Util {

    public interface Comparable {
        /**
         * Returns 0 if this and c are equal, >0 if this is greater than c,
         * or <0 if this is less than c.
         */
        int compareTo(Comparable c);
    }

    public interface Comparer {
        /**
         * Returns 0 if a and b are equal, >0 if a is greater than b,
         * or <0 if a is less than b.
         */
        int compare(Object a, Object b);
    }

    public static interface BindingsToNodesMap {
        public org.eclipse.jdt.internal.compiler.ast.ASTNode get(Binding binding);
    }

    private static final char ARGUMENTS_DELIMITER = '#';

    private static final String EMPTY_ARGUMENT = "   "; //$NON-NLS-1$

    private static char[][] JAVA_LIKE_EXTENSIONS;

    private static final char[] BOOLEAN = "boolean".toCharArray(); //$NON-NLS-1$
    private static final char[] BYTE = "byte".toCharArray(); //$NON-NLS-1$
    private static final char[] CHAR = "char".toCharArray(); //$NON-NLS-1$
    private static final char[] DOUBLE = "double".toCharArray(); //$NON-NLS-1$
    private static final char[] FLOAT = "float".toCharArray(); //$NON-NLS-1$
    private static final char[] INT = "int".toCharArray(); //$NON-NLS-1$
    private static final char[] LONG = "long".toCharArray(); //$NON-NLS-1$
    private static final char[] SHORT = "short".toCharArray(); //$NON-NLS-1$
    private static final char[] VOID = "void".toCharArray(); //$NON-NLS-1$
    private static final char[] INIT = "<init>".toCharArray(); //$NON-NLS-1$

    private static final String TASK_PRIORITIES_PROBLEM = "TASK_PRIORITIES_PB"; //$NON-NLS-1$
    private static List fgRepeatedMessages = new ArrayList(5);

    private Util() {
        // cannot be instantiated
    }

    /**
     * Returns a new array adding the second array at the end of first array.
     * It answers null if the first and second are null.
     * If the first array is null or if it is empty, then a new array is created with second.
     * If the second array is null, then the first array is returned.
     * <br>
     * <br>
     * For example:
     * <ol>
     * <li><pre>
     *    first = null
     *    second = "a"
     *    => result = {"a"}
     * </pre>
     * <li><pre>
     *    first = {"a"}
     *    second = null
     *    => result = {"a"}
     * </pre>
     * </li>
     * <li><pre>
     *    first = {"a"}
     *    second = {"b"}
     *    => result = {"a", "b"}
     * </pre>
     * </li>
     * </ol>
     *
     * @param first the first array to concatenate
     * @param second the array to add at the end of the first array
     * @return a new array adding the second array at the end of first array, or null if the two arrays are null.
     */
    public static final String[] arrayConcat(String[] first, String second) {
        if (second == null)
            return first;
        if (first == null)
            return new String[] { second };

        int length = first.length;
        if (first.length == 0) {
            return new String[] { second };
        }

        String[] result = new String[length + 1];
        System.arraycopy(first, 0, result, 0, length);
        result[length] = second;
        return result;
    }

    /**
     * Checks the type signature in String sig,
     * starting at start and ending before end (end is not included).
     * Returns the index of the character immediately after the signature if valid,
     * or -1 if not valid.
     */
    private static int checkTypeSignature(String sig, int start, int end, boolean allowVoid) {
        if (start >= end)
            return -1;
        int i = start;
        char c = sig.charAt(i++);
        int nestingDepth = 0;
        while (c == '[') {
            ++nestingDepth;
            if (i >= end)
                return -1;
            c = sig.charAt(i++);
        }
        switch (c) {
        case 'B':
        case 'C':
        case 'D':
        case 'F':
        case 'I':
        case 'J':
        case 'S':
        case 'Z':
            break;
        case 'V':
            if (!allowVoid)
                return -1;
            // array of void is not allowed
            if (nestingDepth != 0)
                return -1;
            break;
        case 'L':
            int semicolon = sig.indexOf(';', i);
            // Must have at least one character between L and ;
            if (semicolon <= i || semicolon >= end)
                return -1;
            i = semicolon + 1;
            break;
        default:
            return -1;
        }
        return i;
    }

    /**
     * Combines two hash codes to make a new one.
     */
    public static int combineHashCodes(int hashCode1, int hashCode2) {
        return hashCode1 * 17 + hashCode2;
    }

    /**
     * Compares two byte arrays.
     * Returns <0 if a byte in a is less than the corresponding byte in b, or if a is shorter, or if a is null.
     * Returns >0 if a byte in a is greater than the corresponding byte in b, or if a is longer, or if b is null.
     * Returns 0 if they are equal or both null.
     */
    public static int compare(byte[] a, byte[] b) {
        if (a == b)
            return 0;
        if (a == null)
            return -1;
        if (b == null)
            return 1;
        int len = Math.min(a.length, b.length);
        for (int i = 0; i < len; ++i) {
            int diff = a[i] - b[i];
            if (diff != 0)
                return diff;
        }
        if (a.length > len)
            return 1;
        if (b.length > len)
            return -1;
        return 0;
    }

    /**
     * Compares two strings lexicographically.
     * The comparison is based on the Unicode value of each character in
     * the strings.
     *
     * @return  the value <code>0</code> if the str1 is equal to str2;
     *          a value less than <code>0</code> if str1
     *          is lexicographically less than str2;
     *          and a value greater than <code>0</code> if str1 is
     *          lexicographically greater than str2.
     */
    public static int compare(char[] str1, char[] str2) {
        int len1 = str1.length;
        int len2 = str2.length;
        int n = Math.min(len1, len2);
        int i = 0;
        while (n-- != 0) {
            char c1 = str1[i];
            char c2 = str2[i++];
            if (c1 != c2) {
                return c1 - c2;
            }
        }
        return len1 - len2;
    }

    /**
     * Concatenate a String[] compound name to a continuous char[].
     */
    public static char[] concatCompoundNameToCharArray(String[] compoundName) {
        if (compoundName == null)
            return null;
        int length = compoundName.length;
        if (length == 0)
            return new char[0];
        int size = 0;
        for (int i = 0; i < length; i++) {
            size += compoundName[i].length();
        }
        char[] compoundChars = new char[size + length - 1];
        int pos = 0;
        for (int i = 0; i < length; i++) {
            String name = compoundName[i];
            if (i > 0)
                compoundChars[pos++] = '.';
            int nameLength = name.length();
            name.getChars(0, nameLength, compoundChars, pos);
            pos += nameLength;
        }
        return compoundChars;
    }

    public static String concatenateName(String name1, String name2, char separator) {
        StringBuffer buf = new StringBuffer();
        if (name1 != null && name1.length() > 0) {
            buf.append(name1);
        }
        if (name2 != null && name2.length() > 0) {
            if (buf.length() > 0) {
                buf.append(separator);
            }
            buf.append(name2);
        }
        return buf.toString();
    }

    /**
     * Returns the concatenation of the given array parts using the given separator between each part.
     * <br>
     * <br>
     * For example:<br>
     * <ol>
     * <li><pre>
     *    array = {"a", "b"}
     *    separator = '.'
     *    => result = "a.b"
     * </pre>
     * </li>
     * <li><pre>
     *    array = {}
     *    separator = '.'
     *    => result = ""
     * </pre></li>
     * </ol>
     *
     * @param array the given array
     * @param separator the given separator
     * @return the concatenation of the given array parts using the given separator between each part
     */
    public static final String concatWith(String[] array, char separator) {
        StringBuffer buffer = new StringBuffer();
        for (int i = 0, length = array.length; i < length; i++) {
            buffer.append(array[i]);
            if (i < length - 1)
                buffer.append(separator);
        }
        return buffer.toString();
    }

    /**
     * Returns the concatenation of the given array parts using the given separator between each
     * part and appending the given name at the end.
     * <br>
     * <br>
     * For example:<br>
     * <ol>
     * <li><pre>
     *    name = "c"
     *    array = { "a", "b" }
     *    separator = '.'
     *    => result = "a.b.c"
     * </pre>
     * </li>
     * <li><pre>
     *    name = null
     *    array = { "a", "b" }
     *    separator = '.'
     *    => result = "a.b"
     * </pre></li>
     * <li><pre>
     *    name = " c"
     *    array = null
     *    separator = '.'
     *    => result = "c"
     * </pre></li>
     * </ol>
     *
     * @param array the given array
     * @param name the given name
     * @param separator the given separator
     * @return the concatenation of the given array parts using the given separator between each
     * part and appending the given name at the end
     */
    public static final String concatWith(String[] array, String name, char separator) {

        if (array == null || array.length == 0)
            return name;
        if (name == null || name.length() == 0)
            return concatWith(array, separator);
        StringBuffer buffer = new StringBuffer();
        for (int i = 0, length = array.length; i < length; i++) {
            buffer.append(array[i]);
            buffer.append(separator);
        }
        buffer.append(name);
        return buffer.toString();

    }

    /**
     * Converts a type signature from the IBinaryType representation to the DC representation.
     */
    public static String convertTypeSignature(char[] sig, int start, int length) {
        return new String(sig, start, length).replace('/', '.');
    }

    /*
     * Returns the default java extension (".java").
     * To be used when the extension is not known.
     */
    public static String defaultJavaExtension() {
        return SuffixConstants.SUFFIX_STRING_java;
    }

    /**
     * Apply the given edit on the given string and return the updated string.
     * Return the given string if anything wrong happen while applying the edit.
     *
     * @param original the given string
     * @param edit the given edit
     *
     * @return the updated string
     */
    public final static String editedString(String original, TextEdit edit) {
        if (edit == null) {
            return original;
        }
        SimpleDocument document = new SimpleDocument(original);
        try {
            edit.apply(document, TextEdit.NONE);
            return document.get();
        } catch (MalformedTreeException | BadLocationException e) {
            e.printStackTrace();
        }
        return original;
    }

    /**
     * Returns true iff str.toLowerCase().endsWith(end.toLowerCase())
     * implementation is not creating extra strings.
     */
    public final static boolean endsWithIgnoreCase(String str, String end) {

        int strLength = str == null ? 0 : str.length();
        int endLength = end == null ? 0 : end.length();

        // return false if the string is smaller than the end.
        if (endLength > strLength)
            return false;

        // return false if any character of the end are
        // not the same in lower case.
        for (int i = 1; i <= endLength; i++) {
            if (ScannerHelper.toLowerCase(end.charAt(endLength - i)) != ScannerHelper
                    .toLowerCase(str.charAt(strLength - i)))
                return false;
        }

        return true;
    }

    /**
     * Compares two arrays using equals() on the elements.
     * Neither can be null. Only the first len elements are compared.
     * Return false if either array is shorter than len.
     */
    public static boolean equalArrays(Object[] a, Object[] b, int len) {
        if (a == b)
            return true;
        if (a.length < len || b.length < len)
            return false;
        for (int i = 0; i < len; ++i) {
            if (a[i] == null) {
                if (b[i] != null)
                    return false;
            } else {
                if (!a[i].equals(b[i]))
                    return false;
            }
        }
        return true;
    }

    /**
     * Compares two arrays using equals() on the elements.
     * Either or both arrays may be null.
     * Returns true if both are null.
     * Returns false if only one is null.
     * If both are arrays, returns true iff they have the same length and
     * all elements are equal.
     */
    public static boolean equalArraysOrNull(int[] a, int[] b) {
        if (a == b)
            return true;
        if (a == null || b == null)
            return false;
        int len = a.length;
        if (len != b.length)
            return false;
        for (int i = 0; i < len; ++i) {
            if (a[i] != b[i])
                return false;
        }
        return true;
    }

    /**
     * Compares two arrays using equals() on the elements.
     * Either or both arrays may be null.
     * Returns true if both are null.
     * Returns false if only one is null.
     * If both are arrays, returns true iff they have the same length and
     * all elements compare true with equals.
     */
    public static boolean equalArraysOrNull(Object[] a, Object[] b) {
        if (a == b)
            return true;
        if (a == null || b == null)
            return false;

        int len = a.length;
        if (len != b.length)
            return false;
        // walk array from end to beginning as this optimizes package name cases
        // where the first part is always the same (e.g. org.eclipse.jdt)
        for (int i = len - 1; i >= 0; i--) {
            if (a[i] == null) {
                if (b[i] != null)
                    return false;
            } else {
                if (!a[i].equals(b[i]))
                    return false;
            }
        }
        return true;
    }

    /**
     * Compares two arrays using equals() on the elements.
     * The arrays are first sorted.
     * Either or both arrays may be null.
     * Returns true if both are null.
     * Returns false if only one is null.
     * If both are arrays, returns true iff they have the same length and
     * iff, after sorting both arrays, all elements compare true with equals.
     * The original arrays are left untouched.
     */
    public static boolean equalArraysOrNullSortFirst(Comparable[] a, Comparable[] b) {
        if (a == b)
            return true;
        if (a == null || b == null)
            return false;
        int len = a.length;
        if (len != b.length)
            return false;
        if (len >= 2) { // only need to sort if more than two items
            a = sortCopy(a);
            b = sortCopy(b);
        }
        for (int i = 0; i < len; ++i) {
            if (!a[i].equals(b[i]))
                return false;
        }
        return true;
    }

    /**
     * Compares two String arrays using equals() on the elements.
     * The arrays are first sorted.
     * Either or both arrays may be null.
     * Returns true if both are null.
     * Returns false if only one is null.
     * If both are arrays, returns true iff they have the same length and
     * iff, after sorting both arrays, all elements compare true with equals.
     * The original arrays are left untouched.
     */
    public static boolean equalArraysOrNullSortFirst(String[] a, String[] b) {
        if (a == b)
            return true;
        if (a == null || b == null)
            return false;
        int len = a.length;
        if (len != b.length)
            return false;
        if (len >= 2) { // only need to sort if more than two items
            a = sortCopy(a);
            b = sortCopy(b);
        }
        for (int i = 0; i < len; ++i) {
            if (!a[i].equals(b[i]))
                return false;
        }
        return true;
    }

    /**
     * Compares two objects using equals().
     * Either or both array may be null.
     * Returns true if both are null.
     * Returns false if only one is null.
     * Otherwise, return the result of comparing with equals().
     */
    public static boolean equalOrNull(Object a, Object b) {
        if (a == b) {
            return true;
        }
        if (a == null || b == null) {
            return false;
        }
        return a.equals(b);
    }

    /*
     * Returns whether the given file name equals to the given string ignoring the java like extension
     * of the file name.
     * Returns false if it is not a java like file name.
     */
    public static boolean equalsIgnoreJavaLikeExtension(String fileName, String string) {
        int fileNameLength = fileName.length();
        int stringLength = string.length();
        if (fileNameLength < stringLength)
            return false;
        for (int i = 0; i < stringLength; i++) {
            if (fileName.charAt(i) != string.charAt(i)) {
                return false;
            }
        }
        char[][] javaLikeExtensions = getJavaLikeExtensions();
        suffixes: for (int i = 0, length = javaLikeExtensions.length; i < length; i++) {
            char[] suffix = javaLikeExtensions[i];
            int extensionStart = stringLength + 1;
            if (extensionStart + suffix.length != fileNameLength)
                continue;
            if (fileName.charAt(stringLength) != '.')
                continue;
            for (int j = extensionStart; j < fileNameLength; j++) {
                if (fileName.charAt(j) != suffix[j - extensionStart])
                    continue suffixes;
            }
            return true;
        }
        return false;
    }

    /**
     * Given a qualified name, extract the last component.
     * If the input is not qualified, the same string is answered.
     */
    public static String extractLastName(String qualifiedName) {
        int i = qualifiedName.lastIndexOf('.');
        if (i == -1)
            return qualifiedName;
        return qualifiedName.substring(i + 1);
    }

    /**
     * Extracts the parameter types from a method signature.
     */
    public static String[] extractParameterTypes(char[] sig) {
        int count = getParameterCount(sig);
        String[] result = new String[count];
        if (count == 0)
            return result;
        int i = CharOperation.indexOf('(', sig) + 1;
        count = 0;
        int len = sig.length;
        int start = i;
        for (;;) {
            if (i == len)
                break;
            char c = sig[i];
            if (c == ')')
                break;
            if (c == '[') {
                ++i;
            } else if (c == 'L') {
                i = CharOperation.indexOf(';', sig, i + 1) + 1;
                Assert.isTrue(i != 0);
                result[count++] = convertTypeSignature(sig, start, i - start);
                start = i;
            } else {
                ++i;
                result[count++] = convertTypeSignature(sig, start, i - start);
                start = i;
            }
        }
        return result;
    }

    /**
     * Extracts the return type from a method signature.
     */
    public static String extractReturnType(String sig) {
        int i = sig.lastIndexOf(')');
        Assert.isTrue(i != -1);
        return sig.substring(i + 1);
    }

    private static IFile findFirstClassFile(IFolder folder) {
        try {
            IResource[] members = folder.members();
            for (int i = 0, max = members.length; i < max; i++) {
                IResource member = members[i];
                if (member.getType() == IResource.FOLDER) {
                    return findFirstClassFile((IFolder) member);
                } else if (org.eclipse.jdt.internal.compiler.util.Util.isClassFileName(member.getName())) {
                    return (IFile) member;
                }
            }
        } catch (CoreException e) {
            // ignore
        }
        return null;
    }

    /**
     * Finds the first line separator used by the given text.
     *
     * @return </code>"\n"</code> or </code>"\r"</code> or  </code>"\r\n"</code>,
     *         or <code>null</code> if none found
     */
    public static String findLineSeparator(char[] text) {
        // find the first line separator
        int length = text.length;
        if (length > 0) {
            char nextChar = text[0];
            for (int i = 0; i < length; i++) {
                char currentChar = nextChar;
                nextChar = i < length - 1 ? text[i + 1] : ' ';
                switch (currentChar) {
                case '\n':
                    return "\n"; //$NON-NLS-1$
                case '\r':
                    return nextChar == '\n' ? "\r\n" : "\r"; //$NON-NLS-1$ //$NON-NLS-2$
                }
            }
        }
        // not found
        return null;
    }

    public static IClassFileAttribute getAttribute(IClassFileReader classFileReader, char[] attributeName) {
        IClassFileAttribute[] attributes = classFileReader.getAttributes();
        for (int i = 0, max = attributes.length; i < max; i++) {
            if (CharOperation.equals(attributes[i].getAttributeName(), attributeName)) {
                return attributes[i];
            }
        }
        return null;
    }

    public static IClassFileAttribute getAttribute(ICodeAttribute codeAttribute, char[] attributeName) {
        IClassFileAttribute[] attributes = codeAttribute.getAttributes();
        for (int i = 0, max = attributes.length; i < max; i++) {
            if (CharOperation.equals(attributes[i].getAttributeName(), attributeName)) {
                return attributes[i];
            }
        }
        return null;
    }

    public static IClassFileAttribute getAttribute(IFieldInfo fieldInfo, char[] attributeName) {
        IClassFileAttribute[] attributes = fieldInfo.getAttributes();
        for (int i = 0, max = attributes.length; i < max; i++) {
            if (CharOperation.equals(attributes[i].getAttributeName(), attributeName)) {
                return attributes[i];
            }
        }
        return null;
    }

    public static IClassFileAttribute getAttribute(IMethodInfo methodInfo, char[] attributeName) {
        IClassFileAttribute[] attributes = methodInfo.getAttributes();
        for (int i = 0, max = attributes.length; i < max; i++) {
            if (CharOperation.equals(attributes[i].getAttributeName(), attributeName)) {
                return attributes[i];
            }
        }
        return null;
    }

    private static IClassFile getClassFile(char[] fileName) {
        int jarSeparator = CharOperation.indexOf(IDependent.JAR_FILE_ENTRY_SEPARATOR, fileName);
        int pkgEnd = CharOperation.lastIndexOf('/', fileName); // pkgEnd is exclusive
        if (pkgEnd == -1)
            pkgEnd = CharOperation.lastIndexOf(File.separatorChar, fileName);
        if (jarSeparator != -1 && pkgEnd < jarSeparator) // if in a jar and no slash, it is a default package -> pkgEnd should be equal to jarSeparator
            pkgEnd = jarSeparator;
        if (pkgEnd == -1)
            return null;
        IPackageFragment pkg = getPackageFragment(fileName, pkgEnd, jarSeparator);
        if (pkg == null)
            return null;
        int start;
        return pkg.getClassFile(new String(fileName, start = pkgEnd + 1, fileName.length - start));
    }

    private static ICompilationUnit getCompilationUnit(char[] fileName, WorkingCopyOwner workingCopyOwner) {
        char[] slashSeparatedFileName = CharOperation.replaceOnCopy(fileName, File.separatorChar, '/');
        int pkgEnd = CharOperation.lastIndexOf('/', slashSeparatedFileName); // pkgEnd is exclusive
        if (pkgEnd == -1)
            return null;
        IPackageFragment pkg = getPackageFragment(slashSeparatedFileName, pkgEnd,
                -1/*no jar separator for .java files*/);
        if (pkg != null) {
            int start;
            ICompilationUnit cu = pkg.getCompilationUnit(
                    new String(slashSeparatedFileName, start = pkgEnd + 1, slashSeparatedFileName.length - start));
            if (workingCopyOwner != null) {
                ICompilationUnit workingCopy = cu.findWorkingCopy(workingCopyOwner);
                if (workingCopy != null)
                    return workingCopy;
            }
            return cu;
        }
        IWorkspaceRoot wsRoot = ResourcesPlugin.getWorkspace().getRoot();
        IFile file = wsRoot.getFile(new Path(String.valueOf(fileName)));
        if (file.exists()) {
            // this approach works if file exists but is not on the project's build path:
            return JavaCore.createCompilationUnitFrom(file);
        }
        return null;
    }

    /**
     * Returns the registered Java like extensions.
     */
    public static char[][] getJavaLikeExtensions() {
        if (JAVA_LIKE_EXTENSIONS == null) {
            IContentType javaContentType = Platform.getContentTypeManager()
                    .getContentType(JavaCore.JAVA_SOURCE_CONTENT_TYPE);
            HashSet fileExtensions = new HashSet();
            // content types derived from java content type should be included (https://bugs.eclipse.org/bugs/show_bug.cgi?id=121715)
            IContentType[] contentTypes = Platform.getContentTypeManager().getAllContentTypes();
            for (int i = 0, length = contentTypes.length; i < length; i++) {
                if (contentTypes[i].isKindOf(javaContentType)) { // note that javaContentType.isKindOf(javaContentType) == true
                    String[] fileExtension = contentTypes[i].getFileSpecs(IContentType.FILE_EXTENSION_SPEC);
                    for (int j = 0, length2 = fileExtension.length; j < length2; j++) {
                        fileExtensions.add(fileExtension[j]);
                    }
                }
            }
            int length = fileExtensions.size();
            // note that file extensions contains "java" as it is defined in JDT Core's plugin.xml
            char[][] extensions = new char[length][];
            extensions[0] = SuffixConstants.EXTENSION_java.toCharArray(); // ensure that "java" is first
            int index = 1;
            Iterator iterator = fileExtensions.iterator();
            while (iterator.hasNext()) {
                String fileExtension = (String) iterator.next();
                if (SuffixConstants.EXTENSION_java.equals(fileExtension))
                    continue;
                extensions[index++] = fileExtension.toCharArray();
            }
            JAVA_LIKE_EXTENSIONS = extensions;
        }
        return JAVA_LIKE_EXTENSIONS;
    }

    /**
     * Get the jdk level of this root.
     * The value can be:
     * <ul>
     * <li>major<<16 + minor : see predefined constants on ClassFileConstants </li>
     * <li><code>0</null> if the root is a source package fragment root or if a Java model exception occured</li>
     * </ul>
     * Returns the jdk level
     */
    public static long getJdkLevel(Object targetLibrary) {
        try {
            ClassFileReader reader = null;
            if (targetLibrary instanceof IFolder) {
                IFile classFile = findFirstClassFile((IFolder) targetLibrary); // only internal classfolders are allowed
                if (classFile != null)
                    reader = Util.newClassFileReader(classFile);
            } else {
                // root is a jar file or a zip file
                ZipFile jar = null;
                try {
                    IPath path = null;
                    if (targetLibrary instanceof IResource) {
                        path = ((IResource) targetLibrary).getFullPath();
                    } else if (targetLibrary instanceof File) {
                        File f = (File) targetLibrary;
                        if (!f.isDirectory()) {
                            path = new Path(((File) targetLibrary).getPath());
                        }
                    }
                    if (path != null) {
                        if (JavaModelManager.isJrt(path)) {
                            return ClassFileConstants.JDK9;
                        } else {
                            jar = JavaModelManager.getJavaModelManager().getZipFile(path);
                            for (Enumeration e = jar.entries(); e.hasMoreElements();) {
                                ZipEntry member = (ZipEntry) e.nextElement();
                                String entryName = member.getName();
                                if (org.eclipse.jdt.internal.compiler.util.Util.isClassFileName(entryName)) {
                                    reader = ClassFileReader.read(jar, entryName);
                                    break;
                                }
                            }
                        }
                    }
                } catch (CoreException e) {
                    // ignore
                } finally {
                    JavaModelManager.getJavaModelManager().closeZipFile(jar);
                }
            }
            if (reader != null) {
                return reader.getVersion();
            }
        } catch (CoreException | ClassFormatException | IOException e) {
            // ignore
        }
        return 0;
    }

    /**
     * Returns the substring of the given file name, ending at the start of a
     * Java like extension. The entire file name is returned if it doesn't end
     * with a Java like extension.
     */
    public static String getNameWithoutJavaLikeExtension(String fileName) {
        int index = indexOfJavaLikeExtension(fileName);
        if (index == -1)
            return fileName;
        return fileName.substring(0, index);
    }

    /**
     * Returns the line separator found in the given text.
     * If it is null, or not found return the line delimiter for the given project.
     * If the project is null, returns the line separator for the workspace.
     * If still null, return the system line separator.
     */
    public static String getLineSeparator(String text, IJavaProject project) {
        String lineSeparator = null;

        // line delimiter in given text
        if (text != null && text.length() != 0) {
            lineSeparator = findLineSeparator(text.toCharArray());
            if (lineSeparator != null)
                return lineSeparator;
        }

        if (Platform.isRunning()) {
            // line delimiter in project preference
            IScopeContext[] scopeContext;
            if (project != null) {
                scopeContext = new IScopeContext[] { new ProjectScope(project.getProject()) };
                lineSeparator = Platform.getPreferencesService().getString(Platform.PI_RUNTIME,
                        Platform.PREF_LINE_SEPARATOR, null, scopeContext);
                if (lineSeparator != null)
                    return lineSeparator;
            }

            // line delimiter in workspace preference
            scopeContext = new IScopeContext[] { InstanceScope.INSTANCE };
            lineSeparator = Platform.getPreferencesService().getString(Platform.PI_RUNTIME,
                    Platform.PREF_LINE_SEPARATOR, null, scopeContext);
            if (lineSeparator != null)
                return lineSeparator;
        }

        // system line delimiter
        return org.eclipse.jdt.internal.compiler.util.Util.LINE_SEPARATOR;
    }

    /**
     * Returns the line separator used by the given buffer.
     * Uses the given text if none found.
     *
     * @return </code>"\n"</code> or </code>"\r"</code> or  </code>"\r\n"</code>
     */
    private static String getLineSeparator(char[] text, char[] buffer) {
        // search in this buffer's contents first
        String lineSeparator = findLineSeparator(buffer);
        if (lineSeparator == null) {
            // search in the given text
            lineSeparator = findLineSeparator(text);
            if (lineSeparator == null) {
                // default to system line separator
                return getLineSeparator((String) null, (IJavaProject) null);
            }
        }
        return lineSeparator;
    }

    public static IPackageFragment getPackageFragment(char[] fileName, int pkgEnd, int jarSeparator) {
        if (jarSeparator != -1) {
            String jarMemento = new String(fileName, 0, jarSeparator);
            PackageFragmentRoot root = (PackageFragmentRoot) JavaCore.create(jarMemento);
            if (pkgEnd == jarSeparator)
                return root.getPackageFragment(CharOperation.NO_STRINGS);
            char[] pkgName = CharOperation.subarray(fileName, jarSeparator + 1, pkgEnd);
            char[][] compoundName = CharOperation.splitOn('/', pkgName);
            return root.getPackageFragment(CharOperation.toStrings(compoundName));
        } else {
            Path path = new Path(new String(fileName, 0, pkgEnd));
            IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot();
            IContainer folder = path.segmentCount() == 1 ? workspaceRoot.getProject(path.lastSegment())
                    : (IContainer) workspaceRoot.getFolder(path);
            IJavaElement element = JavaCore.create(folder);
            if (element == null)
                return null;
            switch (element.getElementType()) {
            case IJavaElement.PACKAGE_FRAGMENT:
                return (IPackageFragment) element;
            case IJavaElement.PACKAGE_FRAGMENT_ROOT:
                return ((PackageFragmentRoot) element).getPackageFragment(CharOperation.NO_STRINGS);
            case IJavaElement.JAVA_PROJECT:
                PackageFragmentRoot root = (PackageFragmentRoot) ((IJavaProject) element)
                        .getPackageFragmentRoot(folder);
                if (root == null)
                    return null;
                return root.getPackageFragment(CharOperation.NO_STRINGS);
            }
            return null;
        }
    }

    /**
     * Returns the number of parameter types in a method signature.
     */
    public static int getParameterCount(char[] sig) {
        int i = CharOperation.indexOf('(', sig) + 1;
        Assert.isTrue(i != 0);
        int count = 0;
        int len = sig.length;
        for (;;) {
            if (i == len)
                break;
            char c = sig[i];
            if (c == ')')
                break;
            if (c == '[') {
                ++i;
            } else if (c == 'L') {
                ++count;
                i = CharOperation.indexOf(';', sig, i + 1) + 1;
                Assert.isTrue(i != 0);
            } else {
                ++count;
                ++i;
            }
        }
        return count;
    }

    /**
     * Put all the arguments in one String.
     */
    public static String getProblemArgumentsForMarker(String[] arguments) {
        StringBuffer args = new StringBuffer(10);

        args.append(arguments.length);
        args.append(':');

        for (int j = 0; j < arguments.length; j++) {
            if (j != 0)
                args.append(ARGUMENTS_DELIMITER);

            if (arguments[j].length() == 0) {
                args.append(EMPTY_ARGUMENT);
            } else {
                encodeArgument(arguments[j], args);
            }
        }

        return args.toString();
    }

    /**
     * Encode the argument by doubling the '#' if present into the argument value.
     * 
     * <p>This stores the encoded argument into the given buffer.</p>
     *
     * @param argument the given argument
     * @param buffer the buffer in which the encoded argument is stored
     */
    private static void encodeArgument(String argument, StringBuffer buffer) {
        for (int i = 0, max = argument.length(); i < max; i++) {
            char charAt = argument.charAt(i);
            switch (charAt) {
            case ARGUMENTS_DELIMITER:
                buffer.append(ARGUMENTS_DELIMITER).append(ARGUMENTS_DELIMITER);
                break;
            default:
                buffer.append(charAt);
            }
        }
    }

    /**
     * Separate all the arguments of a String made by getProblemArgumentsForMarker
     */
    public static String[] getProblemArgumentsFromMarker(String argumentsString) {
        if (argumentsString == null) {
            return null;
        }
        int index = argumentsString.indexOf(':');
        if (index == -1)
            return null;

        int length = argumentsString.length();
        int numberOfArg = 0;
        try {
            numberOfArg = Integer.parseInt(argumentsString.substring(0, index));
        } catch (NumberFormatException e) {
            return null;
        }
        argumentsString = argumentsString.substring(index + 1, length);

        return decodeArgumentString(numberOfArg, argumentsString);
    }

    private static String[] decodeArgumentString(int length, String argumentsString) {
        // decode the argumentString knowing that '#' is doubled if part of the argument value
        if (length == 0) {
            if (argumentsString.length() != 0) {
                return null;
            }
            return CharOperation.NO_STRINGS;
        }
        String[] result = new String[length];
        int count = 0;
        StringBuffer buffer = new StringBuffer();
        for (int i = 0, max = argumentsString.length(); i < max; i++) {
            char current = argumentsString.charAt(i);
            switch (current) {
            case ARGUMENTS_DELIMITER:
                /* check the next character. If this is also ARGUMENTS_DELIMITER then only put one into the
                 * decoded argument and proceed with the next character
                 */
                if ((i + 1) == max) {
                    return null;
                }
                char next = argumentsString.charAt(i + 1);
                if (next == ARGUMENTS_DELIMITER) {
                    buffer.append(ARGUMENTS_DELIMITER);
                    i++; // proceed with the next character
                } else {
                    // this means the current argument is over
                    String currentArgumentContents = String.valueOf(buffer);
                    if (EMPTY_ARGUMENT.equals(currentArgumentContents)) {
                        currentArgumentContents = org.eclipse.jdt.internal.compiler.util.Util.EMPTY_STRING;
                    }
                    result[count++] = currentArgumentContents;
                    if (count > length) {
                        // too many elements - ill-formed
                        return null;
                    }
                    buffer.delete(0, buffer.length());
                }
                break;
            default:
                buffer.append(current);
            }
        }
        // process last argument
        String currentArgumentContents = String.valueOf(buffer);
        if (EMPTY_ARGUMENT.equals(currentArgumentContents)) {
            currentArgumentContents = org.eclipse.jdt.internal.compiler.util.Util.EMPTY_STRING;
        }
        result[count++] = currentArgumentContents;
        if (count > length) {
            // too many elements - ill-formed
            return null;
        }
        buffer.delete(0, buffer.length());
        return result;
    }

    /**
     * Returns the given file's contents as a byte array.
     */
    public static byte[] getResourceContentsAsByteArray(IFile file) throws JavaModelException {
        InputStream stream = null;
        try {
            stream = file.getContents(true);
        } catch (CoreException e) {
            throw new JavaModelException(e);
        }
        try {
            return org.eclipse.jdt.internal.compiler.util.Util.getInputStreamAsByteArray(stream, -1);
        } catch (IOException e) {
            throw new JavaModelException(e, IJavaModelStatusConstants.IO_EXCEPTION);
        } finally {
            try {
                stream.close();
            } catch (IOException e) {
                // ignore
            }
        }
    }

    /**
     * Returns the given file's contents as a character array.
     */
    public static char[] getResourceContentsAsCharArray(IFile file) throws JavaModelException {
        // Get encoding from file
        String encoding;
        try {
            encoding = file.getCharset();
        } catch (CoreException ce) {
            // do not use any encoding
            encoding = null;
        }
        return getResourceContentsAsCharArray(file, encoding);
    }

    public static char[] getResourceContentsAsCharArray(IFile file, String encoding) throws JavaModelException {
        // Get file length
        // workaround https://bugs.eclipse.org/bugs/show_bug.cgi?id=130736 by using java.io.File if possible
        IPath location = file.getLocation();
        long length;
        if (location == null) {
            // non local file
            try {
                URI locationURI = file.getLocationURI();
                if (locationURI == null)
                    throw new CoreException(new Status(IStatus.ERROR, JavaCore.PLUGIN_ID,
                            Messages.bind(Messages.file_notFound, file.getFullPath().toString())));
                length = EFS.getStore(locationURI).fetchInfo().getLength();
            } catch (CoreException e) {
                throw new JavaModelException(e, IJavaModelStatusConstants.ELEMENT_DOES_NOT_EXIST);
            }
        } else {
            // local file
            length = location.toFile().length();
        }

        // Get resource contents
        InputStream stream = null;
        try {
            stream = file.getContents(true);
        } catch (CoreException e) {
            throw new JavaModelException(e, IJavaModelStatusConstants.ELEMENT_DOES_NOT_EXIST);
        }
        try {
            return org.eclipse.jdt.internal.compiler.util.Util.getInputStreamAsCharArray(stream, (int) length,
                    encoding);
        } catch (IOException e) {
            throw new JavaModelException(e, IJavaModelStatusConstants.IO_EXCEPTION);
        } finally {
            try {
                stream.close();
            } catch (IOException e) {
                // ignore
            }
        }
    }

    /*
     * Returns the signature of the given type.
     */
    public static String getSignature(Type type) {
        StringBuffer buffer = new StringBuffer();
        getFullyQualifiedName(type, buffer);
        return Signature.createTypeSignature(buffer.toString(), false/*not resolved in source*/);
    }

    /*
     * Returns the source attachment property for this package fragment root's path
     */
    public static String getSourceAttachmentProperty(IPath path) throws JavaModelException {
        Map rootPathToAttachments = JavaModelManager.getJavaModelManager().rootPathToAttachments;
        String property = (String) rootPathToAttachments.get(path);
        if (property == null) {
            try {
                property = ResourcesPlugin.getWorkspace().getRoot()
                        .getPersistentProperty(getSourceAttachmentPropertyName(path));
                if (property == null) {
                    rootPathToAttachments.put(path, PackageFragmentRoot.NO_SOURCE_ATTACHMENT);
                    return null;
                }
                rootPathToAttachments.put(path, property);
                return property;
            } catch (CoreException e) {
                throw new JavaModelException(e);
            }
        } else if (property.equals(PackageFragmentRoot.NO_SOURCE_ATTACHMENT)) {
            return null;
        } else
            return property;
    }

    private static QualifiedName getSourceAttachmentPropertyName(IPath path) {
        return new QualifiedName(JavaCore.PLUGIN_ID, "sourceattachment: " + path.toOSString()); //$NON-NLS-1$
    }

    public static void setSourceAttachmentProperty(IPath path, String property) {
        if (property == null) {
            JavaModelManager.getJavaModelManager().rootPathToAttachments.put(path,
                    PackageFragmentRoot.NO_SOURCE_ATTACHMENT);
        } else {
            JavaModelManager.getJavaModelManager().rootPathToAttachments.put(path, property);
        }
        try {
            ResourcesPlugin.getWorkspace().getRoot().setPersistentProperty(getSourceAttachmentPropertyName(path),
                    property);
        } catch (CoreException e) {
            e.printStackTrace();
        }
    }

    /*
     * Returns the declaring type signature of the element represented by the given binding key.
     * Returns the signature of the element if it is a type.
     *
     * @return the declaring type signature
     */
    public static String getDeclaringTypeSignature(String key) {
        KeyToSignature keyToSignature = new KeyToSignature(key, KeyToSignature.DECLARING_TYPE);
        keyToSignature.parse();
        return keyToSignature.signature.toString();
    }

    /*
     * Appends to the given buffer the fully qualified name (as it appears in the source) of the given type
     */
    private static void getFullyQualifiedName(Type type, StringBuffer buffer) {
        switch (type.getNodeType()) {
        case ASTNode.ARRAY_TYPE:
            ArrayType arrayType = (ArrayType) type;
            getFullyQualifiedName(arrayType.getElementType(), buffer);
            for (int i = 0, length = arrayType.getDimensions(); i < length; i++) {
                buffer.append('[');
                buffer.append(']');
            }
            break;
        case ASTNode.PARAMETERIZED_TYPE:
            ParameterizedType parameterizedType = (ParameterizedType) type;
            getFullyQualifiedName(parameterizedType.getType(), buffer);
            buffer.append('<');
            Iterator iterator = parameterizedType.typeArguments().iterator();
            boolean isFirst = true;
            while (iterator.hasNext()) {
                if (!isFirst)
                    buffer.append(',');
                else
                    isFirst = false;
                Type typeArgument = (Type) iterator.next();
                getFullyQualifiedName(typeArgument, buffer);
            }
            buffer.append('>');
            break;
        case ASTNode.PRIMITIVE_TYPE:
            buffer.append(((PrimitiveType) type).getPrimitiveTypeCode().toString());
            break;
        case ASTNode.QUALIFIED_TYPE:
            buffer.append(((QualifiedType) type).getName().getFullyQualifiedName());
            break;
        case ASTNode.SIMPLE_TYPE:
            buffer.append(((SimpleType) type).getName().getFullyQualifiedName());
            break;
        case ASTNode.WILDCARD_TYPE:
            buffer.append('?');
            WildcardType wildcardType = (WildcardType) type;
            Type bound = wildcardType.getBound();
            if (bound == null)
                return;
            if (wildcardType.isUpperBound()) {
                buffer.append(" extends "); //$NON-NLS-1$
            } else {
                buffer.append(" super "); //$NON-NLS-1$
            }
            getFullyQualifiedName(bound, buffer);
            break;
        }
    }

    /**
     * Returns a trimmed version the simples names returned by Signature.
     */
    public static String[] getTrimmedSimpleNames(String name) {
        String[] result = Signature.getSimpleNames(name);
        for (int i = 0, length = result.length; i < length; i++) {
            result[i] = result[i].trim();
        }
        return result;
    }

    /**
     * Return the java element corresponding to the given compiler binding.
     */
    public static JavaElement getUnresolvedJavaElement(FieldBinding binding, WorkingCopyOwner workingCopyOwner,
            BindingsToNodesMap bindingsToNodes) {
        if (binding.declaringClass == null)
            return null; // array length
        JavaElement unresolvedJavaElement = getUnresolvedJavaElement(binding.declaringClass, workingCopyOwner,
                bindingsToNodes);
        if (unresolvedJavaElement == null || unresolvedJavaElement.getElementType() != IJavaElement.TYPE) {
            return null;
        }
        return (JavaElement) ((IType) unresolvedJavaElement).getField(String.valueOf(binding.name));
    }

    /**
     * Returns the IInitializer that contains the given local variable in the given type
     */
    public static JavaElement getUnresolvedJavaElement(int localSourceStart, int localSourceEnd, JavaElement type) {
        try {
            if (!(type instanceof IType))
                return null;
            IInitializer[] initializers = ((IType) type).getInitializers();
            for (int i = 0; i < initializers.length; i++) {
                IInitializer initializer = initializers[i];
                ISourceRange sourceRange = initializer.getSourceRange();
                if (sourceRange != null) {
                    int initializerStart = sourceRange.getOffset();
                    int initializerEnd = initializerStart + sourceRange.getLength();
                    if (initializerStart <= localSourceStart && localSourceEnd <= initializerEnd) {
                        return (JavaElement) initializer;
                    }
                }
            }
            return null;
        } catch (JavaModelException e) {
            return null;
        }
    }

    /**
     * Return the java element corresponding to the given compiler binding.
     */
    public static JavaElement getUnresolvedJavaElement(MethodBinding methodBinding,
            WorkingCopyOwner workingCopyOwner, BindingsToNodesMap bindingsToNodes) {
        JavaElement unresolvedJavaElement = getUnresolvedJavaElement(methodBinding.declaringClass, workingCopyOwner,
                bindingsToNodes);
        if (unresolvedJavaElement == null || unresolvedJavaElement.getElementType() != IJavaElement.TYPE) {
            return null;
        }
        IType declaringType = (IType) unresolvedJavaElement;

        org.eclipse.jdt.internal.compiler.ast.ASTNode node = bindingsToNodes == null ? null
                : bindingsToNodes.get(methodBinding);
        if (node != null && !declaringType.isBinary()) {
            if (node instanceof AnnotationMethodDeclaration) {
                // node is an AnnotationMethodDeclaration
                AnnotationMethodDeclaration typeMemberDeclaration = (AnnotationMethodDeclaration) node;
                return (JavaElement) declaringType.getMethod(String.valueOf(typeMemberDeclaration.selector),
                        CharOperation.NO_STRINGS); // annotation type members don't have parameters
            } else {
                // node is an MethodDeclaration
                MethodDeclaration methodDeclaration = (MethodDeclaration) node;

                Argument[] arguments = methodDeclaration.arguments;
                String[] parameterSignatures;
                if (arguments != null) {
                    parameterSignatures = new String[arguments.length];
                    for (int i = 0; i < arguments.length; i++) {
                        Argument argument = arguments[i];
                        TypeReference typeReference = argument.type;
                        int arrayDim = typeReference.dimensions();

                        String typeSig = Signature.createTypeSignature(
                                CharOperation.concatWith(typeReference.getTypeName(), '.'), false);
                        if (arrayDim > 0) {
                            typeSig = Signature.createArraySignature(typeSig, arrayDim);
                        }
                        parameterSignatures[i] = typeSig;

                    }
                } else {
                    parameterSignatures = CharOperation.NO_STRINGS;
                }
                return (JavaElement) declaringType.getMethod(String.valueOf(methodDeclaration.selector),
                        parameterSignatures);
            }
        } else {
            // case of method not in the created AST, or a binary method
            org.eclipse.jdt.internal.compiler.lookup.MethodBinding original = methodBinding.original();
            String selector = original.isConstructor() ? declaringType.getElementName()
                    : new String(original.selector);
            boolean isBinary = declaringType.isBinary();
            ReferenceBinding enclosingType = original.declaringClass.enclosingType();
            // Static inner types' constructors don't get receivers (https://bugs.eclipse.org/bugs/show_bug.cgi?id=388137)
            boolean isInnerBinaryTypeConstructor = isBinary && original.isConstructor()
                    && !original.declaringClass.isStatic() && enclosingType != null;
            TypeBinding[] parameters = original.parameters;
            int length = parameters == null ? 0 : parameters.length;
            int declaringIndex = isInnerBinaryTypeConstructor ? 1 : 0;
            String[] parameterSignatures = new String[declaringIndex + length];
            if (isInnerBinaryTypeConstructor)
                parameterSignatures[0] = new String(enclosingType.genericTypeSignature()).replace('/', '.');
            for (int i = 0; i < length; i++) {
                char[] signature = parameters[i].genericTypeSignature();
                if (isBinary) {
                    signature = CharOperation.replaceOnCopy(signature, '/', '.');
                } else {
                    signature = toUnresolvedTypeSignature(signature);
                }
                parameterSignatures[declaringIndex + i] = new String(signature);
            }
            IMethod result = declaringType.getMethod(selector, parameterSignatures);
            if (isBinary)
                return (JavaElement) result;
            if (result.exists()) // if perfect match (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=249567 )
                return (JavaElement) result;
            IMethod[] methods = null;
            try {
                methods = declaringType.getMethods();
            } catch (JavaModelException e) {
                // declaring type doesn't exist
                return null;
            }
            IMethod[] candidates = Member.findMethods(result, methods);
            if (candidates == null || candidates.length == 0)
                return null;
            return (JavaElement) candidates[0];
        }
    }

    /**
     * Return the java element corresponding to the given compiler binding.
     */
    public static JavaElement getUnresolvedJavaElement(TypeBinding typeBinding, WorkingCopyOwner workingCopyOwner,
            BindingsToNodesMap bindingsToNodes) {
        if (typeBinding == null)
            return null;
        switch (typeBinding.kind()) {
        case Binding.ARRAY_TYPE:
            typeBinding = ((org.eclipse.jdt.internal.compiler.lookup.ArrayBinding) typeBinding).leafComponentType();
            return getUnresolvedJavaElement(typeBinding, workingCopyOwner, bindingsToNodes);
        case Binding.BASE_TYPE:
        case Binding.WILDCARD_TYPE:
        case Binding.INTERSECTION_TYPE:
            return null;
        default:
            if (typeBinding.isCapture())
                return null;
        }
        ReferenceBinding referenceBinding;
        if (typeBinding.isParameterizedType() || typeBinding.isRawType())
            referenceBinding = (ReferenceBinding) typeBinding.erasure();
        else
            referenceBinding = (ReferenceBinding) typeBinding;
        char[] fileName = referenceBinding.getFileName();
        if (referenceBinding.isLocalType() || referenceBinding.isAnonymousType()) {
            // local or anonymous type
            if (org.eclipse.jdt.internal.compiler.util.Util.isClassFileName(fileName)) {
                int jarSeparator = CharOperation.indexOf(IDependent.JAR_FILE_ENTRY_SEPARATOR, fileName);
                int pkgEnd = CharOperation.lastIndexOf('/', fileName); // pkgEnd is exclusive
                if (pkgEnd == -1)
                    pkgEnd = CharOperation.lastIndexOf(File.separatorChar, fileName);
                if (jarSeparator != -1 && pkgEnd < jarSeparator) // if in a jar and no slash, it is a default package -> pkgEnd should be equal to jarSeparator
                    pkgEnd = jarSeparator;
                if (pkgEnd == -1)
                    return null;
                IPackageFragment pkg = getPackageFragment(fileName, pkgEnd, jarSeparator);
                char[] constantPoolName = referenceBinding.constantPoolName();
                if (constantPoolName == null) {
                    ClassFile classFile = (ClassFile) getClassFile(fileName);
                    return classFile == null ? null : (JavaElement) classFile.getType();
                }
                pkgEnd = CharOperation.lastIndexOf('/', constantPoolName);
                char[] classFileName = CharOperation.subarray(constantPoolName, pkgEnd + 1,
                        constantPoolName.length);
                ClassFile classFile = (ClassFile) pkg
                        .getClassFile(new String(classFileName) + SuffixConstants.SUFFIX_STRING_class);
                return (JavaElement) classFile.getType();
            }
            ICompilationUnit cu = getCompilationUnit(fileName, workingCopyOwner);
            if (cu == null)
                return null;
            // must use getElementAt(...) as there is no back pointer to the defining method (scope is null after resolution has ended)
            try {
                int sourceStart = ((org.eclipse.jdt.internal.compiler.lookup.LocalTypeBinding) referenceBinding).sourceStart;
                return (JavaElement) cu.getElementAt(sourceStart);
            } catch (JavaModelException e) {
                // does not exist
                return null;
            }
        } else if (referenceBinding.isTypeVariable()) {
            // type parameter
            final String typeVariableName = new String(referenceBinding.sourceName());
            org.eclipse.jdt.internal.compiler.lookup.Binding declaringElement = ((org.eclipse.jdt.internal.compiler.lookup.TypeVariableBinding) referenceBinding).declaringElement;
            if (declaringElement instanceof MethodBinding) {
                IMethod declaringMethod = (IMethod) getUnresolvedJavaElement((MethodBinding) declaringElement,
                        workingCopyOwner, bindingsToNodes);
                return (JavaElement) declaringMethod.getTypeParameter(typeVariableName);
            } else {
                IType declaringType = (IType) getUnresolvedJavaElement((TypeBinding) declaringElement,
                        workingCopyOwner, bindingsToNodes);
                if (declaringType == null)
                    return null;
                return (JavaElement) declaringType.getTypeParameter(typeVariableName);
            }
        } else {
            if (fileName == null)
                return null; // case of a WilCardBinding that doesn't have a corresponding Java element
            // member or top level type
            TypeBinding declaringTypeBinding = typeBinding.enclosingType();
            if (declaringTypeBinding == null) {
                // top level type
                if (org.eclipse.jdt.internal.compiler.util.Util.isClassFileName(fileName)) {
                    ClassFile classFile = (ClassFile) getClassFile(fileName);
                    if (classFile == null)
                        return null;
                    return (JavaElement) classFile.getType();
                }
                ICompilationUnit cu = getCompilationUnit(fileName, workingCopyOwner);
                if (cu == null)
                    return null;
                return (JavaElement) cu.getType(new String(referenceBinding.sourceName()));
            } else {
                // member type
                IType declaringType = (IType) getUnresolvedJavaElement(declaringTypeBinding, workingCopyOwner,
                        bindingsToNodes);
                if (declaringType == null)
                    return null;
                return (JavaElement) declaringType.getType(new String(referenceBinding.sourceName()));
            }
        }
    }

    /*
     * Returns the index of the most specific argument paths which is strictly enclosing the path to check
     */
    public static int indexOfEnclosingPath(IPath checkedPath, IPath[] paths, int pathCount) {

        int bestMatch = -1, bestLength = -1;
        for (int i = 0; i < pathCount; i++) {
            if (paths[i].equals(checkedPath))
                continue;
            if (paths[i].isPrefixOf(checkedPath)) {
                int currentLength = paths[i].segmentCount();
                if (currentLength > bestLength) {
                    bestLength = currentLength;
                    bestMatch = i;
                }
            }
        }
        return bestMatch;
    }

    /*
     * Returns the index of the Java like extension of the given file name
     * or -1 if it doesn't end with a known Java like extension.
     * Note this is the index of the '.' even if it is not considered part of the extension.
     */
    public static int indexOfJavaLikeExtension(String fileName) {
        int fileNameLength = fileName.length();
        char[][] javaLikeExtensions = getJavaLikeExtensions();
        extensions: for (int i = 0, length = javaLikeExtensions.length; i < length; i++) {
            char[] extension = javaLikeExtensions[i];
            int extensionLength = extension.length;
            int extensionStart = fileNameLength - extensionLength;
            int dotIndex = extensionStart - 1;
            if (dotIndex < 0)
                continue;
            if (fileName.charAt(dotIndex) != '.')
                continue;
            for (int j = 0; j < extensionLength; j++) {
                if (fileName.charAt(extensionStart + j) != extension[j])
                    continue extensions;
            }
            return dotIndex;
        }
        return -1;
    }

    /*
     * Returns the index of the first argument paths which is equal to the path to check
     */
    public static int indexOfMatchingPath(IPath checkedPath, IPath[] paths, int pathCount) {

        for (int i = 0; i < pathCount; i++) {
            if (paths[i].equals(checkedPath))
                return i;
        }
        return -1;
    }

    /*
     * Returns the index of the first argument paths which is strictly nested inside the path to check
     */
    public static int indexOfNestedPath(IPath checkedPath, IPath[] paths, int pathCount) {

        for (int i = 0; i < pathCount; i++) {
            if (checkedPath.equals(paths[i]))
                continue;
            if (checkedPath.isPrefixOf(paths[i]))
                return i;
        }
        return -1;
    }

    /**
     * Returns whether the local file system supports accessing and modifying
     * the given attribute.
     */
    protected static boolean isAttributeSupported(int attribute) {
        return (EFS.getLocalFileSystem().attributes() & attribute) != 0;
    }

    /**
     * Returns whether the given resource is read-only or not.
     * @param resource
     * @return <code>true</code> if the resource is read-only, <code>false</code> if it is not or
     *    if the file system does not support the read-only attribute.
     */
    public static boolean isReadOnly(IResource resource) {
        if (isReadOnlySupported()) {
            ResourceAttributes resourceAttributes = resource.getResourceAttributes();
            if (resourceAttributes == null)
                return false; // not supported on this platform for this resource
            return resourceAttributes.isReadOnly();
        }
        return false;
    }

    /**
     * Returns whether the local file system supports accessing and modifying
     * the read only flag.
     */
    public static boolean isReadOnlySupported() {
        return isAttributeSupported(EFS.ATTRIBUTE_READ_ONLY);
    }

    /*
     * Returns whether the given java element is exluded from its root's classpath.
     * It doesn't check whether the root itself is on the classpath or not
     */
    public static final boolean isExcluded(IJavaElement element) {
        int elementType = element.getElementType();
        switch (elementType) {
        case IJavaElement.JAVA_MODEL:
        case IJavaElement.JAVA_PROJECT:
        case IJavaElement.PACKAGE_FRAGMENT_ROOT:
            return false;

        case IJavaElement.PACKAGE_FRAGMENT:
            PackageFragmentRoot root = (PackageFragmentRoot) element
                    .getAncestor(IJavaElement.PACKAGE_FRAGMENT_ROOT);
            IResource resource = ((PackageFragment) element).resource();
            return resource != null
                    && isExcluded(resource, root.fullInclusionPatternChars(), root.fullExclusionPatternChars());

        case IJavaElement.COMPILATION_UNIT:
            root = (PackageFragmentRoot) element.getAncestor(IJavaElement.PACKAGE_FRAGMENT_ROOT);
            resource = element.getResource();
            if (resource == null)
                return false;
            if (isExcluded(resource, root.fullInclusionPatternChars(), root.fullExclusionPatternChars()))
                return true;
            return isExcluded(element.getParent());

        default:
            IJavaElement cu = element.getAncestor(IJavaElement.COMPILATION_UNIT);
            return cu != null && isExcluded(cu);
        }
    }

    /*
     * Returns whether the given resource path matches one of the inclusion/exclusion
     * patterns.
     * NOTE: should not be asked directly using pkg root pathes
     * @see IClasspathEntry#getInclusionPatterns
     * @see IClasspathEntry#getExclusionPatterns
     */
    public final static boolean isExcluded(IPath resourcePath, char[][] inclusionPatterns,
            char[][] exclusionPatterns, boolean isFolderPath) {
        if (inclusionPatterns == null && exclusionPatterns == null)
            return false;
        return org.eclipse.jdt.internal.compiler.util.Util.isExcluded(resourcePath.toString().toCharArray(),
                inclusionPatterns, exclusionPatterns, isFolderPath);
    }

    /*
     * Returns whether the given resource matches one of the exclusion patterns.
     * NOTE: should not be asked directly using pkg root pathes
     * @see IClasspathEntry#getExclusionPatterns
     */
    public final static boolean isExcluded(IResource resource, char[][] inclusionPatterns,
            char[][] exclusionPatterns) {
        IPath path = resource.getFullPath();
        // ensure that folders are only excluded if all of their children are excluded
        int resourceType = resource.getType();
        return isExcluded(path, inclusionPatterns, exclusionPatterns,
                resourceType == IResource.FOLDER || resourceType == IResource.PROJECT);
    }

    /**
     * Validate the given .class file name.
     * A .class file name must obey the following rules:
     * <ul>
     * <li> it must not be null
     * <li> it must include the <code>".class"</code> suffix
     * <li> its prefix must be a valid identifier
     * </ul>
     * </p>
     * @param name the name of a .class file
     * @param sourceLevel the source level
     * @param complianceLevel the compliance level
     * @return a status object with code <code>IStatus.OK</code> if
     *      the given name is valid as a .class file name, otherwise a status
     *      object indicating what is wrong with the name
     */
    public static boolean isValidClassFileName(String name, String sourceLevel, String complianceLevel) {
        return JavaConventions.validateClassFileName(name, sourceLevel, complianceLevel)
                .getSeverity() != IStatus.ERROR;
    }

    /**
     * Validate the given compilation unit name.
     * A compilation unit name must obey the following rules:
     * <ul>
     * <li> it must not be null
     * <li> it must include the <code>".java"</code> suffix
     * <li> its prefix must be a valid identifier
     * </ul>
     * </p>
     * @param name the name of a compilation unit
     * @param sourceLevel the source level
     * @param complianceLevel the compliance level
     * @return a status object with code <code>IStatus.OK</code> if
     *      the given name is valid as a compilation unit name, otherwise a status
     *      object indicating what is wrong with the name
     */
    public static boolean isValidCompilationUnitName(String name, String sourceLevel, String complianceLevel) {
        return JavaConventions.validateCompilationUnitName(name, sourceLevel, complianceLevel)
                .getSeverity() != IStatus.ERROR;
    }

    /**
     * Returns true if the given folder name is valid for a package,
     * false if it is not.
     * @param folderName the name of the folder
     * @param sourceLevel the source level
     * @param complianceLevel the compliance level
     */
    public static boolean isValidFolderNameForPackage(String folderName, String sourceLevel,
            String complianceLevel) {
        return JavaConventions.validateIdentifier(folderName, sourceLevel, complianceLevel)
                .getSeverity() != IStatus.ERROR;
    }

    /**
     * Returns true if the given method signature is valid,
     * false if it is not.
     */
    public static boolean isValidMethodSignature(String sig) {
        int len = sig.length();
        if (len == 0)
            return false;
        int i = 0;
        char c = sig.charAt(i++);
        if (c != '(')
            return false;
        if (i >= len)
            return false;
        while (sig.charAt(i) != ')') {
            // Void is not allowed as a parameter type.
            i = checkTypeSignature(sig, i, len, false);
            if (i == -1)
                return false;
            if (i >= len)
                return false;
        }
        ++i;
        i = checkTypeSignature(sig, i, len, true);
        return i == len;
    }

    /**
     * Returns true if the given type signature is valid,
     * false if it is not.
     */
    public static boolean isValidTypeSignature(String sig, boolean allowVoid) {
        int len = sig.length();
        return checkTypeSignature(sig, 0, len, allowVoid) == len;
    }

    /*
     * Returns the simple name of a local type from the given binary type name.
     * The last '$' is at lastDollar. The last character of the type name is at end-1.
     */
    public static String localTypeName(String binaryTypeName, int lastDollar, int end) {
        if (lastDollar > 0 && binaryTypeName.charAt(lastDollar - 1) == '$')
            // local name starts with a dollar sign
            // (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=103466)
            return binaryTypeName;
        int nameStart = lastDollar + 1;
        while (nameStart < end && Character.isDigit(binaryTypeName.charAt(nameStart)))
            nameStart++;
        return binaryTypeName.substring(nameStart, end);
    }

    /*
     * Add a log entry
     */
    public static void log(Throwable e, String message) {
        Throwable nestedException;
        if (e instanceof JavaModelException
                && (nestedException = ((JavaModelException) e).getException()) != null) {
            e = nestedException;
        }
        log(new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, IStatus.ERROR, message, e));
    }

    /**
     * Log a message that is potentially repeated in the same session.
     * The first time this method is called with a given exception, the
     * exception stack trace is written to the log.
     * <p>Only intended for use in debug statements.</p>
     *
     * @param key the given key
     * @param e the given exception
     * @throws IllegalArgumentException if the given key is null
     */
    public static void logRepeatedMessage(String key, Exception e) {
        if (key == null) {
            throw new IllegalArgumentException("key cannot be null"); //$NON-NLS-1$
        }
        if (fgRepeatedMessages.contains(key)) {
            return;
        }
        fgRepeatedMessages.add(key);
        log(e);
    }

    public static void logRepeatedMessage(String key, int statusErrorID, String message) {
        if (key == null) {
            throw new IllegalArgumentException("key cannot be null"); //$NON-NLS-1$
        }
        if (fgRepeatedMessages.contains(key)) {
            return;
        }
        fgRepeatedMessages.add(key);
        log(statusErrorID, message);
    }

    /*
     * Add a log entry
     */
    public static void log(int statusErrorID, String message) {
        log(new Status(statusErrorID, JavaCore.PLUGIN_ID, message));
    }

    /*
     * Add a log entry
     */
    public static void log(IStatus status) {
        Plugin plugin = JavaCore.getPlugin();
        if (plugin == null) {
            System.err.println(status.toString());
        } else {
            plugin.getLog().log(status);
        }
    }

    public static void log(Throwable e) {
        log(new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, Messages.internal_error, e));
    }

    public static ClassFileReader newClassFileReader(IResource resource)
            throws CoreException, ClassFormatException, IOException {
        InputStream in = null;
        try {
            in = ((IFile) resource).getContents(true);
            return ClassFileReader.read(in, resource.getFullPath().toString());
        } finally {
            if (in != null)
                in.close();
        }
    }

    /**
     * Normalizes the cariage returns in the given text.
     * They are all changed  to use the given buffer's line separator.
     */
    public static char[] normalizeCRs(char[] text, char[] buffer) {
        CharArrayBuffer result = new CharArrayBuffer();
        int lineStart = 0;
        int length = text.length;
        if (length == 0)
            return text;
        String lineSeparator = getLineSeparator(text, buffer);
        char nextChar = text[0];
        for (int i = 0; i < length; i++) {
            char currentChar = nextChar;
            nextChar = i < length - 1 ? text[i + 1] : ' ';
            switch (currentChar) {
            case '\n':
                int lineLength = i - lineStart;
                char[] line = new char[lineLength];
                System.arraycopy(text, lineStart, line, 0, lineLength);
                result.append(line);
                result.append(lineSeparator);
                lineStart = i + 1;
                break;
            case '\r':
                lineLength = i - lineStart;
                if (lineLength >= 0) {
                    line = new char[lineLength];
                    System.arraycopy(text, lineStart, line, 0, lineLength);
                    result.append(line);
                    result.append(lineSeparator);
                    if (nextChar == '\n') {
                        nextChar = ' ';
                        lineStart = i + 2;
                    } else {
                        // when line separator are mixed in the same file
                        // \r might not be followed by a \n. If not, we should increment
                        // lineStart by one and not by two.
                        lineStart = i + 1;
                    }
                } else {
                    // when line separator are mixed in the same file
                    // we need to prevent NegativeArraySizeException
                    lineStart = i + 1;
                }
                break;
            }
        }
        char[] lastLine;
        if (lineStart > 0) {
            int lastLineLength = length - lineStart;
            if (lastLineLength > 0) {
                lastLine = new char[lastLineLength];
                System.arraycopy(text, lineStart, lastLine, 0, lastLineLength);
                result.append(lastLine);
            }
            return result.getContents();
        }
        return text;
    }

    /**
     * Normalizes the carriage returns in the given text.
     * They are all changed to use given buffer's line separator.
     */
    public static String normalizeCRs(String text, String buffer) {
        return new String(normalizeCRs(text.toCharArray(), buffer.toCharArray()));
    }

    /**
     * Converts the given relative path into a package name.
     * Returns null if the path is not a valid package name.
     * @param pkgPath the package path
     * @param sourceLevel the source level
     * @param complianceLevel the compliance level
     */
    public static String packageName(IPath pkgPath, String sourceLevel, String complianceLevel) {
        StringBuffer pkgName = new StringBuffer(IPackageFragment.DEFAULT_PACKAGE_NAME);
        for (int j = 0, max = pkgPath.segmentCount(); j < max; j++) {
            String segment = pkgPath.segment(j);
            if (!isValidFolderNameForPackage(segment, sourceLevel, complianceLevel)) {
                return null;
            }
            pkgName.append(segment);
            if (j < pkgPath.segmentCount() - 1) {
                pkgName.append("."); //$NON-NLS-1$
            }
        }
        return pkgName.toString();
    }

    /**
     * Returns the length of the common prefix between s1 and s2.
     */
    public static int prefixLength(char[] s1, char[] s2) {
        int len = 0;
        int max = Math.min(s1.length, s2.length);
        for (int i = 0; i < max && s1[i] == s2[i]; ++i)
            ++len;
        return len;
    }

    /**
     * Returns the length of the common prefix between s1 and s2.
     */
    public static int prefixLength(String s1, String s2) {
        int len = 0;
        int max = Math.min(s1.length(), s2.length());
        for (int i = 0; i < max && s1.charAt(i) == s2.charAt(i); ++i)
            ++len;
        return len;
    }

    private static void quickSort(char[][] list, int left, int right) {
        int original_left = left;
        int original_right = right;
        char[] mid = list[left + (right - left) / 2];
        do {
            while (compare(list[left], mid) < 0) {
                left++;
            }
            while (compare(mid, list[right]) < 0) {
                right--;
            }
            if (left <= right) {
                char[] tmp = list[left];
                list[left] = list[right];
                list[right] = tmp;
                left++;
                right--;
            }
        } while (left <= right);
        if (original_left < right) {
            quickSort(list, original_left, right);
        }
        if (left < original_right) {
            quickSort(list, left, original_right);
        }
    }

    /**
     * Sort the comparable objects in the given collection.
     */
    private static void quickSort(Comparable[] sortedCollection, int left, int right) {
        int original_left = left;
        int original_right = right;
        Comparable mid = sortedCollection[left + (right - left) / 2];
        do {
            while (sortedCollection[left].compareTo(mid) < 0) {
                left++;
            }
            while (mid.compareTo(sortedCollection[right]) < 0) {
                right--;
            }
            if (left <= right) {
                Comparable tmp = sortedCollection[left];
                sortedCollection[left] = sortedCollection[right];
                sortedCollection[right] = tmp;
                left++;
                right--;
            }
        } while (left <= right);
        if (original_left < right) {
            quickSort(sortedCollection, original_left, right);
        }
        if (left < original_right) {
            quickSort(sortedCollection, left, original_right);
        }
    }

    private static void quickSort(int[] list, int left, int right) {
        int original_left = left;
        int original_right = right;
        int mid = list[left + (right - left) / 2];
        do {
            while (list[left] < mid) {
                left++;
            }
            while (mid < list[right]) {
                right--;
            }
            if (left <= right) {
                int tmp = list[left];
                list[left] = list[right];
                list[right] = tmp;
                left++;
                right--;
            }
        } while (left <= right);
        if (original_left < right) {
            quickSort(list, original_left, right);
        }
        if (left < original_right) {
            quickSort(list, left, original_right);
        }
    }

    /**
     * Sort the objects in the given collection using the given comparer.
     */
    private static void quickSort(Object[] sortedCollection, int left, int right, Comparer comparer) {
        int original_left = left;
        int original_right = right;
        Object mid = sortedCollection[left + (right - left) / 2];
        do {
            while (comparer.compare(sortedCollection[left], mid) < 0) {
                left++;
            }
            while (comparer.compare(mid, sortedCollection[right]) < 0) {
                right--;
            }
            if (left <= right) {
                Object tmp = sortedCollection[left];
                sortedCollection[left] = sortedCollection[right];
                sortedCollection[right] = tmp;
                left++;
                right--;
            }
        } while (left <= right);
        if (original_left < right) {
            quickSort(sortedCollection, original_left, right, comparer);
        }
        if (left < original_right) {
            quickSort(sortedCollection, left, original_right, comparer);
        }
    }

    /**
     * Sort the strings in the given collection.
     */
    private static void quickSort(String[] sortedCollection, int left, int right) {
        int original_left = left;
        int original_right = right;
        String mid = sortedCollection[left + (right - left) / 2];
        do {
            while (sortedCollection[left].compareTo(mid) < 0) {
                left++;
            }
            while (mid.compareTo(sortedCollection[right]) < 0) {
                right--;
            }
            if (left <= right) {
                String tmp = sortedCollection[left];
                sortedCollection[left] = sortedCollection[right];
                sortedCollection[right] = tmp;
                left++;
                right--;
            }
        } while (left <= right);
        if (original_left < right) {
            quickSort(sortedCollection, original_left, right);
        }
        if (left < original_right) {
            quickSort(sortedCollection, left, original_right);
        }
    }

    /**
     * Returns the toString() of the given full path minus the first given number of segments.
     * The returned string is always a relative path (it has no leading slash)
     */
    public static String relativePath(IPath fullPath, int skipSegmentCount) {
        boolean hasTrailingSeparator = fullPath.hasTrailingSeparator();
        String[] segments = fullPath.segments();

        // compute length
        int length = 0;
        int max = segments.length;
        if (max > skipSegmentCount) {
            for (int i1 = skipSegmentCount; i1 < max; i1++) {
                length += segments[i1].length();
            }
            //add the separator lengths
            length += max - skipSegmentCount - 1;
        }
        if (hasTrailingSeparator)
            length++;

        char[] result = new char[length];
        int offset = 0;
        int len = segments.length - 1;
        if (len >= skipSegmentCount) {
            //append all but the last segment, with separators
            for (int i = skipSegmentCount; i < len; i++) {
                int size = segments[i].length();
                segments[i].getChars(0, size, result, offset);
                offset += size;
                result[offset++] = '/';
            }
            //append the last segment
            int size = segments[len].length();
            segments[len].getChars(0, size, result, offset);
            offset += size;
        }
        if (hasTrailingSeparator)
            result[offset++] = '/';
        return new String(result);
    }

    /*
     * Resets the list of Java-like extensions after a change in content-type.
     */
    public static void resetJavaLikeExtensions() {
        JAVA_LIKE_EXTENSIONS = null;
    }

    /**
     * Scans the given string for a type signature starting at the given index
     * and returns the index of the last character.
     * <pre>
     * TypeSignature:
     *  |  BaseTypeSignature
     *  |  ArrayTypeSignature
     *  |  ClassTypeSignature
     *  |  TypeVariableSignature
     * </pre>
     *
     * @param string the signature string
     * @param start the 0-based character index of the first character
     * @return the 0-based character index of the last character
     * @exception IllegalArgumentException if this is not a type signature
     */
    public static int scanTypeSignature(char[] string, int start) {
        // this method is used in jdt.debug
        return org.eclipse.jdt.internal.compiler.util.Util.scanTypeSignature(string, start);
    }

    /**
     * Return a new array which is the split of the given string using the given divider. The given end
     * is exclusive and the given start is inclusive.
     * <br>
     * <br>
     * For example:
     * <ol>
     * <li><pre>
     *    divider = 'b'
     *    string = "abbaba"
     *    start = 2
     *    end = 5
     *    result => { "", "a", "" }
     * </pre>
     * </li>
     * </ol>
     *
     * @param divider the given divider
     * @param string the given string
     * @param start the given starting index
     * @param end the given ending index
     * @return a new array which is the split of the given string using the given divider
     * @throws ArrayIndexOutOfBoundsException if start is lower than 0 or end is greater than the array length
     */
    public static final String[] splitOn(char divider, String string, int start, int end) {
        int length = string == null ? 0 : string.length();
        if (length == 0 || start > end)
            return CharOperation.NO_STRINGS;

        int wordCount = 1;
        for (int i = start; i < end; i++)
            if (string.charAt(i) == divider)
                wordCount++;
        String[] split = new String[wordCount];
        int last = start, currentWord = 0;
        for (int i = start; i < end; i++) {
            if (string.charAt(i) == divider) {
                split[currentWord++] = string.substring(last, i);
                last = i + 1;
            }
        }
        split[currentWord] = string.substring(last, end);
        return split;
    }

    /**
     * Sets or unsets the given resource as read-only in the file system.
     * It's a no-op if the file system does not support the read-only attribute.
     *
     * @param resource The resource to set as read-only
     * @param readOnly <code>true</code> to set it to read-only,
     *      <code>false</code> to unset
     */
    public static void setReadOnly(IResource resource, boolean readOnly) {
        if (isReadOnlySupported()) {
            ResourceAttributes resourceAttributes = resource.getResourceAttributes();
            if (resourceAttributes == null)
                return; // not supported on this platform for this resource
            resourceAttributes.setReadOnly(readOnly);
            try {
                resource.setResourceAttributes(resourceAttributes);
            } catch (CoreException e) {
                // ignore
            }
        }
    }

    public static void sort(char[][] list) {
        if (list.length > 1)
            quickSort(list, 0, list.length - 1);
    }

    /**
     * Sorts an array of Comparable objects in place.
     */
    public static void sort(Comparable[] objects) {
        if (objects.length > 1)
            quickSort(objects, 0, objects.length - 1);
    }

    public static void sort(int[] list) {
        if (list.length > 1)
            quickSort(list, 0, list.length - 1);
    }

    /**
     * Sorts an array of objects in place.
     * The given comparer compares pairs of items.
     */
    public static void sort(Object[] objects, Comparer comparer) {
        if (objects.length > 1)
            quickSort(objects, 0, objects.length - 1, comparer);
    }

    /**
     * Sorts an array of strings in place using quicksort.
     */
    public static void sort(String[] strings) {
        if (strings.length > 1)
            quickSort(strings, 0, strings.length - 1);
    }

    /**
     * Sorts an array of Comparable objects, returning a new array
     * with the sorted items.  The original array is left untouched.
     */
    public static Comparable[] sortCopy(Comparable[] objects) {
        int len = objects.length;
        Comparable[] copy = new Comparable[len];
        System.arraycopy(objects, 0, copy, 0, len);
        sort(copy);
        return copy;
    }

    /**
     * Sorts an array of Java elements based on their toStringWithAncestors(),
     * returning a new array with the sorted items.
     * The original array is left untouched.
     */
    public static IJavaElement[] sortCopy(IJavaElement[] elements) {
        int len = elements.length;
        IJavaElement[] copy = new IJavaElement[len];
        System.arraycopy(elements, 0, copy, 0, len);
        sort(copy, new Comparer() {
            @Override
            public int compare(Object a, Object b) {
                return ((JavaElement) a).toStringWithAncestors()
                        .compareTo(((JavaElement) b).toStringWithAncestors());
            }
        });
        return copy;
    }

    /**
     * Sorts an array of Strings, returning a new array
     * with the sorted items.  The original array is left untouched.
     */
    public static Object[] sortCopy(Object[] objects, Comparer comparer) {
        int len = objects.length;
        Object[] copy = new Object[len];
        System.arraycopy(objects, 0, copy, 0, len);
        sort(copy, comparer);
        return copy;
    }

    /**
     * Sorts an array of Strings, returning a new array
     * with the sorted items.  The original array is left untouched.
     */
    public static String[] sortCopy(String[] objects) {
        int len = objects.length;
        String[] copy = new String[len];
        System.arraycopy(objects, 0, copy, 0, len);
        sort(copy);
        return copy;
    }

    /*
     * Returns whether the given compound name starts with the given prefix.
     * Returns true if the n first elements of the prefix are equals and the last element of the
     * prefix is a prefix of the corresponding element in the compound name.
     */
    public static boolean startsWithIgnoreCase(String[] compoundName, String[] prefix, boolean partialMatch) {
        int prefixLength = prefix.length;
        int nameLength = compoundName.length;
        if (prefixLength > nameLength)
            return false;
        for (int i = 0; i < prefixLength - 1; i++) {
            if (!compoundName[i].equalsIgnoreCase(prefix[i]))
                return false;
        }
        return (partialMatch || prefixLength == nameLength)
                && compoundName[prefixLength - 1].toLowerCase().startsWith(prefix[prefixLength - 1].toLowerCase());
    }

    /**
     * Converts a String[] to char[][].
     */
    public static char[][] toCharArrays(String[] a) {
        int len = a.length;
        if (len == 0)
            return CharOperation.NO_CHAR_CHAR;
        char[][] result = new char[len][];
        for (int i = 0; i < len; ++i) {
            result[i] = a[i].toCharArray();
        }
        return result;
    }

    /**
     * Converts a String to char[][], where segments are separate by '.'.
     */
    public static char[][] toCompoundChars(String s) {
        int len = s.length();
        if (len == 0) {
            return CharOperation.NO_CHAR_CHAR;
        }
        int segCount = 1;
        for (int off = s.indexOf('.'); off != -1; off = s.indexOf('.', off + 1)) {
            ++segCount;
        }
        char[][] segs = new char[segCount][];
        int start = 0;
        for (int i = 0; i < segCount; ++i) {
            int dot = s.indexOf('.', start);
            int end = (dot == -1 ? s.length() : dot);
            segs[i] = new char[end - start];
            s.getChars(start, end, segs[i], 0);
            start = end + 1;
        }
        return segs;
    }

    /*
     * Converts the given URI to a local file. Use the existing file if the uri is on the local file system.
     * Otherwise fetch it.
     * Returns null if unable to fetch it.
     */
    public static File toLocalFile(URI uri, IProgressMonitor monitor) throws CoreException {
        IFileStore fileStore = EFS.getStore(uri);
        File localFile = fileStore.toLocalFile(EFS.NONE, monitor);
        if (localFile == null)
            // non local file system
            localFile = fileStore.toLocalFile(EFS.CACHE, monitor);
        return localFile;
    }

    /**
     * Converts a char[][] to String, where segments are separated by '.'.
     */
    public static String toString(char[][] c) {
        StringBuffer sb = new StringBuffer();
        for (int i = 0, max = c.length; i < max; ++i) {
            if (i != 0)
                sb.append('.');
            sb.append(c[i]);
        }
        return sb.toString();
    }

    /**
     * Converts a char[][] and a char[] to String, where segments are separated by '.'.
     */
    public static String toString(char[][] c, char[] d) {
        if (c == null)
            return new String(d);
        StringBuffer sb = new StringBuffer();
        for (int i = 0, max = c.length; i < max; ++i) {
            sb.append(c[i]);
            sb.append('.');
        }
        sb.append(d);
        return sb.toString();
    }

    /*
     * Converts a char[][] to String[].
     */
    public static String[] toStrings(char[][] a) {
        int len = a.length;
        String[] result = new String[len];
        for (int i = 0; i < len; ++i) {
            result[i] = new String(a[i]);
        }
        return result;
    }

    private static char[] toUnresolvedTypeSignature(char[] signature) {
        int length = signature.length;
        if (length <= 1)
            return signature;
        StringBuffer buffer = new StringBuffer(length);
        toUnresolvedTypeSignature(signature, 0, length, buffer);
        int bufferLength = buffer.length();
        char[] result = new char[bufferLength];
        buffer.getChars(0, bufferLength, result, 0);
        return result;
    }

    private static int toUnresolvedTypeSignature(char[] signature, int start, int length, StringBuffer buffer) {
        if (signature[start] == Signature.C_RESOLVED)
            buffer.append(Signature.C_UNRESOLVED);
        else
            buffer.append(signature[start]);
        for (int i = start + 1; i < length; i++) {
            char c = signature[i];
            switch (c) {
            case '/':
            case Signature.C_DOLLAR:
                buffer.append(Signature.C_DOT);
                break;
            case Signature.C_GENERIC_START:
                buffer.append(Signature.C_GENERIC_START);
                i = toUnresolvedTypeSignature(signature, i + 1, length, buffer);
                break;
            case Signature.C_GENERIC_END:
                buffer.append(Signature.C_GENERIC_END);
                return i;
            default:
                buffer.append(c);
                break;
            }
        }
        return length;
    }

    private static void appendArrayTypeSignature(char[] string, int start, StringBuffer buffer, boolean compact) {
        int length = string.length;
        // need a minimum 2 char
        if (start >= length - 1) {
            throw newIllegalArgumentException(string, start);
        }
        char c = string[start];
        if (c != Signature.C_ARRAY) {
            throw newUnexpectedCharacterException(string, start, c);
        }

        int index = start;
        c = string[++index];
        while (c == Signature.C_ARRAY) {
            // need a minimum 2 char
            if (index >= length - 1) {
                throw newIllegalArgumentException(string, start);
            }
            c = string[++index];
        }

        appendTypeSignature(string, index, buffer, compact);

        for (int i = 0, dims = index - start; i < dims; i++) {
            buffer.append('[').append(']');
        }
    }

    private static void appendClassTypeSignature(char[] string, int start, StringBuffer buffer, boolean compact) {
        char c = string[start];
        if (c != Signature.C_RESOLVED) {
            return;
        }
        int p = start + 1;
        int checkpoint = buffer.length();
        while (true) {
            c = string[p];
            switch (c) {
            case Signature.C_SEMICOLON:
                // all done
                return;
            case Signature.C_DOT:
            case '/':
                // erase package prefix
                if (compact) {
                    buffer.setLength(checkpoint);
                } else {
                    buffer.append('.');
                }
                break;
            case Signature.C_DOLLAR:
                /**
                 * Convert '$' in resolved type signatures into '.'.
                 * NOTE: This assumes that the type signature is an inner type
                 * signature. This is true in most cases, but someone can define a
                 * non-inner type name containing a '$'.
                 */
                buffer.append('.');
                break;
            default:
                buffer.append(c);
            }
            p++;
        }
    }

    static void appendTypeSignature(char[] string, int start, StringBuffer buffer, boolean compact) {
        char c = string[start];
        switch (c) {
        case Signature.C_ARRAY:
            appendArrayTypeSignature(string, start, buffer, compact);
            break;
        case Signature.C_RESOLVED:
            appendClassTypeSignature(string, start, buffer, compact);
            break;
        case Signature.C_TYPE_VARIABLE:
            int e = org.eclipse.jdt.internal.compiler.util.Util.scanTypeVariableSignature(string, start);
            buffer.append(string, start + 1, e - start - 1);
            break;
        case Signature.C_BOOLEAN:
            buffer.append(BOOLEAN);
            break;
        case Signature.C_BYTE:
            buffer.append(BYTE);
            break;
        case Signature.C_CHAR:
            buffer.append(CHAR);
            break;
        case Signature.C_DOUBLE:
            buffer.append(DOUBLE);
            break;
        case Signature.C_FLOAT:
            buffer.append(FLOAT);
            break;
        case Signature.C_INT:
            buffer.append(INT);
            break;
        case Signature.C_LONG:
            buffer.append(LONG);
            break;
        case Signature.C_SHORT:
            buffer.append(SHORT);
            break;
        case Signature.C_VOID:
            buffer.append(VOID);
            break;
        }
    }

    public static String toString(char[] declaringClass, char[] methodName, char[] methodSignature,
            boolean includeReturnType, boolean compact) {
        final boolean isConstructor = CharOperation.equals(methodName, INIT);
        int firstParen = CharOperation.indexOf(Signature.C_PARAM_START, methodSignature);
        if (firstParen == -1) {
            return ""; //$NON-NLS-1$
        }

        StringBuffer buffer = new StringBuffer(methodSignature.length + 10);

        // decode declaring class name
        // it can be either an array signature or a type signature
        if (declaringClass != null && declaringClass.length > 0) {
            char[] declaringClassSignature = null;
            if (declaringClass[0] == Signature.C_ARRAY) {
                CharOperation.replace(declaringClass, '/', '.');
                declaringClassSignature = Signature.toCharArray(declaringClass);
            } else {
                CharOperation.replace(declaringClass, '/', '.');
                declaringClassSignature = declaringClass;
            }
            int lastIndexOfSlash = CharOperation.lastIndexOf('.', declaringClassSignature);
            if (compact && lastIndexOfSlash != -1) {
                buffer.append(declaringClassSignature, lastIndexOfSlash + 1,
                        declaringClassSignature.length - lastIndexOfSlash - 1);
            } else {
                buffer.append(declaringClassSignature);
            }
            if (!isConstructor) {
                buffer.append('.');
            }
        }

        // selector
        if (!isConstructor && methodName != null) {
            buffer.append(methodName);
        }

        // parameters
        buffer.append('(');
        char[][] pts = Signature.getParameterTypes(methodSignature);
        for (int i = 0, max = pts.length; i < max; i++) {
            appendTypeSignature(pts[i], 0, buffer, compact);
            if (i != pts.length - 1) {
                buffer.append(',');
                buffer.append(' ');
            }
        }
        buffer.append(')');

        if (!isConstructor) {
            buffer.append(" : "); //$NON-NLS-1$
            // return type
            if (includeReturnType) {
                char[] rts = Signature.getReturnType(methodSignature);
                appendTypeSignature(rts, 0, buffer, compact);
            }
        }
        return String.valueOf(buffer);
    }

    /*
     * Returns the unresolved type parameter signatures of the given method
     * e.g. {"QString;", "[int", "[[Qjava.util.Vector;"}
     */
    public static String[] typeParameterSignatures(AbstractMethodDeclaration method) {
        Argument[] args = method.arguments;
        if (args != null) {
            int length = args.length;
            String[] signatures = new String[length];
            for (int i = 0; i < args.length; i++) {
                Argument arg = args[i];
                signatures[i] = typeSignature(arg.type);
            }
            return signatures;
        }
        return CharOperation.NO_STRINGS;
    }

    /*
     * Returns the unresolved type signature of the given type reference,
     * e.g. "QString;", "[int", "[[Qjava.util.Vector;"
     */
    public static String typeSignature(TypeReference type) {
        String signature = null;
        if ((type.bits & org.eclipse.jdt.internal.compiler.ast.ASTNode.IsUnionType) != 0) {
            // special treatment for union type reference
            UnionTypeReference unionTypeReference = (UnionTypeReference) type;
            TypeReference[] typeReferences = unionTypeReference.typeReferences;
            String[] typeSignatures = typeSignatures(typeReferences);
            signature = Signature.createIntersectionTypeSignature(typeSignatures);
        } else if (type instanceof IntersectionCastTypeReference) {
            IntersectionCastTypeReference intersection = (IntersectionCastTypeReference) type;
            TypeReference[] typeReferences = intersection.typeReferences;
            String[] typeSignatures = typeSignatures(typeReferences);
            signature = Signature.createUnionTypeSignature(typeSignatures);
        } else {
            char[][] compoundName = type.getParameterizedTypeName();
            char[] typeName = CharOperation.concatWith(compoundName, '.');
            signature = Signature.createTypeSignature(typeName, false/*don't resolve*/);
        }
        return signature;
    }

    private static String[] typeSignatures(TypeReference[] types) {
        int length = types.length;
        String[] typeSignatures = new String[length];
        for (int i = 0; i < length; i++) {
            char[][] compoundName = types[i].getParameterizedTypeName();
            char[] typeName = CharOperation.concatWith(compoundName, '.');
            typeSignatures[i] = Signature.createTypeSignature(typeName, false/*don't resolve*/);
        }
        return typeSignatures;
    }

    /**
     * Asserts that the given method signature is valid.
     */
    public static void validateMethodSignature(String sig) {
        Assert.isTrue(isValidMethodSignature(sig));
    }

    /**
     * Asserts that the given type signature is valid.
     */
    public static void validateTypeSignature(String sig, boolean allowVoid) {
        Assert.isTrue(isValidTypeSignature(sig, allowVoid));
    }

    public static void verbose(String log) {
        verbose(log, System.out);
    }

    public static synchronized void verbose(String log, PrintStream printStream) {
        int start = 0;
        do {
            int end = log.indexOf('\n', start);
            printStream.print(Thread.currentThread());
            printStream.print(" "); //$NON-NLS-1$
            printStream.print(log.substring(start, end == -1 ? log.length() : end + 1));
            start = end + 1;
        } while (start != 0);
        printStream.println();
    }

    /**
     * Returns true if the given name ends with one of the known java like extension.
     * (implementation is not creating extra strings)
     */
    public final static boolean isJavaLikeFileName(String name) {
        if (name == null)
            return false;
        return indexOfJavaLikeExtension(name) != -1;
    }

    /**
     * Returns true if the given name ends with one of the known java like extension.
     * (implementation is not creating extra strings)
     */
    public final static boolean isJavaLikeFileName(char[] fileName) {
        if (fileName == null)
            return false;
        int fileNameLength = fileName.length;
        char[][] javaLikeExtensions = getJavaLikeExtensions();
        extensions: for (int i = 0, length = javaLikeExtensions.length; i < length; i++) {
            char[] extension = javaLikeExtensions[i];
            int extensionLength = extension.length;
            int extensionStart = fileNameLength - extensionLength;
            if (extensionStart - 1 < 0)
                continue;
            if (fileName[extensionStart - 1] != '.')
                continue;
            for (int j = 0; j < extensionLength; j++) {
                if (fileName[extensionStart + j] != extension[j])
                    continue extensions;
            }
            return true;
        }
        return false;
    }

    /**
     * Get all type arguments from an array of signatures.
     *
     * Example:
     *    For following type X<Y<Z>,V<W>,U>.A<B> signatures is:
     *    [
     *       ['L','X','<','L','Y','<','L','Z',';'>',';','L','V','<','L','W',';'>',';','L','U',';',>',';'],
     *       ['L','A','<','L','B',';','>',';']
     *    ]
     *    @see #splitTypeLevelsSignature(String)
     *    Then, this method returns:
     *    [
     *       [
     *          ['L','Y','<','L','Z',';'>',';'],
     *          ['L','V','<','L','W',';'>',';'],
     *          ['L','U',';']
     *       ],
     *       [
     *          ['L','B',';']
     *       ]
     *    ]
     *
     * @param typeSignatures Array of signatures (one per each type levels)
     * @throws IllegalArgumentException If one of provided signature is malformed
     * @return char[][][] Array of type arguments for each signature
     */
    public final static char[][][] getAllTypeArguments(char[][] typeSignatures) {
        if (typeSignatures == null)
            return null;
        int length = typeSignatures.length;
        char[][][] typeArguments = new char[length][][];
        for (int i = 0; i < length; i++) {
            typeArguments[i] = Signature.getTypeArguments(typeSignatures[i]);
        }
        return typeArguments;
    }

    public static IAnnotation getAnnotation(JavaElement parent, IBinaryAnnotation binaryAnnotation,
            String memberValuePairName) {
        char[] typeName = org.eclipse.jdt.core.Signature
                .toCharArray(CharOperation.replaceOnCopy(binaryAnnotation.getTypeName(), '/', '.'));
        return new Annotation(parent, new String(typeName), memberValuePairName);
    }

    public static Object getAnnotationMemberValue(JavaElement parent, MemberValuePair memberValuePair,
            Object binaryValue) {
        if (binaryValue instanceof Constant) {
            return getAnnotationMemberValue(memberValuePair, (Constant) binaryValue);
        } else if (binaryValue instanceof IBinaryAnnotation) {
            memberValuePair.valueKind = IMemberValuePair.K_ANNOTATION;
            return getAnnotation(parent, (IBinaryAnnotation) binaryValue, memberValuePair.getMemberName());
        } else if (binaryValue instanceof ClassSignature) {
            memberValuePair.valueKind = IMemberValuePair.K_CLASS;
            char[] className = Signature.toCharArray(
                    CharOperation.replaceOnCopy(((ClassSignature) binaryValue).getTypeName(), '/', '.'));
            return new String(className);
        } else if (binaryValue instanceof EnumConstantSignature) {
            memberValuePair.valueKind = IMemberValuePair.K_QUALIFIED_NAME;
            EnumConstantSignature enumConstant = (EnumConstantSignature) binaryValue;
            char[] enumName = Signature
                    .toCharArray(CharOperation.replaceOnCopy(enumConstant.getTypeName(), '/', '.'));
            char[] qualifiedName = CharOperation.concat(enumName, enumConstant.getEnumConstantName(), '.');
            return new String(qualifiedName);
        } else if (binaryValue instanceof Object[]) {
            memberValuePair.valueKind = -1; // modified below by the first call to getMemberValue(...)
            Object[] binaryValues = (Object[]) binaryValue;
            int length = binaryValues.length;
            Object[] values = new Object[length];
            for (int i = 0; i < length; i++) {
                int previousValueKind = memberValuePair.valueKind;
                Object value = getAnnotationMemberValue(parent, memberValuePair, binaryValues[i]);
                if (previousValueKind != -1 && memberValuePair.valueKind != previousValueKind) {
                    // values are heterogeneous, value kind is thus unknown
                    memberValuePair.valueKind = IMemberValuePair.K_UNKNOWN;
                }
                if (value instanceof Annotation) {
                    Annotation annotation = (Annotation) value;
                    for (int j = 0; j < i; j++) {
                        if (annotation.equals(values[j])) {
                            annotation.occurrenceCount++;
                        }
                    }
                }
                values[i] = value;
            }
            if (memberValuePair.valueKind == -1)
                memberValuePair.valueKind = IMemberValuePair.K_UNKNOWN;
            return values;
        } else {
            memberValuePair.valueKind = IMemberValuePair.K_UNKNOWN;
            return null;
        }
    }

    /*
     * Creates a member value from the given constant, and sets the valueKind on the given memberValuePair
     */
    public static Object getAnnotationMemberValue(MemberValuePair memberValuePair, Constant constant) {
        if (constant == null) {
            memberValuePair.valueKind = IMemberValuePair.K_UNKNOWN;
            return null;
        }
        switch (constant.typeID()) {
        case TypeIds.T_int:
            memberValuePair.valueKind = IMemberValuePair.K_INT;
            return Integer.valueOf(constant.intValue());
        case TypeIds.T_byte:
            memberValuePair.valueKind = IMemberValuePair.K_BYTE;
            return Byte.valueOf(constant.byteValue());
        case TypeIds.T_short:
            memberValuePair.valueKind = IMemberValuePair.K_SHORT;
            return Short.valueOf(constant.shortValue());
        case TypeIds.T_char:
            memberValuePair.valueKind = IMemberValuePair.K_CHAR;
            return Character.valueOf(constant.charValue());
        case TypeIds.T_float:
            memberValuePair.valueKind = IMemberValuePair.K_FLOAT;
            return new Float(constant.floatValue());
        case TypeIds.T_double:
            memberValuePair.valueKind = IMemberValuePair.K_DOUBLE;
            return new Double(constant.doubleValue());
        case TypeIds.T_boolean:
            memberValuePair.valueKind = IMemberValuePair.K_BOOLEAN;
            return Boolean.valueOf(constant.booleanValue());
        case TypeIds.T_long:
            memberValuePair.valueKind = IMemberValuePair.K_LONG;
            return Long.valueOf(constant.longValue());
        case TypeIds.T_JavaLangString:
            memberValuePair.valueKind = IMemberValuePair.K_STRING;
            return constant.stringValue();
        default:
            memberValuePair.valueKind = IMemberValuePair.K_UNKNOWN;
            return null;
        }
    }

    /*
     * Creates a member value from the given constant in case of negative numerals,
     * and sets the valueKind on the given memberValuePair
     */
    public static Object getNegativeAnnotationMemberValue(MemberValuePair memberValuePair, Constant constant) {
        if (constant == null) {
            memberValuePair.valueKind = IMemberValuePair.K_UNKNOWN;
            return null;
        }
        switch (constant.typeID()) {
        case TypeIds.T_int:
            memberValuePair.valueKind = IMemberValuePair.K_INT;
            return Integer.valueOf(constant.intValue() * -1);
        case TypeIds.T_float:
            memberValuePair.valueKind = IMemberValuePair.K_FLOAT;
            return new Float(constant.floatValue() * -1.0f);
        case TypeIds.T_double:
            memberValuePair.valueKind = IMemberValuePair.K_DOUBLE;
            return new Double(constant.doubleValue() * -1.0);
        case TypeIds.T_long:
            memberValuePair.valueKind = IMemberValuePair.K_LONG;
            return Long.valueOf(constant.longValue() * -1L);
        default:
            memberValuePair.valueKind = IMemberValuePair.K_UNKNOWN;
            return null;
        }
    }

    /**
     * Split signatures of all levels  from a type unique key.
     *
     * Example:
     *    For following type X<Y<Z>,V<W>,U>.A<B>, unique key is:
     *    "LX<LY<LZ;>;LV<LW;>;LU;>.LA<LB;>;"
     *
     *    The return splitted signatures array is:
     *    [
     *       ['L','X','<','L','Y','<','L','Z',';'>',';','L','V','<','L','W',';'>',';','L','U','>',';'],
     *       ['L','A','<','L','B',';','>',';']
     *
     * @param typeSignature ParameterizedSourceType type signature
     * @return char[][] Array of signatures for each level of given unique key
     */
    public final static char[][] splitTypeLevelsSignature(String typeSignature) {
        // In case of IJavaElement signature, replace '$' by '.'
        char[] source = Signature.removeCapture(typeSignature.toCharArray());
        CharOperation.replace(source, '$', '.');

        // Init counters and arrays
        char[][] signatures = new char[10][];
        int signaturesCount = 0;
        //      int[] lengthes = new int [10];
        int paramOpening = 0;

        // Scan each signature character
        for (int idx = 0, ln = source.length; idx < ln; idx++) {
            switch (source[idx]) {
            case '>':
                paramOpening--;
                if (paramOpening == 0) {
                    if (signaturesCount == signatures.length) {
                        System.arraycopy(signatures, 0, signatures = new char[signaturesCount + 10][], 0,
                                signaturesCount);
                    }
                }
                break;
            case '<':
                paramOpening++;
                break;
            case '.':
                if (paramOpening == 0) {
                    if (signaturesCount == signatures.length) {
                        System.arraycopy(signatures, 0, signatures = new char[signaturesCount + 10][], 0,
                                signaturesCount);
                    }
                    signatures[signaturesCount] = new char[idx + 1];
                    System.arraycopy(source, 0, signatures[signaturesCount], 0, idx);
                    signatures[signaturesCount][idx] = Signature.C_SEMICOLON;
                    signaturesCount++;
                }
                break;
            case '/':
                source[idx] = '.';
                break;
            }
        }

        // Resize signatures array
        char[][] typeSignatures = new char[signaturesCount + 1][];
        typeSignatures[0] = source;
        for (int i = 1, j = signaturesCount - 1; i <= signaturesCount; i++, j--) {
            typeSignatures[i] = signatures[j];
        }
        return typeSignatures;
    }

    /*
     * Can throw IllegalArgumentException or ArrayIndexOutOfBoundsException
     */
    public static String toAnchor(int startingIndex, char[] methodSignature, String methodName, boolean isVarArgs) {
        try {
            return new String(toAnchor(startingIndex, methodSignature, methodName.toCharArray(), isVarArgs));
        } catch (IllegalArgumentException e) {
            return null;
        }
    }

    public static char[] toAnchor(int startingIndex, char[] methodSignature, char[] methodName,
            boolean isVargArgs) {
        int firstParen = CharOperation.indexOf(Signature.C_PARAM_START, methodSignature);
        if (firstParen == -1) {
            throw new IllegalArgumentException(String.valueOf(methodSignature));
        }

        StringBuffer buffer = new StringBuffer(methodSignature.length + 10);

        // selector
        if (methodName != null) {
            buffer.append(methodName);
        }

        // parameters
        buffer.append('(');
        char[][] pts = Signature.getParameterTypes(methodSignature);
        for (int i = startingIndex, max = pts.length; i < max; i++) {
            if (i == max - 1) {
                appendTypeSignatureForAnchor(pts[i], 0, buffer, isVargArgs);
            } else {
                appendTypeSignatureForAnchor(pts[i], 0, buffer, false);
            }
            if (i != pts.length - 1) {
                buffer.append(',');
                buffer.append(' ');
            }
        }
        buffer.append(')');
        char[] result = new char[buffer.length()];
        buffer.getChars(0, buffer.length(), result, 0);
        return result;
    }

    private static int appendTypeSignatureForAnchor(char[] string, int start, StringBuffer buffer,
            boolean isVarArgs) {
        // need a minimum 1 char
        if (start >= string.length) {
            throw newIllegalArgumentException(string, start);
        }
        char c = string[start];
        if (isVarArgs) {
            switch (c) {
            case Signature.C_ARRAY:
                return appendArrayTypeSignatureForAnchor(string, start, buffer, true);
            case Signature.C_RESOLVED:
            case Signature.C_TYPE_VARIABLE:
            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:
            case Signature.C_VOID:
            case Signature.C_STAR:
            case Signature.C_EXTENDS:
            case Signature.C_SUPER:
            case Signature.C_CAPTURE:
            default:
                // a var args is an array type
                throw newUnexpectedCharacterException(string, start, c);
            }
        } else {
            switch (c) {
            case Signature.C_ARRAY:
                return appendArrayTypeSignatureForAnchor(string, start, buffer, false);
            case Signature.C_RESOLVED:
                return appendClassTypeSignatureForAnchor(string, start, buffer);
            case Signature.C_TYPE_VARIABLE:
                int e = org.eclipse.jdt.internal.compiler.util.Util.scanTypeVariableSignature(string, start);
                buffer.append(string, start + 1, e - start - 1);
                return e;
            case Signature.C_BOOLEAN:
                buffer.append(BOOLEAN);
                return start;
            case Signature.C_BYTE:
                buffer.append(BYTE);
                return start;
            case Signature.C_CHAR:
                buffer.append(CHAR);
                return start;
            case Signature.C_DOUBLE:
                buffer.append(DOUBLE);
                return start;
            case Signature.C_FLOAT:
                buffer.append(FLOAT);
                return start;
            case Signature.C_INT:
                buffer.append(INT);
                return start;
            case Signature.C_LONG:
                buffer.append(LONG);
                return start;
            case Signature.C_SHORT:
                buffer.append(SHORT);
                return start;
            case Signature.C_VOID:
                buffer.append(VOID);
                return start;
            case Signature.C_CAPTURE:
                return appendCaptureTypeSignatureForAnchor(string, start, buffer);
            case Signature.C_STAR:
            case Signature.C_EXTENDS:
            case Signature.C_SUPER:
                return appendTypeArgumentSignatureForAnchor(string, start, buffer);
            default:
                throw newIllegalArgumentException(string, start);
            }
        }
    }

    private static int appendTypeArgumentSignatureForAnchor(char[] string, int start, StringBuffer buffer) {
        // need a minimum 1 char
        if (start >= string.length) {
            throw newIllegalArgumentException(string, start);
        }
        char c = string[start];
        switch (c) {
        case Signature.C_STAR:
            return start;
        case Signature.C_EXTENDS:
            return appendTypeSignatureForAnchor(string, start + 1, buffer, false);
        case Signature.C_SUPER:
            return appendTypeSignatureForAnchor(string, start + 1, buffer, false);
        default:
            return appendTypeSignatureForAnchor(string, start, buffer, false);
        }
    }

    private static int appendCaptureTypeSignatureForAnchor(char[] string, int start, StringBuffer buffer) {
        // need a minimum 2 char
        if (start >= string.length - 1) {
            throw newIllegalArgumentException(string, start);
        }
        char c = string[start];
        if (c != Signature.C_CAPTURE) {
            throw newUnexpectedCharacterException(string, start, c);
        }
        return appendTypeArgumentSignatureForAnchor(string, start + 1, buffer);
    }

    private static int appendArrayTypeSignatureForAnchor(char[] string, int start, StringBuffer buffer,
            boolean isVarArgs) {
        int length = string.length;
        // need a minimum 2 char
        if (start >= length - 1) {
            throw newIllegalArgumentException(string, start);
        }
        char c = string[start];
        if (c != Signature.C_ARRAY) {
            throw newUnexpectedCharacterException(string, start, c);
        }

        int index = start;
        c = string[++index];
        while (c == Signature.C_ARRAY) {
            // need a minimum 2 char
            if (index >= length - 1) {
                throw newIllegalArgumentException(string, start);
            }
            c = string[++index];
        }

        int e = appendTypeSignatureForAnchor(string, index, buffer, false);

        for (int i = 1, dims = index - start; i < dims; i++) {
            buffer.append('[').append(']');
        }

        if (isVarArgs) {
            buffer.append('.').append('.').append('.');
        } else {
            buffer.append('[').append(']');
        }
        return e;
    }

    private static int appendClassTypeSignatureForAnchor(char[] string, int start, StringBuffer buffer) {
        // need a minimum 3 chars "Lx;"
        if (start >= string.length - 2) {
            throw newIllegalArgumentException(string, start);
        }
        // must start in "L" or "Q"
        char c = string[start];
        if (c != Signature.C_RESOLVED && c != Signature.C_UNRESOLVED) {
            throw newUnexpectedCharacterException(string, start, c);
        }
        int p = start + 1;
        while (true) {
            if (p >= string.length) {
                throw newIllegalArgumentException(string, start);
            }
            c = string[p];
            switch (c) {
            case Signature.C_SEMICOLON:
                // all done
                return p;
            case Signature.C_GENERIC_START:
                int e = scanGenericEnd(string, p + 1);
                // once we hit type arguments there are no more package prefixes
                p = e;
                break;
            case Signature.C_DOT:
                buffer.append('.');
                break;
            case '/':
                buffer.append('/');
                break;
            case Signature.C_DOLLAR:
                // once we hit "$" there are no more package prefixes
                /**
                 * Convert '$' in resolved type signatures into '.'.
                 * NOTE: This assumes that the type signature is an inner type
                 * signature. This is true in most cases, but someone can define a
                 * non-inner type name containing a '$'.
                 */
                buffer.append('.');
                break;
            default:
                buffer.append(c);
            }
            p++;
        }
    }

    private static IllegalArgumentException newIllegalArgumentException(char[] string, int index) {
        return new IllegalArgumentException("\"" + String.valueOf(string) + "\" at " + index); //$NON-NLS-1$ //$NON-NLS-2$
    }

    private static IllegalArgumentException newUnexpectedCharacterException(char[] string, int start,
            char unexpected) {
        return new IllegalArgumentException(
                "Unexpected '" + unexpected + "' in \"" + String.valueOf(string) + "\" starting at " + start); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
    }

    private static int scanGenericEnd(char[] string, int start) {
        if (string[start] == Signature.C_GENERIC_END) {
            return start;
        }
        int length = string.length;
        int balance = 1;
        start++;
        while (start <= length) {
            switch (string[start]) {
            case Signature.C_GENERIC_END:
                balance--;
                if (balance == 0) {
                    return start;
                }
                break;
            case Signature.C_GENERIC_START:
                balance++;
                break;
            }
            start++;
        }
        return start;
    }

    /*
     * This method adjusts the task tags and task priorities so that they have the same size
     */
    public static void fixTaskTags(Map defaultOptionsMap) {
        Object taskTagsValue = defaultOptionsMap.get(JavaCore.COMPILER_TASK_TAGS);
        char[][] taskTags = null;
        if (taskTagsValue instanceof String) {
            taskTags = CharOperation.splitAndTrimOn(',', ((String) taskTagsValue).toCharArray());
        }
        Object taskPrioritiesValue = defaultOptionsMap.get(JavaCore.COMPILER_TASK_PRIORITIES);
        char[][] taskPriorities = null;
        if (taskPrioritiesValue instanceof String) {
            taskPriorities = CharOperation.splitAndTrimOn(',', ((String) taskPrioritiesValue).toCharArray());
        }
        if (taskPriorities == null) {
            if (taskTags != null) {
                Util.logRepeatedMessage(TASK_PRIORITIES_PROBLEM, IStatus.ERROR,
                        "Inconsistent values for taskTags (not null) and task priorities (null)"); //$NON-NLS-1$
                defaultOptionsMap.remove(JavaCore.COMPILER_TASK_TAGS);
            }
            return;
        } else if (taskTags == null) {
            Util.logRepeatedMessage(TASK_PRIORITIES_PROBLEM, IStatus.ERROR,
                    "Inconsistent values for taskTags (null) and task priorities (not null)"); //$NON-NLS-1$
            defaultOptionsMap.remove(JavaCore.COMPILER_TASK_PRIORITIES);
            return;
        }
        int taskTagsLength = taskTags.length;
        int taskPrioritiesLength = taskPriorities.length;
        if (taskTagsLength != taskPrioritiesLength) {
            Util.logRepeatedMessage(TASK_PRIORITIES_PROBLEM, IStatus.ERROR,
                    "Inconsistent values for taskTags and task priorities : length is different"); //$NON-NLS-1$
            if (taskTagsLength > taskPrioritiesLength) {
                System.arraycopy(taskTags, 0, (taskTags = new char[taskPrioritiesLength][]), 0,
                        taskPrioritiesLength);
                defaultOptionsMap.put(JavaCore.COMPILER_TASK_TAGS,
                        new String(CharOperation.concatWith(taskTags, ',')));
            } else {
                System.arraycopy(taskPriorities, 0, (taskPriorities = new char[taskTagsLength][]), 0,
                        taskTagsLength);
                defaultOptionsMap.put(JavaCore.COMPILER_TASK_PRIORITIES,
                        new String(CharOperation.concatWith(taskPriorities, ',')));
            }
        }
    }

    /**
     * Finds the IMethod element corresponding to the given selector, 
     * without creating a new dummy instance of a binary method. 
     * @param type the type in which the method is declared
     * @param selector the method name
     * @param paramTypeSignatures the type signatures of the method arguments
     * @param isConstructor whether we're looking for a constructor
     * @return an IMethod if found, otherwise null
     * @throws JavaModelException
     */
    public static IMethod findMethod(IType type, char[] selector, String[] paramTypeSignatures,
            boolean isConstructor) throws JavaModelException {
        IMethod method = null;
        int startingIndex = 0;
        String[] args;
        IType enclosingType = type.getDeclaringType();
        // If the method is a constructor of a non-static inner type, add the enclosing type as an 
        // additional parameter to the constructor
        if (enclosingType != null && isConstructor && !Flags.isStatic(type.getFlags())) {
            args = new String[paramTypeSignatures.length + 1];
            startingIndex = 1;
            args[0] = Signature.createTypeSignature(enclosingType.getFullyQualifiedName(), true);
        } else {
            args = new String[paramTypeSignatures.length];
        }
        int length = args.length;
        for (int i = startingIndex; i < length; i++) {
            args[i] = paramTypeSignatures[i - startingIndex];
        }
        method = type.getMethod(new String(selector), args);

        IMethod[] methods = type.findMethods(method);
        if (methods != null && methods.length > 0) {
            method = methods[0];
        }
        return method;
    }
}