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