Java tutorial
/******************************************************************************* * 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$ } } }