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

Java tutorial

Introduction

Here is the source code for org.eclipse.jdt.internal.core.ClasspathEntry.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
 *     Terry Parker <tparker@google.com> - DeltaProcessor misses state changes in archive files, see https://bugs.eclipse.org/bugs/show_bug.cgi?id=357425
 *     Thirumala Reddy Mutchukota <thirumala@google.com> - Avoid optional library classpath entries validation - https://bugs.eclipse.org/bugs/show_bug.cgi?id=412882
 *     Stephan Herrmann - Contribution for
 *                        Bug 440477 - [null] Infrastructure for feeding external annotations into compilation
 *                        Bug 462768 - [null] NPE when using linked folder for external annotations
 *                              Bug 465296 - precedence of extra attributes on a classpath container
 *     Karsten Thoms - Bug 532505 - Reduce memory footprint of ClasspathAccessRule
 *******************************************************************************/
package org.eclipse.jdt.internal.core;

import static org.eclipse.jdt.internal.compiler.util.Util.UTF_8;
import static org.eclipse.jdt.internal.compiler.util.Util.getInputStreamAsCharArray;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.zip.CRC32;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.jdt.core.IAccessRule;
import org.eclipse.jdt.core.IClasspathAttribute;
import org.eclipse.jdt.core.IClasspathContainer;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaModelMarker;
import org.eclipse.jdt.core.IJavaModelStatus;
import org.eclipse.jdt.core.IJavaModelStatusConstants;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.core.compiler.IProblem;
import org.eclipse.jdt.internal.compiler.env.AccessRestriction;
import org.eclipse.jdt.internal.compiler.env.AccessRule;
import org.eclipse.jdt.internal.compiler.env.AccessRuleSet;
import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
import org.eclipse.jdt.internal.compiler.lookup.TypeConstants;
import org.eclipse.jdt.internal.compiler.util.ManifestAnalyzer;
import org.eclipse.jdt.internal.core.nd.IReader;
import org.eclipse.jdt.internal.core.nd.java.JavaIndex;
import org.eclipse.jdt.internal.core.nd.java.NdResourceFile;
import org.eclipse.jdt.internal.core.util.Messages;
import org.eclipse.jdt.internal.core.util.Util;
import org.w3c.dom.DOMException;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;

/**
 * @see IClasspathEntry
 */
@SuppressWarnings({ "rawtypes", "unchecked" })
public class ClasspathEntry implements IClasspathEntry {

    public static class AssertionFailedException extends RuntimeException {

        private static final long serialVersionUID = -171699380721189572L;

        public AssertionFailedException(String message) {
            super(message);
        }
    }

    public static final String TAG_CLASSPATH = "classpath"; //$NON-NLS-1$
    public static final String TAG_CLASSPATHENTRY = "classpathentry"; //$NON-NLS-1$
    public static final String TAG_REFERENCED_ENTRY = "referencedentry"; //$NON-NLS-1$
    public static final String TAG_OUTPUT = "output"; //$NON-NLS-1$
    public static final String TAG_KIND = "kind"; //$NON-NLS-1$
    public static final String TAG_PATH = "path"; //$NON-NLS-1$
    public static final String TAG_SOURCEPATH = "sourcepath"; //$NON-NLS-1$
    public static final String TAG_ROOTPATH = "rootpath"; //$NON-NLS-1$
    public static final String TAG_EXPORTED = "exported"; //$NON-NLS-1$
    public static final String TAG_INCLUDING = "including"; //$NON-NLS-1$
    public static final String TAG_EXCLUDING = "excluding"; //$NON-NLS-1$
    public static final String TAG_ATTRIBUTES = "attributes"; //$NON-NLS-1$
    public static final String TAG_ATTRIBUTE = "attribute"; //$NON-NLS-1$
    public static final String TAG_ATTRIBUTE_NAME = "name"; //$NON-NLS-1$
    public static final String TAG_ATTRIBUTE_VALUE = "value"; //$NON-NLS-1$
    public static final String TAG_COMBINE_ACCESS_RULES = "combineaccessrules"; //$NON-NLS-1$
    public static final String TAG_ACCESS_RULES = "accessrules"; //$NON-NLS-1$
    public static final String TAG_ACCESS_RULE = "accessrule"; //$NON-NLS-1$
    public static final String TAG_PATTERN = "pattern"; //$NON-NLS-1$
    public static final String TAG_ACCESSIBLE = "accessible"; //$NON-NLS-1$
    public static final String TAG_NON_ACCESSIBLE = "nonaccessible"; //$NON-NLS-1$
    public static final String TAG_DISCOURAGED = "discouraged"; //$NON-NLS-1$
    public static final String TAG_IGNORE_IF_BETTER = "ignoreifbetter"; //$NON-NLS-1$

    // common index location for all workspaces
    private static String SHARED_INDEX_LOCATION = System.getProperty("jdt.core.sharedIndexLocation"); //$NON-NLS-1$

    /**
     * Describes the kind of classpath entry - one of
     * CPE_PROJECT, CPE_LIBRARY, CPE_SOURCE, CPE_VARIABLE or CPE_CONTAINER
     */
    public int entryKind;

    /**
     * Describes the kind of package fragment roots found on
     * this classpath entry - either K_BINARY or K_SOURCE or
     * K_OUTPUT.
     */
    public int contentKind;

    /**
     * The meaning of the path of a classpath entry depends on its entry kind:<ul>
     *   <li>Source code in the current project (<code>CPE_SOURCE</code>) -
     *      The path associated with this entry is the absolute path to the root folder. </li>
     *   <li>A binary library in the current project (<code>CPE_LIBRARY</code>) - the path
     *      associated with this entry is the absolute path to the JAR (or root folder), and
     *      in case it refers to an external JAR, then there is no associated resource in
     *      the workbench.
     *   <li>A required project (<code>CPE_PROJECT</code>) - the path of the entry denotes the
     *      path to the corresponding project resource.</li>
     *  <li>A variable entry (<code>CPE_VARIABLE</code>) - the first segment of the path
     *      is the name of a classpath variable. If this classpath variable
     *      is bound to the path <it>P</it>, the path of the corresponding classpath entry
     *      is computed by appending to <it>P</it> the segments of the returned
     *      path without the variable.</li>
     *  <li> A container entry (<code>CPE_CONTAINER</code>) - the first segment of the path is denoting
     *     the unique container identifier (for which a <code>ClasspathContainerInitializer</code> could be
     *    registered), and the remaining segments are used as additional hints for resolving the container entry to
     *    an actual <code>IClasspathContainer</code>.</li>
     */
    public IPath path;

    /**
     * Patterns allowing to include/exclude portions of the resource tree denoted by this entry path.
     */
    private IPath[] inclusionPatterns;
    private char[][] fullInclusionPatternChars;
    private IPath[] exclusionPatterns;
    private char[][] fullExclusionPatternChars;
    private final static char[][] UNINIT_PATTERNS = new char[][] { "Non-initialized yet".toCharArray() }; //$NON-NLS-1$
    public final static ClasspathEntry[] NO_ENTRIES = new ClasspathEntry[0];
    private final static IPath[] NO_PATHS = new IPath[0];
    private final static IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot();

    private boolean combineAccessRules;

    private String rootID;
    private AccessRuleSet accessRuleSet;

    static class UnknownXmlElements {
        String[] attributes;
        ArrayList children;
    }

    /*
     * Default inclusion pattern set
     */
    public final static IPath[] INCLUDE_ALL = {};

    /*
     * Default exclusion pattern set
     */
    public final static IPath[] EXCLUDE_NONE = {};

    /*
     * Default extra attributes
     */
    public final static IClasspathAttribute[] NO_EXTRA_ATTRIBUTES = {};

    /*
     * Default access rules
     */
    public final static IAccessRule[] NO_ACCESS_RULES = {};

    /**
     * Describes the path to the source archive associated with this
     * classpath entry, or <code>null</code> if this classpath entry has no
     * source attachment.
     * <p>
     * Only library and variable classpath entries may have source attachments.
     * For library classpath entries, the result path (if present) locates a source
     * archive. For variable classpath entries, the result path (if present) has
     * an analogous form and meaning as the variable path, namely the first segment
     * is the name of a classpath variable.
     */
    public IPath sourceAttachmentPath;

    /**
     * Describes the path within the source archive where package fragments
     * are located. An empty path indicates that packages are located at
     * the root of the source archive. Returns a non-<code>null</code> value
     * if and only if <code>getSourceAttachmentPath</code> returns
     * a non-<code>null</code> value.
     */
    public IPath sourceAttachmentRootPath;

    /**
     * See {@link IClasspathEntry#getReferencingEntry()}
     */
    public IClasspathEntry referencingEntry;

    /**
     * Specific output location (for this source entry)
     */
    public IPath specificOutputLocation;

    /**
     * A constant indicating an output location.
     */
    public static final int K_OUTPUT = 10;

    public static final String DOT_DOT = ".."; //$NON-NLS-1$

    /**
     * The export flag
     */
    public boolean isExported;

    /**
     * The extra attributes
     */
    public IClasspathAttribute[] extraAttributes;

    public ClasspathEntry(int contentKind, int entryKind, IPath path, IPath[] inclusionPatterns,
            IPath[] exclusionPatterns, IPath sourceAttachmentPath, IPath sourceAttachmentRootPath,
            IPath specificOutputLocation, boolean isExported, IAccessRule[] accessRules, boolean combineAccessRules,
            IClasspathAttribute[] extraAttributes) {

        this(contentKind, entryKind, path, inclusionPatterns, exclusionPatterns, sourceAttachmentPath,
                sourceAttachmentRootPath, specificOutputLocation, null, isExported, accessRules, combineAccessRules,
                extraAttributes);
    }

    /**
     * Creates a class path entry of the specified kind with the given path.
     */
    public ClasspathEntry(int contentKind, int entryKind, IPath path, IPath[] inclusionPatterns,
            IPath[] exclusionPatterns, IPath sourceAttachmentPath, IPath sourceAttachmentRootPath,
            IPath specificOutputLocation, IClasspathEntry referencingEntry, boolean isExported,
            IAccessRule[] accessRules, boolean combineAccessRules, IClasspathAttribute[] extraAttributes) {

        this.contentKind = contentKind;
        this.entryKind = entryKind;
        this.path = path;
        this.inclusionPatterns = inclusionPatterns;
        this.exclusionPatterns = exclusionPatterns;
        this.referencingEntry = referencingEntry;

        int length;
        if (accessRules != null && (length = accessRules.length) > 0) {
            AccessRule[] rules = new AccessRule[length];
            System.arraycopy(accessRules, 0, rules, 0, length);
            byte classpathEntryType;
            String classpathEntryName;
            JavaModelManager manager = JavaModelManager.getJavaModelManager();
            if (this.entryKind == CPE_PROJECT || this.entryKind == CPE_SOURCE) { // can be remote source entry when reconciling
                classpathEntryType = AccessRestriction.PROJECT;
                classpathEntryName = manager.intern(getPath().segment(0));
            } else {
                classpathEntryType = AccessRestriction.LIBRARY;
                Object target = JavaModel.getWorkspaceTarget(path);
                if (target == null) {
                    classpathEntryName = manager.intern(path.toOSString());
                } else {
                    classpathEntryName = manager.intern(path.makeRelative().toString());
                }
            }
            this.accessRuleSet = new AccessRuleSet(rules, classpathEntryType, classpathEntryName);
        }
        //      else { -- implicit!
        //         this.accessRuleSet = null;
        //      }

        this.combineAccessRules = combineAccessRules;
        this.extraAttributes = extraAttributes.length > 0 ? extraAttributes : NO_EXTRA_ATTRIBUTES;

        if (inclusionPatterns != INCLUDE_ALL && inclusionPatterns.length > 0) {
            this.fullInclusionPatternChars = UNINIT_PATTERNS;
        }
        if (exclusionPatterns.length > 0) {
            this.fullExclusionPatternChars = UNINIT_PATTERNS;
        }
        this.sourceAttachmentPath = sourceAttachmentPath;
        this.sourceAttachmentRootPath = sourceAttachmentRootPath;
        this.specificOutputLocation = specificOutputLocation;
        this.isExported = isExported;
    }

    @Override
    public boolean combineAccessRules() {
        return this.combineAccessRules;
    }

    /**
     * Used to perform export/restriction propagation across referring projects/containers.
     * Also: propagating extraAttributes.
     */
    public ClasspathEntry combineWith(ClasspathEntry referringEntry) {
        if (referringEntry == null)
            return this;
        IClasspathAttribute[] referringExtraAttributes = referringEntry.getExtraAttributes();
        if (referringEntry.isExported() || referringEntry.getAccessRuleSet() != null
                || referringExtraAttributes.length > 0) {
            boolean combine = this.entryKind == CPE_SOURCE || referringEntry.combineAccessRules();
            IClasspathAttribute[] combinedAttributes = this.extraAttributes;
            int lenRefer = referringExtraAttributes.length;
            if (lenRefer > 0) {
                int lenEntry = combinedAttributes.length;
                if (referringEntry.path.isPrefixOf(this.path)) {
                    // consider prefix location as less specific, put to back (e.g.: referring to a library via a project):
                    System.arraycopy(combinedAttributes, 0,
                            combinedAttributes = new IClasspathAttribute[lenEntry + lenRefer], 0, lenEntry);
                    System.arraycopy(referringExtraAttributes, 0, combinedAttributes, lenEntry, lenRefer);
                } else {
                    // otherwise consider the referring entry as more specific than the referee:
                    System.arraycopy(combinedAttributes, 0,
                            combinedAttributes = new IClasspathAttribute[lenEntry + lenRefer], lenRefer, lenEntry);
                    System.arraycopy(referringExtraAttributes, 0, combinedAttributes, 0, lenRefer);
                }
            }
            return new ClasspathEntry(getContentKind(), getEntryKind(), getPath(), this.inclusionPatterns,
                    this.exclusionPatterns, getSourceAttachmentPath(), getSourceAttachmentRootPath(),
                    getOutputLocation(), referringEntry.isExported() || this.isExported, // duplicate container entry for tagging it as exported
                    combine(referringEntry.getAccessRules(), getAccessRules(), combine), this.combineAccessRules,
                    combinedAttributes);
        }
        // no need to clone
        return this;
    }

    public ClasspathEntry withExtraAttributeRemoved(String attrName) {
        IClasspathAttribute[] changedAttributes = Arrays.stream(this.getExtraAttributes())
                .filter(a -> !a.getName().equals(attrName)).toArray(IClasspathAttribute[]::new);
        return new ClasspathEntry(this.getContentKind(), this.getEntryKind(), this.getPath(),
                this.getInclusionPatterns(), this.getExclusionPatterns(), this.getSourceAttachmentPath(),
                this.getSourceAttachmentRootPath(), this.getOutputLocation(), this.getReferencingEntry(),
                this.isExported(), this.getAccessRules(), this.combineAccessRules(), changedAttributes);
    }

    private IAccessRule[] combine(IAccessRule[] referringRules, IAccessRule[] rules, boolean combine) {
        if (!combine)
            return rules;
        if (rules == null || rules.length == 0)
            return referringRules;

        // concat access rules
        int referringRulesLength = referringRules.length;
        int accessRulesLength = rules.length;
        int rulesLength = referringRulesLength + accessRulesLength;
        IAccessRule[] result = new IAccessRule[rulesLength];
        System.arraycopy(referringRules, 0, result, 0, referringRulesLength);
        System.arraycopy(rules, 0, result, referringRulesLength, accessRulesLength);

        return result;
    }

    static IClasspathAttribute[] decodeExtraAttributes(NodeList attributes) {
        if (attributes == null)
            return NO_EXTRA_ATTRIBUTES;
        int length = attributes.getLength();
        if (length == 0)
            return NO_EXTRA_ATTRIBUTES;
        IClasspathAttribute[] result = new IClasspathAttribute[length];
        int index = 0;
        for (int i = 0; i < length; ++i) {
            Node node = attributes.item(i);
            if (node.getNodeType() == Node.ELEMENT_NODE) {
                Element attribute = (Element) node;
                String name = attribute.getAttribute(TAG_ATTRIBUTE_NAME);
                if (name == null)
                    continue;
                String value = attribute.getAttribute(TAG_ATTRIBUTE_VALUE);
                if (value == null)
                    continue;
                result[index++] = new ClasspathAttribute(name, value);
            }
        }
        if (index != length)
            System.arraycopy(result, 0, result = new IClasspathAttribute[index], 0, index);
        return result;
    }

    static IAccessRule[] decodeAccessRules(NodeList list) {
        if (list == null)
            return null;
        int length = list.getLength();
        if (length == 0)
            return null;
        IAccessRule[] result = new IAccessRule[length];
        int index = 0;
        for (int i = 0; i < length; i++) {
            Node accessRule = list.item(i);
            if (accessRule.getNodeType() == Node.ELEMENT_NODE) {
                Element elementAccessRule = (Element) accessRule;
                String pattern = elementAccessRule.getAttribute(TAG_PATTERN);
                if (pattern == null)
                    continue;
                String tagKind = elementAccessRule.getAttribute(TAG_KIND);
                int kind;
                if (TAG_ACCESSIBLE.equals(tagKind))
                    kind = IAccessRule.K_ACCESSIBLE;
                else if (TAG_NON_ACCESSIBLE.equals(tagKind))
                    kind = IAccessRule.K_NON_ACCESSIBLE;
                else if (TAG_DISCOURAGED.equals(tagKind))
                    kind = IAccessRule.K_DISCOURAGED;
                else
                    continue;
                boolean ignoreIfBetter = "true".equals(elementAccessRule.getAttribute(TAG_IGNORE_IF_BETTER)); //$NON-NLS-1$
                result[index++] = JavaCore.newAccessRule(new Path(pattern),
                        ignoreIfBetter ? kind | IAccessRule.IGNORE_IF_BETTER : kind);
            }
        }
        if (index != length)
            System.arraycopy(result, 0, result = new IAccessRule[index], 0, index);
        return result;
    }

    /**
     * Decode some element tag containing a sequence of patterns into IPath[]
     */
    private static IPath[] decodePatterns(NamedNodeMap nodeMap, String tag) {
        String sequence = removeAttribute(tag, nodeMap);
        if (!sequence.equals("")) { //$NON-NLS-1$
            char[][] patterns = CharOperation.splitOn('|', sequence.toCharArray());
            int patternCount;
            if ((patternCount = patterns.length) > 0) {
                IPath[] paths = new IPath[patternCount];
                int index = 0;
                for (int j = 0; j < patternCount; j++) {
                    char[] pattern = patterns[j];
                    if (pattern.length == 0)
                        continue; // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=105581
                    paths[index++] = new Path(new String(pattern));
                }
                if (index < patternCount)
                    System.arraycopy(paths, 0, paths = new IPath[index], 0, index);
                return paths;
            }
        }
        return null;
    }

    private static void decodeUnknownNode(Node node, StringBuffer buffer, IJavaProject project) {
        ByteArrayOutputStream s = new ByteArrayOutputStream();
        OutputStreamWriter writer;
        try {
            writer = new OutputStreamWriter(s, "UTF8"); //$NON-NLS-1$
            XMLWriter xmlWriter = new XMLWriter(writer, project, false/*don't print XML version*/);
            decodeUnknownNode(node, xmlWriter, true/*insert new line*/);
            xmlWriter.flush();
            xmlWriter.close();
            buffer.append(s.toString("UTF8")); //$NON-NLS-1$
        } catch (UnsupportedEncodingException e) {
            // ignore (UTF8 is always supported)
        }
    }

    private static void decodeUnknownNode(Node node, XMLWriter xmlWriter, boolean insertNewLine) {
        switch (node.getNodeType()) {
        case Node.ELEMENT_NODE:
            NamedNodeMap attributes;
            HashMap parameters = null;
            if ((attributes = node.getAttributes()) != null) {
                int length = attributes.getLength();
                if (length > 0) {
                    parameters = new HashMap();
                    for (int i = 0; i < length; i++) {
                        Node attribute = attributes.item(i);
                        parameters.put(attribute.getNodeName(), attribute.getNodeValue());
                    }
                }
            }
            NodeList children = node.getChildNodes();
            int childrenLength = children.getLength();
            String nodeName = node.getNodeName();
            xmlWriter.printTag(nodeName, parameters, false/*don't insert tab*/, false/*don't insert new line*/,
                    childrenLength == 0/*close tag if no children*/);
            if (childrenLength > 0) {
                for (int i = 0; i < childrenLength; i++) {
                    decodeUnknownNode(children.item(i), xmlWriter, false/*don't insert new line*/);
                }
                xmlWriter.endTag(nodeName, false/*don't insert tab*/, insertNewLine);
            }
            break;
        case Node.TEXT_NODE:
            String data = ((Text) node).getData();
            xmlWriter.printString(data, false/*don't insert tab*/, false/*don't insert new line*/);
            break;
        }
    }

    /*
     * Returns a char based representation of the exclusions patterns full path.
     */
    public char[][] fullExclusionPatternChars() {

        if (this.fullExclusionPatternChars == UNINIT_PATTERNS) {
            int length = this.exclusionPatterns.length;
            this.fullExclusionPatternChars = new char[length][];
            IPath prefixPath = this.path.removeTrailingSeparator();
            for (int i = 0; i < length; i++) {
                this.fullExclusionPatternChars[i] = prefixPath.append(this.exclusionPatterns[i]).toString()
                        .toCharArray();
            }
        }
        return this.fullExclusionPatternChars;
    }

    /*
     * Returns a char based representation of the exclusions patterns full path.
     */
    public char[][] fullInclusionPatternChars() {

        if (this.fullInclusionPatternChars == UNINIT_PATTERNS) {
            int length = this.inclusionPatterns.length;
            this.fullInclusionPatternChars = new char[length][];
            IPath prefixPath = this.path.removeTrailingSeparator();
            for (int i = 0; i < length; i++) {
                this.fullInclusionPatternChars[i] = prefixPath.append(this.inclusionPatterns[i]).toString()
                        .toCharArray();
            }
        }
        return this.fullInclusionPatternChars;
    }

    /**
     * Returns the XML encoding of the class path.
     */
    public void elementEncode(XMLWriter writer, IPath projectPath, boolean indent, boolean newLine,
            Map unknownElements, boolean isReferencedEntry) {
        HashMap parameters = new HashMap();

        parameters.put(TAG_KIND, ClasspathEntry.kindToString(this.entryKind));

        IPath xmlPath = this.path;
        if (this.entryKind != IClasspathEntry.CPE_VARIABLE && this.entryKind != IClasspathEntry.CPE_CONTAINER) {
            // translate to project relative from absolute (unless a device path)
            if (xmlPath.isAbsolute()) {
                if (projectPath != null && projectPath.isPrefixOf(xmlPath)) {
                    if (xmlPath.segment(0).equals(projectPath.segment(0))) {
                        xmlPath = xmlPath.removeFirstSegments(1);
                        xmlPath = xmlPath.makeRelative();
                    } else {
                        xmlPath = xmlPath.makeAbsolute();
                    }
                }
            }
        }
        parameters.put(TAG_PATH, String.valueOf(xmlPath));

        if (this.sourceAttachmentPath != null) {
            xmlPath = this.sourceAttachmentPath;
            // translate to project relative from absolute
            if (this.entryKind != IClasspathEntry.CPE_VARIABLE && projectPath != null
                    && projectPath.isPrefixOf(xmlPath)) {
                if (xmlPath.segment(0).equals(projectPath.segment(0))) {
                    xmlPath = xmlPath.removeFirstSegments(1);
                    xmlPath = xmlPath.makeRelative();
                }
            }
            parameters.put(TAG_SOURCEPATH, String.valueOf(xmlPath));
        }
        if (this.sourceAttachmentRootPath != null) {
            parameters.put(TAG_ROOTPATH, String.valueOf(this.sourceAttachmentRootPath));
        }
        if (this.isExported) {
            parameters.put(TAG_EXPORTED, "true");//$NON-NLS-1$
        }
        encodePatterns(this.inclusionPatterns, TAG_INCLUDING, parameters);
        encodePatterns(this.exclusionPatterns, TAG_EXCLUDING, parameters);
        if (this.entryKind == CPE_PROJECT && !this.combineAccessRules)
            parameters.put(TAG_COMBINE_ACCESS_RULES, "false"); //$NON-NLS-1$

        // unknown attributes
        UnknownXmlElements unknownXmlElements = unknownElements == null ? null
                : (UnknownXmlElements) unknownElements.get(this.path);
        String[] unknownAttributes;
        if (unknownXmlElements != null && (unknownAttributes = unknownXmlElements.attributes) != null)
            for (int i = 0, length = unknownAttributes.length; i < length; i += 2) {
                String tagName = unknownAttributes[i];
                String tagValue = unknownAttributes[i + 1];
                parameters.put(tagName, tagValue);
            }

        if (this.specificOutputLocation != null) {
            IPath outputLocation = this.specificOutputLocation.removeFirstSegments(1);
            outputLocation = outputLocation.makeRelative();
            parameters.put(TAG_OUTPUT, String.valueOf(outputLocation));
        }

        boolean hasExtraAttributes = this.extraAttributes.length != 0;
        boolean hasRestrictions = getAccessRuleSet() != null; // access rule set is null if no access rules
        ArrayList unknownChildren = unknownXmlElements != null ? unknownXmlElements.children : null;
        boolean hasUnknownChildren = unknownChildren != null;

        /* close tag if no extra attributes, no restriction and no unknown children */
        String tagName = isReferencedEntry ? TAG_REFERENCED_ENTRY : TAG_CLASSPATHENTRY;
        writer.printTag(tagName, parameters, indent, newLine,
                !hasExtraAttributes && !hasRestrictions && !hasUnknownChildren);

        if (hasExtraAttributes)
            encodeExtraAttributes(writer, indent, newLine);

        if (hasRestrictions)
            encodeAccessRules(writer, indent, newLine);

        if (hasUnknownChildren)
            encodeUnknownChildren(writer, indent, newLine, unknownChildren);

        if (hasExtraAttributes || hasRestrictions || hasUnknownChildren)
            writer.endTag(tagName, indent, true/*insert new line*/);
    }

    void encodeExtraAttributes(XMLWriter writer, boolean indent, boolean newLine) {
        writer.startTag(TAG_ATTRIBUTES, indent);
        for (int i = 0; i < this.extraAttributes.length; i++) {
            IClasspathAttribute attribute = this.extraAttributes[i];
            HashMap parameters = new HashMap();
            parameters.put(TAG_ATTRIBUTE_NAME, attribute.getName());
            parameters.put(TAG_ATTRIBUTE_VALUE, attribute.getValue());
            writer.printTag(TAG_ATTRIBUTE, parameters, indent, newLine, true);
        }
        writer.endTag(TAG_ATTRIBUTES, indent, true/*insert new line*/);
    }

    void encodeAccessRules(XMLWriter writer, boolean indent, boolean newLine) {

        writer.startTag(TAG_ACCESS_RULES, indent);
        AccessRule[] rules = getAccessRuleSet().getAccessRules();
        for (int i = 0, length = rules.length; i < length; i++) {
            encodeAccessRule(rules[i], writer, indent, newLine);
        }
        writer.endTag(TAG_ACCESS_RULES, indent, true/*insert new line*/);
    }

    private void encodeAccessRule(AccessRule accessRule, XMLWriter writer, boolean indent, boolean newLine) {

        HashMap parameters = new HashMap();
        parameters.put(TAG_PATTERN, new String(accessRule.pattern));

        switch (accessRule.getProblemId()) {
        case IProblem.ForbiddenReference:
            parameters.put(TAG_KIND, TAG_NON_ACCESSIBLE);
            break;
        case IProblem.DiscouragedReference:
            parameters.put(TAG_KIND, TAG_DISCOURAGED);
            break;
        default:
            parameters.put(TAG_KIND, TAG_ACCESSIBLE);
            break;
        }
        if (accessRule.ignoreIfBetter())
            parameters.put(TAG_IGNORE_IF_BETTER, "true"); //$NON-NLS-1$

        writer.printTag(TAG_ACCESS_RULE, parameters, indent, newLine, true);

    }

    private void encodeUnknownChildren(XMLWriter writer, boolean indent, boolean newLine,
            ArrayList unknownChildren) {
        for (int i = 0, length = unknownChildren.size(); i < length; i++) {
            String child = (String) unknownChildren.get(i);
            writer.printString(child, indent, false/*don't insert new line*/);
        }
    }

    public static IClasspathEntry elementDecode(Element element, IJavaProject project, Map unknownElements) {

        IPath projectPath = project.getProject().getFullPath();
        NamedNodeMap attributes = element.getAttributes();
        NodeList children = element.getChildNodes();
        boolean[] foundChildren = new boolean[children.getLength()];
        String kindAttr = removeAttribute(TAG_KIND, attributes);
        String pathAttr = removeAttribute(TAG_PATH, attributes);

        // ensure path is absolute
        IPath path = new Path(pathAttr);
        int kind = kindFromString(kindAttr);
        if (kind != IClasspathEntry.CPE_VARIABLE && kind != IClasspathEntry.CPE_CONTAINER && !path.isAbsolute()) {
            if (!(path.segmentCount() > 0 && path.segment(0).equals(ClasspathEntry.DOT_DOT))) {
                path = projectPath.append(path);
            }
        }
        // source attachment info (optional)
        IPath sourceAttachmentPath = element.hasAttribute(TAG_SOURCEPATH)
                ? new Path(removeAttribute(TAG_SOURCEPATH, attributes))
                : null;
        if (kind != IClasspathEntry.CPE_VARIABLE && sourceAttachmentPath != null
                && !sourceAttachmentPath.isAbsolute()) {
            sourceAttachmentPath = projectPath.append(sourceAttachmentPath);
        }
        IPath sourceAttachmentRootPath = element.hasAttribute(TAG_ROOTPATH)
                ? new Path(removeAttribute(TAG_ROOTPATH, attributes))
                : null;

        // exported flag (optional)
        boolean isExported = removeAttribute(TAG_EXPORTED, attributes).equals("true"); //$NON-NLS-1$

        // inclusion patterns (optional)
        IPath[] inclusionPatterns = decodePatterns(attributes, TAG_INCLUDING);
        if (inclusionPatterns == null)
            inclusionPatterns = INCLUDE_ALL;

        // exclusion patterns (optional)
        IPath[] exclusionPatterns = decodePatterns(attributes, TAG_EXCLUDING);
        if (exclusionPatterns == null)
            exclusionPatterns = EXCLUDE_NONE;

        // access rules (optional)
        NodeList attributeList = getChildAttributes(TAG_ACCESS_RULES, children, foundChildren);
        IAccessRule[] accessRules = decodeAccessRules(attributeList);

        // backward compatibility
        if (accessRules == null) {
            accessRules = getAccessRules(inclusionPatterns, exclusionPatterns);
        }

        // combine access rules (optional)
        boolean combineAccessRestrictions = !removeAttribute(TAG_COMBINE_ACCESS_RULES, attributes).equals("false"); //$NON-NLS-1$

        // extra attributes (optional)
        attributeList = getChildAttributes(TAG_ATTRIBUTES, children, foundChildren);
        IClasspathAttribute[] extraAttributes = decodeExtraAttributes(attributeList);

        // custom output location
        IPath outputLocation = element.hasAttribute(TAG_OUTPUT)
                ? projectPath.append(removeAttribute(TAG_OUTPUT, attributes))
                : null;

        String[] unknownAttributes = null;
        ArrayList unknownChildren = null;

        if (unknownElements != null) {
            // unknown attributes
            int unknownAttributeLength = attributes.getLength();
            if (unknownAttributeLength != 0) {
                unknownAttributes = new String[unknownAttributeLength * 2];
                for (int i = 0; i < unknownAttributeLength; i++) {
                    Node attribute = attributes.item(i);
                    unknownAttributes[i * 2] = attribute.getNodeName();
                    unknownAttributes[i * 2 + 1] = attribute.getNodeValue();
                }
            }

            // unknown children
            for (int i = 0, length = foundChildren.length; i < length; i++) {
                if (!foundChildren[i]) {
                    Node node = children.item(i);
                    if (node.getNodeType() != Node.ELEMENT_NODE)
                        continue;
                    if (unknownChildren == null)
                        unknownChildren = new ArrayList();
                    StringBuffer buffer = new StringBuffer();
                    decodeUnknownNode(node, buffer, project);
                    unknownChildren.add(buffer.toString());
                }
            }
        }

        // recreate the CP entry
        IClasspathEntry entry = null;
        switch (kind) {

        case IClasspathEntry.CPE_PROJECT:
            entry = new ClasspathEntry(IPackageFragmentRoot.K_SOURCE, IClasspathEntry.CPE_PROJECT, path,
                    ClasspathEntry.INCLUDE_ALL, // inclusion patterns
                    ClasspathEntry.EXCLUDE_NONE, // exclusion patterns
                    null, // source attachment
                    null, // source attachment root
                    null, // specific output folder
                    isExported, accessRules, combineAccessRestrictions, extraAttributes);
            break;
        case IClasspathEntry.CPE_LIBRARY:
            entry = JavaCore.newLibraryEntry(path, sourceAttachmentPath, sourceAttachmentRootPath, accessRules,
                    extraAttributes, isExported);
            break;
        case IClasspathEntry.CPE_SOURCE:
            // must be an entry in this project or specify another project
            String projSegment = path.segment(0);
            if (projSegment != null && projSegment.equals(project.getElementName())) { // this project
                entry = JavaCore.newSourceEntry(path, inclusionPatterns, exclusionPatterns, outputLocation,
                        extraAttributes);
            } else {
                if (path.segmentCount() == 1) {
                    // another project
                    entry = JavaCore.newProjectEntry(path, accessRules, combineAccessRestrictions, extraAttributes,
                            isExported);
                } else {
                    // an invalid source folder
                    entry = JavaCore.newSourceEntry(path, inclusionPatterns, exclusionPatterns, outputLocation,
                            extraAttributes);
                }
            }
            break;
        case IClasspathEntry.CPE_VARIABLE:
            entry = JavaCore.newVariableEntry(path, sourceAttachmentPath, sourceAttachmentRootPath, accessRules,
                    extraAttributes, isExported);
            break;
        case IClasspathEntry.CPE_CONTAINER:
            entry = JavaCore.newContainerEntry(path, accessRules, extraAttributes, isExported);
            break;
        case ClasspathEntry.K_OUTPUT:
            if (!path.isAbsolute())
                return null;
            entry = new ClasspathEntry(ClasspathEntry.K_OUTPUT, IClasspathEntry.CPE_LIBRARY, path, INCLUDE_ALL,
                    EXCLUDE_NONE, null, // source attachment
                    null, // source attachment root
                    null, // custom output location
                    false, null, // no access rules
                    false, // no accessible files to combine
                    NO_EXTRA_ATTRIBUTES);
            break;
        default:
            throw new AssertionFailedException(Messages.bind(Messages.classpath_unknownKind, kindAttr));
        }

        if (unknownAttributes != null || unknownChildren != null) {
            UnknownXmlElements unknownXmlElements = new UnknownXmlElements();
            unknownXmlElements.attributes = unknownAttributes;
            unknownXmlElements.children = unknownChildren;
            unknownElements.put(path, unknownXmlElements);
        }

        return entry;
    }

    /*
     * Returns whether the given path as a ".." segment
     */
    public static boolean hasDotDot(IPath path) {
        for (int i = 0, length = path.segmentCount(); i < length; i++) {
            if (DOT_DOT.equals(path.segment(i)))
                return true;
        }
        return false;
    }

    public static NodeList getChildAttributes(String childName, NodeList children, boolean[] foundChildren) {
        for (int i = 0, length = foundChildren.length; i < length; i++) {
            Node node = children.item(i);
            if (childName.equals(node.getNodeName())) {
                foundChildren[i] = true;
                return node.getChildNodes();
            }
        }
        return null;
    }

    private static String removeAttribute(String nodeName, NamedNodeMap nodeMap) {
        Node node = removeNode(nodeName, nodeMap);
        if (node == null)
            return ""; // //$NON-NLS-1$
        return node.getNodeValue();
    }

    private static Node removeNode(String nodeName, NamedNodeMap nodeMap) {
        try {
            return nodeMap.removeNamedItem(nodeName);
        } catch (DOMException e) {
            if (e.code != DOMException.NOT_FOUND_ERR)
                throw e;
            return null;
        }
    }

    /*
     * Read the Class-Path clause of the manifest of the jar pointed by this path, and return
     * the corresponding paths.
     */
    public static IPath[] resolvedChainedLibraries(IPath jarPath) {
        ArrayList result = new ArrayList();
        resolvedChainedLibraries(jarPath, new HashSet(), result);
        if (result.size() == 0)
            return NO_PATHS;
        return (IPath[]) result.toArray(new IPath[result.size()]);
    }

    private static void resolvedChainedLibraries(IPath jarPath, HashSet visited, ArrayList result) {
        if (visited.contains(jarPath))
            return;
        visited.add(jarPath);
        JavaModelManager manager = JavaModelManager.getJavaModelManager();
        if (manager.isNonChainingJar(jarPath))
            return;
        List calledFileNames = getCalledFileNames(jarPath);
        if (calledFileNames == null) {
            manager.addNonChainingJar(jarPath);
        } else {
            Iterator calledFilesIterator = calledFileNames.iterator();
            IPath directoryPath = jarPath.removeLastSegments(1);
            while (calledFilesIterator.hasNext()) {
                String calledFileName = (String) calledFilesIterator.next();
                if (!directoryPath.isValidPath(calledFileName)) {
                    if (JavaModelManager.CP_RESOLVE_VERBOSE_FAILURE) {
                        Util.verbose("Invalid Class-Path entry " + calledFileName + " in manifest of jar file: " //$NON-NLS-1$//$NON-NLS-2$
                                + jarPath.toOSString());
                    }
                } else {
                    IPath calledJar = directoryPath.append(new Path(calledFileName));
                    // Ignore if segment count is Zero (https://bugs.eclipse.org/bugs/show_bug.cgi?id=308150)
                    if (calledJar.segmentCount() == 0) {
                        if (JavaModelManager.CP_RESOLVE_VERBOSE_FAILURE) {
                            Util.verbose("Invalid Class-Path entry " + calledFileName + " in manifest of jar file: " //$NON-NLS-1$//$NON-NLS-2$
                                    + jarPath.toOSString());
                        }
                        continue;
                    }
                    resolvedChainedLibraries(calledJar, visited, result);
                    result.add(calledJar);
                }
            }
        }
    }

    private static char[] getManifestContents(IPath jarPath) throws CoreException, IOException {
        // Try to read a cached manifest from the index
        if (JavaIndex.isEnabled()) {
            JavaIndex index = JavaIndex.getIndex();
            String location = JavaModelManager.getLocalFile(jarPath).getAbsolutePath();
            try (IReader reader = index.getNd().acquireReadLock()) {
                NdResourceFile resourceFile = index.getResourceFile(location.toCharArray());
                if (index.isUpToDate(resourceFile)) {
                    char[] manifestContent = resourceFile.getManifestContent().getChars();
                    if (manifestContent.length == 0) {
                        return null;
                    }
                    return manifestContent;
                }
            }
        }

        ZipFile zip = null;
        InputStream inputStream = null;
        JavaModelManager manager = JavaModelManager.getJavaModelManager();
        try {
            zip = manager.getZipFile(jarPath);
            ZipEntry manifest = zip.getEntry(TypeConstants.META_INF_MANIFEST_MF);
            if (manifest == null) {
                return null;
            }
            inputStream = zip.getInputStream(manifest);
            char[] chars = getInputStreamAsCharArray(inputStream, -1, UTF_8);
            return chars;
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    // best effort
                }
            }
            manager.closeZipFile(zip);
        }
    }

    private static List getCalledFileNames(IPath jarPath) {
        Object target = JavaModel.getTarget(jarPath,
                true/*check existence, otherwise the manifest cannot be read*/);
        if (!(target instanceof IFile || target instanceof File))
            return null;

        List calledFileNames = null;
        try {
            char[] manifestContents = getManifestContents(jarPath);
            if (manifestContents == null)
                return null;
            // non-null implies regular file
            ManifestAnalyzer analyzer = new ManifestAnalyzer();
            boolean success = analyzer.analyzeManifestContents(manifestContents);
            calledFileNames = analyzer.getCalledFileNames();
            if (!success || analyzer.getClasspathSectionsCount() == 1 && calledFileNames == null) {
                if (JavaModelManager.CP_RESOLVE_VERBOSE_FAILURE) {
                    Util.verbose("Invalid Class-Path header in manifest of jar file: " + jarPath.toOSString()); //$NON-NLS-1$
                }
                return null;
            } else if (analyzer.getClasspathSectionsCount() > 1) {
                if (JavaModelManager.CP_RESOLVE_VERBOSE_FAILURE) {
                    Util.verbose("Multiple Class-Path headers in manifest of jar file: " + jarPath.toOSString()); //$NON-NLS-1$
                }
                return null;
            }
        } catch (CoreException | IOException e) {
            // not a zip file
            if (JavaModelManager.CP_RESOLVE_VERBOSE_FAILURE) {
                Util.verbose("Could not read Class-Path header in manifest of jar file: " + jarPath.toOSString()); //$NON-NLS-1$
                e.printStackTrace();
            }
        }
        return calledFileNames;
    }

    /*
     * Resolves the ".." in the given path. Returns the given path if it contains no ".." segment.
     */
    public static IPath resolveDotDot(IPath reference, IPath path) {
        IPath newPath = null;
        IPath workspaceLocation = workspaceRoot.getLocation();
        if (reference == null || workspaceLocation.isPrefixOf(reference)) {
            for (int i = 0, length = path.segmentCount(); i < length; i++) {
                String segment = path.segment(i);
                if (DOT_DOT.equals(segment)) {
                    if (newPath == null) {
                        if (i == 0) {
                            newPath = workspaceLocation;
                        } else {
                            newPath = path.removeFirstSegments(i);
                        }
                    } else {
                        if (newPath.segmentCount() > 0) {
                            newPath = newPath.removeLastSegments(1);
                        } else {
                            newPath = workspaceLocation;
                        }
                    }
                } else if (newPath != null) {
                    if (newPath.equals(workspaceLocation) && workspaceRoot.getProject(segment).isAccessible()) {
                        newPath = new Path(segment).makeAbsolute();
                    } else {
                        newPath = newPath.append(segment);
                    }
                }
            }
        } else {
            for (int i = 0, length = path.segmentCount(); i < length; i++) {
                String segment = path.segment(i);
                if (DOT_DOT.equals(segment)) {
                    if (newPath == null) {
                        newPath = reference;
                    }
                    if (newPath.segmentCount() > 0) {
                        newPath = newPath.removeLastSegments(1);
                    }
                } else if (newPath != null) {
                    newPath = newPath.append(segment);
                }
            }
        }
        if (newPath == null)
            return path;
        return newPath;
    }

    /**
     * Encode some patterns into XML parameter tag
     */
    private static void encodePatterns(IPath[] patterns, String tag, Map parameters) {
        if (patterns != null && patterns.length > 0) {
            StringBuffer rule = new StringBuffer(10);
            for (int i = 0, max = patterns.length; i < max; i++) {
                if (i > 0)
                    rule.append('|');
                rule.append(patterns[i]);
            }
            parameters.put(tag, String.valueOf(rule));
        }
    }

    /**
     * Returns true if the given object is a classpath entry
     * with equivalent attributes.
     */
    @Override
    public boolean equals(Object object) {
        if (this == object)
            return true;
        if (object instanceof ClasspathEntry) {
            ClasspathEntry otherEntry = (ClasspathEntry) object;

            if (this.contentKind != otherEntry.getContentKind())
                return false;

            if (this.entryKind != otherEntry.getEntryKind())
                return false;

            if (this.isExported != otherEntry.isExported())
                return false;

            if (!this.path.equals(otherEntry.getPath()))
                return false;

            IPath otherPath = otherEntry.getSourceAttachmentPath();
            if (this.sourceAttachmentPath == null) {
                if (otherPath != null)
                    return false;
            } else {
                if (!this.sourceAttachmentPath.equals(otherPath))
                    return false;
            }

            otherPath = otherEntry.getSourceAttachmentRootPath();
            if (this.sourceAttachmentRootPath == null) {
                if (otherPath != null)
                    return false;
            } else {
                if (!this.sourceAttachmentRootPath.equals(otherPath))
                    return false;
            }

            if (!equalPatterns(this.inclusionPatterns, otherEntry.getInclusionPatterns()))
                return false;
            if (!equalPatterns(this.exclusionPatterns, otherEntry.getExclusionPatterns()))
                return false;
            AccessRuleSet otherRuleSet = otherEntry.getAccessRuleSet();
            if (getAccessRuleSet() != null) {
                if (!getAccessRuleSet().equals(otherRuleSet))
                    return false;
            } else if (otherRuleSet != null)
                return false;
            if (this.combineAccessRules != otherEntry.combineAccessRules())
                return false;
            otherPath = otherEntry.getOutputLocation();
            if (this.specificOutputLocation == null) {
                if (otherPath != null)
                    return false;
            } else {
                if (!this.specificOutputLocation.equals(otherPath))
                    return false;
            }
            if (!equalAttributes(this.extraAttributes, otherEntry.getExtraAttributes()))
                return false;
            return true;
        } else {
            return false;
        }
    }

    private static boolean equalAttributes(IClasspathAttribute[] firstAttributes,
            IClasspathAttribute[] secondAttributes) {
        if (firstAttributes != secondAttributes) {
            if (firstAttributes == null)
                return false;
            int length = firstAttributes.length;
            if (secondAttributes == null || secondAttributes.length != length)
                return false;
            for (int i = 0; i < length; i++) {
                if (!firstAttributes[i].equals(secondAttributes[i]))
                    return false;
            }
        }
        return true;
    }

    private static boolean equalPatterns(IPath[] firstPatterns, IPath[] secondPatterns) {
        if (firstPatterns != secondPatterns) {
            if (firstPatterns == null)
                return false;
            int length = firstPatterns.length;
            if (secondPatterns == null || secondPatterns.length != length)
                return false;
            for (int i = 0; i < length; i++) {
                // compare toStrings instead of IPaths
                // since IPath.equals is specified to ignore trailing separators
                if (!firstPatterns[i].toString().equals(secondPatterns[i].toString()))
                    return false;
            }
        }
        return true;
    }

    /**
     * @see IClasspathEntry#getAccessRules()
     */
    @Override
    public IAccessRule[] getAccessRules() {
        if (this.accessRuleSet == null)
            return NO_ACCESS_RULES;
        AccessRule[] rules = this.accessRuleSet.getAccessRules();
        int length = rules.length;
        if (length == 0)
            return NO_ACCESS_RULES;
        IAccessRule[] result = new IAccessRule[length];
        System.arraycopy(rules, 0, result, 0, length);
        return result;
    }

    public AccessRuleSet getAccessRuleSet() {
        return this.accessRuleSet;
    }

    /**
     * @see IClasspathEntry
     */
    @Override
    public int getContentKind() {
        return this.contentKind;
    }

    /**
     * @see IClasspathEntry
     */
    @Override
    public int getEntryKind() {
        return this.entryKind;
    }

    /**
     * @see IClasspathEntry#getExclusionPatterns()
     */
    @Override
    public IPath[] getExclusionPatterns() {
        return this.exclusionPatterns;
    }

    @Override
    public IClasspathAttribute[] getExtraAttributes() {
        return this.extraAttributes;
    }

    /**
     * @see IClasspathEntry#getExclusionPatterns()
     */
    @Override
    public IPath[] getInclusionPatterns() {
        return this.inclusionPatterns;
    }

    /**
     * @see IClasspathEntry#getOutputLocation()
     */
    @Override
    public IPath getOutputLocation() {
        return this.specificOutputLocation;
    }

    /**
     * @see IClasspathEntry
     */
    @Override
    public IPath getPath() {
        return this.path;
    }

    /**
     * @see IClasspathEntry
     */
    @Override
    public IPath getSourceAttachmentPath() {
        return this.sourceAttachmentPath;
    }

    /**
     * @see IClasspathEntry
     */
    @Override
    public IPath getSourceAttachmentRootPath() {
        return this.sourceAttachmentRootPath;
    }

    /**
     * Internal API: answer the path for external annotations (for null analysis) associated with
     * the given classpath entry.
     * Four shapes of paths are supported:
     * <ol>
     * <li>relative, variable (VAR/relpath): resolve classpath variable VAR and append relpath</li>
     * <li>relative, project (relpath): interpret relpath as a relative path within the given project</li>
     * <li>absolute, workspace (/Proj/relpath): an absolute path in the workspace</li>
     * <li>absolute, filesystem (/abspath): an absolute path in the filesystem</li>
     * </ol>
     * In case of ambiguity, workspace lookup has higher priority than filesystem lookup
     * (in fact filesystem paths are never validated).
     * 
     * @param entry classpath entry to work on
     * @param project project whose classpath we are analysing
     * @param resolve if true, any workspace-relative paths will be resolved to filesystem paths.
     * @return a path (in the workspace or filesystem-absolute) or null
     */
    public static IPath getExternalAnnotationPath(IClasspathEntry entry, IProject project, boolean resolve) {
        String rawAnnotationPath = getRawExternalAnnotationPath(entry);
        if (rawAnnotationPath != null) {
            IPath annotationPath = new Path(rawAnnotationPath);
            if (annotationPath.isAbsolute()) {
                if (!resolve)
                    return annotationPath;

                // try Workspace-absolute:
                IResource resource = project.getWorkspace().getRoot().findMember(annotationPath);
                if (resource != null) {
                    return resource.getLocation();
                } else if (new File(annotationPath.toOSString()).exists()) { // absolute, not in workspace, must be Filesystem-absolute
                    return annotationPath;
                }
                invalidExternalAnnotationPath(project);
            } else {
                // try Variable (always resolved):
                IPath resolved = JavaCore.getResolvedVariablePath(annotationPath);
                if (resolved != null)
                    return resolved;

                // Project-relative:
                if (project != null) {
                    if (resolve) {
                        IResource member = project.findMember(annotationPath);
                        if (member != null)
                            return member.getLocation();
                        invalidExternalAnnotationPath(project);
                    } else {
                        return new Path(project.getName()).append(annotationPath).makeAbsolute();
                    }
                }
            }
        }
        return null;
    }

    /**
     * Answer the raw external annotation path as specified in .classpath, or null.
     * @param entry where to look
     * @return the attached external annotation path, or null.
     */
    static String getRawExternalAnnotationPath(IClasspathEntry entry) {
        return getExtraAttribute(entry, IClasspathAttribute.EXTERNAL_ANNOTATION_PATH);
    }

    private static void invalidExternalAnnotationPath(IProject project) {
        try {
            IMarker[] markers = project.findMarkers(IJavaModelMarker.BUILDPATH_PROBLEM_MARKER, false,
                    IResource.DEPTH_ZERO);
            for (int i = 0, l = markers.length; i < l; i++) {
                if (markers[i].getAttribute(IMarker.SEVERITY, -1) == IMarker.SEVERITY_ERROR)
                    return; // one marker is enough
            }
        } catch (CoreException ce) {
            return;
        }
        // no buildpath marker yet, trigger validation to create one:
        new ClasspathValidation((JavaProject) JavaCore.create(project)).validate();
    }

    private IJavaModelStatus validateExternalAnnotationPath(IJavaProject javaProject, IPath annotationPath) {
        IProject project = javaProject.getProject();
        if (annotationPath.isAbsolute()) {
            if (project.getWorkspace().getRoot().exists(annotationPath) // workspace absolute
                    || new File(annotationPath.toOSString()).exists()) // file system abolute
            {
                return null;
            }
        } else {
            if (JavaCore.getResolvedVariablePath(annotationPath) != null // variable (relative)
                    || project.exists(annotationPath)) // project relative
            {
                return null;
            }
        }
        return new JavaModelStatus(IJavaModelStatusConstants.CP_INVALID_EXTERNAL_ANNOTATION_PATH, javaProject,
                Messages.bind(Messages.classpath_invalidExternalAnnotationPath,
                        new String[] { annotationPath.toString(), project.getName(), this.path.toString() }));
    }

    public static String getExtraAttribute(IClasspathEntry entry, String attributeName) {
        IClasspathAttribute[] extraAttributes = entry.getExtraAttributes();
        for (int i = 0, length = extraAttributes.length; i < length; i++) {
            IClasspathAttribute attribute = extraAttributes[i];
            if (attributeName.equals(attribute.getName())) {
                return attribute.getValue();
            }
        }
        return null;
    }

    @Override
    public IClasspathEntry getReferencingEntry() {
        return this.referencingEntry;
    }

    /**
     * Returns the hash code for this classpath entry
     */
    @Override
    public int hashCode() {
        return this.path.hashCode();
    }

    /**
     * @see IClasspathEntry#isExported()
     */
    @Override
    public boolean isExported() {
        return this.isExported;
    }

    public boolean isOptional() {
        for (int i = 0, length = this.extraAttributes.length; i < length; i++) {
            IClasspathAttribute attribute = this.extraAttributes[i];
            if (IClasspathAttribute.OPTIONAL.equals(attribute.getName()) && "true".equals(attribute.getValue())) //$NON-NLS-1$
                return true;
        }
        return false;
    }

    public boolean isModular() {
        for (int i = 0, length = this.extraAttributes.length; i < length; i++) {
            IClasspathAttribute attribute = this.extraAttributes[i];
            if (IClasspathAttribute.MODULE.equals(attribute.getName()) && "true".equals(attribute.getValue())) //$NON-NLS-1$
                return true;
        }
        return false;
    }

    public String getSourceAttachmentEncoding() {
        for (int i = 0, length = this.extraAttributes.length; i < length; i++) {
            IClasspathAttribute attribute = this.extraAttributes[i];
            if (IClasspathAttribute.SOURCE_ATTACHMENT_ENCODING.equals(attribute.getName()))
                return attribute.getValue();
        }
        return null;
    }

    /**
     * Returns the kind of a <code>PackageFragmentRoot</code> from its <code>String</code> form.
     */
    static int kindFromString(String kindStr) {

        if (kindStr.equalsIgnoreCase("prj")) //$NON-NLS-1$
            return IClasspathEntry.CPE_PROJECT;
        if (kindStr.equalsIgnoreCase("var")) //$NON-NLS-1$
            return IClasspathEntry.CPE_VARIABLE;
        if (kindStr.equalsIgnoreCase("con")) //$NON-NLS-1$
            return IClasspathEntry.CPE_CONTAINER;
        if (kindStr.equalsIgnoreCase("src")) //$NON-NLS-1$
            return IClasspathEntry.CPE_SOURCE;
        if (kindStr.equalsIgnoreCase("lib")) //$NON-NLS-1$
            return IClasspathEntry.CPE_LIBRARY;
        if (kindStr.equalsIgnoreCase("output")) //$NON-NLS-1$
            return ClasspathEntry.K_OUTPUT;
        return -1;
    }

    /**
     * Returns a <code>String</code> for the kind of a class path entry.
     */
    static String kindToString(int kind) {

        switch (kind) {
        case IClasspathEntry.CPE_PROJECT:
            return "src"; // backward compatibility //$NON-NLS-1$
        case IClasspathEntry.CPE_SOURCE:
            return "src"; //$NON-NLS-1$
        case IClasspathEntry.CPE_LIBRARY:
            return "lib"; //$NON-NLS-1$
        case IClasspathEntry.CPE_VARIABLE:
            return "var"; //$NON-NLS-1$
        case IClasspathEntry.CPE_CONTAINER:
            return "con"; //$NON-NLS-1$
        case ClasspathEntry.K_OUTPUT:
            return "output"; //$NON-NLS-1$
        default:
            return "unknown"; //$NON-NLS-1$
        }
    }

    /*
     * Backward compatibility: only accessible and non-accessible files are supported.
     */
    public static IAccessRule[] getAccessRules(IPath[] accessibleFiles, IPath[] nonAccessibleFiles) {
        int accessibleFilesLength = accessibleFiles == null ? 0 : accessibleFiles.length;
        int nonAccessibleFilesLength = nonAccessibleFiles == null ? 0 : nonAccessibleFiles.length;
        int length = accessibleFilesLength + nonAccessibleFilesLength;
        if (length == 0)
            return null;
        IAccessRule[] accessRules = new IAccessRule[length];
        for (int i = 0; i < accessibleFilesLength; i++) {
            accessRules[i] = JavaCore.newAccessRule(accessibleFiles[i], IAccessRule.K_ACCESSIBLE);
        }
        for (int i = 0; i < nonAccessibleFilesLength; i++) {
            accessRules[accessibleFilesLength + i] = JavaCore.newAccessRule(nonAccessibleFiles[i],
                    IAccessRule.K_NON_ACCESSIBLE);
        }
        return accessRules;
    }

    /**
     * Returns a printable representation of this classpath entry.
     */
    @Override
    public String toString() {
        StringBuffer buffer = new StringBuffer();
        Object target = JavaModel.getTarget(getPath(), true);
        if (target instanceof File)
            buffer.append(getPath().toOSString());
        else
            buffer.append(String.valueOf(getPath()));
        buffer.append('[');
        switch (getEntryKind()) {
        case IClasspathEntry.CPE_LIBRARY:
            buffer.append("CPE_LIBRARY"); //$NON-NLS-1$
            break;
        case IClasspathEntry.CPE_PROJECT:
            buffer.append("CPE_PROJECT"); //$NON-NLS-1$
            break;
        case IClasspathEntry.CPE_SOURCE:
            buffer.append("CPE_SOURCE"); //$NON-NLS-1$
            break;
        case IClasspathEntry.CPE_VARIABLE:
            buffer.append("CPE_VARIABLE"); //$NON-NLS-1$
            break;
        case IClasspathEntry.CPE_CONTAINER:
            buffer.append("CPE_CONTAINER"); //$NON-NLS-1$
            break;
        }
        buffer.append("]["); //$NON-NLS-1$
        switch (getContentKind()) {
        case IPackageFragmentRoot.K_BINARY:
            buffer.append("K_BINARY"); //$NON-NLS-1$
            break;
        case IPackageFragmentRoot.K_SOURCE:
            buffer.append("K_SOURCE"); //$NON-NLS-1$
            break;
        case ClasspathEntry.K_OUTPUT:
            buffer.append("K_OUTPUT"); //$NON-NLS-1$
            break;
        }
        buffer.append(']');
        if (getSourceAttachmentPath() != null) {
            buffer.append("[sourcePath:"); //$NON-NLS-1$
            buffer.append(getSourceAttachmentPath());
            buffer.append(']');
        }
        if (getSourceAttachmentRootPath() != null) {
            buffer.append("[rootPath:"); //$NON-NLS-1$
            buffer.append(getSourceAttachmentRootPath());
            buffer.append(']');
        }
        buffer.append("[isExported:"); //$NON-NLS-1$
        buffer.append(this.isExported);
        buffer.append(']');
        IPath[] patterns = this.inclusionPatterns;
        int length;
        if ((length = patterns == null ? 0 : patterns.length) > 0) {
            buffer.append("[including:"); //$NON-NLS-1$
            for (int i = 0; i < length; i++) {
                buffer.append(patterns[i]);
                if (i != length - 1) {
                    buffer.append('|');
                }
            }
            buffer.append(']');
        }
        patterns = this.exclusionPatterns;
        if ((length = patterns == null ? 0 : patterns.length) > 0) {
            buffer.append("[excluding:"); //$NON-NLS-1$
            for (int i = 0; i < length; i++) {
                buffer.append(patterns[i]);
                if (i != length - 1) {
                    buffer.append('|');
                }
            }
            buffer.append(']');
        }
        if (this.accessRuleSet != null) {
            buffer.append('[');
            buffer.append(this.accessRuleSet.toString(false/*on one line*/));
            buffer.append(']');
        }
        if (this.entryKind == CPE_PROJECT) {
            buffer.append("[combine access rules:"); //$NON-NLS-1$
            buffer.append(this.combineAccessRules);
            buffer.append(']');
        }
        if (getOutputLocation() != null) {
            buffer.append("[output:"); //$NON-NLS-1$
            buffer.append(getOutputLocation());
            buffer.append(']');
        }
        if ((length = this.extraAttributes == null ? 0 : this.extraAttributes.length) > 0) {
            buffer.append("[attributes:"); //$NON-NLS-1$
            for (int i = 0; i < length; i++) {
                buffer.append(this.extraAttributes[i]);
                if (i != length - 1) {
                    buffer.append(',');
                }
            }
            buffer.append(']');
        }
        return buffer.toString();
    }

    public ClasspathEntry resolvedDotDot(IPath reference) {
        IPath resolvedPath = resolveDotDot(reference, this.path);
        if (resolvedPath == this.path)
            return this;
        return new ClasspathEntry(getContentKind(), getEntryKind(), resolvedPath, this.inclusionPatterns,
                this.exclusionPatterns, getSourceAttachmentPath(), getSourceAttachmentRootPath(),
                getOutputLocation(), this.getReferencingEntry(), this.isExported, getAccessRules(),
                this.combineAccessRules, this.extraAttributes);
    }

    /*
     * Read the Class-Path clause of the manifest of the jar pointed by this entry, and return
     * the corresponding library entries.
     */
    public ClasspathEntry[] resolvedChainedLibraries() {
        IPath[] paths = resolvedChainedLibraries(getPath());
        int length = paths.length;
        if (length == 0)
            return NO_ENTRIES;
        ClasspathEntry[] result = new ClasspathEntry[length];
        for (int i = 0; i < length; i++) {
            // Chained(referenced) libraries can have their own attachment path. Hence, set them to null
            result[i] = new ClasspathEntry(getContentKind(), getEntryKind(), paths[i], this.inclusionPatterns,
                    this.exclusionPatterns, null, null, getOutputLocation(), this, this.isExported,
                    getAccessRules(), this.combineAccessRules, NO_EXTRA_ATTRIBUTES);
        }
        return result;
    }

    /**
     * Answers an ID which is used to distinguish entries during package
     * fragment root computations
     */
    public String rootID() {

        if (this.rootID == null) {
            switch (this.entryKind) {
            case IClasspathEntry.CPE_LIBRARY:
                this.rootID = "[LIB]" + this.path; //$NON-NLS-1$
                break;
            case IClasspathEntry.CPE_PROJECT:
                this.rootID = "[PRJ]" + this.path; //$NON-NLS-1$
                break;
            case IClasspathEntry.CPE_SOURCE:
                this.rootID = "[SRC]" + this.path; //$NON-NLS-1$
                break;
            case IClasspathEntry.CPE_VARIABLE:
                this.rootID = "[VAR]" + this.path; //$NON-NLS-1$
                break;
            case IClasspathEntry.CPE_CONTAINER:
                this.rootID = "[CON]" + this.path; //$NON-NLS-1$
                break;
            default:
                this.rootID = ""; //$NON-NLS-1$
                break;
            }
        }
        return this.rootID;
    }

    /**
     * @see IClasspathEntry
     * @deprecated
     */
    @Override
    public IClasspathEntry getResolvedEntry() {

        return JavaCore.getResolvedClasspathEntry(this);
    }

    /**
     * This function computes the URL of the index location for this classpath entry. It returns null if the URL is
     * invalid.
     */
    public URL getLibraryIndexLocation() {
        switch (getEntryKind()) {
        case IClasspathEntry.CPE_LIBRARY:
            if (SHARED_INDEX_LOCATION != null) {
                try {
                    String pathString = getPath().toPortableString();
                    CRC32 checksumCalculator = new CRC32();
                    checksumCalculator.update(pathString.getBytes());
                    String fileName = Long.toString(checksumCalculator.getValue()) + ".index"; //$NON-NLS-1$
                    return new URL("file", null, Paths.get(SHARED_INDEX_LOCATION, fileName).toString()); //$NON-NLS-1$
                } catch (MalformedURLException e1) {
                    Util.log(e1); // should not happen if protocol known (eg. 'file')
                }
            }
            break;
        case IClasspathEntry.CPE_VARIABLE:
            break;
        default:
            return null;
        }
        if (this.extraAttributes == null)
            return null;
        for (int i = 0; i < this.extraAttributes.length; i++) {
            IClasspathAttribute attrib = this.extraAttributes[i];
            if (IClasspathAttribute.INDEX_LOCATION_ATTRIBUTE_NAME.equals(attrib.getName())) {
                String value = attrib.getValue();
                try {
                    return new URL(value);
                } catch (MalformedURLException e) {
                    return null;
                }
            }
        }
        return null;
    }

    public boolean ignoreOptionalProblems() {
        if (this.entryKind == IClasspathEntry.CPE_SOURCE) {
            for (int i = 0; i < this.extraAttributes.length; i++) {
                IClasspathAttribute attrib = this.extraAttributes[i];
                if (IClasspathAttribute.IGNORE_OPTIONAL_PROBLEMS.equals(attrib.getName())) {
                    return "true".equals(attrib.getValue()); //$NON-NLS-1$
                }
            }
        }
        return false;
    }

    /**
     * Validate a given classpath and output location for a project, using the following rules:
     * <ul>
     *   <li> Classpath entries cannot collide with each other; that is, all entry paths must be unique.
     *   <li> The project output location path cannot be null, must be absolute and located inside the project.
     *   <li> Specific output locations (specified on source entries) can be null, if not they must be located inside the project,
     *   <li> A project entry cannot refer to itself directly (that is, a project cannot prerequisite itself).
     *   <li> Classpath entries or output locations cannot coincidate or be nested in each other, except for the following scenario listed below:
     *      <ul><li> A source folder can coincidate with its own output location, in which case this output can then contain library archives.
     *                     However, a specific output location cannot coincidate with any library or a distinct source folder than the one referring to it. </li>
     *              <li> A source/library folder can be nested in any source folder as long as the nested folder is excluded from the enclosing one. </li>
     *          <li> An output location can be nested in a source folder, if the source folder coincidates with the project itself, or if the output
     *                location is excluded from the source folder. </li>
     *      </ul>
     * </ul>
     *
     *  Note that the classpath entries are not validated automatically. Only bound variables or containers are considered
     *  in the checking process (this allows to perform a consistency check on a classpath which has references to
     *  yet non existing projects, folders, ...).
     *  <p>
     *  This validation is intended to anticipate classpath issues prior to assigning it to a project. In particular, it will automatically
     *  be performed during the classpath setting operation (if validation fails, the classpath setting will not complete).
     *  <p>
     * @param javaProject the given java project
     * @param rawClasspath a given classpath
     * @param projectOutputLocation a given output location
     * @return a status object with code <code>IStatus.OK</code> if
     *      the given classpath and output location are compatible, otherwise a status
     *      object indicating what is wrong with the classpath or output location
     */
    public static IJavaModelStatus validateClasspath(IJavaProject javaProject, IClasspathEntry[] rawClasspath,
            IPath projectOutputLocation) {

        IProject project = javaProject.getProject();
        IPath projectPath = project.getFullPath();
        String projectName = javaProject.getElementName();

        /* validate output location */
        if (projectOutputLocation == null) {
            return new JavaModelStatus(IJavaModelStatusConstants.NULL_PATH);
        }
        if (projectOutputLocation.isAbsolute()) {
            if (!projectPath.isPrefixOf(projectOutputLocation)) {
                return new JavaModelStatus(IJavaModelStatusConstants.PATH_OUTSIDE_PROJECT, javaProject,
                        projectOutputLocation.toString());
            }
        } else {
            return new JavaModelStatus(IJavaModelStatusConstants.RELATIVE_PATH, projectOutputLocation);
        }

        boolean hasSource = false;
        boolean hasLibFolder = false;

        // tolerate null path, it will be reset to default
        if (rawClasspath == null)
            return JavaModelStatus.VERIFIED_OK;

        // check duplicate entries on raw classpath only (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=175226 )
        int rawLength = rawClasspath.length;
        HashSet pathes = new HashSet(rawLength);
        for (int i = 0; i < rawLength; i++) {
            IPath entryPath = rawClasspath[i].getPath();
            if (!pathes.add(entryPath)) {
                String entryPathMsg = projectName.equals(entryPath.segment(0))
                        ? entryPath.removeFirstSegments(1).toString()
                        : entryPath.makeRelative().toString();
                return new JavaModelStatus(IJavaModelStatusConstants.NAME_COLLISION, Messages
                        .bind(Messages.classpath_duplicateEntryPath, new String[] { entryPathMsg, projectName }));
            }
        }

        // retrieve resolved classpath
        IClasspathEntry[] classpath;
        try {
            // don't resolve chained libraries: see https://bugs.eclipse.org/bugs/show_bug.cgi?id=259685
            classpath = ((JavaProject) javaProject).resolveClasspath(rawClasspath,
                    false/*don't use previous session*/,
                    false/*don't resolve chained libraries*/).resolvedClasspath;
        } catch (JavaModelException e) {
            return e.getJavaModelStatus();
        }

        int outputCount = 1;
        IPath[] outputLocations = new IPath[classpath.length + 1];
        boolean[] allowNestingInOutputLocations = new boolean[classpath.length + 1];
        outputLocations[0] = projectOutputLocation;

        // retrieve and check output locations
        IPath potentialNestedOutput = null; // for error reporting purpose
        int sourceEntryCount = 0;
        boolean disableExclusionPatterns = JavaCore.DISABLED
                .equals(javaProject.getOption(JavaCore.CORE_ENABLE_CLASSPATH_EXCLUSION_PATTERNS, true));
        boolean disableCustomOutputLocations = JavaCore.DISABLED
                .equals(javaProject.getOption(JavaCore.CORE_ENABLE_CLASSPATH_MULTIPLE_OUTPUT_LOCATIONS, true));
        ArrayList<IClasspathEntry> testSourcesFolders = new ArrayList<>();
        HashSet<IPath> mainOutputLocations = new HashSet<>();
        for (IClasspathEntry resolvedEntry : classpath) {
            if (disableExclusionPatterns && ((resolvedEntry.getInclusionPatterns() != null
                    && resolvedEntry.getInclusionPatterns().length > 0)
                    || (resolvedEntry.getExclusionPatterns() != null
                            && resolvedEntry.getExclusionPatterns().length > 0))) {
                return new JavaModelStatus(IJavaModelStatusConstants.DISABLED_CP_EXCLUSION_PATTERNS, javaProject,
                        resolvedEntry.getPath());
            }
            switch (resolvedEntry.getEntryKind()) {
            case IClasspathEntry.CPE_SOURCE:
                sourceEntryCount++;
                boolean isTest = resolvedEntry.isTest();
                if (isTest) {
                    testSourcesFolders.add(resolvedEntry);
                }

                IPath customOutput;
                if ((customOutput = resolvedEntry.getOutputLocation()) != null) {

                    if (disableCustomOutputLocations) {
                        return new JavaModelStatus(IJavaModelStatusConstants.DISABLED_CP_MULTIPLE_OUTPUT_LOCATIONS,
                                javaProject, resolvedEntry.getPath());
                    }
                    // ensure custom output is in project
                    if (customOutput.isAbsolute()) {
                        if (!javaProject.getPath().isPrefixOf(customOutput)) {
                            return new JavaModelStatus(IJavaModelStatusConstants.PATH_OUTSIDE_PROJECT, javaProject,
                                    customOutput.toString());
                        }
                    } else {
                        return new JavaModelStatus(IJavaModelStatusConstants.RELATIVE_PATH, customOutput);
                    }
                    if (!isTest) {
                        mainOutputLocations.add(customOutput);
                    }
                    // ensure custom output doesn't conflict with other outputs
                    // check exact match
                    if (Util.indexOfMatchingPath(customOutput, outputLocations, outputCount) != -1) {
                        continue; // already found
                    }
                    // accumulate all outputs, will check nesting once all available (to handle ordering issues)
                    outputLocations[outputCount++] = customOutput;
                }
            }
        }
        // check nesting across output locations
        for (int i = 1 /*no check for default output*/ ; i < outputCount; i++) {
            IPath customOutput = outputLocations[i];
            int index;
            // check nesting
            if ((index = Util.indexOfEnclosingPath(customOutput, outputLocations, outputCount)) != -1
                    && index != i) {
                if (index == 0) {
                    // custom output is nested in project's output: need to check if all source entries have a custom
                    // output before complaining
                    if (potentialNestedOutput == null)
                        potentialNestedOutput = customOutput;
                } else {
                    return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH,
                            Messages.bind(Messages.classpath_cannotNestOutputInOutput,
                                    new String[] { customOutput.makeRelative().toString(),
                                            outputLocations[index].makeRelative().toString() }));
                }
            }
        }
        // allow custom output nesting in project's output if all source entries have a custom output
        if (sourceEntryCount <= outputCount - 1) {
            allowNestingInOutputLocations[0] = true;
        } else if (potentialNestedOutput != null) {
            return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH,
                    Messages.bind(Messages.classpath_cannotNestOutputInOutput,
                            new String[] { potentialNestedOutput.makeRelative().toString(),
                                    outputLocations[0].makeRelative().toString() }));
        } else {
            if (sourceEntryCount > testSourcesFolders.size()) {
                // if there are source folders with main sources, treat project output location as main output location
                mainOutputLocations.add(outputLocations[0]);
            }
        }
        for (IClasspathEntry resolvedEntry : testSourcesFolders) {
            IPath customOutput;
            if ((customOutput = resolvedEntry.getOutputLocation()) != null) {
                if (mainOutputLocations.contains(customOutput)) {
                    return new JavaModelStatus(
                            IJavaModelStatusConstants.TEST_OUTPUT_FOLDER_MUST_BE_SEPARATE_FROM_MAIN_OUTPUT_FOLDERS,
                            javaProject, resolvedEntry.getPath());
                }
            } else {
                if (sourceEntryCount > testSourcesFolders.size()) {
                    return new JavaModelStatus(
                            IJavaModelStatusConstants.TEST_SOURCE_REQUIRES_SEPARATE_OUTPUT_LOCATION, javaProject,
                            resolvedEntry.getPath());
                }
            }
        }

        for (IClasspathEntry resolvedEntry : classpath) {
            IPath path = resolvedEntry.getPath();
            int index;
            switch (resolvedEntry.getEntryKind()) {

            case IClasspathEntry.CPE_SOURCE:
                hasSource = true;
                if ((index = Util.indexOfMatchingPath(path, outputLocations, outputCount)) != -1) {
                    allowNestingInOutputLocations[index] = true;
                }
                break;

            case IClasspathEntry.CPE_LIBRARY:
                Object target = JavaModel.getTarget(path, false/*don't check resource existence*/);
                hasLibFolder |= target instanceof IContainer;
                if ((index = Util.indexOfMatchingPath(path, outputLocations, outputCount)) != -1) {
                    allowNestingInOutputLocations[index] = true;
                }
                break;
            }
        }
        if (!hasSource && !hasLibFolder) { // if no source and no lib folder, then allowed
            for (int i = 0; i < outputCount; i++)
                allowNestingInOutputLocations[i] = true;
        }

        // check all entries
        for (IClasspathEntry entry : classpath) {
            if (entry == null)
                continue;
            IPath entryPath = entry.getPath();
            int kind = entry.getEntryKind();

            // no further check if entry coincidates with project or output location
            if (entryPath.equals(projectPath)) {
                // complain if self-referring project entry
                if (kind == IClasspathEntry.CPE_PROJECT) {
                    return new JavaModelStatus(IJavaModelStatusConstants.INVALID_PATH, Messages
                            .bind(Messages.classpath_cannotReferToItself, entryPath.makeRelative().toString()));
                }
                // tolerate nesting output in src if src==prj
                continue;
            }

            // allow nesting source entries in each other as long as the outer entry excludes the inner one
            if (kind == IClasspathEntry.CPE_SOURCE || (kind == IClasspathEntry.CPE_LIBRARY
                    && (JavaModel.getTarget(entryPath, false/*don't check existence*/) instanceof IContainer))) {
                for (IClasspathEntry otherEntry : classpath) {
                    if (otherEntry == null)
                        continue;
                    int otherKind = otherEntry.getEntryKind();
                    IPath otherPath = otherEntry.getPath();
                    if (entry != otherEntry && (otherKind == IClasspathEntry.CPE_SOURCE
                            || (otherKind == IClasspathEntry.CPE_LIBRARY && (JavaModel.getTarget(otherPath,
                                    false/*don't check existence*/) instanceof IContainer)))) {
                        char[][] inclusionPatterns, exclusionPatterns;
                        if (otherPath.isPrefixOf(entryPath) && !otherPath.equals(entryPath) && !Util.isExcluded(
                                entryPath.append("*"), //$NON-NLS-1$
                                inclusionPatterns = ((ClasspathEntry) otherEntry).fullInclusionPatternChars(),
                                exclusionPatterns = ((ClasspathEntry) otherEntry).fullExclusionPatternChars(),
                                false)) {
                            String exclusionPattern = entryPath.removeFirstSegments(otherPath.segmentCount())
                                    .segment(0);
                            if (Util.isExcluded(entryPath, inclusionPatterns, exclusionPatterns, false)) {
                                return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH,
                                        Messages.bind(Messages.classpath_mustEndWithSlash, new String[] {
                                                exclusionPattern, entryPath.makeRelative().toString() }));
                            } else {
                                if (otherKind == IClasspathEntry.CPE_SOURCE) {
                                    exclusionPattern += '/';
                                    if (!disableExclusionPatterns) {
                                        return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH,
                                                Messages.bind(Messages.classpath_cannotNestEntryInEntry,
                                                        new String[] { entryPath.makeRelative().toString(),
                                                                otherEntry.getPath().makeRelative().toString(),
                                                                exclusionPattern }));
                                    } else {
                                        return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH,
                                                Messages.bind(Messages.classpath_cannotNestEntryInEntryNoExclusion,
                                                        new String[] { entryPath.makeRelative().toString(),
                                                                otherEntry.getPath().makeRelative().toString(),
                                                                exclusionPattern }));
                                    }
                                } else {
                                    return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH,
                                            Messages.bind(Messages.classpath_cannotNestEntryInLibrary,
                                                    new String[] { entryPath.makeRelative().toString(),
                                                            otherEntry.getPath().makeRelative().toString() }));
                                }
                            }
                        }
                    }
                }
            }

            // prevent nesting output location inside entry unless enclosing is a source entry which explicitly exclude the output location
            char[][] inclusionPatterns = ((ClasspathEntry) entry).fullInclusionPatternChars();
            char[][] exclusionPatterns = ((ClasspathEntry) entry).fullExclusionPatternChars();
            for (int j = 0; j < outputCount; j++) {
                IPath currentOutput = outputLocations[j];
                if (entryPath.equals(currentOutput))
                    continue;
                if (entryPath.isPrefixOf(currentOutput)) {
                    if (kind != IClasspathEntry.CPE_SOURCE
                            || !Util.isExcluded(currentOutput, inclusionPatterns, exclusionPatterns, true)) {
                        return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH,
                                Messages.bind(Messages.classpath_cannotNestOutputInEntry,
                                        new String[] { currentOutput.makeRelative().toString(),
                                                entryPath.makeRelative().toString() }));
                    }
                }
            }

            // prevent nesting entry inside output location - when distinct from project or a source folder
            for (int j = 0; j < outputCount; j++) {
                if (allowNestingInOutputLocations[j])
                    continue;
                IPath currentOutput = outputLocations[j];
                if (currentOutput.isPrefixOf(entryPath)) {
                    return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH,
                            Messages.bind(Messages.classpath_cannotNestEntryInOutput,
                                    new String[] { entryPath.makeRelative().toString(),
                                            currentOutput.makeRelative().toString() }));
                }
            }
        }
        // ensure that no specific output is coincidating with another source folder (only allowed if matching current source folder)
        // 36465 - for 2.0 backward compatibility, only check specific output locations (the default can still coincidate)
        // perform one separate iteration so as to not take precedence over previously checked scenarii (in particular should
        // diagnose nesting source folder issue before this one, for example, [src]"Project/", [src]"Project/source/" and output="Project/" should
        // first complain about missing exclusion pattern
        IJavaModelStatus cachedStatus = null;
        for (IClasspathEntry entry : classpath) {
            if (entry == null)
                continue;
            IPath entryPath = entry.getPath();
            int kind = entry.getEntryKind();

            // Build some common strings for status message
            boolean isProjectRelative = projectName.equals(entryPath.segment(0));
            String entryPathMsg = isProjectRelative ? entryPath.removeFirstSegments(1).toString()
                    : entryPath.makeRelative().toString();

            if (kind == IClasspathEntry.CPE_SOURCE) {
                IPath output = entry.getOutputLocation();
                if (output == null)
                    output = projectOutputLocation; // if no specific output, still need to check using default output (this line would check default output)
                for (IClasspathEntry otherEntry : classpath) {
                    if (otherEntry == entry)
                        continue;

                    switch (otherEntry.getEntryKind()) {
                    case IClasspathEntry.CPE_SOURCE:
                        // Bug 287164 : Report errors of overlapping output locations only if the user sets the corresponding preference.
                        // The check is required for backward compatibility with bug-fix 36465.
                        String option = javaProject
                                .getOption(JavaCore.CORE_OUTPUT_LOCATION_OVERLAPPING_ANOTHER_SOURCE, true);
                        if (otherEntry.getPath().equals(output) && !JavaCore.IGNORE.equals(option)) {
                            boolean opStartsWithProject = projectName.equals(otherEntry.getPath().segment(0));
                            String otherPathMsg = opStartsWithProject
                                    ? otherEntry.getPath().removeFirstSegments(1).toString()
                                    : otherEntry.getPath().makeRelative().toString();
                            if (JavaCore.ERROR.equals(option)) {
                                return new JavaModelStatus(IStatus.ERROR,
                                        IJavaModelStatusConstants.OUTPUT_LOCATION_OVERLAPPING_ANOTHER_SOURCE,
                                        Messages.bind(Messages.classpath_cannotUseDistinctSourceFolderAsOutput,
                                                new String[] { entryPathMsg, otherPathMsg, projectName }));
                            }
                            if (cachedStatus == null) {
                                // Note that the isOK() is being overridden to return true. This is an exceptional scenario
                                cachedStatus = new JavaModelStatus(IStatus.OK,
                                        IJavaModelStatusConstants.OUTPUT_LOCATION_OVERLAPPING_ANOTHER_SOURCE,
                                        Messages.bind(Messages.classpath_cannotUseDistinctSourceFolderAsOutput,
                                                new String[] { entryPathMsg, otherPathMsg, projectName })) {
                                    @Override
                                    public boolean isOK() {
                                        return true;
                                    }
                                };
                            }
                        }
                        break;
                    case IClasspathEntry.CPE_LIBRARY:
                        if (output != projectOutputLocation && otherEntry.getPath().equals(output)) {
                            boolean opStartsWithProject = projectName.equals(otherEntry.getPath().segment(0));
                            String otherPathMsg = opStartsWithProject
                                    ? otherEntry.getPath().removeFirstSegments(1).toString()
                                    : otherEntry.getPath().makeRelative().toString();
                            return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH,
                                    Messages.bind(Messages.classpath_cannotUseLibraryAsOutput,
                                            new String[] { entryPathMsg, otherPathMsg, projectName }));
                        }
                    }
                }
            }
        }

        if (hasSource && testSourcesFolders.size() == 0 && !JavaCore.IGNORE
                .equals(javaProject.getOption(JavaCore.CORE_MAIN_ONLY_PROJECT_HAS_TEST_ONLY_DEPENDENCY, true))) {
            for (IClasspathEntry entry : classpath) {
                if (entry == null)
                    continue;
                IPath entryPath = entry.getPath();
                if (entry.getEntryKind() == IClasspathEntry.CPE_PROJECT) {
                    if (entryPath.isAbsolute() && entryPath.segmentCount() == 1) {
                        IProject prereqProjectRsc = workspaceRoot.getProject(entryPath.segment(0));
                        IJavaProject prereqProject = JavaCore.create(prereqProjectRsc);
                        boolean hasMain = false;
                        boolean hasTest = false;
                        try {
                            for (IClasspathEntry nested : prereqProject.getRawClasspath()) {
                                if (nested.getEntryKind() == IClasspathEntry.CPE_SOURCE) {
                                    if (nested.isTest()) {
                                        hasTest = true;
                                    } else {
                                        hasMain = true;
                                    }
                                    if (hasTest && hasMain)
                                        break;
                                }
                            }
                        } catch (JavaModelException e) {
                            // is reported elsewhere
                        }
                        if (hasTest && !hasMain) {
                            return new JavaModelStatus(
                                    IJavaModelStatusConstants.MAIN_ONLY_PROJECT_DEPENDS_ON_TEST_ONLY_PROJECT,
                                    Messages.bind(Messages.classpath_main_only_project_depends_on_test_only_project,
                                            new String[] { prereqProject.getElementName() }));
                        }
                    }
                }
            }
        }

        // NOTE: The above code that checks for IJavaModelStatusConstants.OUTPUT_LOCATION_OVERLAPPING_ANOTHER_SOURCE, can be configured to return
        // a WARNING status and hence should be at the end of this validation method. Any other code that might return a more severe ERROR should be 
        // inserted before the mentioned code.
        if (cachedStatus != null)
            return cachedStatus;

        return JavaModelStatus.VERIFIED_OK;
    }

    /**
     * Returns a Java model status describing the problem related to this classpath entry if any,
     * a status object with code <code>IStatus.OK</code> if the entry is fine (that is, if the
     * given classpath entry denotes a valid element to be referenced onto a classpath).
     *
     * @param project the given java project
     * @param entry the given classpath entry
     * @param checkSourceAttachment a flag to determine if source attachment should be checked
     * @param referredByContainer flag indicating whether the given entry is referred by a classpath container
     * @return a java model status describing the problem related to this classpath entry if any, a status object with code <code>IStatus.OK</code> if the entry is fine
     */
    public static IJavaModelStatus validateClasspathEntry(IJavaProject project, IClasspathEntry entry,
            boolean checkSourceAttachment, boolean referredByContainer) {
        if (entry.getEntryKind() == IClasspathEntry.CPE_LIBRARY) {
            JavaModelManager.getJavaModelManager().removeFromInvalidArchiveCache(entry.getPath());
        }
        IJavaModelStatus status = validateClasspathEntry(project, entry, null, checkSourceAttachment,
                referredByContainer);
        // https://bugs.eclipse.org/bugs/show_bug.cgi?id=171136 and https://bugs.eclipse.org/bugs/show_bug.cgi?id=300136
        // Ignore class path errors from optional entries.
        int statusCode = status.getCode();
        if ((statusCode == IJavaModelStatusConstants.INVALID_CLASSPATH
                || statusCode == IJavaModelStatusConstants.CP_CONTAINER_PATH_UNBOUND
                || statusCode == IJavaModelStatusConstants.CP_VARIABLE_PATH_UNBOUND
                || statusCode == IJavaModelStatusConstants.INVALID_PATH) && ((ClasspathEntry) entry).isOptional())
            return JavaModelStatus.VERIFIED_OK;
        return status;
    }

    private static IJavaModelStatus validateClasspathEntry(IJavaProject project, IClasspathEntry entry,
            IClasspathContainer entryContainer, boolean checkSourceAttachment, boolean referredByContainer) {

        IPath path = entry.getPath();

        // Build some common strings for status message
        String projectName = project.getElementName();
        String entryPathMsg = projectName.equals(path.segment(0))
                ? path.removeFirstSegments(1).makeRelative().toString()
                : path.toString();

        switch (entry.getEntryKind()) {

        // container entry check
        case IClasspathEntry.CPE_CONTAINER:
            if (path.segmentCount() >= 1) {
                try {
                    IJavaModelStatus status = null;
                    // Validate extra attributes
                    IClasspathAttribute[] extraAttributes = entry.getExtraAttributes();
                    if (extraAttributes != null) {
                        int length = extraAttributes.length;
                        HashSet set = new HashSet(length);
                        for (int i = 0; i < length; i++) {
                            String attName = extraAttributes[i].getName();
                            if (!set.add(attName)) {
                                status = new JavaModelStatus(IJavaModelStatusConstants.NAME_COLLISION,
                                        Messages.bind(Messages.classpath_duplicateEntryExtraAttribute,
                                                new String[] { attName, entryPathMsg, projectName }));
                                break;
                            }
                        }
                        if (status == null) {
                            String annotationPath = getRawExternalAnnotationPath(entry);
                            if (annotationPath != null) {
                                status = ((ClasspathEntry) entry).validateExternalAnnotationPath(project,
                                        new Path(annotationPath));
                                if (status != null)
                                    return status;
                            }
                        }
                    }
                    IClasspathContainer container = JavaModelManager.getJavaModelManager()
                            .getClasspathContainer(path, project);
                    // container retrieval is performing validation check on container entry kinds.
                    if (container == null) {
                        if (status != null)
                            return status;
                        return new JavaModelStatus(IJavaModelStatusConstants.CP_CONTAINER_PATH_UNBOUND, project,
                                path);
                    } else if (container == JavaModelManager.CONTAINER_INITIALIZATION_IN_PROGRESS) {
                        // don't create a marker if initialization is in progress (case of cp initialization batching)
                        return JavaModelStatus.VERIFIED_OK;
                    }
                    IClasspathEntry[] containerEntries = container.getClasspathEntries();
                    if (containerEntries != null) {
                        for (int i = 0, length = containerEntries.length; i < length; i++) {
                            IClasspathEntry containerEntry = containerEntries[i];
                            int kind = containerEntry == null ? 0 : containerEntry.getEntryKind();
                            if (containerEntry == null || kind == IClasspathEntry.CPE_SOURCE
                                    || kind == IClasspathEntry.CPE_VARIABLE
                                    || kind == IClasspathEntry.CPE_CONTAINER) {
                                return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CP_CONTAINER_ENTRY,
                                        project, path);
                            }
                            IJavaModelStatus containerEntryStatus = validateClasspathEntry(project, containerEntry,
                                    container, checkSourceAttachment, true/*referred by container*/);
                            if (!containerEntryStatus.isOK()) {
                                return containerEntryStatus;
                            }
                        }
                    }
                } catch (JavaModelException e) {
                    return new JavaModelStatus(e);
                }
            } else {
                return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH, Messages
                        .bind(Messages.classpath_illegalContainerPath, new String[] { entryPathMsg, projectName }));
            }
            break;

        // variable entry check
        case IClasspathEntry.CPE_VARIABLE:
            if (path.segmentCount() >= 1) {
                try {
                    entry = JavaCore.getResolvedClasspathEntry(entry);
                } catch (AssertionFailedException e) {
                    // Catch the assertion failure and throw java model exception instead
                    // see bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=55992
                    return new JavaModelStatus(IJavaModelStatusConstants.INVALID_PATH, e.getMessage());
                }
                if (entry == null) {
                    return new JavaModelStatus(IJavaModelStatusConstants.CP_VARIABLE_PATH_UNBOUND, project, path);
                }

                // get validation status
                IJavaModelStatus status = validateClasspathEntry(project, entry, null, checkSourceAttachment,
                        false/*not referred by container*/);
                if (!status.isOK())
                    return status;

                // return deprecation status if any
                String variableName = path.segment(0);
                String deprecatedMessage = JavaCore.getClasspathVariableDeprecationMessage(variableName);
                if (deprecatedMessage != null) {
                    return new JavaModelStatus(IStatus.WARNING, IJavaModelStatusConstants.DEPRECATED_VARIABLE,
                            project, path, deprecatedMessage);
                }
                return status;
            } else {
                return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH, Messages
                        .bind(Messages.classpath_illegalVariablePath, new String[] { entryPathMsg, projectName }));
            }

            // library entry check
        case IClasspathEntry.CPE_LIBRARY:
            path = ClasspathEntry.resolveDotDot(project.getProject().getLocation(), path);

            // do not validate entries from Class-Path: in manifest
            // (these entries are considered optional since the user cannot act on them)
            // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=252392

            String containerInfo = null;
            if (entryContainer != null) {
                if (entryContainer instanceof UserLibraryClasspathContainer) {
                    containerInfo = Messages.bind(Messages.classpath_userLibraryInfo,
                            new String[] { entryContainer.getDescription() });
                } else {
                    containerInfo = Messages.bind(Messages.classpath_containerInfo,
                            new String[] { entryContainer.getDescription() });
                }
            }
            IJavaModelStatus status = validateLibraryEntry(path, project, containerInfo,
                    checkSourceAttachment ? entry.getSourceAttachmentPath() : null, entryPathMsg,
                    ((ClasspathEntry) entry).isOptional());
            if (!status.isOK())
                return status;
            break;

        // project entry check
        case IClasspathEntry.CPE_PROJECT:
            if (path.isAbsolute() && path.segmentCount() == 1) {
                IProject prereqProjectRsc = workspaceRoot.getProject(path.segment(0));
                IJavaProject prereqProject = JavaCore.create(prereqProjectRsc);
                try {
                    if (!prereqProjectRsc.exists() || !prereqProjectRsc.hasNature(JavaCore.NATURE_ID)) {
                        return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH, Messages.bind(
                                Messages.classpath_unboundProject, new String[] { path.segment(0), projectName }));
                    }
                    if (!prereqProjectRsc.isOpen()) {
                        return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH,
                                Messages.bind(Messages.classpath_closedProject, new String[] { path.segment(0) }));
                    }
                    if (!JavaCore.IGNORE.equals(project.getOption(JavaCore.CORE_INCOMPATIBLE_JDK_LEVEL, true))) {
                        long projectTargetJDK = CompilerOptions.versionToJdkLevel(
                                project.getOption(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM, true));
                        long prereqProjectTargetJDK = CompilerOptions.versionToJdkLevel(
                                prereqProject.getOption(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM, true));
                        if (prereqProjectTargetJDK > projectTargetJDK) {
                            return new JavaModelStatus(IJavaModelStatusConstants.INCOMPATIBLE_JDK_LEVEL, project,
                                    path,
                                    Messages.bind(Messages.classpath_incompatibleLibraryJDKLevel,
                                            new String[] { project.getElementName(),
                                                    CompilerOptions.versionFromJdkLevel(projectTargetJDK),
                                                    path.makeRelative().toString(),
                                                    CompilerOptions.versionFromJdkLevel(prereqProjectTargetJDK) }));
                        }
                    }
                } catch (CoreException e) {
                    return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH, Messages.bind(
                            Messages.classpath_unboundProject, new String[] { path.segment(0), projectName }));
                }
            } else {
                return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH, Messages.bind(
                        Messages.classpath_illegalProjectPath, new String[] { path.toString(), projectName }));
            }
            break;

        // project source folder
        case IClasspathEntry.CPE_SOURCE:
            if (((entry.getInclusionPatterns() != null && entry.getInclusionPatterns().length > 0)
                    || (entry.getExclusionPatterns() != null && entry.getExclusionPatterns().length > 0))
                    && JavaCore.DISABLED
                            .equals(project.getOption(JavaCore.CORE_ENABLE_CLASSPATH_EXCLUSION_PATTERNS, true))) {
                return new JavaModelStatus(IJavaModelStatusConstants.DISABLED_CP_EXCLUSION_PATTERNS, project, path);
            }
            if (entry.getOutputLocation() != null && JavaCore.DISABLED
                    .equals(project.getOption(JavaCore.CORE_ENABLE_CLASSPATH_MULTIPLE_OUTPUT_LOCATIONS, true))) {
                return new JavaModelStatus(IJavaModelStatusConstants.DISABLED_CP_MULTIPLE_OUTPUT_LOCATIONS, project,
                        path);
            }
            if (path.isAbsolute() && !path.isEmpty()) {
                IPath projectPath = project.getProject().getFullPath();
                if (!projectPath.isPrefixOf(path) || JavaModel.getTarget(path, true) == null) {
                    return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH, Messages.bind(
                            Messages.classpath_unboundSourceFolder, new String[] { entryPathMsg, projectName }));
                }
            } else {
                return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH, Messages.bind(
                        Messages.classpath_illegalSourceFolderPath, new String[] { entryPathMsg, projectName }));
            }
            break;
        }

        // Validate extra attributes
        IClasspathAttribute[] extraAttributes = entry.getExtraAttributes();
        if (extraAttributes != null) {
            int length = extraAttributes.length;
            HashSet set = new HashSet(length);
            for (int i = 0; i < length; i++) {
                String attName = extraAttributes[i].getName();
                if (!set.add(attName)) {
                    return new JavaModelStatus(IJavaModelStatusConstants.NAME_COLLISION,
                            Messages.bind(Messages.classpath_duplicateEntryExtraAttribute,
                                    new String[] { attName, entryPathMsg, projectName }));
                }
            }
        }

        return JavaModelStatus.VERIFIED_OK;
    }

    // https://bugs.eclipse.org/bugs/show_bug.cgi?id=232816, Now we have the facility to include a container
    // name in diagnostics. If the parameter ``container'' is not null, it is used to point to the library
    // more fully.
    private static IJavaModelStatus validateLibraryEntry(IPath path, IJavaProject project, String container,
            IPath sourceAttachment, String entryPathMsg, boolean isOptionalLibrary) {
        if (path.isAbsolute() && !path.isEmpty()) {
            boolean validateJdkLevelCompatibility = !JavaCore.IGNORE
                    .equals(project.getOption(JavaCore.CORE_INCOMPATIBLE_JDK_LEVEL, true));
            // https://bugs.eclipse.org/bugs/show_bug.cgi?id=412882, avoid validating optional entries
            if (!validateJdkLevelCompatibility && isOptionalLibrary) {
                return JavaModelStatus.VERIFIED_OK;
            }
            Object target = JavaModel.getTarget(path, true);
            if (target == null) { // https://bugs.eclipse.org/bugs/show_bug.cgi?id=248661
                IPath workspaceLocation = workspaceRoot.getLocation();
                if (workspaceLocation.isPrefixOf(path)) {
                    target = JavaModel.getTarget(path.makeRelativeTo(workspaceLocation).makeAbsolute(), true);
                }
            }
            if (target != null && validateJdkLevelCompatibility) {
                long projectTargetJDK = CompilerOptions
                        .versionToJdkLevel(project.getOption(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM, true));
                long libraryJDK = Util.getJdkLevel(target);
                if (libraryJDK != 0 && libraryJDK > projectTargetJDK) {
                    if (container != null) {
                        return new JavaModelStatus(IJavaModelStatusConstants.INCOMPATIBLE_JDK_LEVEL, project, path,
                                Messages.bind(Messages.classpath_incompatibleLibraryJDKLevelInContainer,
                                        new String[] { project.getElementName(),
                                                CompilerOptions.versionFromJdkLevel(projectTargetJDK),
                                                path.makeRelative().toString(), container,
                                                CompilerOptions.versionFromJdkLevel(libraryJDK) }));
                    } else {
                        return new JavaModelStatus(IJavaModelStatusConstants.INCOMPATIBLE_JDK_LEVEL, project, path,
                                Messages.bind(Messages.classpath_incompatibleLibraryJDKLevel,
                                        new String[] { project.getElementName(),
                                                CompilerOptions.versionFromJdkLevel(projectTargetJDK),
                                                path.makeRelative().toString(),
                                                CompilerOptions.versionFromJdkLevel(libraryJDK) }));
                    }
                }
            }
            if (isOptionalLibrary) {
                return JavaModelStatus.VERIFIED_OK;
            }
            if (target instanceof IResource) {
                IResource resolvedResource = (IResource) target;
                switch (resolvedResource.getType()) {
                case IResource.FILE:
                    if (sourceAttachment != null && !sourceAttachment.isEmpty()
                            && JavaModel.getTarget(sourceAttachment, true) == null) {
                        if (container != null) {
                            return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH, Messages.bind(
                                    Messages.classpath_unboundSourceAttachmentInContainedLibrary,
                                    new String[] { sourceAttachment.toString(), path.toString(), container }));
                        } else {
                            return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH,
                                    Messages.bind(Messages.classpath_unboundSourceAttachment,
                                            new String[] { sourceAttachment.toString(), path.toString(),
                                                    project.getElementName() }));
                        }
                    }
                    // https://bugs.eclipse.org/bugs/show_bug.cgi?id=229042
                    // Validate the contents of the archive
                    IJavaModelStatus status = validateLibraryContents(path, project, entryPathMsg);
                    if (status != JavaModelStatus.VERIFIED_OK)
                        return status;
                    break;
                case IResource.FOLDER: // internal binary folder
                    if (sourceAttachment != null && !sourceAttachment.isEmpty()
                            && JavaModel.getTarget(sourceAttachment, true) == null) {
                        if (container != null) {
                            return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH, Messages.bind(
                                    Messages.classpath_unboundSourceAttachmentInContainedLibrary,
                                    new String[] { sourceAttachment.toString(), path.toString(), container }));
                        } else {
                            return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH,
                                    Messages.bind(Messages.classpath_unboundSourceAttachment,
                                            new String[] { sourceAttachment.toString(), path.toString(),
                                                    project.getElementName() }));
                        }
                    }
                }
            } else if (target instanceof File) {
                File file = JavaModel.getFile(target);
                if (file == null) {
                    if (container != null) {
                        return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH,
                                Messages.bind(Messages.classpath_illegalExternalFolderInContainer,
                                        new String[] { path.toOSString(), container }));
                    } else {
                        return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH,
                                Messages.bind(Messages.classpath_illegalExternalFolder,
                                        new String[] { path.toOSString(), project.getElementName() }));
                    }
                } else {
                    if (sourceAttachment != null && !sourceAttachment.isEmpty()
                            && JavaModel.getTarget(sourceAttachment, true) == null) {
                        if (container != null) {
                            return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH, Messages.bind(
                                    Messages.classpath_unboundSourceAttachmentInContainedLibrary,
                                    new String[] { sourceAttachment.toString(), path.toOSString(), container }));
                        } else {
                            return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH,
                                    Messages.bind(Messages.classpath_unboundSourceAttachment,
                                            new String[] { sourceAttachment.toString(), path.toOSString(),
                                                    project.getElementName() }));
                        }
                    }
                    // https://bugs.eclipse.org/bugs/show_bug.cgi?id=229042
                    // Validate the contents of the archive
                    if (file.isFile()) {
                        IJavaModelStatus status = validateLibraryContents(path, project, entryPathMsg);
                        if (status != JavaModelStatus.VERIFIED_OK)
                            return status;
                    }
                }
            } else {
                boolean isExternal = path.getDevice() != null
                        || !ResourcesPlugin.getWorkspace().getRoot().getProject(path.segment(0)).exists();
                if (isExternal) {
                    if (container != null) {
                        return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH,
                                Messages.bind(Messages.classpath_unboundLibraryInContainer,
                                        new String[] { path.toOSString(), container }));
                    } else {
                        return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH,
                                Messages.bind(Messages.classpath_unboundLibrary,
                                        new String[] { path.toOSString(), project.getElementName() }));
                    }
                } else {
                    if (entryPathMsg == null)
                        entryPathMsg = project.getElementName().equals(path.segment(0))
                                ? path.removeFirstSegments(1).makeRelative().toString()
                                : path.toString();
                    if (container != null) {
                        return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH,
                                Messages.bind(Messages.classpath_unboundLibraryInContainer,
                                        new String[] { entryPathMsg, container }));
                    } else {
                        return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH,
                                Messages.bind(Messages.classpath_unboundLibrary,
                                        new String[] { entryPathMsg, project.getElementName() }));
                    }
                }
            }
        } else {
            if (entryPathMsg == null)
                entryPathMsg = project.getElementName().equals(path.segment(0))
                        ? path.removeFirstSegments(1).makeRelative().toString()
                        : path.toString();
            if (container != null) {
                return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH,
                        Messages.bind(Messages.classpath_illegalLibraryPathInContainer,
                                new String[] { entryPathMsg, container }));
            } else {
                return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH,
                        Messages.bind(Messages.classpath_illegalLibraryPath,
                                new String[] { entryPathMsg, project.getElementName() }));
            }
        }
        return JavaModelStatus.VERIFIED_OK;
    }

    private static IJavaModelStatus validateLibraryContents(IPath path, IJavaProject project, String entryPathMsg) {
        JavaModelManager manager = JavaModelManager.getJavaModelManager();
        try {
            manager.verifyArchiveContent(path);
        } catch (CoreException e) {
            if (e.getStatus().getMessage() == Messages.status_IOException) {
                return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH,
                        Messages.bind(Messages.classpath_archiveReadError,
                                new String[] { entryPathMsg, project.getElementName() }));
            }
        }
        return JavaModelStatus.VERIFIED_OK;
    }

    /*
     * For testing shared index location in JavaIndexTests only
     */
    public static void setSharedIndexLocation(String value, Class<?> clazz) throws IllegalArgumentException {
        if (clazz != null && "org.eclipse.jdt.core.tests.model.JavaIndexTests".equals(clazz.getName())) { //$NON-NLS-1$
            SHARED_INDEX_LOCATION = value;
        } else {
            throw new IllegalArgumentException("Cannot set index location for specified test class"); //$NON-NLS-1$
        }
    }
}