1 /******************************************************************************* 2 * Copyright (c) 2000, 2019 IBM Corporation and others. 3 * 4 * This program and the accompanying materials 5 * are made available under the terms of the Eclipse Public License 2.0 6 * which accompanies this distribution, and is available at 7 * https://www.eclipse.org/legal/epl-2.0/ 8 * 9 * SPDX-License-Identifier: EPL-2.0 10 * 11 * Contributors: 12 * IBM Corporation - initial API and implementation 13 * Terry Parker <tparker@google.com> - DeltaProcessor misses state changes in archive files, see https://bugs.eclipse.org/bugs/show_bug.cgi?id=357425 14 * Thirumala Reddy Mutchukota <thirumala@google.com> - Avoid optional library classpath entries validation - https://bugs.eclipse.org/bugs/show_bug.cgi?id=412882 15 * Stephan Herrmann - Contribution for 16 * Bug 440477 - [null] Infrastructure for feeding external annotations into compilation 17 * Bug 462768 - [null] NPE when using linked folder for external annotations 18 * Bug 465296 - precedence of extra attributes on a classpath container 19 * Karsten Thoms - Bug 532505 - Reduce memory footprint of ClasspathAccessRule 20 *******************************************************************************/ 21 package org.eclipse.jdt.internal.core; 22 23 import static org.eclipse.jdt.internal.compiler.util.Util.UTF_8; 24 import static org.eclipse.jdt.internal.compiler.util.Util.getInputStreamAsCharArray; 25 26 import java.io.ByteArrayOutputStream; 27 import java.io.File; 28 import java.io.IOException; 29 import java.io.InputStream; 30 import java.io.OutputStreamWriter; 31 import java.io.UnsupportedEncodingException; 32 import java.net.MalformedURLException; 33 import java.net.URL; 34 import java.nio.file.Paths; 35 import java.util.ArrayList; 36 import java.util.Arrays; 37 import java.util.HashMap; 38 import java.util.HashSet; 39 import java.util.Iterator; 40 import java.util.List; 41 import java.util.Map; 42 import java.util.zip.CRC32; 43 import java.util.zip.ZipEntry; 44 import java.util.zip.ZipFile; 45 46 import org.eclipse.core.resources.IContainer; 47 import org.eclipse.core.resources.IFile; 48 import org.eclipse.core.resources.IMarker; 49 import org.eclipse.core.resources.IProject; 50 import org.eclipse.core.resources.IResource; 51 import org.eclipse.core.resources.IWorkspaceRoot; 52 import org.eclipse.core.resources.ResourcesPlugin; 53 import org.eclipse.core.runtime.CoreException; 54 import org.eclipse.core.runtime.IPath; 55 import org.eclipse.core.runtime.IStatus; 56 import org.eclipse.core.runtime.Path; 57 import org.eclipse.jdt.core.IAccessRule; 58 import org.eclipse.jdt.core.IClasspathAttribute; 59 import org.eclipse.jdt.core.IClasspathContainer; 60 import org.eclipse.jdt.core.IClasspathEntry; 61 import org.eclipse.jdt.core.IJavaModelMarker; 62 import org.eclipse.jdt.core.IJavaModelStatus; 63 import org.eclipse.jdt.core.IJavaModelStatusConstants; 64 import org.eclipse.jdt.core.IJavaProject; 65 import org.eclipse.jdt.core.IPackageFragmentRoot; 66 import org.eclipse.jdt.core.JavaCore; 67 import org.eclipse.jdt.core.JavaModelException; 68 import org.eclipse.jdt.core.compiler.CharOperation; 69 import org.eclipse.jdt.core.compiler.IProblem; 70 import org.eclipse.jdt.internal.compiler.env.AccessRestriction; 71 import org.eclipse.jdt.internal.compiler.env.AccessRule; 72 import org.eclipse.jdt.internal.compiler.env.AccessRuleSet; 73 import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; 74 import org.eclipse.jdt.internal.compiler.lookup.TypeConstants; 75 import org.eclipse.jdt.internal.compiler.util.ManifestAnalyzer; 76 import org.eclipse.jdt.internal.core.nd.IReader; 77 import org.eclipse.jdt.internal.core.nd.java.JavaIndex; 78 import org.eclipse.jdt.internal.core.nd.java.NdResourceFile; 79 import org.eclipse.jdt.internal.core.util.Messages; 80 import org.eclipse.jdt.internal.core.util.Util; 81 import org.w3c.dom.DOMException; 82 import org.w3c.dom.Element; 83 import org.w3c.dom.NamedNodeMap; 84 import org.w3c.dom.Node; 85 import org.w3c.dom.NodeList; 86 import org.w3c.dom.Text; 87 88 /** 89 * @see IClasspathEntry 90 */ 91 @SuppressWarnings({ "rawtypes", "unchecked" }) 92 public class ClasspathEntry implements IClasspathEntry { 93 94 public static class AssertionFailedException extends RuntimeException { 95 96 private static final long serialVersionUID = -171699380721189572L; 97 AssertionFailedException(String message)98 public AssertionFailedException(String message) { 99 super(message); 100 } 101 } 102 103 public static final String TAG_CLASSPATH = "classpath"; //$NON-NLS-1$ 104 public static final String TAG_CLASSPATHENTRY = "classpathentry"; //$NON-NLS-1$ 105 public static final String TAG_REFERENCED_ENTRY = "referencedentry"; //$NON-NLS-1$ 106 public static final String TAG_OUTPUT = "output"; //$NON-NLS-1$ 107 public static final String TAG_KIND = "kind"; //$NON-NLS-1$ 108 public static final String TAG_PATH = "path"; //$NON-NLS-1$ 109 public static final String TAG_SOURCEPATH = "sourcepath"; //$NON-NLS-1$ 110 public static final String TAG_ROOTPATH = "rootpath"; //$NON-NLS-1$ 111 public static final String TAG_EXPORTED = "exported"; //$NON-NLS-1$ 112 public static final String TAG_INCLUDING = "including"; //$NON-NLS-1$ 113 public static final String TAG_EXCLUDING = "excluding"; //$NON-NLS-1$ 114 public static final String TAG_ATTRIBUTES = "attributes"; //$NON-NLS-1$ 115 public static final String TAG_ATTRIBUTE = "attribute"; //$NON-NLS-1$ 116 public static final String TAG_ATTRIBUTE_NAME = "name"; //$NON-NLS-1$ 117 public static final String TAG_ATTRIBUTE_VALUE = "value"; //$NON-NLS-1$ 118 public static final String TAG_COMBINE_ACCESS_RULES = "combineaccessrules"; //$NON-NLS-1$ 119 public static final String TAG_ACCESS_RULES = "accessrules"; //$NON-NLS-1$ 120 public static final String TAG_ACCESS_RULE = "accessrule"; //$NON-NLS-1$ 121 public static final String TAG_PATTERN = "pattern"; //$NON-NLS-1$ 122 public static final String TAG_ACCESSIBLE = "accessible"; //$NON-NLS-1$ 123 public static final String TAG_NON_ACCESSIBLE = "nonaccessible"; //$NON-NLS-1$ 124 public static final String TAG_DISCOURAGED = "discouraged"; //$NON-NLS-1$ 125 public static final String TAG_IGNORE_IF_BETTER = "ignoreifbetter"; //$NON-NLS-1$ 126 127 // common index location for all workspaces 128 private static String SHARED_INDEX_LOCATION = System.getProperty("jdt.core.sharedIndexLocation"); //$NON-NLS-1$ 129 130 /** 131 * Describes the kind of classpath entry - one of 132 * CPE_PROJECT, CPE_LIBRARY, CPE_SOURCE, CPE_VARIABLE or CPE_CONTAINER 133 */ 134 public int entryKind; 135 136 /** 137 * Describes the kind of package fragment roots found on 138 * this classpath entry - either K_BINARY or K_SOURCE or 139 * K_OUTPUT. 140 */ 141 public int contentKind; 142 143 /** 144 * The meaning of the path of a classpath entry depends on its entry kind:<ul> 145 * <li>Source code in the current project (<code>CPE_SOURCE</code>) - 146 * The path associated with this entry is the absolute path to the root folder. </li> 147 * <li>A binary library in the current project (<code>CPE_LIBRARY</code>) - the path 148 * associated with this entry is the absolute path to the JAR (or root folder), and 149 * in case it refers to an external JAR, then there is no associated resource in 150 * the workbench. 151 * <li>A required project (<code>CPE_PROJECT</code>) - the path of the entry denotes the 152 * path to the corresponding project resource.</li> 153 * <li>A variable entry (<code>CPE_VARIABLE</code>) - the first segment of the path 154 * is the name of a classpath variable. If this classpath variable 155 * is bound to the path <it>P</it>, the path of the corresponding classpath entry 156 * is computed by appending to <it>P</it> the segments of the returned 157 * path without the variable.</li> 158 * <li> A container entry (<code>CPE_CONTAINER</code>) - the first segment of the path is denoting 159 * the unique container identifier (for which a <code>ClasspathContainerInitializer</code> could be 160 * registered), and the remaining segments are used as additional hints for resolving the container entry to 161 * an actual <code>IClasspathContainer</code>.</li> 162 */ 163 public IPath path; 164 165 /** 166 * Patterns allowing to include/exclude portions of the resource tree denoted by this entry path. 167 */ 168 private IPath[] inclusionPatterns; 169 private char[][] fullInclusionPatternChars; 170 private IPath[] exclusionPatterns; 171 private char[][] fullExclusionPatternChars; 172 private final static char[][] UNINIT_PATTERNS = new char[][] { "Non-initialized yet".toCharArray() }; //$NON-NLS-1$ 173 public final static ClasspathEntry[] NO_ENTRIES = new ClasspathEntry[0]; 174 private final static IPath[] NO_PATHS = new IPath[0]; 175 private final static IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot(); 176 177 private boolean combineAccessRules; 178 179 private String rootID; 180 private AccessRuleSet accessRuleSet; 181 182 183 static class UnknownXmlElements { 184 String[] attributes; 185 ArrayList children; 186 } 187 188 /* 189 * Default inclusion pattern set 190 */ 191 public final static IPath[] INCLUDE_ALL = {}; 192 193 /* 194 * Default exclusion pattern set 195 */ 196 public final static IPath[] EXCLUDE_NONE = {}; 197 198 /* 199 * Default extra attributes 200 */ 201 public final static IClasspathAttribute[] NO_EXTRA_ATTRIBUTES = {}; 202 203 /* 204 * Default access rules 205 */ 206 public final static IAccessRule[] NO_ACCESS_RULES = {}; 207 208 /** 209 * Describes the path to the source archive associated with this 210 * classpath entry, or <code>null</code> if this classpath entry has no 211 * source attachment. 212 * <p> 213 * Only library and variable classpath entries may have source attachments. 214 * For library classpath entries, the result path (if present) locates a source 215 * archive. For variable classpath entries, the result path (if present) has 216 * an analogous form and meaning as the variable path, namely the first segment 217 * is the name of a classpath variable. 218 */ 219 public IPath sourceAttachmentPath; 220 221 /** 222 * Describes the path within the source archive where package fragments 223 * are located. An empty path indicates that packages are located at 224 * the root of the source archive. Returns a non-<code>null</code> value 225 * if and only if <code>getSourceAttachmentPath</code> returns 226 * a non-<code>null</code> value. 227 */ 228 public IPath sourceAttachmentRootPath; 229 230 /** 231 * See {@link IClasspathEntry#getReferencingEntry()} 232 */ 233 public IClasspathEntry referencingEntry; 234 235 /** 236 * Specific output location (for this source entry) 237 */ 238 public IPath specificOutputLocation; 239 240 /** 241 * A constant indicating an output location. 242 */ 243 public static final int K_OUTPUT = 10; 244 245 public static final String DOT_DOT = ".."; //$NON-NLS-1$ 246 247 /** 248 * The export flag 249 */ 250 public boolean isExported; 251 252 /** 253 * The extra attributes 254 */ 255 public IClasspathAttribute[] extraAttributes; 256 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)257 public ClasspathEntry( 258 int contentKind, 259 int entryKind, 260 IPath path, 261 IPath[] inclusionPatterns, 262 IPath[] exclusionPatterns, 263 IPath sourceAttachmentPath, 264 IPath sourceAttachmentRootPath, 265 IPath specificOutputLocation, 266 boolean isExported, 267 IAccessRule[] accessRules, 268 boolean combineAccessRules, 269 IClasspathAttribute[] extraAttributes) { 270 271 this( contentKind, 272 entryKind, 273 path, 274 inclusionPatterns, 275 exclusionPatterns, 276 sourceAttachmentPath, 277 sourceAttachmentRootPath, 278 specificOutputLocation, 279 null, 280 isExported, 281 accessRules, 282 combineAccessRules, 283 extraAttributes); 284 } 285 286 /** 287 * Creates a class path entry of the specified kind with the given path. 288 */ 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)289 public ClasspathEntry( 290 int contentKind, 291 int entryKind, 292 IPath path, 293 IPath[] inclusionPatterns, 294 IPath[] exclusionPatterns, 295 IPath sourceAttachmentPath, 296 IPath sourceAttachmentRootPath, 297 IPath specificOutputLocation, 298 IClasspathEntry referencingEntry, 299 boolean isExported, 300 IAccessRule[] accessRules, 301 boolean combineAccessRules, 302 IClasspathAttribute[] extraAttributes) { 303 304 this.contentKind = contentKind; 305 this.entryKind = entryKind; 306 this.path = path; 307 this.inclusionPatterns = inclusionPatterns; 308 this.exclusionPatterns = exclusionPatterns; 309 this.referencingEntry = referencingEntry; 310 311 int length; 312 if (accessRules != null && (length = accessRules.length) > 0) { 313 AccessRule[] rules = new AccessRule[length]; 314 System.arraycopy(accessRules, 0, rules, 0, length); 315 byte classpathEntryType; 316 String classpathEntryName; 317 JavaModelManager manager = JavaModelManager.getJavaModelManager(); 318 if (this.entryKind == CPE_PROJECT || this.entryKind == CPE_SOURCE) { // can be remote source entry when reconciling 319 classpathEntryType = AccessRestriction.PROJECT; 320 classpathEntryName = manager.intern(getPath().segment(0)); 321 } else { 322 classpathEntryType = AccessRestriction.LIBRARY; 323 Object target = JavaModel.getWorkspaceTarget(path); 324 if (target == null) { 325 classpathEntryName = manager.intern(path.toOSString()); 326 } else { 327 classpathEntryName = manager.intern(path.makeRelative().toString()); 328 } 329 } 330 this.accessRuleSet = new AccessRuleSet(rules, classpathEntryType, classpathEntryName); 331 } 332 // else { -- implicit! 333 // this.accessRuleSet = null; 334 // } 335 336 this.combineAccessRules = combineAccessRules; 337 this.extraAttributes = extraAttributes.length > 0 ? extraAttributes : NO_EXTRA_ATTRIBUTES; 338 339 if (inclusionPatterns != INCLUDE_ALL && inclusionPatterns.length > 0) { 340 this.fullInclusionPatternChars = UNINIT_PATTERNS; 341 } 342 if (exclusionPatterns.length > 0) { 343 this.fullExclusionPatternChars = UNINIT_PATTERNS; 344 } 345 this.sourceAttachmentPath = sourceAttachmentPath; 346 this.sourceAttachmentRootPath = sourceAttachmentRootPath; 347 this.specificOutputLocation = specificOutputLocation; 348 this.isExported = isExported; 349 } 350 351 @Override combineAccessRules()352 public boolean combineAccessRules() { 353 return this.combineAccessRules; 354 } 355 356 /** 357 * Used to perform export/restriction propagation across referring projects/containers. 358 * Also: propagating extraAttributes. 359 */ combineWith(ClasspathEntry referringEntry)360 public ClasspathEntry combineWith(ClasspathEntry referringEntry) { 361 if (referringEntry == null) return this; 362 IClasspathAttribute[] referringExtraAttributes = referringEntry.getExtraAttributes(); 363 if (referringEntry.isExported() || referringEntry.getAccessRuleSet() != null || referringExtraAttributes.length > 0) { 364 boolean combine = this.entryKind == CPE_SOURCE || referringEntry.combineAccessRules(); 365 IClasspathAttribute[] combinedAttributes = this.extraAttributes; 366 int lenRefer = referringExtraAttributes.length; 367 if (lenRefer > 0) { 368 int lenEntry = combinedAttributes.length; 369 if (referringEntry.path.isPrefixOf(this.path)) { 370 // consider prefix location as less specific, put to back (e.g.: referring to a library via a project): 371 System.arraycopy(combinedAttributes, 0, combinedAttributes=new IClasspathAttribute[lenEntry+lenRefer], 0, lenEntry); 372 System.arraycopy(referringExtraAttributes, 0, combinedAttributes, lenEntry, lenRefer); 373 } else { 374 // otherwise consider the referring entry as more specific than the referee: 375 System.arraycopy(combinedAttributes, 0, combinedAttributes=new IClasspathAttribute[lenEntry+lenRefer], lenRefer, lenEntry); 376 System.arraycopy(referringExtraAttributes, 0, combinedAttributes, 0, lenRefer); 377 } 378 } 379 return new ClasspathEntry( 380 getContentKind(), 381 getEntryKind(), 382 getPath(), 383 this.inclusionPatterns, 384 this.exclusionPatterns, 385 getSourceAttachmentPath(), 386 getSourceAttachmentRootPath(), 387 getOutputLocation(), 388 referringEntry.isExported() || this.isExported, // duplicate container entry for tagging it as exported 389 combine(referringEntry.getAccessRules(), getAccessRules(), combine), 390 this.combineAccessRules, 391 combinedAttributes); 392 } 393 // no need to clone 394 return this; 395 } 396 397 withExtraAttributeRemoved(String attrName)398 public ClasspathEntry withExtraAttributeRemoved(String attrName) { 399 IClasspathAttribute[] changedAttributes = Arrays.stream(this.getExtraAttributes()) 400 .filter(a -> !a.getName().equals(attrName)).toArray(IClasspathAttribute[]::new); 401 return new ClasspathEntry( 402 this.getContentKind(), 403 this.getEntryKind(), 404 this.getPath(), 405 this.getInclusionPatterns(), 406 this.getExclusionPatterns(), 407 this.getSourceAttachmentPath(), 408 this.getSourceAttachmentRootPath(), 409 this.getOutputLocation(), 410 this.getReferencingEntry(), 411 this.isExported(), 412 this.getAccessRules(), 413 this.combineAccessRules(), 414 changedAttributes); 415 } 416 417 combine(IAccessRule[] referringRules, IAccessRule[] rules, boolean combine)418 private IAccessRule[] combine(IAccessRule[] referringRules, IAccessRule[] rules, boolean combine) { 419 if (!combine) return rules; 420 if (rules == null || rules.length == 0) return referringRules; 421 422 // concat access rules 423 int referringRulesLength = referringRules.length; 424 int accessRulesLength = rules.length; 425 int rulesLength = referringRulesLength + accessRulesLength; 426 IAccessRule[] result = new IAccessRule[rulesLength]; 427 System.arraycopy(referringRules, 0, result, 0, referringRulesLength); 428 System.arraycopy(rules, 0, result, referringRulesLength, accessRulesLength); 429 430 return result; 431 } 432 decodeExtraAttributes(NodeList attributes)433 static IClasspathAttribute[] decodeExtraAttributes(NodeList attributes) { 434 if (attributes == null) return NO_EXTRA_ATTRIBUTES; 435 int length = attributes.getLength(); 436 if (length == 0) return NO_EXTRA_ATTRIBUTES; 437 IClasspathAttribute[] result = new IClasspathAttribute[length]; 438 int index = 0; 439 for (int i = 0; i < length; ++i) { 440 Node node = attributes.item(i); 441 if (node.getNodeType() == Node.ELEMENT_NODE) { 442 Element attribute = (Element)node; 443 String name = attribute.getAttribute(TAG_ATTRIBUTE_NAME); 444 if (name == null) continue; 445 String value = attribute.getAttribute(TAG_ATTRIBUTE_VALUE); 446 if (value == null) continue; 447 result[index++] = new ClasspathAttribute(name, value); 448 } 449 } 450 if (index != length) 451 System.arraycopy(result, 0, result = new IClasspathAttribute[index], 0, index); 452 return result; 453 } 454 decodeAccessRules(NodeList list)455 static IAccessRule[] decodeAccessRules(NodeList list) { 456 if (list == null) return null; 457 int length = list.getLength(); 458 if (length == 0) return null; 459 IAccessRule[] result = new IAccessRule[length]; 460 int index = 0; 461 for (int i = 0; i < length; i++) { 462 Node accessRule = list.item(i); 463 if (accessRule.getNodeType() == Node.ELEMENT_NODE) { 464 Element elementAccessRule = (Element) accessRule; 465 String pattern = elementAccessRule.getAttribute(TAG_PATTERN); 466 if (pattern == null) continue; 467 String tagKind = elementAccessRule.getAttribute(TAG_KIND); 468 int kind; 469 if (TAG_ACCESSIBLE.equals(tagKind)) 470 kind = IAccessRule.K_ACCESSIBLE; 471 else if (TAG_NON_ACCESSIBLE.equals(tagKind)) 472 kind = IAccessRule.K_NON_ACCESSIBLE; 473 else if (TAG_DISCOURAGED.equals(tagKind)) 474 kind = IAccessRule.K_DISCOURAGED; 475 else 476 continue; 477 boolean ignoreIfBetter = "true".equals(elementAccessRule.getAttribute(TAG_IGNORE_IF_BETTER)); //$NON-NLS-1$ 478 result[index++] = JavaCore.newAccessRule(new Path(pattern), ignoreIfBetter ? kind | IAccessRule.IGNORE_IF_BETTER : kind); 479 } 480 } 481 if (index != length) 482 System.arraycopy(result, 0, result = new IAccessRule[index], 0, index); 483 return result; 484 } 485 486 /** 487 * Decode some element tag containing a sequence of patterns into IPath[] 488 */ decodePatterns(NamedNodeMap nodeMap, String tag)489 private static IPath[] decodePatterns(NamedNodeMap nodeMap, String tag) { 490 String sequence = removeAttribute(tag, nodeMap); 491 if (!sequence.equals("")) { //$NON-NLS-1$ 492 char[][] patterns = CharOperation.splitOn('|', sequence.toCharArray()); 493 int patternCount; 494 if ((patternCount = patterns.length) > 0) { 495 IPath[] paths = new IPath[patternCount]; 496 int index = 0; 497 for (int j = 0; j < patternCount; j++) { 498 char[] pattern = patterns[j]; 499 if (pattern.length == 0) continue; // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=105581 500 paths[index++] = new Path(new String(pattern)); 501 } 502 if (index < patternCount) 503 System.arraycopy(paths, 0, paths = new IPath[index], 0, index); 504 return paths; 505 } 506 } 507 return null; 508 } 509 decodeUnknownNode(Node node, StringBuffer buffer, IJavaProject project)510 private static void decodeUnknownNode(Node node, StringBuffer buffer, IJavaProject project) { 511 ByteArrayOutputStream s = new ByteArrayOutputStream(); 512 OutputStreamWriter writer; 513 try { 514 writer = new OutputStreamWriter(s, "UTF8"); //$NON-NLS-1$ 515 XMLWriter xmlWriter = new XMLWriter(writer, project, false/*don't print XML version*/); 516 decodeUnknownNode(node, xmlWriter, true/*insert new line*/); 517 xmlWriter.flush(); 518 xmlWriter.close(); 519 buffer.append(s.toString("UTF8")); //$NON-NLS-1$ 520 } catch (UnsupportedEncodingException e) { 521 // ignore (UTF8 is always supported) 522 } 523 } 524 decodeUnknownNode(Node node, XMLWriter xmlWriter, boolean insertNewLine)525 private static void decodeUnknownNode(Node node, XMLWriter xmlWriter, boolean insertNewLine) { 526 switch (node.getNodeType()) { 527 case Node.ELEMENT_NODE: 528 NamedNodeMap attributes; 529 HashMap parameters = null; 530 if ((attributes = node.getAttributes()) != null) { 531 int length = attributes.getLength(); 532 if (length > 0) { 533 parameters = new HashMap(); 534 for (int i = 0; i < length; i++) { 535 Node attribute = attributes.item(i); 536 parameters.put(attribute.getNodeName(), attribute.getNodeValue()); 537 } 538 } 539 } 540 NodeList children = node.getChildNodes(); 541 int childrenLength = children.getLength(); 542 String nodeName = node.getNodeName(); 543 xmlWriter.printTag(nodeName, parameters, false/*don't insert tab*/, false/*don't insert new line*/, childrenLength == 0/*close tag if no children*/); 544 if (childrenLength > 0) { 545 for (int i = 0; i < childrenLength; i++) { 546 decodeUnknownNode(children.item(i), xmlWriter, false/*don't insert new line*/); 547 } 548 xmlWriter.endTag(nodeName, false/*don't insert tab*/, insertNewLine); 549 } 550 break; 551 case Node.TEXT_NODE: 552 String data = ((Text) node).getData(); 553 xmlWriter.printString(data, false/*don't insert tab*/, false/*don't insert new line*/); 554 break; 555 } 556 } 557 558 /* 559 * Returns a char based representation of the exclusions patterns full path. 560 */ fullExclusionPatternChars()561 public char[][] fullExclusionPatternChars() { 562 563 if (this.fullExclusionPatternChars == UNINIT_PATTERNS) { 564 int length = this.exclusionPatterns.length; 565 this.fullExclusionPatternChars = new char[length][]; 566 IPath prefixPath = this.path.removeTrailingSeparator(); 567 for (int i = 0; i < length; i++) { 568 this.fullExclusionPatternChars[i] = 569 prefixPath.append(this.exclusionPatterns[i]).toString().toCharArray(); 570 } 571 } 572 return this.fullExclusionPatternChars; 573 } 574 575 /* 576 * Returns a char based representation of the exclusions patterns full path. 577 */ fullInclusionPatternChars()578 public char[][] fullInclusionPatternChars() { 579 580 if (this.fullInclusionPatternChars == UNINIT_PATTERNS) { 581 int length = this.inclusionPatterns.length; 582 this.fullInclusionPatternChars = new char[length][]; 583 IPath prefixPath = this.path.removeTrailingSeparator(); 584 for (int i = 0; i < length; i++) { 585 this.fullInclusionPatternChars[i] = 586 prefixPath.append(this.inclusionPatterns[i]).toString().toCharArray(); 587 } 588 } 589 return this.fullInclusionPatternChars; 590 } 591 592 /** 593 * Returns the XML encoding of the class path. 594 */ elementEncode(XMLWriter writer, IPath projectPath, boolean indent, boolean newLine, Map unknownElements, boolean isReferencedEntry)595 public void elementEncode(XMLWriter writer, IPath projectPath, boolean indent, boolean newLine, Map unknownElements, boolean isReferencedEntry) { 596 HashMap parameters = new HashMap(); 597 598 parameters.put(TAG_KIND, ClasspathEntry.kindToString(this.entryKind)); 599 600 IPath xmlPath = this.path; 601 if (this.entryKind != IClasspathEntry.CPE_VARIABLE && this.entryKind != IClasspathEntry.CPE_CONTAINER) { 602 // translate to project relative from absolute (unless a device path) 603 if (xmlPath.isAbsolute()) { 604 if (projectPath != null && projectPath.isPrefixOf(xmlPath)) { 605 if (xmlPath.segment(0).equals(projectPath.segment(0))) { 606 xmlPath = xmlPath.removeFirstSegments(1); 607 xmlPath = xmlPath.makeRelative(); 608 } else { 609 xmlPath = xmlPath.makeAbsolute(); 610 } 611 } 612 } 613 } 614 parameters.put(TAG_PATH, String.valueOf(xmlPath)); 615 616 if (this.sourceAttachmentPath != null) { 617 xmlPath = this.sourceAttachmentPath; 618 // translate to project relative from absolute 619 if (this.entryKind != IClasspathEntry.CPE_VARIABLE && projectPath != null && projectPath.isPrefixOf(xmlPath)) { 620 if (xmlPath.segment(0).equals(projectPath.segment(0))) { 621 xmlPath = xmlPath.removeFirstSegments(1); 622 xmlPath = xmlPath.makeRelative(); 623 } 624 } 625 parameters.put(TAG_SOURCEPATH, String.valueOf(xmlPath)); 626 } 627 if (this.sourceAttachmentRootPath != null) { 628 parameters.put(TAG_ROOTPATH, String.valueOf(this.sourceAttachmentRootPath)); 629 } 630 if (this.isExported) { 631 parameters.put(TAG_EXPORTED, "true");//$NON-NLS-1$ 632 } 633 encodePatterns(this.inclusionPatterns, TAG_INCLUDING, parameters); 634 encodePatterns(this.exclusionPatterns, TAG_EXCLUDING, parameters); 635 if (this.entryKind == CPE_PROJECT && !this.combineAccessRules) 636 parameters.put(TAG_COMBINE_ACCESS_RULES, "false"); //$NON-NLS-1$ 637 638 639 // unknown attributes 640 UnknownXmlElements unknownXmlElements = unknownElements == null ? null : (UnknownXmlElements) unknownElements.get(this.path); 641 String[] unknownAttributes; 642 if (unknownXmlElements != null && (unknownAttributes = unknownXmlElements.attributes) != null) 643 for (int i = 0, length = unknownAttributes.length; i < length; i+=2) { 644 String tagName = unknownAttributes[i]; 645 String tagValue = unknownAttributes[i+1]; 646 parameters.put(tagName, tagValue); 647 } 648 649 if (this.specificOutputLocation != null) { 650 IPath outputLocation = this.specificOutputLocation.removeFirstSegments(1); 651 outputLocation = outputLocation.makeRelative(); 652 parameters.put(TAG_OUTPUT, String.valueOf(outputLocation)); 653 } 654 655 boolean hasExtraAttributes = this.extraAttributes.length != 0; 656 boolean hasRestrictions = getAccessRuleSet() != null; // access rule set is null if no access rules 657 ArrayList unknownChildren = unknownXmlElements != null ? unknownXmlElements.children : null; 658 boolean hasUnknownChildren = unknownChildren != null; 659 660 /* close tag if no extra attributes, no restriction and no unknown children */ 661 String tagName = isReferencedEntry ? TAG_REFERENCED_ENTRY : TAG_CLASSPATHENTRY; 662 writer.printTag( 663 tagName, 664 parameters, 665 indent, 666 newLine, 667 !hasExtraAttributes && !hasRestrictions && !hasUnknownChildren); 668 669 if (hasExtraAttributes) 670 encodeExtraAttributes(writer, indent, newLine); 671 672 if (hasRestrictions) 673 encodeAccessRules(writer, indent, newLine); 674 675 if (hasUnknownChildren) 676 encodeUnknownChildren(writer, indent, newLine, unknownChildren); 677 678 if (hasExtraAttributes || hasRestrictions || hasUnknownChildren) 679 writer.endTag(tagName, indent, true/*insert new line*/); 680 } 681 encodeExtraAttributes(XMLWriter writer, boolean indent, boolean newLine)682 void encodeExtraAttributes(XMLWriter writer, boolean indent, boolean newLine) { 683 writer.startTag(TAG_ATTRIBUTES, indent); 684 for (int i = 0; i < this.extraAttributes.length; i++) { 685 IClasspathAttribute attribute = this.extraAttributes[i]; 686 HashMap parameters = new HashMap(); 687 parameters.put(TAG_ATTRIBUTE_NAME, attribute.getName()); 688 parameters.put(TAG_ATTRIBUTE_VALUE, attribute.getValue()); 689 writer.printTag(TAG_ATTRIBUTE, parameters, indent, newLine, true); 690 } 691 writer.endTag(TAG_ATTRIBUTES, indent, true/*insert new line*/); 692 } 693 encodeAccessRules(XMLWriter writer, boolean indent, boolean newLine)694 void encodeAccessRules(XMLWriter writer, boolean indent, boolean newLine) { 695 696 writer.startTag(TAG_ACCESS_RULES, indent); 697 AccessRule[] rules = getAccessRuleSet().getAccessRules(); 698 for (int i = 0, length = rules.length; i < length; i++) { 699 encodeAccessRule(rules[i], writer, indent, newLine); 700 } 701 writer.endTag(TAG_ACCESS_RULES, indent, true/*insert new line*/); 702 } 703 encodeAccessRule(AccessRule accessRule, XMLWriter writer, boolean indent, boolean newLine)704 private void encodeAccessRule(AccessRule accessRule, XMLWriter writer, boolean indent, boolean newLine) { 705 706 HashMap parameters = new HashMap(); 707 parameters.put(TAG_PATTERN, new String(accessRule.pattern)); 708 709 switch (accessRule.getProblemId()) { 710 case IProblem.ForbiddenReference: 711 parameters.put(TAG_KIND, TAG_NON_ACCESSIBLE); 712 break; 713 case IProblem.DiscouragedReference: 714 parameters.put(TAG_KIND, TAG_DISCOURAGED); 715 break; 716 default: 717 parameters.put(TAG_KIND, TAG_ACCESSIBLE); 718 break; 719 } 720 if (accessRule.ignoreIfBetter()) 721 parameters.put(TAG_IGNORE_IF_BETTER, "true"); //$NON-NLS-1$ 722 723 writer.printTag(TAG_ACCESS_RULE, parameters, indent, newLine, true); 724 725 } 726 encodeUnknownChildren(XMLWriter writer, boolean indent, boolean newLine, ArrayList unknownChildren)727 private void encodeUnknownChildren(XMLWriter writer, boolean indent, boolean newLine, ArrayList unknownChildren) { 728 for (int i = 0, length = unknownChildren.size(); i < length; i++) { 729 String child = (String) unknownChildren.get(i); 730 writer.printString(child, indent, false/*don't insert new line*/); 731 } 732 } 733 elementDecode(Element element, IJavaProject project, Map unknownElements)734 public static IClasspathEntry elementDecode(Element element, IJavaProject project, Map unknownElements) { 735 736 IPath projectPath = project.getProject().getFullPath(); 737 NamedNodeMap attributes = element.getAttributes(); 738 NodeList children = element.getChildNodes(); 739 boolean[] foundChildren = new boolean[children.getLength()]; 740 String kindAttr = removeAttribute(TAG_KIND, attributes); 741 String pathAttr = removeAttribute(TAG_PATH, attributes); 742 743 // ensure path is absolute 744 IPath path = new Path(pathAttr); 745 int kind = kindFromString(kindAttr); 746 if (kind != IClasspathEntry.CPE_VARIABLE && kind != IClasspathEntry.CPE_CONTAINER && !path.isAbsolute()) { 747 if (!(path.segmentCount() > 0 && path.segment(0).equals(ClasspathEntry.DOT_DOT))) { 748 path = projectPath.append(path); 749 } 750 } 751 // source attachment info (optional) 752 IPath sourceAttachmentPath = 753 element.hasAttribute(TAG_SOURCEPATH) 754 ? new Path(removeAttribute(TAG_SOURCEPATH, attributes)) 755 : null; 756 if (kind != IClasspathEntry.CPE_VARIABLE && sourceAttachmentPath != null && !sourceAttachmentPath.isAbsolute()) { 757 sourceAttachmentPath = projectPath.append(sourceAttachmentPath); 758 } 759 IPath sourceAttachmentRootPath = 760 element.hasAttribute(TAG_ROOTPATH) 761 ? new Path(removeAttribute(TAG_ROOTPATH, attributes)) 762 : null; 763 764 // exported flag (optional) 765 boolean isExported = removeAttribute(TAG_EXPORTED, attributes).equals("true"); //$NON-NLS-1$ 766 767 // inclusion patterns (optional) 768 IPath[] inclusionPatterns = decodePatterns(attributes, TAG_INCLUDING); 769 if (inclusionPatterns == null) inclusionPatterns = INCLUDE_ALL; 770 771 // exclusion patterns (optional) 772 IPath[] exclusionPatterns = decodePatterns(attributes, TAG_EXCLUDING); 773 if (exclusionPatterns == null) exclusionPatterns = EXCLUDE_NONE; 774 775 // access rules (optional) 776 NodeList attributeList = getChildAttributes(TAG_ACCESS_RULES, children, foundChildren); 777 IAccessRule[] accessRules = decodeAccessRules(attributeList); 778 779 // backward compatibility 780 if (accessRules == null) { 781 accessRules = getAccessRules(inclusionPatterns, exclusionPatterns); 782 } 783 784 // combine access rules (optional) 785 boolean combineAccessRestrictions = !removeAttribute(TAG_COMBINE_ACCESS_RULES, attributes).equals("false"); //$NON-NLS-1$ 786 787 // extra attributes (optional) 788 attributeList = getChildAttributes(TAG_ATTRIBUTES, children, foundChildren); 789 IClasspathAttribute[] extraAttributes = decodeExtraAttributes(attributeList); 790 791 // custom output location 792 IPath outputLocation = element.hasAttribute(TAG_OUTPUT) ? projectPath.append(removeAttribute(TAG_OUTPUT, attributes)) : null; 793 794 String[] unknownAttributes = null; 795 ArrayList unknownChildren = null; 796 797 if (unknownElements != null) { 798 // unknown attributes 799 int unknownAttributeLength = attributes.getLength(); 800 if (unknownAttributeLength != 0) { 801 unknownAttributes = new String[unknownAttributeLength*2]; 802 for (int i = 0; i < unknownAttributeLength; i++) { 803 Node attribute = attributes.item(i); 804 unknownAttributes[i*2] = attribute.getNodeName(); 805 unknownAttributes[i*2 + 1] = attribute.getNodeValue(); 806 } 807 } 808 809 // unknown children 810 for (int i = 0, length = foundChildren.length; i < length; i++) { 811 if (!foundChildren[i]) { 812 Node node = children.item(i); 813 if (node.getNodeType() != Node.ELEMENT_NODE) continue; 814 if (unknownChildren == null) 815 unknownChildren = new ArrayList(); 816 StringBuffer buffer = new StringBuffer(); 817 decodeUnknownNode(node, buffer, project); 818 unknownChildren.add(buffer.toString()); 819 } 820 } 821 } 822 823 // recreate the CP entry 824 IClasspathEntry entry = null; 825 switch (kind) { 826 827 case IClasspathEntry.CPE_PROJECT : 828 entry = new ClasspathEntry( 829 IPackageFragmentRoot.K_SOURCE, 830 IClasspathEntry.CPE_PROJECT, 831 path, 832 ClasspathEntry.INCLUDE_ALL, // inclusion patterns 833 ClasspathEntry.EXCLUDE_NONE, // exclusion patterns 834 null, // source attachment 835 null, // source attachment root 836 null, // specific output folder 837 isExported, 838 accessRules, 839 combineAccessRestrictions, 840 extraAttributes); 841 break; 842 case IClasspathEntry.CPE_LIBRARY : 843 entry = JavaCore.newLibraryEntry( 844 path, 845 sourceAttachmentPath, 846 sourceAttachmentRootPath, 847 accessRules, 848 extraAttributes, 849 isExported); 850 break; 851 case IClasspathEntry.CPE_SOURCE : 852 // must be an entry in this project or specify another project 853 String projSegment = path.segment(0); 854 if (projSegment != null && projSegment.equals(project.getElementName())) { // this project 855 entry = JavaCore.newSourceEntry( 856 path, 857 inclusionPatterns, 858 exclusionPatterns, 859 outputLocation, 860 extraAttributes); 861 } else { 862 if (path.segmentCount() == 1) { 863 // another project 864 entry = JavaCore.newProjectEntry( 865 path, 866 accessRules, 867 combineAccessRestrictions, 868 extraAttributes, 869 isExported); 870 } else { 871 // an invalid source folder 872 entry = JavaCore.newSourceEntry( 873 path, 874 inclusionPatterns, 875 exclusionPatterns, 876 outputLocation, 877 extraAttributes); 878 } 879 } 880 break; 881 case IClasspathEntry.CPE_VARIABLE : 882 entry = JavaCore.newVariableEntry( 883 path, 884 sourceAttachmentPath, 885 sourceAttachmentRootPath, 886 accessRules, 887 extraAttributes, 888 isExported); 889 break; 890 case IClasspathEntry.CPE_CONTAINER : 891 entry = JavaCore.newContainerEntry( 892 path, 893 accessRules, 894 extraAttributes, 895 isExported); 896 break; 897 case ClasspathEntry.K_OUTPUT : 898 if (!path.isAbsolute()) return null; 899 entry = new ClasspathEntry( 900 ClasspathEntry.K_OUTPUT, 901 IClasspathEntry.CPE_LIBRARY, 902 path, 903 INCLUDE_ALL, 904 EXCLUDE_NONE, 905 null, // source attachment 906 null, // source attachment root 907 null, // custom output location 908 false, 909 null, // no access rules 910 false, // no accessible files to combine 911 NO_EXTRA_ATTRIBUTES); 912 break; 913 default : 914 throw new AssertionFailedException(Messages.bind(Messages.classpath_unknownKind, kindAttr)); 915 } 916 917 if (unknownAttributes != null || unknownChildren != null) { 918 UnknownXmlElements unknownXmlElements = new UnknownXmlElements(); 919 unknownXmlElements.attributes = unknownAttributes; 920 unknownXmlElements.children = unknownChildren; 921 unknownElements.put(path, unknownXmlElements); 922 } 923 924 return entry; 925 } 926 927 /* 928 * Returns whether the given path as a ".." segment 929 */ hasDotDot(IPath path)930 public static boolean hasDotDot(IPath path) { 931 for (int i = 0, length = path.segmentCount(); i < length; i++) { 932 if (DOT_DOT.equals(path.segment(i))) 933 return true; 934 } 935 return false; 936 } 937 getChildAttributes(String childName, NodeList children, boolean[] foundChildren)938 public static NodeList getChildAttributes(String childName, NodeList children, boolean[] foundChildren) { 939 for (int i = 0, length = foundChildren.length; i < length; i++) { 940 Node node = children.item(i); 941 if (childName.equals(node.getNodeName())) { 942 foundChildren[i] = true; 943 return node.getChildNodes(); 944 } 945 } 946 return null; 947 } 948 949 removeAttribute(String nodeName, NamedNodeMap nodeMap)950 private static String removeAttribute(String nodeName, NamedNodeMap nodeMap) { 951 Node node = removeNode(nodeName, nodeMap); 952 if (node == null) 953 return ""; // //$NON-NLS-1$ 954 return node.getNodeValue(); 955 } 956 removeNode(String nodeName, NamedNodeMap nodeMap)957 private static Node removeNode(String nodeName, NamedNodeMap nodeMap) { 958 try { 959 return nodeMap.removeNamedItem(nodeName); 960 } catch (DOMException e) { 961 if (e.code != DOMException.NOT_FOUND_ERR) 962 throw e; 963 return null; 964 } 965 } 966 967 /* 968 * Read the Class-Path clause of the manifest of the jar pointed by this path, and return 969 * the corresponding paths. 970 */ resolvedChainedLibraries(IPath jarPath)971 public static IPath[] resolvedChainedLibraries(IPath jarPath) { 972 ArrayList result = new ArrayList(); 973 resolvedChainedLibraries(jarPath, new HashSet(), result); 974 if (result.size() == 0) 975 return NO_PATHS; 976 return (IPath[]) result.toArray(new IPath[result.size()]); 977 } 978 resolvedChainedLibraries(IPath jarPath, HashSet visited, ArrayList result)979 private static void resolvedChainedLibraries(IPath jarPath, HashSet visited, ArrayList result) { 980 if (visited.contains( jarPath)) 981 return; 982 visited.add(jarPath); 983 JavaModelManager manager = JavaModelManager.getJavaModelManager(); 984 if (manager.isNonChainingJar(jarPath)) 985 return; 986 List calledFileNames = getCalledFileNames(jarPath); 987 if (calledFileNames == null) { 988 manager.addNonChainingJar(jarPath); 989 } else { 990 Iterator calledFilesIterator = calledFileNames.iterator(); 991 IPath directoryPath = jarPath.removeLastSegments(1); 992 while (calledFilesIterator.hasNext()) { 993 String calledFileName = (String) calledFilesIterator.next(); 994 if (!directoryPath.isValidPath(calledFileName)) { 995 if (JavaModelManager.CP_RESOLVE_VERBOSE_FAILURE) { 996 Util.verbose("Invalid Class-Path entry " + calledFileName + " in manifest of jar file: " + jarPath.toOSString()); //$NON-NLS-1$ //$NON-NLS-2$ 997 } 998 } else { 999 IPath calledJar = directoryPath.append(new Path(calledFileName)); 1000 // Ignore if segment count is Zero (https://bugs.eclipse.org/bugs/show_bug.cgi?id=308150) 1001 if (calledJar.segmentCount() == 0) { 1002 if (JavaModelManager.CP_RESOLVE_VERBOSE_FAILURE) { 1003 Util.verbose("Invalid Class-Path entry " + calledFileName + " in manifest of jar file: " + jarPath.toOSString()); //$NON-NLS-1$ //$NON-NLS-2$ 1004 } 1005 continue; 1006 } 1007 resolvedChainedLibraries(calledJar, visited, result); 1008 result.add(calledJar); 1009 } 1010 } 1011 } 1012 } 1013 getManifestContents(IPath jarPath)1014 private static char[] getManifestContents(IPath jarPath) throws CoreException, IOException { 1015 // Try to read a cached manifest from the index 1016 if (JavaIndex.isEnabled()) { 1017 JavaIndex index = JavaIndex.getIndex(); 1018 String location = JavaModelManager.getLocalFile(jarPath).getAbsolutePath(); 1019 try (IReader reader = index.getNd().acquireReadLock()) { 1020 NdResourceFile resourceFile = index.getResourceFile(location.toCharArray()); 1021 if (index.isUpToDate(resourceFile)) { 1022 char[] manifestContent = resourceFile.getManifestContent().getChars(); 1023 if (manifestContent.length == 0) { 1024 return null; 1025 } 1026 return manifestContent; 1027 } 1028 } 1029 } 1030 1031 ZipFile zip = null; 1032 InputStream inputStream = null; 1033 JavaModelManager manager = JavaModelManager.getJavaModelManager(); 1034 try { 1035 zip = manager.getZipFile(jarPath); 1036 ZipEntry manifest = zip.getEntry(TypeConstants.META_INF_MANIFEST_MF); 1037 if (manifest == null) { 1038 return null; 1039 } 1040 inputStream = zip.getInputStream(manifest); 1041 char[] chars = getInputStreamAsCharArray(inputStream, -1, UTF_8); 1042 return chars; 1043 } finally { 1044 if (inputStream != null) { 1045 try { 1046 inputStream.close(); 1047 } catch (IOException e) { 1048 // best effort 1049 } 1050 } 1051 manager.closeZipFile(zip); 1052 } 1053 } 1054 getCalledFileNames(IPath jarPath)1055 private static List getCalledFileNames(IPath jarPath) { 1056 Object target = JavaModel.getTarget(jarPath, true/*check existence, otherwise the manifest cannot be read*/); 1057 if (!(target instanceof IFile || target instanceof File)) 1058 return null; 1059 1060 List calledFileNames = null; 1061 try { 1062 char[] manifestContents = getManifestContents(jarPath); 1063 if (manifestContents == null) 1064 return null; 1065 // non-null implies regular file 1066 ManifestAnalyzer analyzer = new ManifestAnalyzer(); 1067 boolean success = analyzer.analyzeManifestContents(manifestContents); 1068 calledFileNames = analyzer.getCalledFileNames(); 1069 if (!success || analyzer.getClasspathSectionsCount() == 1 && calledFileNames == null) { 1070 if (JavaModelManager.CP_RESOLVE_VERBOSE_FAILURE) { 1071 Util.verbose("Invalid Class-Path header in manifest of jar file: " + jarPath.toOSString()); //$NON-NLS-1$ 1072 } 1073 return null; 1074 } else if (analyzer.getClasspathSectionsCount() > 1) { 1075 if (JavaModelManager.CP_RESOLVE_VERBOSE_FAILURE) { 1076 Util.verbose("Multiple Class-Path headers in manifest of jar file: " + jarPath.toOSString()); //$NON-NLS-1$ 1077 } 1078 return null; 1079 } 1080 } catch (CoreException | IOException e) { 1081 // not a zip file 1082 if (JavaModelManager.CP_RESOLVE_VERBOSE_FAILURE) { 1083 Util.verbose("Could not read Class-Path header in manifest of jar file: " + jarPath.toOSString()); //$NON-NLS-1$ 1084 e.printStackTrace(); 1085 } 1086 } 1087 return calledFileNames; 1088 } 1089 1090 /* 1091 * Resolves the ".." in the given path. Returns the given path if it contains no ".." segment. 1092 */ resolveDotDot(IPath reference, IPath path)1093 public static IPath resolveDotDot(IPath reference, IPath path) { 1094 IPath newPath = null; 1095 IPath workspaceLocation = workspaceRoot.getLocation(); 1096 if (reference == null || workspaceLocation.isPrefixOf(reference)) { 1097 for (int i = 0, length = path.segmentCount(); i < length; i++) { 1098 String segment = path.segment(i); 1099 if (DOT_DOT.equals(segment)) { 1100 if (newPath == null) { 1101 if (i == 0) { 1102 newPath = workspaceLocation; 1103 } else { 1104 newPath = path.removeFirstSegments(i); 1105 } 1106 } else { 1107 if (newPath.segmentCount() > 0) { 1108 newPath = newPath.removeLastSegments(1); 1109 } else { 1110 newPath = workspaceLocation; 1111 } 1112 } 1113 } else if (newPath != null) { 1114 if (newPath.equals(workspaceLocation) && workspaceRoot.getProject(segment).isAccessible()) { 1115 newPath = new Path(segment).makeAbsolute(); 1116 } else { 1117 newPath = newPath.append(segment); 1118 } 1119 } 1120 } 1121 } 1122 else { 1123 for (int i = 0, length = path.segmentCount(); i < length; i++) { 1124 String segment = path.segment(i); 1125 if (DOT_DOT.equals(segment)) { 1126 if (newPath == null){ 1127 newPath = reference; 1128 } 1129 if (newPath.segmentCount() > 0) { 1130 newPath = newPath.removeLastSegments(1); 1131 } 1132 } else if (newPath != null) { 1133 newPath = newPath.append(segment); 1134 } 1135 } 1136 } 1137 if (newPath == null) 1138 return path; 1139 return newPath; 1140 } 1141 1142 /** 1143 * Encode some patterns into XML parameter tag 1144 */ encodePatterns(IPath[] patterns, String tag, Map parameters)1145 private static void encodePatterns(IPath[] patterns, String tag, Map parameters) { 1146 if (patterns != null && patterns.length > 0) { 1147 StringBuffer rule = new StringBuffer(10); 1148 for (int i = 0, max = patterns.length; i < max; i++){ 1149 if (i > 0) rule.append('|'); 1150 rule.append(patterns[i]); 1151 } 1152 parameters.put(tag, String.valueOf(rule)); 1153 } 1154 } 1155 1156 /** 1157 * Returns true if the given object is a classpath entry 1158 * with equivalent attributes. 1159 */ 1160 @Override equals(Object object)1161 public boolean equals(Object object) { 1162 if (this == object) 1163 return true; 1164 if (object instanceof ClasspathEntry) { 1165 ClasspathEntry otherEntry = (ClasspathEntry) object; 1166 1167 if (this.contentKind != otherEntry.getContentKind()) 1168 return false; 1169 1170 if (this.entryKind != otherEntry.getEntryKind()) 1171 return false; 1172 1173 if (this.isExported != otherEntry.isExported()) 1174 return false; 1175 1176 if (!this.path.equals(otherEntry.getPath())) 1177 return false; 1178 1179 IPath otherPath = otherEntry.getSourceAttachmentPath(); 1180 if (this.sourceAttachmentPath == null) { 1181 if (otherPath != null) 1182 return false; 1183 } else { 1184 if (!this.sourceAttachmentPath.equals(otherPath)) 1185 return false; 1186 } 1187 1188 otherPath = otherEntry.getSourceAttachmentRootPath(); 1189 if (this.sourceAttachmentRootPath == null) { 1190 if (otherPath != null) 1191 return false; 1192 } else { 1193 if (!this.sourceAttachmentRootPath.equals(otherPath)) 1194 return false; 1195 } 1196 1197 if (!equalPatterns(this.inclusionPatterns, otherEntry.getInclusionPatterns())) 1198 return false; 1199 if (!equalPatterns(this.exclusionPatterns, otherEntry.getExclusionPatterns())) 1200 return false; 1201 AccessRuleSet otherRuleSet = otherEntry.getAccessRuleSet(); 1202 if (getAccessRuleSet() != null) { 1203 if (!getAccessRuleSet().equals(otherRuleSet)) 1204 return false; 1205 } else if (otherRuleSet != null) 1206 return false; 1207 if (this.combineAccessRules != otherEntry.combineAccessRules()) 1208 return false; 1209 otherPath = otherEntry.getOutputLocation(); 1210 if (this.specificOutputLocation == null) { 1211 if (otherPath != null) 1212 return false; 1213 } else { 1214 if (!this.specificOutputLocation.equals(otherPath)) 1215 return false; 1216 } 1217 if (!equalAttributes(this.extraAttributes, otherEntry.getExtraAttributes())) 1218 return false; 1219 return true; 1220 } else { 1221 return false; 1222 } 1223 } 1224 equalAttributes(IClasspathAttribute[] firstAttributes, IClasspathAttribute[] secondAttributes)1225 private static boolean equalAttributes(IClasspathAttribute[] firstAttributes, IClasspathAttribute[] secondAttributes) { 1226 if (firstAttributes != secondAttributes){ 1227 if (firstAttributes == null) return false; 1228 int length = firstAttributes.length; 1229 if (secondAttributes == null || secondAttributes.length != length) 1230 return false; 1231 for (int i = 0; i < length; i++) { 1232 if (!firstAttributes[i].equals(secondAttributes[i])) 1233 return false; 1234 } 1235 } 1236 return true; 1237 } 1238 equalPatterns(IPath[] firstPatterns, IPath[] secondPatterns)1239 private static boolean equalPatterns(IPath[] firstPatterns, IPath[] secondPatterns) { 1240 if (firstPatterns != secondPatterns){ 1241 if (firstPatterns == null) return false; 1242 int length = firstPatterns.length; 1243 if (secondPatterns == null || secondPatterns.length != length) 1244 return false; 1245 for (int i = 0; i < length; i++) { 1246 // compare toStrings instead of IPaths 1247 // since IPath.equals is specified to ignore trailing separators 1248 if (!firstPatterns[i].toString().equals(secondPatterns[i].toString())) 1249 return false; 1250 } 1251 } 1252 return true; 1253 } 1254 1255 /** 1256 * @see IClasspathEntry#getAccessRules() 1257 */ 1258 @Override getAccessRules()1259 public IAccessRule[] getAccessRules() { 1260 if (this.accessRuleSet == null) return NO_ACCESS_RULES; 1261 AccessRule[] rules = this.accessRuleSet.getAccessRules(); 1262 int length = rules.length; 1263 if (length == 0) return NO_ACCESS_RULES; 1264 IAccessRule[] result = new IAccessRule[length]; 1265 System.arraycopy(rules, 0, result, 0, length); 1266 return result; 1267 } 1268 getAccessRuleSet()1269 public AccessRuleSet getAccessRuleSet() { 1270 return this.accessRuleSet; 1271 } 1272 1273 /** 1274 * @see IClasspathEntry 1275 */ 1276 @Override getContentKind()1277 public int getContentKind() { 1278 return this.contentKind; 1279 } 1280 1281 /** 1282 * @see IClasspathEntry 1283 */ 1284 @Override getEntryKind()1285 public int getEntryKind() { 1286 return this.entryKind; 1287 } 1288 1289 /** 1290 * @see IClasspathEntry#getExclusionPatterns() 1291 */ 1292 @Override getExclusionPatterns()1293 public IPath[] getExclusionPatterns() { 1294 return this.exclusionPatterns; 1295 } 1296 1297 @Override getExtraAttributes()1298 public IClasspathAttribute[] getExtraAttributes() { 1299 return this.extraAttributes; 1300 } 1301 1302 /** 1303 * @see IClasspathEntry#getExclusionPatterns() 1304 */ 1305 @Override getInclusionPatterns()1306 public IPath[] getInclusionPatterns() { 1307 return this.inclusionPatterns; 1308 } 1309 1310 /** 1311 * @see IClasspathEntry#getOutputLocation() 1312 */ 1313 @Override getOutputLocation()1314 public IPath getOutputLocation() { 1315 return this.specificOutputLocation; 1316 } 1317 1318 /** 1319 * @see IClasspathEntry 1320 */ 1321 @Override getPath()1322 public IPath getPath() { 1323 return this.path; 1324 } 1325 1326 /** 1327 * @see IClasspathEntry 1328 */ 1329 @Override getSourceAttachmentPath()1330 public IPath getSourceAttachmentPath() { 1331 return this.sourceAttachmentPath; 1332 } 1333 1334 /** 1335 * @see IClasspathEntry 1336 */ 1337 @Override getSourceAttachmentRootPath()1338 public IPath getSourceAttachmentRootPath() { 1339 return this.sourceAttachmentRootPath; 1340 } 1341 1342 /** 1343 * Internal API: answer the path for external annotations (for null analysis) associated with 1344 * the given classpath entry. 1345 * Four shapes of paths are supported: 1346 * <ol> 1347 * <li>relative, variable (VAR/relpath): resolve classpath variable VAR and append relpath</li> 1348 * <li>relative, project (relpath): interpret relpath as a relative path within the given project</li> 1349 * <li>absolute, workspace (/Proj/relpath): an absolute path in the workspace</li> 1350 * <li>absolute, filesystem (/abspath): an absolute path in the filesystem</li> 1351 * </ol> 1352 * In case of ambiguity, workspace lookup has higher priority than filesystem lookup 1353 * (in fact filesystem paths are never validated). 1354 * 1355 * @param entry classpath entry to work on 1356 * @param project project whose classpath we are analysing 1357 * @param resolve if true, any workspace-relative paths will be resolved to filesystem paths. 1358 * @return a path (in the workspace or filesystem-absolute) or null 1359 */ getExternalAnnotationPath(IClasspathEntry entry, IProject project, boolean resolve)1360 public static IPath getExternalAnnotationPath(IClasspathEntry entry, IProject project, boolean resolve) { 1361 String rawAnnotationPath = getRawExternalAnnotationPath(entry); 1362 if (rawAnnotationPath != null) { 1363 IPath annotationPath = new Path(rawAnnotationPath); 1364 if (annotationPath.isAbsolute()) { 1365 if (!resolve) 1366 return annotationPath; 1367 1368 // try Workspace-absolute: 1369 IResource resource = project.getWorkspace().getRoot().findMember(annotationPath); 1370 if (resource != null) { 1371 return resource.getLocation(); 1372 } else if (new File(annotationPath.toOSString()).exists()) { // absolute, not in workspace, must be Filesystem-absolute 1373 return annotationPath; 1374 } 1375 invalidExternalAnnotationPath(project); 1376 } else { 1377 // try Variable (always resolved): 1378 IPath resolved = JavaCore.getResolvedVariablePath(annotationPath); 1379 if (resolved != null) 1380 return resolved; 1381 1382 // Project-relative: 1383 if (project != null) { 1384 if (resolve) { 1385 IResource member = project.findMember(annotationPath); 1386 if (member != null) 1387 return member.getLocation(); 1388 invalidExternalAnnotationPath(project); 1389 } else { 1390 return new Path(project.getName()).append(annotationPath).makeAbsolute(); 1391 } 1392 } 1393 } 1394 } 1395 return null; 1396 } 1397 1398 /** 1399 * Answer the raw external annotation path as specified in .classpath, or null. 1400 * @param entry where to look 1401 * @return the attached external annotation path, or null. 1402 */ getRawExternalAnnotationPath(IClasspathEntry entry)1403 static String getRawExternalAnnotationPath(IClasspathEntry entry) { 1404 return getExtraAttribute(entry, IClasspathAttribute.EXTERNAL_ANNOTATION_PATH); 1405 } 1406 invalidExternalAnnotationPath(IProject project)1407 private static void invalidExternalAnnotationPath(IProject project) { 1408 try { 1409 IMarker[] markers = project.findMarkers(IJavaModelMarker.BUILDPATH_PROBLEM_MARKER, false, IResource.DEPTH_ZERO); 1410 for (int i = 0, l = markers.length; i < l; i++) { 1411 if (markers[i].getAttribute(IMarker.SEVERITY, -1) == IMarker.SEVERITY_ERROR) 1412 return; // one marker is enough 1413 } 1414 } catch (CoreException ce) { 1415 return; 1416 } 1417 // no buildpath marker yet, trigger validation to create one: 1418 new ClasspathValidation((JavaProject) JavaCore.create(project)).validate(); 1419 } 1420 validateExternalAnnotationPath(IJavaProject javaProject, IPath annotationPath)1421 private IJavaModelStatus validateExternalAnnotationPath(IJavaProject javaProject, IPath annotationPath) { 1422 IProject project = javaProject.getProject(); 1423 if (annotationPath.isAbsolute()) { 1424 if (project.getWorkspace().getRoot().exists(annotationPath) // workspace absolute 1425 || new File(annotationPath.toOSString()).exists()) // file system abolute 1426 { 1427 return null; 1428 } 1429 } else { 1430 if (JavaCore.getResolvedVariablePath(annotationPath) != null // variable (relative) 1431 || project.exists(annotationPath)) // project relative 1432 { 1433 return null; 1434 } 1435 } 1436 return new JavaModelStatus(IJavaModelStatusConstants.CP_INVALID_EXTERNAL_ANNOTATION_PATH, 1437 javaProject, 1438 Messages.bind(Messages.classpath_invalidExternalAnnotationPath, 1439 new String[] { annotationPath.toString(), project.getName(), this.path.toString()})); 1440 } 1441 getExtraAttribute(IClasspathEntry entry, String attributeName)1442 public static String getExtraAttribute(IClasspathEntry entry, String attributeName) { 1443 IClasspathAttribute[] extraAttributes = entry.getExtraAttributes(); 1444 for (int i = 0, length = extraAttributes.length; i < length; i++) { 1445 IClasspathAttribute attribute = extraAttributes[i]; 1446 if (attributeName.equals(attribute.getName())) { 1447 return attribute.getValue(); 1448 } 1449 } 1450 return null; 1451 } 1452 1453 @Override getReferencingEntry()1454 public IClasspathEntry getReferencingEntry() { 1455 return this.referencingEntry; 1456 } 1457 1458 /** 1459 * Returns the hash code for this classpath entry 1460 */ 1461 @Override hashCode()1462 public int hashCode() { 1463 return this.path.hashCode(); 1464 } 1465 1466 /** 1467 * @see IClasspathEntry#isExported() 1468 */ 1469 @Override isExported()1470 public boolean isExported() { 1471 return this.isExported; 1472 } 1473 isOptional()1474 public boolean isOptional() { 1475 for (int i = 0, length = this.extraAttributes.length; i < length; i++) { 1476 IClasspathAttribute attribute = this.extraAttributes[i]; 1477 if (IClasspathAttribute.OPTIONAL.equals(attribute.getName()) && "true".equals(attribute.getValue())) //$NON-NLS-1$ 1478 return true; 1479 } 1480 return false; 1481 } isModular()1482 public boolean isModular() { 1483 for (int i = 0, length = this.extraAttributes.length; i < length; i++) { 1484 IClasspathAttribute attribute = this.extraAttributes[i]; 1485 if (IClasspathAttribute.MODULE.equals(attribute.getName()) && "true".equals(attribute.getValue())) //$NON-NLS-1$ 1486 return true; 1487 } 1488 return false; 1489 } 1490 getSourceAttachmentEncoding()1491 public String getSourceAttachmentEncoding() { 1492 for (int i = 0, length = this.extraAttributes.length; i < length; i++) { 1493 IClasspathAttribute attribute = this.extraAttributes[i]; 1494 if (IClasspathAttribute.SOURCE_ATTACHMENT_ENCODING.equals(attribute.getName())) 1495 return attribute.getValue(); 1496 } 1497 return null; 1498 } 1499 1500 /** 1501 * Returns the kind of a <code>PackageFragmentRoot</code> from its <code>String</code> form. 1502 */ kindFromString(String kindStr)1503 static int kindFromString(String kindStr) { 1504 1505 if (kindStr.equalsIgnoreCase("prj")) //$NON-NLS-1$ 1506 return IClasspathEntry.CPE_PROJECT; 1507 if (kindStr.equalsIgnoreCase("var")) //$NON-NLS-1$ 1508 return IClasspathEntry.CPE_VARIABLE; 1509 if (kindStr.equalsIgnoreCase("con")) //$NON-NLS-1$ 1510 return IClasspathEntry.CPE_CONTAINER; 1511 if (kindStr.equalsIgnoreCase("src")) //$NON-NLS-1$ 1512 return IClasspathEntry.CPE_SOURCE; 1513 if (kindStr.equalsIgnoreCase("lib")) //$NON-NLS-1$ 1514 return IClasspathEntry.CPE_LIBRARY; 1515 if (kindStr.equalsIgnoreCase("output")) //$NON-NLS-1$ 1516 return ClasspathEntry.K_OUTPUT; 1517 return -1; 1518 } 1519 1520 /** 1521 * Returns a <code>String</code> for the kind of a class path entry. 1522 */ kindToString(int kind)1523 static String kindToString(int kind) { 1524 1525 switch (kind) { 1526 case IClasspathEntry.CPE_PROJECT : 1527 return "src"; // backward compatibility //$NON-NLS-1$ 1528 case IClasspathEntry.CPE_SOURCE : 1529 return "src"; //$NON-NLS-1$ 1530 case IClasspathEntry.CPE_LIBRARY : 1531 return "lib"; //$NON-NLS-1$ 1532 case IClasspathEntry.CPE_VARIABLE : 1533 return "var"; //$NON-NLS-1$ 1534 case IClasspathEntry.CPE_CONTAINER : 1535 return "con"; //$NON-NLS-1$ 1536 case ClasspathEntry.K_OUTPUT : 1537 return "output"; //$NON-NLS-1$ 1538 default : 1539 return "unknown"; //$NON-NLS-1$ 1540 } 1541 } 1542 1543 /* 1544 * Backward compatibility: only accessible and non-accessible files are supported. 1545 */ getAccessRules(IPath[] accessibleFiles, IPath[] nonAccessibleFiles)1546 public static IAccessRule[] getAccessRules(IPath[] accessibleFiles, IPath[] nonAccessibleFiles) { 1547 int accessibleFilesLength = accessibleFiles == null ? 0 : accessibleFiles.length; 1548 int nonAccessibleFilesLength = nonAccessibleFiles == null ? 0 : nonAccessibleFiles.length; 1549 int length = accessibleFilesLength + nonAccessibleFilesLength; 1550 if (length == 0) return null; 1551 IAccessRule[] accessRules = new IAccessRule[length]; 1552 for (int i = 0; i < accessibleFilesLength; i++) { 1553 accessRules[i] = JavaCore.newAccessRule(accessibleFiles[i], IAccessRule.K_ACCESSIBLE); 1554 } 1555 for (int i = 0; i < nonAccessibleFilesLength; i++) { 1556 accessRules[accessibleFilesLength + i] = JavaCore.newAccessRule(nonAccessibleFiles[i], IAccessRule.K_NON_ACCESSIBLE); 1557 } 1558 return accessRules; 1559 } 1560 1561 /** 1562 * Returns a printable representation of this classpath entry. 1563 */ 1564 @Override toString()1565 public String toString() { 1566 StringBuffer buffer = new StringBuffer(); 1567 Object target = JavaModel.getTarget(getPath(), true); 1568 if (target instanceof File) 1569 buffer.append(getPath().toOSString()); 1570 else 1571 buffer.append(String.valueOf(getPath())); 1572 buffer.append('['); 1573 switch (getEntryKind()) { 1574 case IClasspathEntry.CPE_LIBRARY : 1575 buffer.append("CPE_LIBRARY"); //$NON-NLS-1$ 1576 break; 1577 case IClasspathEntry.CPE_PROJECT : 1578 buffer.append("CPE_PROJECT"); //$NON-NLS-1$ 1579 break; 1580 case IClasspathEntry.CPE_SOURCE : 1581 buffer.append("CPE_SOURCE"); //$NON-NLS-1$ 1582 break; 1583 case IClasspathEntry.CPE_VARIABLE : 1584 buffer.append("CPE_VARIABLE"); //$NON-NLS-1$ 1585 break; 1586 case IClasspathEntry.CPE_CONTAINER : 1587 buffer.append("CPE_CONTAINER"); //$NON-NLS-1$ 1588 break; 1589 } 1590 buffer.append("]["); //$NON-NLS-1$ 1591 switch (getContentKind()) { 1592 case IPackageFragmentRoot.K_BINARY : 1593 buffer.append("K_BINARY"); //$NON-NLS-1$ 1594 break; 1595 case IPackageFragmentRoot.K_SOURCE : 1596 buffer.append("K_SOURCE"); //$NON-NLS-1$ 1597 break; 1598 case ClasspathEntry.K_OUTPUT : 1599 buffer.append("K_OUTPUT"); //$NON-NLS-1$ 1600 break; 1601 } 1602 buffer.append(']'); 1603 if (getSourceAttachmentPath() != null) { 1604 buffer.append("[sourcePath:"); //$NON-NLS-1$ 1605 buffer.append(getSourceAttachmentPath()); 1606 buffer.append(']'); 1607 } 1608 if (getSourceAttachmentRootPath() != null) { 1609 buffer.append("[rootPath:"); //$NON-NLS-1$ 1610 buffer.append(getSourceAttachmentRootPath()); 1611 buffer.append(']'); 1612 } 1613 buffer.append("[isExported:"); //$NON-NLS-1$ 1614 buffer.append(this.isExported); 1615 buffer.append(']'); 1616 IPath[] patterns = this.inclusionPatterns; 1617 int length; 1618 if ((length = patterns == null ? 0 : patterns.length) > 0) { 1619 buffer.append("[including:"); //$NON-NLS-1$ 1620 for (int i = 0; i < length; i++) { 1621 buffer.append(patterns[i]); 1622 if (i != length-1) { 1623 buffer.append('|'); 1624 } 1625 } 1626 buffer.append(']'); 1627 } 1628 patterns = this.exclusionPatterns; 1629 if ((length = patterns == null ? 0 : patterns.length) > 0) { 1630 buffer.append("[excluding:"); //$NON-NLS-1$ 1631 for (int i = 0; i < length; i++) { 1632 buffer.append(patterns[i]); 1633 if (i != length-1) { 1634 buffer.append('|'); 1635 } 1636 } 1637 buffer.append(']'); 1638 } 1639 if (this.accessRuleSet != null) { 1640 buffer.append('['); 1641 buffer.append(this.accessRuleSet.toString(false/*on one line*/)); 1642 buffer.append(']'); 1643 } 1644 if (this.entryKind == CPE_PROJECT) { 1645 buffer.append("[combine access rules:"); //$NON-NLS-1$ 1646 buffer.append(this.combineAccessRules); 1647 buffer.append(']'); 1648 } 1649 if (getOutputLocation() != null) { 1650 buffer.append("[output:"); //$NON-NLS-1$ 1651 buffer.append(getOutputLocation()); 1652 buffer.append(']'); 1653 } 1654 if ((length = this.extraAttributes == null ? 0 : this.extraAttributes.length) > 0) { 1655 buffer.append("[attributes:"); //$NON-NLS-1$ 1656 for (int i = 0; i < length; i++) { 1657 buffer.append(this.extraAttributes[i]); 1658 if (i != length-1) { 1659 buffer.append(','); 1660 } 1661 } 1662 buffer.append(']'); 1663 } 1664 return buffer.toString(); 1665 } 1666 resolvedDotDot(IPath reference)1667 public ClasspathEntry resolvedDotDot(IPath reference) { 1668 IPath resolvedPath = resolveDotDot(reference, this.path); 1669 if (resolvedPath == this.path) 1670 return this; 1671 return new ClasspathEntry( 1672 getContentKind(), 1673 getEntryKind(), 1674 resolvedPath, 1675 this.inclusionPatterns, 1676 this.exclusionPatterns, 1677 getSourceAttachmentPath(), 1678 getSourceAttachmentRootPath(), 1679 getOutputLocation(), 1680 this.getReferencingEntry(), 1681 this.isExported, 1682 getAccessRules(), 1683 this.combineAccessRules, 1684 this.extraAttributes); 1685 } 1686 1687 /* 1688 * Read the Class-Path clause of the manifest of the jar pointed by this entry, and return 1689 * the corresponding library entries. 1690 */ resolvedChainedLibraries()1691 public ClasspathEntry[] resolvedChainedLibraries() { 1692 IPath[] paths = resolvedChainedLibraries(getPath()); 1693 int length = paths.length; 1694 if (length == 0) 1695 return NO_ENTRIES; 1696 ClasspathEntry[] result = new ClasspathEntry[length]; 1697 for (int i = 0; i < length; i++) { 1698 // Chained(referenced) libraries can have their own attachment path. Hence, set them to null 1699 result[i] = new ClasspathEntry( 1700 getContentKind(), 1701 getEntryKind(), 1702 paths[i], 1703 this.inclusionPatterns, 1704 this.exclusionPatterns, 1705 null, 1706 null, 1707 getOutputLocation(), 1708 this, 1709 this.isExported, 1710 getAccessRules(), 1711 this.combineAccessRules, 1712 NO_EXTRA_ATTRIBUTES); 1713 } 1714 return result; 1715 } 1716 1717 /** 1718 * Answers an ID which is used to distinguish entries during package 1719 * fragment root computations 1720 */ rootID()1721 public String rootID(){ 1722 1723 if (this.rootID == null) { 1724 switch(this.entryKind){ 1725 case IClasspathEntry.CPE_LIBRARY : 1726 this.rootID = "[LIB]"+this.path; //$NON-NLS-1$ 1727 break; 1728 case IClasspathEntry.CPE_PROJECT : 1729 this.rootID = "[PRJ]"+this.path; //$NON-NLS-1$ 1730 break; 1731 case IClasspathEntry.CPE_SOURCE : 1732 this.rootID = "[SRC]"+this.path; //$NON-NLS-1$ 1733 break; 1734 case IClasspathEntry.CPE_VARIABLE : 1735 this.rootID = "[VAR]"+this.path; //$NON-NLS-1$ 1736 break; 1737 case IClasspathEntry.CPE_CONTAINER : 1738 this.rootID = "[CON]"+this.path; //$NON-NLS-1$ 1739 break; 1740 default : 1741 this.rootID = ""; //$NON-NLS-1$ 1742 break; 1743 } 1744 } 1745 return this.rootID; 1746 } 1747 1748 /** 1749 * @see IClasspathEntry 1750 * @deprecated 1751 */ 1752 @Override getResolvedEntry()1753 public IClasspathEntry getResolvedEntry() { 1754 1755 return JavaCore.getResolvedClasspathEntry(this); 1756 } 1757 1758 /** 1759 * This function computes the URL of the index location for this classpath entry. It returns null if the URL is 1760 * invalid. 1761 */ getLibraryIndexLocation()1762 public URL getLibraryIndexLocation() { 1763 switch(getEntryKind()) { 1764 case IClasspathEntry.CPE_LIBRARY : 1765 if (SHARED_INDEX_LOCATION != null) { 1766 try { 1767 String pathString = getPath().toPortableString(); 1768 CRC32 checksumCalculator = new CRC32(); 1769 checksumCalculator.update(pathString.getBytes()); 1770 String fileName = Long.toString(checksumCalculator.getValue()) + ".index"; //$NON-NLS-1$ 1771 return new URL("file", null, Paths.get(SHARED_INDEX_LOCATION, fileName).toString()); //$NON-NLS-1$ 1772 } catch (MalformedURLException e1) { 1773 Util.log(e1); // should not happen if protocol known (eg. 'file') 1774 } 1775 } 1776 break; 1777 case IClasspathEntry.CPE_VARIABLE : 1778 break; 1779 default : 1780 return null; 1781 } 1782 if (this.extraAttributes == null) return null; 1783 for (int i= 0; i < this.extraAttributes.length; i++) { 1784 IClasspathAttribute attrib= this.extraAttributes[i]; 1785 if (IClasspathAttribute.INDEX_LOCATION_ATTRIBUTE_NAME.equals(attrib.getName())) { 1786 String value = attrib.getValue(); 1787 try { 1788 return new URL(value); 1789 } catch (MalformedURLException e) { 1790 return null; 1791 } 1792 } 1793 } 1794 return null; 1795 } 1796 ignoreOptionalProblems()1797 public boolean ignoreOptionalProblems() { 1798 if (this.entryKind == IClasspathEntry.CPE_SOURCE) { 1799 for (int i = 0; i < this.extraAttributes.length; i++) { 1800 IClasspathAttribute attrib = this.extraAttributes[i]; 1801 if (IClasspathAttribute.IGNORE_OPTIONAL_PROBLEMS.equals(attrib.getName())) { 1802 return "true".equals(attrib.getValue()); //$NON-NLS-1$ 1803 } 1804 } 1805 } 1806 return false; 1807 } 1808 1809 /** 1810 * Validate a given classpath and output location for a project, using the following rules: 1811 * <ul> 1812 * <li> Classpath entries cannot collide with each other; that is, all entry paths must be unique. 1813 * <li> The project output location path cannot be null, must be absolute and located inside the project. 1814 * <li> Specific output locations (specified on source entries) can be null, if not they must be located inside the project, 1815 * <li> A project entry cannot refer to itself directly (that is, a project cannot prerequisite itself). 1816 * <li> Classpath entries or output locations cannot coincidate or be nested in each other, except for the following scenario listed below: 1817 * <ul><li> A source folder can coincidate with its own output location, in which case this output can then contain library archives. 1818 * However, a specific output location cannot coincidate with any library or a distinct source folder than the one referring to it. </li> 1819 * <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> 1820 * <li> An output location can be nested in a source folder, if the source folder coincidates with the project itself, or if the output 1821 * location is excluded from the source folder. </li> 1822 * </ul> 1823 * </ul> 1824 * 1825 * Note that the classpath entries are not validated automatically. Only bound variables or containers are considered 1826 * in the checking process (this allows to perform a consistency check on a classpath which has references to 1827 * yet non existing projects, folders, ...). 1828 * <p> 1829 * This validation is intended to anticipate classpath issues prior to assigning it to a project. In particular, it will automatically 1830 * be performed during the classpath setting operation (if validation fails, the classpath setting will not complete). 1831 * <p> 1832 * @param javaProject the given java project 1833 * @param rawClasspath a given classpath 1834 * @param projectOutputLocation a given output location 1835 * @return a status object with code <code>IStatus.OK</code> if 1836 * the given classpath and output location are compatible, otherwise a status 1837 * object indicating what is wrong with the classpath or output location 1838 */ validateClasspath(IJavaProject javaProject, IClasspathEntry[] rawClasspath, IPath projectOutputLocation)1839 public static IJavaModelStatus validateClasspath(IJavaProject javaProject, IClasspathEntry[] rawClasspath, IPath projectOutputLocation) { 1840 1841 IProject project = javaProject.getProject(); 1842 IPath projectPath= project.getFullPath(); 1843 String projectName = javaProject.getElementName(); 1844 1845 /* validate output location */ 1846 if (projectOutputLocation == null) { 1847 return new JavaModelStatus(IJavaModelStatusConstants.NULL_PATH); 1848 } 1849 if (projectOutputLocation.isAbsolute()) { 1850 if (!projectPath.isPrefixOf(projectOutputLocation)) { 1851 return new JavaModelStatus(IJavaModelStatusConstants.PATH_OUTSIDE_PROJECT, javaProject, projectOutputLocation.toString()); 1852 } 1853 } else { 1854 return new JavaModelStatus(IJavaModelStatusConstants.RELATIVE_PATH, projectOutputLocation); 1855 } 1856 1857 boolean hasSource = false; 1858 boolean hasLibFolder = false; 1859 1860 1861 // tolerate null path, it will be reset to default 1862 if (rawClasspath == null) 1863 return JavaModelStatus.VERIFIED_OK; 1864 1865 // check duplicate entries on raw classpath only (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=175226 ) 1866 int rawLength = rawClasspath.length; 1867 HashSet pathes = new HashSet(rawLength); 1868 for (int i = 0 ; i < rawLength; i++) { 1869 IPath entryPath = rawClasspath[i].getPath(); 1870 if (!pathes.add(entryPath)){ 1871 String entryPathMsg = projectName.equals(entryPath.segment(0)) ? entryPath.removeFirstSegments(1).toString() : entryPath.makeRelative().toString(); 1872 return new JavaModelStatus(IJavaModelStatusConstants.NAME_COLLISION, Messages.bind(Messages.classpath_duplicateEntryPath, new String[] {entryPathMsg, projectName})); 1873 } 1874 } 1875 1876 // retrieve resolved classpath 1877 IClasspathEntry[] classpath; 1878 try { 1879 // don't resolve chained libraries: see https://bugs.eclipse.org/bugs/show_bug.cgi?id=259685 1880 classpath = ((JavaProject)javaProject).resolveClasspath(rawClasspath, false/*don't use previous session*/, false/*don't resolve chained libraries*/).resolvedClasspath; 1881 } catch(JavaModelException e){ 1882 return e.getJavaModelStatus(); 1883 } 1884 1885 int outputCount = 1; 1886 IPath[] outputLocations = new IPath[classpath.length+1]; 1887 boolean[] allowNestingInOutputLocations = new boolean[classpath.length+1]; 1888 outputLocations[0] = projectOutputLocation; 1889 1890 // retrieve and check output locations 1891 IPath potentialNestedOutput = null; // for error reporting purpose 1892 int sourceEntryCount = 0; 1893 boolean disableExclusionPatterns = JavaCore.DISABLED.equals(javaProject.getOption(JavaCore.CORE_ENABLE_CLASSPATH_EXCLUSION_PATTERNS, true)); 1894 boolean disableCustomOutputLocations = JavaCore.DISABLED.equals(javaProject.getOption(JavaCore.CORE_ENABLE_CLASSPATH_MULTIPLE_OUTPUT_LOCATIONS, true)); 1895 ArrayList<IClasspathEntry> testSourcesFolders=new ArrayList<>(); 1896 HashSet<IPath> mainOutputLocations=new HashSet<>(); 1897 for (IClasspathEntry resolvedEntry : classpath) { 1898 if (disableExclusionPatterns && 1899 ((resolvedEntry.getInclusionPatterns() != null && resolvedEntry.getInclusionPatterns().length > 0) 1900 || (resolvedEntry.getExclusionPatterns() != null && resolvedEntry.getExclusionPatterns().length > 0))) { 1901 return new JavaModelStatus(IJavaModelStatusConstants.DISABLED_CP_EXCLUSION_PATTERNS, javaProject, resolvedEntry.getPath()); 1902 } 1903 switch(resolvedEntry.getEntryKind()){ 1904 case IClasspathEntry.CPE_SOURCE : 1905 sourceEntryCount++; 1906 boolean isTest = resolvedEntry.isTest(); 1907 if(isTest) { 1908 testSourcesFolders.add(resolvedEntry); 1909 } 1910 1911 IPath customOutput; 1912 if ((customOutput = resolvedEntry.getOutputLocation()) != null) { 1913 1914 if (disableCustomOutputLocations) { 1915 return new JavaModelStatus(IJavaModelStatusConstants.DISABLED_CP_MULTIPLE_OUTPUT_LOCATIONS, javaProject, resolvedEntry.getPath()); 1916 } 1917 // ensure custom output is in project 1918 if (customOutput.isAbsolute()) { 1919 if (!javaProject.getPath().isPrefixOf(customOutput)) { 1920 return new JavaModelStatus(IJavaModelStatusConstants.PATH_OUTSIDE_PROJECT, javaProject, customOutput.toString()); 1921 } 1922 } else { 1923 return new JavaModelStatus(IJavaModelStatusConstants.RELATIVE_PATH, customOutput); 1924 } 1925 if(!isTest) { 1926 mainOutputLocations.add(customOutput); 1927 } 1928 // ensure custom output doesn't conflict with other outputs 1929 // check exact match 1930 if (Util.indexOfMatchingPath(customOutput, outputLocations, outputCount) != -1) { 1931 continue; // already found 1932 } 1933 // accumulate all outputs, will check nesting once all available (to handle ordering issues) 1934 outputLocations[outputCount++] = customOutput; 1935 } 1936 } 1937 } 1938 // check nesting across output locations 1939 for (int i = 1 /*no check for default output*/ ; i < outputCount; i++) { 1940 IPath customOutput = outputLocations[i]; 1941 int index; 1942 // check nesting 1943 if ((index = Util.indexOfEnclosingPath(customOutput, outputLocations, outputCount)) != -1 && index != i) { 1944 if (index == 0) { 1945 // custom output is nested in project's output: need to check if all source entries have a custom 1946 // output before complaining 1947 if (potentialNestedOutput == null) potentialNestedOutput = customOutput; 1948 } else { 1949 return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH, Messages.bind(Messages.classpath_cannotNestOutputInOutput, new String[] {customOutput.makeRelative().toString(), outputLocations[index].makeRelative().toString()})); 1950 } 1951 } 1952 } 1953 // allow custom output nesting in project's output if all source entries have a custom output 1954 if (sourceEntryCount <= outputCount-1) { 1955 allowNestingInOutputLocations[0] = true; 1956 } else if (potentialNestedOutput != null) { 1957 return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH, Messages.bind(Messages.classpath_cannotNestOutputInOutput, new String[] {potentialNestedOutput.makeRelative().toString(), outputLocations[0].makeRelative().toString()})); 1958 } else { 1959 if (sourceEntryCount > testSourcesFolders.size()) { 1960 // if there are source folders with main sources, treat project output location as main output location 1961 mainOutputLocations.add(outputLocations[0]); 1962 } 1963 } 1964 for (IClasspathEntry resolvedEntry : testSourcesFolders) { 1965 IPath customOutput; 1966 if ((customOutput = resolvedEntry.getOutputLocation()) != null) { 1967 if(mainOutputLocations.contains(customOutput)) { 1968 return new JavaModelStatus(IJavaModelStatusConstants.TEST_OUTPUT_FOLDER_MUST_BE_SEPARATE_FROM_MAIN_OUTPUT_FOLDERS, javaProject, resolvedEntry.getPath()); 1969 } 1970 } else { 1971 if(sourceEntryCount > testSourcesFolders.size()) { 1972 return new JavaModelStatus(IJavaModelStatusConstants.TEST_SOURCE_REQUIRES_SEPARATE_OUTPUT_LOCATION, javaProject, resolvedEntry.getPath()); 1973 } 1974 } 1975 } 1976 1977 for (IClasspathEntry resolvedEntry : classpath) { 1978 IPath path = resolvedEntry.getPath(); 1979 int index; 1980 switch(resolvedEntry.getEntryKind()){ 1981 1982 case IClasspathEntry.CPE_SOURCE : 1983 hasSource = true; 1984 if ((index = Util.indexOfMatchingPath(path, outputLocations, outputCount)) != -1){ 1985 allowNestingInOutputLocations[index] = true; 1986 } 1987 break; 1988 1989 case IClasspathEntry.CPE_LIBRARY: 1990 Object target = JavaModel.getTarget(path, false/*don't check resource existence*/); 1991 hasLibFolder |= target instanceof IContainer; 1992 if ((index = Util.indexOfMatchingPath(path, outputLocations, outputCount)) != -1){ 1993 allowNestingInOutputLocations[index] = true; 1994 } 1995 break; 1996 } 1997 } 1998 if (!hasSource && !hasLibFolder) { // if no source and no lib folder, then allowed 1999 for (int i = 0; i < outputCount; i++) allowNestingInOutputLocations[i] = true; 2000 } 2001 2002 // check all entries 2003 for (IClasspathEntry entry : classpath) { 2004 if (entry == null) continue; 2005 IPath entryPath = entry.getPath(); 2006 int kind = entry.getEntryKind(); 2007 2008 // no further check if entry coincidates with project or output location 2009 if (entryPath.equals(projectPath)){ 2010 // complain if self-referring project entry 2011 if (kind == IClasspathEntry.CPE_PROJECT){ 2012 return new JavaModelStatus(IJavaModelStatusConstants.INVALID_PATH, Messages.bind(Messages.classpath_cannotReferToItself, entryPath.makeRelative().toString())); 2013 } 2014 // tolerate nesting output in src if src==prj 2015 continue; 2016 } 2017 2018 // allow nesting source entries in each other as long as the outer entry excludes the inner one 2019 if (kind == IClasspathEntry.CPE_SOURCE 2020 || (kind == IClasspathEntry.CPE_LIBRARY && (JavaModel.getTarget(entryPath, false/*don't check existence*/) instanceof IContainer))) { 2021 for (IClasspathEntry otherEntry : classpath) { 2022 if (otherEntry == null) continue; 2023 int otherKind = otherEntry.getEntryKind(); 2024 IPath otherPath = otherEntry.getPath(); 2025 if (entry != otherEntry 2026 && (otherKind == IClasspathEntry.CPE_SOURCE 2027 || (otherKind == IClasspathEntry.CPE_LIBRARY 2028 && (JavaModel.getTarget(otherPath, false/*don't check existence*/) instanceof IContainer)))) { 2029 char[][] inclusionPatterns, exclusionPatterns; 2030 if (otherPath.isPrefixOf(entryPath) 2031 && !otherPath.equals(entryPath) 2032 && !Util.isExcluded(entryPath.append("*"), inclusionPatterns = ((ClasspathEntry)otherEntry).fullInclusionPatternChars(), exclusionPatterns = ((ClasspathEntry)otherEntry).fullExclusionPatternChars(), false)) { //$NON-NLS-1$ 2033 String exclusionPattern = entryPath.removeFirstSegments(otherPath.segmentCount()).segment(0); 2034 if (Util.isExcluded(entryPath, inclusionPatterns, exclusionPatterns, false)) { 2035 return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH, Messages.bind(Messages.classpath_mustEndWithSlash, new String[] {exclusionPattern, entryPath.makeRelative().toString()})); 2036 } else { 2037 if (otherKind == IClasspathEntry.CPE_SOURCE) { 2038 exclusionPattern += '/'; 2039 if (!disableExclusionPatterns) { 2040 return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH, Messages.bind(Messages.classpath_cannotNestEntryInEntry, new String[] {entryPath.makeRelative().toString(), otherEntry.getPath().makeRelative().toString(), exclusionPattern})); 2041 } else { 2042 return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH, Messages.bind(Messages.classpath_cannotNestEntryInEntryNoExclusion, new String[] {entryPath.makeRelative().toString(), otherEntry.getPath().makeRelative().toString(), exclusionPattern})); 2043 } 2044 } else { 2045 return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH, Messages.bind(Messages.classpath_cannotNestEntryInLibrary, new String[] {entryPath.makeRelative().toString(), otherEntry.getPath().makeRelative().toString()})); 2046 } 2047 } 2048 } 2049 } 2050 } 2051 } 2052 2053 // prevent nesting output location inside entry unless enclosing is a source entry which explicitly exclude the output location 2054 char[][] inclusionPatterns = ((ClasspathEntry)entry).fullInclusionPatternChars(); 2055 char[][] exclusionPatterns = ((ClasspathEntry)entry).fullExclusionPatternChars(); 2056 for (int j = 0; j < outputCount; j++){ 2057 IPath currentOutput = outputLocations[j]; 2058 if (entryPath.equals(currentOutput)) continue; 2059 if (entryPath.isPrefixOf(currentOutput)) { 2060 if (kind != IClasspathEntry.CPE_SOURCE || !Util.isExcluded(currentOutput, inclusionPatterns, exclusionPatterns, true)) { 2061 return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH, Messages.bind(Messages.classpath_cannotNestOutputInEntry, new String[] {currentOutput.makeRelative().toString(), entryPath.makeRelative().toString()})); 2062 } 2063 } 2064 } 2065 2066 // prevent nesting entry inside output location - when distinct from project or a source folder 2067 for (int j = 0; j < outputCount; j++){ 2068 if (allowNestingInOutputLocations[j]) continue; 2069 IPath currentOutput = outputLocations[j]; 2070 if (currentOutput.isPrefixOf(entryPath)) { 2071 return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH, Messages.bind(Messages.classpath_cannotNestEntryInOutput, new String[] {entryPath.makeRelative().toString(), currentOutput.makeRelative().toString()})); 2072 } 2073 } 2074 } 2075 // ensure that no specific output is coincidating with another source folder (only allowed if matching current source folder) 2076 // 36465 - for 2.0 backward compatibility, only check specific output locations (the default can still coincidate) 2077 // perform one separate iteration so as to not take precedence over previously checked scenarii (in particular should 2078 // diagnose nesting source folder issue before this one, for example, [src]"Project/", [src]"Project/source/" and output="Project/" should 2079 // first complain about missing exclusion pattern 2080 IJavaModelStatus cachedStatus = null; 2081 for (IClasspathEntry entry : classpath) { 2082 if (entry == null) continue; 2083 IPath entryPath = entry.getPath(); 2084 int kind = entry.getEntryKind(); 2085 2086 // Build some common strings for status message 2087 boolean isProjectRelative = projectName.equals(entryPath.segment(0)); 2088 String entryPathMsg = isProjectRelative ? entryPath.removeFirstSegments(1).toString() : entryPath.makeRelative().toString(); 2089 2090 if (kind == IClasspathEntry.CPE_SOURCE) { 2091 IPath output = entry.getOutputLocation(); 2092 if (output == null) output = projectOutputLocation; // if no specific output, still need to check using default output (this line would check default output) 2093 for (IClasspathEntry otherEntry : classpath) { 2094 if (otherEntry == entry) continue; 2095 2096 switch (otherEntry.getEntryKind()) { 2097 case IClasspathEntry.CPE_SOURCE : 2098 // Bug 287164 : Report errors of overlapping output locations only if the user sets the corresponding preference. 2099 // The check is required for backward compatibility with bug-fix 36465. 2100 String option = javaProject.getOption(JavaCore.CORE_OUTPUT_LOCATION_OVERLAPPING_ANOTHER_SOURCE, true); 2101 if (otherEntry.getPath().equals(output) 2102 && !JavaCore.IGNORE.equals(option)) { 2103 boolean opStartsWithProject = projectName.equals(otherEntry.getPath().segment(0)); 2104 String otherPathMsg = opStartsWithProject ? otherEntry.getPath().removeFirstSegments(1).toString() : otherEntry.getPath().makeRelative().toString(); 2105 if (JavaCore.ERROR.equals(option)) { 2106 return new JavaModelStatus(IStatus.ERROR, IJavaModelStatusConstants.OUTPUT_LOCATION_OVERLAPPING_ANOTHER_SOURCE, 2107 Messages.bind(Messages.classpath_cannotUseDistinctSourceFolderAsOutput, new String[] { 2108 entryPathMsg, otherPathMsg, projectName })); 2109 } 2110 if (cachedStatus == null) { 2111 // Note that the isOK() is being overridden to return true. This is an exceptional scenario 2112 cachedStatus = new JavaModelStatus(IStatus.OK, IJavaModelStatusConstants.OUTPUT_LOCATION_OVERLAPPING_ANOTHER_SOURCE, 2113 Messages.bind(Messages.classpath_cannotUseDistinctSourceFolderAsOutput, new String[] { 2114 entryPathMsg, otherPathMsg, projectName })){ 2115 @Override 2116 public boolean isOK() { 2117 return true; 2118 } 2119 }; 2120 } 2121 } 2122 break; 2123 case IClasspathEntry.CPE_LIBRARY : 2124 if (output != projectOutputLocation && otherEntry.getPath().equals(output)) { 2125 boolean opStartsWithProject = projectName.equals(otherEntry.getPath().segment(0)); 2126 String otherPathMsg = opStartsWithProject ? otherEntry.getPath().removeFirstSegments(1).toString() : otherEntry.getPath().makeRelative().toString(); 2127 return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH, Messages.bind(Messages.classpath_cannotUseLibraryAsOutput, new String[] {entryPathMsg, otherPathMsg, projectName})); 2128 } 2129 } 2130 } 2131 } 2132 } 2133 2134 if (hasSource && testSourcesFolders.size() == 0 && !JavaCore.IGNORE.equals(javaProject.getOption(JavaCore.CORE_MAIN_ONLY_PROJECT_HAS_TEST_ONLY_DEPENDENCY, true))) { 2135 for (IClasspathEntry entry : classpath) { 2136 if (entry == null) 2137 continue; 2138 IPath entryPath = entry.getPath(); 2139 if (entry.getEntryKind() == IClasspathEntry.CPE_PROJECT) { 2140 if (entryPath.isAbsolute() && entryPath.segmentCount() == 1) { 2141 IProject prereqProjectRsc = workspaceRoot.getProject(entryPath.segment(0)); 2142 IJavaProject prereqProject = JavaCore.create(prereqProjectRsc); 2143 boolean hasMain = false; 2144 boolean hasTest = false; 2145 try { 2146 for (IClasspathEntry nested : prereqProject.getRawClasspath()) { 2147 if (nested.getEntryKind() == IClasspathEntry.CPE_SOURCE) { 2148 if (nested.isTest()) { 2149 hasTest = true; 2150 } else { 2151 hasMain = true; 2152 } 2153 if (hasTest && hasMain) 2154 break; 2155 } 2156 } 2157 } catch (JavaModelException e) { 2158 // is reported elsewhere 2159 } 2160 if (hasTest && !hasMain) { 2161 return new JavaModelStatus(IJavaModelStatusConstants.MAIN_ONLY_PROJECT_DEPENDS_ON_TEST_ONLY_PROJECT, 2162 Messages.bind(Messages.classpath_main_only_project_depends_on_test_only_project, 2163 new String[] { prereqProject.getElementName() })); 2164 } 2165 } 2166 } 2167 } 2168 } 2169 2170 // NOTE: The above code that checks for IJavaModelStatusConstants.OUTPUT_LOCATION_OVERLAPPING_ANOTHER_SOURCE, can be configured to return 2171 // 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 2172 // inserted before the mentioned code. 2173 if (cachedStatus != null) return cachedStatus; 2174 2175 return JavaModelStatus.VERIFIED_OK; 2176 } 2177 2178 /** 2179 * Returns a Java model status describing the problem related to this classpath entry if any, 2180 * a status object with code <code>IStatus.OK</code> if the entry is fine (that is, if the 2181 * given classpath entry denotes a valid element to be referenced onto a classpath). 2182 * 2183 * @param project the given java project 2184 * @param entry the given classpath entry 2185 * @param checkSourceAttachment a flag to determine if source attachment should be checked 2186 * @param referredByContainer flag indicating whether the given entry is referred by a classpath container 2187 * @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 2188 */ validateClasspathEntry(IJavaProject project, IClasspathEntry entry, boolean checkSourceAttachment, boolean referredByContainer)2189 public static IJavaModelStatus validateClasspathEntry(IJavaProject project, IClasspathEntry entry, boolean checkSourceAttachment, boolean referredByContainer){ 2190 if (entry.getEntryKind() == IClasspathEntry.CPE_LIBRARY) { 2191 JavaModelManager.getJavaModelManager().removeFromInvalidArchiveCache(entry.getPath()); 2192 } 2193 IJavaModelStatus status = validateClasspathEntry(project, entry, null, checkSourceAttachment, referredByContainer); 2194 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=171136 and https://bugs.eclipse.org/bugs/show_bug.cgi?id=300136 2195 // Ignore class path errors from optional entries. 2196 int statusCode = status.getCode(); 2197 if ( (statusCode == IJavaModelStatusConstants.INVALID_CLASSPATH || 2198 statusCode == IJavaModelStatusConstants.CP_CONTAINER_PATH_UNBOUND || 2199 statusCode == IJavaModelStatusConstants.CP_VARIABLE_PATH_UNBOUND || 2200 statusCode == IJavaModelStatusConstants.INVALID_PATH) && 2201 ((ClasspathEntry) entry).isOptional()) 2202 return JavaModelStatus.VERIFIED_OK; 2203 return status; 2204 } 2205 validateClasspathEntry(IJavaProject project, IClasspathEntry entry, IClasspathContainer entryContainer, boolean checkSourceAttachment, boolean referredByContainer)2206 private static IJavaModelStatus validateClasspathEntry(IJavaProject project, IClasspathEntry entry, IClasspathContainer entryContainer, boolean checkSourceAttachment, boolean referredByContainer){ 2207 2208 IPath path = entry.getPath(); 2209 2210 // Build some common strings for status message 2211 String projectName = project.getElementName(); 2212 String entryPathMsg = projectName.equals(path.segment(0)) ? path.removeFirstSegments(1).makeRelative().toString() : path.toString(); 2213 2214 switch(entry.getEntryKind()){ 2215 2216 // container entry check 2217 case IClasspathEntry.CPE_CONTAINER : 2218 if (path.segmentCount() >= 1){ 2219 try { 2220 IJavaModelStatus status = null; 2221 // Validate extra attributes 2222 IClasspathAttribute[] extraAttributes = entry.getExtraAttributes(); 2223 if (extraAttributes != null) { 2224 int length = extraAttributes.length; 2225 HashSet set = new HashSet(length); 2226 for (int i=0; i<length; i++) { 2227 String attName = extraAttributes[i].getName(); 2228 if (!set.add(attName)) { 2229 status = new JavaModelStatus(IJavaModelStatusConstants.NAME_COLLISION, Messages.bind(Messages.classpath_duplicateEntryExtraAttribute, new String[] {attName, entryPathMsg, projectName})); 2230 break; 2231 } 2232 } 2233 if (status == null) { 2234 String annotationPath = getRawExternalAnnotationPath(entry); 2235 if (annotationPath != null) { 2236 status = ((ClasspathEntry) entry).validateExternalAnnotationPath(project, new Path(annotationPath)); 2237 if (status != null) 2238 return status; 2239 } 2240 } 2241 } 2242 IClasspathContainer container = JavaModelManager.getJavaModelManager().getClasspathContainer(path, project); 2243 // container retrieval is performing validation check on container entry kinds. 2244 if (container == null) { 2245 if (status != null) 2246 return status; 2247 return new JavaModelStatus(IJavaModelStatusConstants.CP_CONTAINER_PATH_UNBOUND, project, path); 2248 } else if (container == JavaModelManager.CONTAINER_INITIALIZATION_IN_PROGRESS) { 2249 // don't create a marker if initialization is in progress (case of cp initialization batching) 2250 return JavaModelStatus.VERIFIED_OK; 2251 } 2252 IClasspathEntry[] containerEntries = container.getClasspathEntries(); 2253 if (containerEntries != null){ 2254 for (int i = 0, length = containerEntries.length; i < length; i++){ 2255 IClasspathEntry containerEntry = containerEntries[i]; 2256 int kind = containerEntry == null ? 0 : containerEntry.getEntryKind(); 2257 if (containerEntry == null 2258 || kind == IClasspathEntry.CPE_SOURCE 2259 || kind == IClasspathEntry.CPE_VARIABLE 2260 || kind == IClasspathEntry.CPE_CONTAINER){ 2261 return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CP_CONTAINER_ENTRY, project, path); 2262 } 2263 IJavaModelStatus containerEntryStatus = validateClasspathEntry(project, containerEntry, container, checkSourceAttachment, true/*referred by container*/); 2264 if (!containerEntryStatus.isOK()){ 2265 return containerEntryStatus; 2266 } 2267 } 2268 } 2269 } catch(JavaModelException e){ 2270 return new JavaModelStatus(e); 2271 } 2272 } else { 2273 return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH, Messages.bind(Messages.classpath_illegalContainerPath, new String[] {entryPathMsg, projectName})); 2274 } 2275 break; 2276 2277 // variable entry check 2278 case IClasspathEntry.CPE_VARIABLE : 2279 if (path.segmentCount() >= 1){ 2280 try { 2281 entry = JavaCore.getResolvedClasspathEntry(entry); 2282 } catch (AssertionFailedException e) { 2283 // Catch the assertion failure and throw java model exception instead 2284 // see bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=55992 2285 return new JavaModelStatus(IJavaModelStatusConstants.INVALID_PATH, e.getMessage()); 2286 } 2287 if (entry == null){ 2288 return new JavaModelStatus(IJavaModelStatusConstants.CP_VARIABLE_PATH_UNBOUND, project, path); 2289 } 2290 2291 // get validation status 2292 IJavaModelStatus status = validateClasspathEntry(project, entry, null, checkSourceAttachment, false/*not referred by container*/); 2293 if (!status.isOK()) return status; 2294 2295 // return deprecation status if any 2296 String variableName = path.segment(0); 2297 String deprecatedMessage = JavaCore.getClasspathVariableDeprecationMessage(variableName); 2298 if (deprecatedMessage != null) { 2299 return new JavaModelStatus(IStatus.WARNING, IJavaModelStatusConstants.DEPRECATED_VARIABLE, project, path, deprecatedMessage); 2300 } 2301 return status; 2302 } else { 2303 return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH, Messages.bind(Messages.classpath_illegalVariablePath, new String[] {entryPathMsg, projectName})); 2304 } 2305 2306 // library entry check 2307 case IClasspathEntry.CPE_LIBRARY : 2308 path = ClasspathEntry.resolveDotDot(project.getProject().getLocation(), path); 2309 2310 // do not validate entries from Class-Path: in manifest 2311 // (these entries are considered optional since the user cannot act on them) 2312 // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=252392 2313 2314 String containerInfo = null; 2315 if (entryContainer != null) { 2316 if (entryContainer instanceof UserLibraryClasspathContainer) { 2317 containerInfo = Messages.bind(Messages.classpath_userLibraryInfo, new String[] {entryContainer.getDescription()}); 2318 } else { 2319 containerInfo = Messages.bind(Messages.classpath_containerInfo, new String[] {entryContainer.getDescription()}); 2320 } 2321 } 2322 IJavaModelStatus status = validateLibraryEntry(path, project, containerInfo, checkSourceAttachment ? entry.getSourceAttachmentPath() : null, entryPathMsg, ((ClasspathEntry) entry).isOptional()); 2323 if (!status.isOK()) 2324 return status; 2325 break; 2326 2327 // project entry check 2328 case IClasspathEntry.CPE_PROJECT : 2329 if (path.isAbsolute() && path.segmentCount() == 1) { 2330 IProject prereqProjectRsc = workspaceRoot.getProject(path.segment(0)); 2331 IJavaProject prereqProject = JavaCore.create(prereqProjectRsc); 2332 try { 2333 if (!prereqProjectRsc.exists() || !prereqProjectRsc.hasNature(JavaCore.NATURE_ID)){ 2334 return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH, Messages.bind(Messages.classpath_unboundProject, new String[] {path.segment(0), projectName})); 2335 } 2336 if (!prereqProjectRsc.isOpen()){ 2337 return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH, Messages.bind(Messages.classpath_closedProject, new String[] {path.segment(0)})); 2338 } 2339 if (!JavaCore.IGNORE.equals(project.getOption(JavaCore.CORE_INCOMPATIBLE_JDK_LEVEL, true))) { 2340 long projectTargetJDK = CompilerOptions.versionToJdkLevel(project.getOption(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM, true)); 2341 long prereqProjectTargetJDK = CompilerOptions.versionToJdkLevel(prereqProject.getOption(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM, true)); 2342 if (prereqProjectTargetJDK > projectTargetJDK) { 2343 return new JavaModelStatus(IJavaModelStatusConstants.INCOMPATIBLE_JDK_LEVEL, 2344 project, path, 2345 Messages.bind(Messages.classpath_incompatibleLibraryJDKLevel, 2346 new String[] { 2347 project.getElementName(), 2348 CompilerOptions.versionFromJdkLevel(projectTargetJDK), 2349 path.makeRelative().toString(), 2350 CompilerOptions.versionFromJdkLevel(prereqProjectTargetJDK)})); 2351 } 2352 } 2353 } catch (CoreException e){ 2354 return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH, Messages.bind(Messages.classpath_unboundProject, new String[] {path.segment(0), projectName})); 2355 } 2356 } else { 2357 return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH, Messages.bind(Messages.classpath_illegalProjectPath, new String[] {path.toString(), projectName})); 2358 } 2359 break; 2360 2361 // project source folder 2362 case IClasspathEntry.CPE_SOURCE : 2363 if (((entry.getInclusionPatterns() != null && entry.getInclusionPatterns().length > 0) 2364 || (entry.getExclusionPatterns() != null && entry.getExclusionPatterns().length > 0)) 2365 && JavaCore.DISABLED.equals(project.getOption(JavaCore.CORE_ENABLE_CLASSPATH_EXCLUSION_PATTERNS, true))) { 2366 return new JavaModelStatus(IJavaModelStatusConstants.DISABLED_CP_EXCLUSION_PATTERNS, project, path); 2367 } 2368 if (entry.getOutputLocation() != null && JavaCore.DISABLED.equals(project.getOption(JavaCore.CORE_ENABLE_CLASSPATH_MULTIPLE_OUTPUT_LOCATIONS, true))) { 2369 return new JavaModelStatus(IJavaModelStatusConstants.DISABLED_CP_MULTIPLE_OUTPUT_LOCATIONS, project, path); 2370 } 2371 if (path.isAbsolute() && !path.isEmpty()) { 2372 IPath projectPath= project.getProject().getFullPath(); 2373 if (!projectPath.isPrefixOf(path) || JavaModel.getTarget(path, true) == null){ 2374 return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH, Messages.bind(Messages.classpath_unboundSourceFolder, new String[] {entryPathMsg, projectName})); 2375 } 2376 } else { 2377 return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH, Messages.bind(Messages.classpath_illegalSourceFolderPath, new String[] {entryPathMsg, projectName})); 2378 } 2379 break; 2380 } 2381 2382 // Validate extra attributes 2383 IClasspathAttribute[] extraAttributes = entry.getExtraAttributes(); 2384 if (extraAttributes != null) { 2385 int length = extraAttributes.length; 2386 HashSet set = new HashSet(length); 2387 for (int i=0; i<length; i++) { 2388 String attName = extraAttributes[i].getName(); 2389 if (!set.add(attName)) { 2390 return new JavaModelStatus(IJavaModelStatusConstants.NAME_COLLISION, Messages.bind(Messages.classpath_duplicateEntryExtraAttribute, new String[] {attName, entryPathMsg, projectName})); 2391 } 2392 } 2393 } 2394 2395 return JavaModelStatus.VERIFIED_OK; 2396 } 2397 2398 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=232816, Now we have the facility to include a container 2399 // name in diagnostics. If the parameter ``container'' is not null, it is used to point to the library 2400 // more fully. validateLibraryEntry(IPath path, IJavaProject project, String container, IPath sourceAttachment, String entryPathMsg, boolean isOptionalLibrary)2401 private static IJavaModelStatus validateLibraryEntry(IPath path, IJavaProject project, String container, IPath sourceAttachment, String entryPathMsg, boolean isOptionalLibrary) { 2402 if (path.isAbsolute() && !path.isEmpty()) { 2403 boolean validateJdkLevelCompatibility = !JavaCore.IGNORE.equals(project.getOption(JavaCore.CORE_INCOMPATIBLE_JDK_LEVEL, true)); 2404 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=412882, avoid validating optional entries 2405 if (!validateJdkLevelCompatibility && isOptionalLibrary) { 2406 return JavaModelStatus.VERIFIED_OK; 2407 } 2408 Object target = JavaModel.getTarget(path, true); 2409 if (target == null) { // https://bugs.eclipse.org/bugs/show_bug.cgi?id=248661 2410 IPath workspaceLocation = workspaceRoot.getLocation(); 2411 if (workspaceLocation.isPrefixOf(path)) { 2412 target = JavaModel.getTarget(path.makeRelativeTo(workspaceLocation).makeAbsolute(), true); 2413 } 2414 } 2415 if (target != null && validateJdkLevelCompatibility) { 2416 long projectTargetJDK = CompilerOptions.versionToJdkLevel(project.getOption(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM, true)); 2417 long libraryJDK = Util.getJdkLevel(target); 2418 if (libraryJDK != 0 && libraryJDK > projectTargetJDK) { 2419 if (container != null) { 2420 return new JavaModelStatus(IJavaModelStatusConstants.INCOMPATIBLE_JDK_LEVEL, 2421 project, path, 2422 Messages.bind(Messages.classpath_incompatibleLibraryJDKLevelInContainer, 2423 new String [] { 2424 project.getElementName(), 2425 CompilerOptions.versionFromJdkLevel(projectTargetJDK), 2426 path.makeRelative().toString(), 2427 container, 2428 CompilerOptions.versionFromJdkLevel(libraryJDK)})); 2429 } else { 2430 return new JavaModelStatus(IJavaModelStatusConstants.INCOMPATIBLE_JDK_LEVEL, 2431 project, path, 2432 Messages.bind(Messages.classpath_incompatibleLibraryJDKLevel, 2433 new String[] { 2434 project.getElementName(), 2435 CompilerOptions.versionFromJdkLevel(projectTargetJDK), 2436 path.makeRelative().toString(), 2437 CompilerOptions.versionFromJdkLevel(libraryJDK)})); 2438 } 2439 } 2440 } 2441 if (isOptionalLibrary) { 2442 return JavaModelStatus.VERIFIED_OK; 2443 } 2444 if (target instanceof IResource){ 2445 IResource resolvedResource = (IResource) target; 2446 switch(resolvedResource.getType()){ 2447 case IResource.FILE : 2448 if (sourceAttachment != null 2449 && !sourceAttachment.isEmpty() 2450 && JavaModel.getTarget(sourceAttachment, true) == null){ 2451 if (container != null) { 2452 return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH, Messages.bind(Messages.classpath_unboundSourceAttachmentInContainedLibrary, new String [] {sourceAttachment.toString(), path.toString(), container})); 2453 } else { 2454 return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH, Messages.bind(Messages.classpath_unboundSourceAttachment, new String [] {sourceAttachment.toString(), path.toString(), project.getElementName()})); 2455 } 2456 } 2457 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=229042 2458 // Validate the contents of the archive 2459 IJavaModelStatus status = validateLibraryContents(path, project, entryPathMsg); 2460 if (status != JavaModelStatus.VERIFIED_OK) 2461 return status; 2462 break; 2463 case IResource.FOLDER : // internal binary folder 2464 if (sourceAttachment != null 2465 && !sourceAttachment.isEmpty() 2466 && JavaModel.getTarget(sourceAttachment, true) == null){ 2467 if (container != null) { 2468 return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH, Messages.bind(Messages.classpath_unboundSourceAttachmentInContainedLibrary, new String [] {sourceAttachment.toString(), path.toString(), container})); 2469 } else { 2470 return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH, Messages.bind(Messages.classpath_unboundSourceAttachment, new String [] {sourceAttachment.toString(), path.toString(), project.getElementName()})); 2471 } 2472 } 2473 } 2474 } else if (target instanceof File){ 2475 File file = JavaModel.getFile(target); 2476 if (file == null) { 2477 if (container != null) { 2478 return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH, Messages.bind(Messages.classpath_illegalExternalFolderInContainer, new String[] {path.toOSString(), container})); 2479 } else { 2480 return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH, Messages.bind(Messages.classpath_illegalExternalFolder, new String[] {path.toOSString(), project.getElementName()})); 2481 } 2482 } else { 2483 if (sourceAttachment != null 2484 && !sourceAttachment.isEmpty() 2485 && JavaModel.getTarget(sourceAttachment, true) == null){ 2486 if (container != null) { 2487 return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH, Messages.bind(Messages.classpath_unboundSourceAttachmentInContainedLibrary, new String [] {sourceAttachment.toString(), path.toOSString(), container})); 2488 } else { 2489 return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH, Messages.bind(Messages.classpath_unboundSourceAttachment, new String [] {sourceAttachment.toString(), path.toOSString(), project.getElementName()})); 2490 } 2491 } 2492 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=229042 2493 // Validate the contents of the archive 2494 if(file.isFile()) { 2495 IJavaModelStatus status = validateLibraryContents(path, project, entryPathMsg); 2496 if (status != JavaModelStatus.VERIFIED_OK) 2497 return status; 2498 } 2499 } 2500 } else { 2501 boolean isExternal = path.getDevice() != null || !ResourcesPlugin.getWorkspace().getRoot().getProject(path.segment(0)).exists(); 2502 if (isExternal) { 2503 if (container != null) { 2504 return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH, Messages.bind(Messages.classpath_unboundLibraryInContainer, new String[] {path.toOSString(), container})); 2505 } else { 2506 return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH, Messages.bind(Messages.classpath_unboundLibrary, new String[] {path.toOSString(), project.getElementName()})); 2507 } 2508 } else { 2509 if (entryPathMsg == null) 2510 entryPathMsg = project.getElementName().equals(path.segment(0)) ? path.removeFirstSegments(1).makeRelative().toString() : path.toString(); 2511 if (container!= null) { 2512 return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH, Messages.bind(Messages.classpath_unboundLibraryInContainer, new String[] {entryPathMsg, container})); 2513 } else { 2514 return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH, Messages.bind(Messages.classpath_unboundLibrary, new String[] {entryPathMsg, project.getElementName()})); 2515 } 2516 } 2517 } 2518 } else { 2519 if (entryPathMsg == null) 2520 entryPathMsg = project.getElementName().equals(path.segment(0)) ? path.removeFirstSegments(1).makeRelative().toString() : path.toString(); 2521 if (container != null) { 2522 return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH, Messages.bind(Messages.classpath_illegalLibraryPathInContainer, new String[] {entryPathMsg, container})); 2523 } else { 2524 return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH, Messages.bind(Messages.classpath_illegalLibraryPath, new String[] {entryPathMsg, project.getElementName()})); 2525 } 2526 } 2527 return JavaModelStatus.VERIFIED_OK; 2528 } 2529 validateLibraryContents(IPath path, IJavaProject project, String entryPathMsg)2530 private static IJavaModelStatus validateLibraryContents(IPath path, IJavaProject project, String entryPathMsg) { 2531 JavaModelManager manager = JavaModelManager.getJavaModelManager(); 2532 try { 2533 manager.verifyArchiveContent(path); 2534 } catch (CoreException e) { 2535 if (e.getStatus().getMessage() == Messages.status_IOException) { 2536 return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH, Messages.bind( 2537 Messages.classpath_archiveReadError, 2538 new String[] {entryPathMsg, project.getElementName()})); 2539 } 2540 } 2541 return JavaModelStatus.VERIFIED_OK; 2542 } 2543 2544 /* 2545 * For testing shared index location in JavaIndexTests only 2546 */ setSharedIndexLocation(String value, Class<?> clazz)2547 public static void setSharedIndexLocation(String value, Class<?> clazz) throws IllegalArgumentException{ 2548 if (clazz != null && "org.eclipse.jdt.core.tests.model.JavaIndexTests".equals(clazz.getName())) { //$NON-NLS-1$ 2549 SHARED_INDEX_LOCATION = value; 2550 } else { 2551 throw new IllegalArgumentException("Cannot set index location for specified test class"); //$NON-NLS-1$ 2552 } 2553 } 2554 } 2555