blob: c69d88fb8f3749e8217058ad734f10a232c10f0f [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2017 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
* 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
*******************************************************************************/
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.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
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.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$
/**
* 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;
}
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;
}
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++] = new ClasspathAccessRule(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: " + jarPath.toOSString()); //$NON-NLS-1$ //$NON-NLS-2$
}
} 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: " + jarPath.toOSString()); //$NON-NLS-1$ //$NON-NLS-2$
}
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("META-INF/MANIFEST.MF"); //$NON-NLS-1$
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 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();
}
} catch (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.
*/
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()
*/
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
*/
public int getContentKind() {
return this.contentKind;
}
/**
* @see IClasspathEntry
*/
public int getEntryKind() {
return this.entryKind;
}
/**
* @see IClasspathEntry#getExclusionPatterns()
*/
public IPath[] getExclusionPatterns() {
return this.exclusionPatterns;
}
public IClasspathAttribute[] getExtraAttributes() {
return this.extraAttributes;
}
/**
* @see IClasspathEntry#getExclusionPatterns()
*/
public IPath[] getInclusionPatterns() {
return this.inclusionPatterns;
}
/**
* @see IClasspathEntry#getOutputLocation()
*/
public IPath getOutputLocation() {
return this.specificOutputLocation;
}
/**
* @see IClasspathEntry
*/
public IPath getPath() {
return this.path;
}
/**
* @see IClasspathEntry
*/
public IPath getSourceAttachmentPath() {
return this.sourceAttachmentPath;
}
/**
* @see IClasspathEntry
*/
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) {
IClasspathAttribute[] extraAttributes = entry.getExtraAttributes();
for (int i = 0, length = extraAttributes.length; i < length; i++) {
IClasspathAttribute attribute = extraAttributes[i];
if (IClasspathAttribute.EXTERNAL_ANNOTATION_PATH.equals(attribute.getName())) {
return attribute.getValue();
}
}
return null;
}
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 IClasspathEntry getReferencingEntry() {
return this.referencingEntry;
}
/**
* Returns the hash code for this classpath entry
*/
public int hashCode() {
return this.path.hashCode();
}
/**
* @see IClasspathEntry#isExported()
*/
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 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.
*/
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
*/
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 :
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 length = classpath.length;
int outputCount = 1;
IPath[] outputLocations = new IPath[length+1];
boolean[] allowNestingInOutputLocations = new boolean[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));
for (int i = 0 ; i < length; i++) {
IClasspathEntry resolvedEntry = classpath[i];
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++;
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);
}
// 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()}));
}
for (int i = 0 ; i < length; i++) {
IClasspathEntry resolvedEntry = classpath[i];
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 (int i = 0 ; i < length; i++) {
IClasspathEntry entry = classpath[i];
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 (int j = 0; j < classpath.length; j++){
IClasspathEntry otherEntry = classpath[j];
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("*"), inclusionPatterns = ((ClasspathEntry)otherEntry).fullInclusionPatternChars(), exclusionPatterns = ((ClasspathEntry)otherEntry).fullExclusionPatternChars(), false)) { //$NON-NLS-1$
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 (int i = 0 ; i < length; i++) {
IClasspathEntry entry = classpath[i];
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 (int j = 0; j < length; j++) {
IClasspathEntry otherEntry = classpath[j];
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 })){
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}));
}
}
}
}
}
// 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;
}
}