blob: 2eae3617455884157a61a428ba07f55d89b00ad8 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2004 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Common Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/cpl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.jdt.internal.core;
import java.io.File;
import java.util.HashMap;
import java.util.HashSet;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.jdt.core.IClasspathContainer;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaModelStatus;
import org.eclipse.jdt.core.IJavaModelStatusConstants;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
import org.eclipse.jdt.internal.core.util.Util;
import org.w3c.dom.Element;
/**
* @see IClasspathEntry
*/
public class ClasspathEntry implements IClasspathEntry {
/**
* Describes the kind of classpath entry - one of
* CPE_PROJECT, CPE_LIBRARY, CPE_SOURCE, CPE_VARIABLE or CPE_CONTAINER
*/
public int entryKind;
/**
* Describes the kind of package fragment roots found on
* this classpath entry - either K_BINARY or K_SOURCE or
* K_OUTPUT.
*/
public int contentKind;
/**
* The meaning of the path of a classpath entry depends on its entry kind:<ul>
* <li>Source code in the current project (<code>CPE_SOURCE</code>) -
* The path associated with this entry is the absolute path to the root folder. </li>
* <li>A binary library in the current project (<code>CPE_LIBRARY</code>) - the path
* associated with this entry is the absolute path to the JAR (or root folder), and
* in case it refers to an external JAR, then there is no associated resource in
* the workbench.
* <li>A required project (<code>CPE_PROJECT</code>) - the path of the entry denotes the
* path to the corresponding project resource.</li>
* <li>A variable entry (<code>CPE_VARIABLE</code>) - the first segment of the path
* is the name of a classpath variable. If this classpath variable
* is bound to the path <it>P</it>, the path of the corresponding classpath entry
* is computed by appending to <it>P</it> the segments of the returned
* path without the variable.</li>
* <li> A container entry (<code>CPE_CONTAINER</code>) - the first segment of the path is denoting
* the unique container identifier (for which a <code>ClasspathContainerInitializer</code> could be
* registered), and the remaining segments are used as additional hints for resolving the container entry to
* an actual <code>IClasspathContainer</code>.</li>
*/
public IPath path;
/**
* Patterns allowing to include/exclude portions of the resource tree denoted by this entry path.
*/
public IPath[] inclusionPatterns;
private char[][] fullCharInclusionPatterns;
public IPath[] exclusionPatterns;
private char[][] fullCharExclusionPatterns;
private final static char[][] UNINIT_PATTERNS = new char[][] { "Non-initialized yet".toCharArray() }; //$NON-NLS-1$
private String rootID;
/*
* Default inclusion pattern set
*/
public final static IPath[] INCLUDE_ALL = {};
/*
* Default exclusion pattern set
*/
public final static IPath[] EXCLUDE_NONE = {};
/**
* Describes the path to the source archive associated with this
* classpath entry, or <code>null</code> if this classpath entry has no
* source attachment.
* <p>
* Only library and variable classpath entries may have source attachments.
* For library classpath entries, the result path (if present) locates a source
* archive. For variable classpath entries, the result path (if present) has
* an analogous form and meaning as the variable path, namely the first segment
* is the name of a classpath variable.
*/
public IPath sourceAttachmentPath;
/**
* Describes the path within the source archive where package fragments
* are located. An empty path indicates that packages are located at
* the root of the source archive. Returns a non-<code>null</code> value
* if and only if <code>getSourceAttachmentPath</code> returns
* a non-<code>null</code> value.
*/
public IPath sourceAttachmentRootPath;
/**
* Specific output location (for this source entry)
*/
public IPath specificOutputLocation;
/**
* A constant indicating an output location.
*/
public static final int K_OUTPUT = 10;
/**
* The export flag
*/
public boolean isExported;
/**
* Creates a class path entry of the specified kind with the given path.
*/
public ClasspathEntry(
int contentKind,
int entryKind,
IPath path,
IPath[] inclusionPatterns,
IPath[] exclusionPatterns,
IPath sourceAttachmentPath,
IPath sourceAttachmentRootPath,
IPath specificOutputLocation,
boolean isExported) {
this.contentKind = contentKind;
this.entryKind = entryKind;
this.path = path;
this.inclusionPatterns = inclusionPatterns;
if (inclusionPatterns != INCLUDE_ALL && inclusionPatterns.length > 0) {
this.fullCharInclusionPatterns = UNINIT_PATTERNS;
} else {
this.fullCharInclusionPatterns = null; // empty inclusion pattern means everything is included
}
this.exclusionPatterns = exclusionPatterns;
if (exclusionPatterns.length > 0) {
this.fullCharExclusionPatterns = UNINIT_PATTERNS;
} else {
this.fullCharExclusionPatterns = null; // empty exclusion pattern means nothing is excluded
}
this.sourceAttachmentPath = sourceAttachmentPath;
this.sourceAttachmentRootPath = sourceAttachmentRootPath;
this.specificOutputLocation = specificOutputLocation;
this.isExported = isExported;
}
/*
* Returns a char based representation of the exclusions patterns full path.
*/
public char[][] fullExclusionPatternChars() {
if (this.fullCharExclusionPatterns == UNINIT_PATTERNS) {
int length = this.exclusionPatterns.length;
this.fullCharExclusionPatterns = new char[length][];
IPath prefixPath = this.path.removeTrailingSeparator();
for (int i = 0; i < length; i++) {
this.fullCharExclusionPatterns[i] =
prefixPath.append(this.exclusionPatterns[i]).toString().toCharArray();
}
}
return this.fullCharExclusionPatterns;
}
/*
* Returns a char based representation of the exclusions patterns full path.
*/
public char[][] fullInclusionPatternChars() {
if (this.fullCharInclusionPatterns == UNINIT_PATTERNS) {
int length = this.inclusionPatterns.length;
this.fullCharInclusionPatterns = new char[length][];
IPath prefixPath = this.path.removeTrailingSeparator();
for (int i = 0; i < length; i++) {
this.fullCharInclusionPatterns[i] =
prefixPath.append(this.inclusionPatterns[i]).toString().toCharArray();
}
}
return this.fullCharInclusionPatterns;
}
/**
* Returns the XML encoding of the class path.
*/
public void elementEncode(XMLWriter writer, IPath projectPath, boolean indent, boolean newLine) {
HashMap parameters = new HashMap();
parameters.put("kind", ClasspathEntry.kindToString(this.entryKind));//$NON-NLS-1$
IPath xmlPath = this.path;
if (this.entryKind != IClasspathEntry.CPE_VARIABLE && this.entryKind != IClasspathEntry.CPE_CONTAINER) {
// translate to project relative from absolute (unless a device path)
if (xmlPath.isAbsolute()) {
if (projectPath != null && projectPath.isPrefixOf(xmlPath)) {
if (xmlPath.segment(0).equals(projectPath.segment(0))) {
xmlPath = xmlPath.removeFirstSegments(1);
xmlPath = xmlPath.makeRelative();
} else {
xmlPath = xmlPath.makeAbsolute();
}
}
}
}
parameters.put("path", String.valueOf(xmlPath));//$NON-NLS-1$
if (this.sourceAttachmentPath != null) {
xmlPath = this.sourceAttachmentPath;
// translate to project relative from absolute
if (this.entryKind != IClasspathEntry.CPE_VARIABLE && projectPath != null && projectPath.isPrefixOf(xmlPath)) {
if (xmlPath.segment(0).equals(projectPath.segment(0))) {
xmlPath = xmlPath.removeFirstSegments(1);
xmlPath = xmlPath.makeRelative();
}
}
parameters.put("sourcepath", String.valueOf(xmlPath));//$NON-NLS-1$
}
if (this.sourceAttachmentRootPath != null) {
parameters.put("rootpath", String.valueOf(this.sourceAttachmentRootPath));//$NON-NLS-1$
}
if (this.isExported) {
parameters.put("exported", "true");//$NON-NLS-1$//$NON-NLS-2$
}
if (this.inclusionPatterns != null && this.inclusionPatterns.length > 0) {
StringBuffer includeRule = new StringBuffer(10);
for (int i = 0, max = this.inclusionPatterns.length; i < max; i++){
if (i > 0) includeRule.append('|');
includeRule.append(this.inclusionPatterns[i]);
}
parameters.put("including", String.valueOf(includeRule));//$NON-NLS-1$
}
if (this.exclusionPatterns != null && this.exclusionPatterns.length > 0) {
StringBuffer excludeRule = new StringBuffer(10);
for (int i = 0, max = this.exclusionPatterns.length; i < max; i++){
if (i > 0) excludeRule.append('|');
excludeRule.append(this.exclusionPatterns[i]);
}
parameters.put("excluding", String.valueOf(excludeRule));//$NON-NLS-1$
}
if (this.specificOutputLocation != null) {
IPath outputLocation = this.specificOutputLocation.removeFirstSegments(1);
outputLocation = outputLocation.makeRelative();
parameters.put("output", String.valueOf(outputLocation));//$NON-NLS-1$
}
writer.printTag("classpathentry", parameters, indent, newLine, true);//$NON-NLS-1$
}
public static IClasspathEntry elementDecode(Element element, IJavaProject project) {
IPath projectPath = project.getProject().getFullPath();
String kindAttr = element.getAttribute("kind"); //$NON-NLS-1$
String pathAttr = element.getAttribute("path"); //$NON-NLS-1$
// ensure path is absolute
IPath path = new Path(pathAttr);
int kind = kindFromString(kindAttr);
if (kind != IClasspathEntry.CPE_VARIABLE && kind != IClasspathEntry.CPE_CONTAINER && !path.isAbsolute()) {
path = projectPath.append(path);
}
// source attachment info (optional)
IPath sourceAttachmentPath =
element.hasAttribute("sourcepath") //$NON-NLS-1$
? new Path(element.getAttribute("sourcepath")) //$NON-NLS-1$
: null;
if (kind != IClasspathEntry.CPE_VARIABLE && sourceAttachmentPath != null && !sourceAttachmentPath.isAbsolute()) {
sourceAttachmentPath = projectPath.append(sourceAttachmentPath);
}
IPath sourceAttachmentRootPath =
element.hasAttribute("rootpath") //$NON-NLS-1$
? new Path(element.getAttribute("rootpath")) //$NON-NLS-1$
: null;
// exported flag (optional)
boolean isExported = element.getAttribute("exported").equals("true"); //$NON-NLS-1$ //$NON-NLS-2$
// inclusion patterns (optional)
String inclusion = element.getAttribute("including"); //$NON-NLS-1$
IPath[] inclusionPatterns = INCLUDE_ALL;
if (!inclusion.equals("")) { //$NON-NLS-1$
char[][] patterns = CharOperation.splitOn('|', inclusion.toCharArray());
int patternCount;
if ((patternCount = patterns.length) > 0) {
inclusionPatterns = new IPath[patternCount];
for (int j = 0; j < patterns.length; j++){
inclusionPatterns[j] = new Path(new String(patterns[j]));
}
}
}
// exclusion patterns (optional)
String exclusion = element.getAttribute("excluding"); //$NON-NLS-1$
IPath[] exclusionPatterns = EXCLUDE_NONE;
if (!exclusion.equals("")) { //$NON-NLS-1$
char[][] patterns = CharOperation.splitOn('|', exclusion.toCharArray());
int patternCount;
if ((patternCount = patterns.length) > 0) {
exclusionPatterns = new IPath[patternCount];
for (int j = 0; j < patterns.length; j++){
exclusionPatterns[j] = new Path(new String(patterns[j]));
}
}
}
// custom output location
IPath outputLocation = element.hasAttribute("output") ? projectPath.append(element.getAttribute("output")) : null; //$NON-NLS-1$ //$NON-NLS-2$
// recreate the CP entry
switch (kind) {
case IClasspathEntry.CPE_PROJECT :
return JavaCore.newProjectEntry(path, isExported);
case IClasspathEntry.CPE_LIBRARY :
return JavaCore.newLibraryEntry(
path,
sourceAttachmentPath,
sourceAttachmentRootPath,
isExported);
case IClasspathEntry.CPE_SOURCE :
// must be an entry in this project or specify another project
String projSegment = path.segment(0);
if (projSegment != null && projSegment.equals(project.getElementName())) { // this project
return JavaCore.newSourceEntry(path, inclusionPatterns, exclusionPatterns, outputLocation);
} else { // another project
return JavaCore.newProjectEntry(path, isExported);
}
case IClasspathEntry.CPE_VARIABLE :
return JavaCore.newVariableEntry(
path,
sourceAttachmentPath,
sourceAttachmentRootPath,
isExported);
case IClasspathEntry.CPE_CONTAINER :
return JavaCore.newContainerEntry(
path,
isExported);
case ClasspathEntry.K_OUTPUT :
if (!path.isAbsolute()) return null;
return new ClasspathEntry(
ClasspathEntry.K_OUTPUT,
IClasspathEntry.CPE_LIBRARY,
path,
ClasspathEntry.INCLUDE_ALL,
ClasspathEntry.EXCLUDE_NONE,
null, // source attachment
null, // source attachment root
null, // custom output location
false);
default :
throw new Assert.AssertionFailedException(Util.bind("classpath.unknownKind", kindAttr)); //$NON-NLS-1$
}
}
/**
* Returns true if the given object is a classpath entry
* with equivalent attributes.
*/
public boolean equals(Object object) {
if (this == object)
return true;
if (object instanceof IClasspathEntry) {
IClasspathEntry otherEntry = (IClasspathEntry) object;
if (this.contentKind != otherEntry.getContentKind())
return false;
if (this.entryKind != otherEntry.getEntryKind())
return false;
if (this.isExported != otherEntry.isExported())
return false;
if (!this.path.equals(otherEntry.getPath()))
return false;
IPath otherPath = otherEntry.getSourceAttachmentPath();
if (this.sourceAttachmentPath == null) {
if (otherPath != null)
return false;
} else {
if (!this.sourceAttachmentPath.equals(otherPath))
return false;
}
otherPath = otherEntry.getSourceAttachmentRootPath();
if (this.sourceAttachmentRootPath == null) {
if (otherPath != null)
return false;
} else {
if (!this.sourceAttachmentRootPath.equals(otherPath))
return false;
}
IPath[] otherIncludes = otherEntry.getInclusionPatterns();
if (this.inclusionPatterns != otherIncludes){
if (this.inclusionPatterns == null) return false;
int includeLength = this.inclusionPatterns.length;
if (otherIncludes == null || otherIncludes.length != includeLength)
return false;
for (int i = 0; i < includeLength; i++) {
// compare toStrings instead of IPaths
// since IPath.equals is specified to ignore trailing separators
if (!this.inclusionPatterns[i].toString().equals(otherIncludes[i].toString()))
return false;
}
}
IPath[] otherExcludes = otherEntry.getExclusionPatterns();
if (this.exclusionPatterns != otherExcludes){
if (this.exclusionPatterns == null) return false;
int excludeLength = this.exclusionPatterns.length;
if (otherExcludes == null || otherExcludes.length != excludeLength)
return false;
for (int i = 0; i < excludeLength; i++) {
// compare toStrings instead of IPaths
// since IPath.equals is specified to ignore trailing separators
if (!this.exclusionPatterns[i].toString().equals(otherExcludes[i].toString()))
return false;
}
}
otherPath = otherEntry.getOutputLocation();
if (this.specificOutputLocation == null) {
if (otherPath != null)
return false;
} else {
if (!this.specificOutputLocation.equals(otherPath))
return false;
}
return true;
} else {
return false;
}
}
/**
* @see IClasspathEntry
*/
public int getContentKind() {
return this.contentKind;
}
/**
* @see IClasspathEntry
*/
public int getEntryKind() {
return this.entryKind;
}
/**
* @see IClasspathEntry#getExclusionPatterns()
*/
public IPath[] getExclusionPatterns() {
return this.exclusionPatterns;
}
/**
* @see IClasspathEntry#getExclusionPatterns()
*/
public IPath[] getInclusionPatterns() {
return this.inclusionPatterns;
}
/**
* @see IClasspathEntry#getOutputLocation()
*/
public IPath getOutputLocation() {
return this.specificOutputLocation;
}
/**
* @see IClasspathEntry
*/
public IPath getPath() {
return this.path;
}
/**
* @see IClasspathEntry
*/
public IPath getSourceAttachmentPath() {
return this.sourceAttachmentPath;
}
/**
* @see IClasspathEntry
*/
public IPath getSourceAttachmentRootPath() {
return this.sourceAttachmentRootPath;
}
/**
* Returns the hash code for this classpath entry
*/
public int hashCode() {
return this.path.hashCode();
}
/**
* @see IClasspathEntry#isExported()
*/
public boolean isExported() {
return this.isExported;
}
/**
* Returns the kind of a <code>PackageFragmentRoot</code> from its <code>String</code> form.
*/
static int kindFromString(String kindStr) {
if (kindStr.equalsIgnoreCase("prj")) //$NON-NLS-1$
return IClasspathEntry.CPE_PROJECT;
if (kindStr.equalsIgnoreCase("var")) //$NON-NLS-1$
return IClasspathEntry.CPE_VARIABLE;
if (kindStr.equalsIgnoreCase("con")) //$NON-NLS-1$
return IClasspathEntry.CPE_CONTAINER;
if (kindStr.equalsIgnoreCase("src")) //$NON-NLS-1$
return IClasspathEntry.CPE_SOURCE;
if (kindStr.equalsIgnoreCase("lib")) //$NON-NLS-1$
return IClasspathEntry.CPE_LIBRARY;
if (kindStr.equalsIgnoreCase("output")) //$NON-NLS-1$
return ClasspathEntry.K_OUTPUT;
return -1;
}
/**
* Returns a <code>String</code> for the kind of a class path entry.
*/
static String kindToString(int kind) {
switch (kind) {
case IClasspathEntry.CPE_PROJECT :
return "src"; // backward compatibility //$NON-NLS-1$
case IClasspathEntry.CPE_SOURCE :
return "src"; //$NON-NLS-1$
case IClasspathEntry.CPE_LIBRARY :
return "lib"; //$NON-NLS-1$
case IClasspathEntry.CPE_VARIABLE :
return "var"; //$NON-NLS-1$
case IClasspathEntry.CPE_CONTAINER :
return "con"; //$NON-NLS-1$
case ClasspathEntry.K_OUTPUT :
return "output"; //$NON-NLS-1$
default :
return "unknown"; //$NON-NLS-1$
}
}
/**
* Returns a printable representation of this classpath entry.
*/
public String toString() {
StringBuffer buffer = new StringBuffer();
buffer.append(getPath().toString());
buffer.append('[');
switch (getEntryKind()) {
case IClasspathEntry.CPE_LIBRARY :
buffer.append("CPE_LIBRARY"); //$NON-NLS-1$
break;
case IClasspathEntry.CPE_PROJECT :
buffer.append("CPE_PROJECT"); //$NON-NLS-1$
break;
case IClasspathEntry.CPE_SOURCE :
buffer.append("CPE_SOURCE"); //$NON-NLS-1$
break;
case IClasspathEntry.CPE_VARIABLE :
buffer.append("CPE_VARIABLE"); //$NON-NLS-1$
break;
case IClasspathEntry.CPE_CONTAINER :
buffer.append("CPE_CONTAINER"); //$NON-NLS-1$
break;
}
buffer.append("]["); //$NON-NLS-1$
switch (getContentKind()) {
case IPackageFragmentRoot.K_BINARY :
buffer.append("K_BINARY"); //$NON-NLS-1$
break;
case IPackageFragmentRoot.K_SOURCE :
buffer.append("K_SOURCE"); //$NON-NLS-1$
break;
case ClasspathEntry.K_OUTPUT :
buffer.append("K_OUTPUT"); //$NON-NLS-1$
break;
}
buffer.append(']');
if (getSourceAttachmentPath() != null) {
buffer.append("[sourcePath:"); //$NON-NLS-1$
buffer.append(getSourceAttachmentPath());
buffer.append(']');
}
if (getSourceAttachmentRootPath() != null) {
buffer.append("[rootPath:"); //$NON-NLS-1$
buffer.append(getSourceAttachmentRootPath());
buffer.append(']');
}
buffer.append("[isExported:"); //$NON-NLS-1$
buffer.append(this.isExported);
buffer.append(']');
IPath[] patterns = getInclusionPatterns();
int length;
if ((length = patterns == null ? 0 : patterns.length) > 0) {
buffer.append("[including:"); //$NON-NLS-1$
for (int i = 0; i < length; i++) {
buffer.append(patterns[i]);
if (i != length-1) {
buffer.append('|');
}
}
buffer.append(']');
}
patterns = getExclusionPatterns();
if ((length = patterns == null ? 0 : patterns.length) > 0) {
buffer.append("[excluding:"); //$NON-NLS-1$
for (int i = 0; i < length; i++) {
buffer.append(patterns[i]);
if (i != length-1) {
buffer.append('|');
}
}
buffer.append(']');
}
if (getOutputLocation() != null) {
buffer.append("[output:"); //$NON-NLS-1$
buffer.append(getOutputLocation());
buffer.append(']');
}
return buffer.toString();
}
/**
* Answers an ID which is used to distinguish entries during package
* fragment root computations
*/
public String rootID(){
if (this.rootID == null) {
switch(this.entryKind){
case IClasspathEntry.CPE_LIBRARY :
this.rootID = "[LIB]"+this.path; //$NON-NLS-1$
break;
case IClasspathEntry.CPE_PROJECT :
this.rootID = "[PRJ]"+this.path; //$NON-NLS-1$
break;
case IClasspathEntry.CPE_SOURCE :
this.rootID = "[SRC]"+this.path; //$NON-NLS-1$
break;
case IClasspathEntry.CPE_VARIABLE :
this.rootID = "[VAR]"+this.path; //$NON-NLS-1$
break;
case IClasspathEntry.CPE_CONTAINER :
this.rootID = "[CON]"+this.path; //$NON-NLS-1$
break;
default :
this.rootID = ""; //$NON-NLS-1$
break;
}
}
return this.rootID;
}
/**
* @see IClasspathEntry
* @deprecated
*/
public IClasspathEntry getResolvedEntry() {
return JavaCore.getResolvedClasspathEntry(this);
}
/**
* Validate a given classpath and output location for a project, using the following rules:
* <ul>
* <li> Classpath entries cannot collide with each other; that is, all entry paths must be unique.
* <li> The project output location path cannot be null, must be absolute and located inside the project.
* <li> Specific output locations (specified on source entries) can be null, if not they must be located inside the project,
* <li> A project entry cannot refer to itself directly (that is, a project cannot prerequisite itself).
* <li> Classpath entries or output locations cannot coincidate or be nested in each other, except for the following scenarii listed below:
* <ul><li> A source folder can coincidate with its own output location, in which case this output can then contain library archives.
* However, a specific output location cannot coincidate with any library or a distinct source folder than the one referring to it. </li>
* <li> A source/library folder can be nested in any source folder as long as the nested folder is excluded from the enclosing one. </li>
* <li> An output location can be nested in a source folder, if the source folder coincidates with the project itself, or if the output
* location is excluded from the source folder. </li>
* </ul>
* </ul>
*
* Note that the classpath entries are not validated automatically. Only bound variables or containers are considered
* in the checking process (this allows to perform a consistency check on a classpath which has references to
* yet non existing projects, folders, ...).
* <p>
* This validation is intended to anticipate classpath issues prior to assigning it to a project. In particular, it will automatically
* be performed during the classpath setting operation (if validation fails, the classpath setting will not complete).
* <p>
* @param javaProject the given java project
* @param classpath a given classpath
* @param outputLocation a given output location
* @return a status object with code <code>IStatus.OK</code> if
* the given classpath and output location are compatible, otherwise a status
* object indicating what is wrong with the classpath or output location
*/
public static IJavaModelStatus validateClasspath(IJavaProject javaProject, IClasspathEntry[] rawClasspath, IPath projectOutputLocation) {
IProject project = javaProject.getProject();
IPath projectPath= project.getFullPath();
String projectName = javaProject.getElementName();
/* validate output location */
if (projectOutputLocation == null) {
return new JavaModelStatus(IJavaModelStatusConstants.NULL_PATH);
}
if (projectOutputLocation.isAbsolute()) {
if (!projectPath.isPrefixOf(projectOutputLocation)) {
return new JavaModelStatus(IJavaModelStatusConstants.PATH_OUTSIDE_PROJECT, javaProject, projectOutputLocation.toString());
}
} else {
return new JavaModelStatus(IJavaModelStatusConstants.RELATIVE_PATH, projectOutputLocation);
}
boolean hasSource = false;
boolean hasLibFolder = false;
// tolerate null path, it will be reset to default
if (rawClasspath == null)
return JavaModelStatus.VERIFIED_OK;
// retrieve resolved classpath
IClasspathEntry[] classpath;
try {
classpath = ((JavaProject)javaProject).getResolvedClasspath(rawClasspath, null /*output*/, true/*ignore pb*/, false/*no marker*/, null /*no reverse map*/);
} catch(JavaModelException e){
return e.getJavaModelStatus();
}
int length = classpath.length;
int outputCount = 1;
IPath[] outputLocations = new IPath[length+1];
boolean[] allowNestingInOutputLocations = new boolean[length+1];
outputLocations[0] = projectOutputLocation;
// retrieve and check output locations
IPath potentialNestedOutput = null; // for error reporting purpose
int sourceEntryCount = 0;
boolean disableExclusionPatterns = JavaCore.DISABLED.equals(javaProject.getOption(JavaCore.CORE_ENABLE_CLASSPATH_EXCLUSION_PATTERNS, true));
boolean disableCustomOutputLocations = JavaCore.DISABLED.equals(javaProject.getOption(JavaCore.CORE_ENABLE_CLASSPATH_MULTIPLE_OUTPUT_LOCATIONS, true));
for (int i = 0 ; i < length; i++) {
IClasspathEntry resolvedEntry = classpath[i];
switch(resolvedEntry.getEntryKind()){
case IClasspathEntry.CPE_SOURCE :
sourceEntryCount++;
if (disableExclusionPatterns &&
((resolvedEntry.getInclusionPatterns() != null && resolvedEntry.getInclusionPatterns().length > 0)
|| (resolvedEntry.getExclusionPatterns() != null && resolvedEntry.getExclusionPatterns().length > 0))) {
return new JavaModelStatus(IJavaModelStatusConstants.DISABLED_CP_EXCLUSION_PATTERNS, javaProject, resolvedEntry.getPath());
}
IPath customOutput;
if ((customOutput = resolvedEntry.getOutputLocation()) != null) {
if (disableCustomOutputLocations) {
return new JavaModelStatus(IJavaModelStatusConstants.DISABLED_CP_MULTIPLE_OUTPUT_LOCATIONS, javaProject, resolvedEntry.getPath());
}
// ensure custom output is in project
if (customOutput.isAbsolute()) {
if (!javaProject.getPath().isPrefixOf(customOutput)) {
return new JavaModelStatus(IJavaModelStatusConstants.PATH_OUTSIDE_PROJECT, javaProject, customOutput.toString());
}
} else {
return new JavaModelStatus(IJavaModelStatusConstants.RELATIVE_PATH, customOutput);
}
// ensure custom output doesn't conflict with other outputs
// check exact match
if (Util.indexOfMatchingPath(customOutput, outputLocations, outputCount) != -1) {
continue; // already found
}
// accumulate all outputs, will check nesting once all available (to handle ordering issues)
outputLocations[outputCount++] = customOutput;
}
}
}
// check nesting across output locations
for (int i = 1 /*no check for default output*/ ; i < outputCount; i++) {
IPath customOutput = outputLocations[i];
int index;
// check nesting
if ((index = Util.indexOfEnclosingPath(customOutput, outputLocations, outputCount)) != -1 && index != i) {
if (index == 0) {
// custom output is nested in project's output: need to check if all source entries have a custom
// output before complaining
if (potentialNestedOutput == null) potentialNestedOutput = customOutput;
} else {
return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH, Util.bind("classpath.cannotNestOutputInOutput", customOutput.makeRelative().toString(), outputLocations[index].makeRelative().toString())); //$NON-NLS-1$
}
}
}
// allow custom output nesting in project's output if all source entries have a custom output
if (sourceEntryCount <= outputCount-1) {
allowNestingInOutputLocations[0] = true;
} else if (potentialNestedOutput != null) {
return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH, Util.bind("classpath.cannotNestOutputInOutput", potentialNestedOutput.makeRelative().toString(), outputLocations[0].makeRelative().toString())); //$NON-NLS-1$
}
for (int i = 0 ; i < length; i++) {
IClasspathEntry resolvedEntry = classpath[i];
IPath path = resolvedEntry.getPath();
int index;
switch(resolvedEntry.getEntryKind()){
case IClasspathEntry.CPE_SOURCE :
hasSource = true;
if ((index = Util.indexOfMatchingPath(path, outputLocations, outputCount)) != -1){
allowNestingInOutputLocations[index] = true;
}
break;
case IClasspathEntry.CPE_LIBRARY:
hasLibFolder |= !org.eclipse.jdt.internal.compiler.util.Util.isArchiveFileName(path.lastSegment());
if ((index = Util.indexOfMatchingPath(path, outputLocations, outputCount)) != -1){
allowNestingInOutputLocations[index] = true;
}
break;
}
}
if (!hasSource && !hasLibFolder) { // if no source and no lib folder, then allowed
for (int i = 0; i < outputCount; i++) allowNestingInOutputLocations[i] = true;
}
HashSet pathes = new HashSet(length);
// check all entries
for (int i = 0 ; i < length; i++) {
IClasspathEntry entry = classpath[i];
if (entry == null) continue;
IPath entryPath = entry.getPath();
int kind = entry.getEntryKind();
// Build some common strings for status message
boolean isProjectRelative = entryPath.segment(0).toString().equals(projectName);
String entryPathMsg = isProjectRelative ? entryPath.removeFirstSegments(1).toString() : entryPath.makeRelative().toString();
// complain if duplicate path
if (!pathes.add(entryPath)){
return new JavaModelStatus(IJavaModelStatusConstants.NAME_COLLISION, Util.bind("classpath.duplicateEntryPath", entryPathMsg, projectName)); //$NON-NLS-1$
}
// no further check if entry coincidates with project or output location
if (entryPath.equals(projectPath)){
// complain if self-referring project entry
if (kind == IClasspathEntry.CPE_PROJECT){
return new JavaModelStatus(IJavaModelStatusConstants.INVALID_PATH, Util.bind("classpath.cannotReferToItself", entryPath.makeRelative().toString()));//$NON-NLS-1$
}
// tolerate nesting output in src if src==prj
continue;
}
// allow nesting source entries in each other as long as the outer entry excludes the inner one
if (kind == IClasspathEntry.CPE_SOURCE
|| (kind == IClasspathEntry.CPE_LIBRARY && !org.eclipse.jdt.internal.compiler.util.Util.isArchiveFileName(entryPath.lastSegment()))){
for (int j = 0; j < classpath.length; j++){
IClasspathEntry otherEntry = classpath[j];
if (otherEntry == null) continue;
int otherKind = otherEntry.getEntryKind();
IPath otherPath = otherEntry.getPath();
if (entry != otherEntry
&& (otherKind == IClasspathEntry.CPE_SOURCE
|| (otherKind == IClasspathEntry.CPE_LIBRARY
&& !org.eclipse.jdt.internal.compiler.util.Util.isArchiveFileName(otherPath.lastSegment())))){
char[][] inclusionPatterns, exclusionPatterns;
if (otherPath.isPrefixOf(entryPath)
&& !otherPath.equals(entryPath)
&& !Util.isExcluded(entryPath.append("*"), inclusionPatterns = ((ClasspathEntry)otherEntry).fullInclusionPatternChars(), exclusionPatterns = ((ClasspathEntry)otherEntry).fullExclusionPatternChars(), false)) { //$NON-NLS-1$
String exclusionPattern = entryPath.removeFirstSegments(otherPath.segmentCount()).segment(0);
if (Util.isExcluded(entryPath, inclusionPatterns, exclusionPatterns, false)) {
return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH, Util.bind("classpath.mustEndWithSlash", exclusionPattern, entryPath.makeRelative().toString())); //$NON-NLS-1$
} else {
if (otherKind == IClasspathEntry.CPE_SOURCE) {
exclusionPattern += '/';
return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH, Util.bind("classpath.cannotNestEntryInEntry", new String[] {entryPath.makeRelative().toString(), otherEntry.getPath().makeRelative().toString(), exclusionPattern})); //$NON-NLS-1$
} else {
return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH, Util.bind("classpath.cannotNestEntryInLibrary", entryPath.makeRelative().toString(), otherEntry.getPath().makeRelative().toString())); //$NON-NLS-1$
}
}
}
}
}
}
// prevent nesting output location inside entry unless enclosing is a source entry which explicitly exclude the output location
char[][] inclusionPatterns = ((ClasspathEntry)entry).fullInclusionPatternChars();
char[][] exclusionPatterns = ((ClasspathEntry)entry).fullExclusionPatternChars();
for (int j = 0; j < outputCount; j++){
IPath currentOutput = outputLocations[j];
if (entryPath.equals(currentOutput)) continue;
if (entryPath.isPrefixOf(currentOutput)) {
if (kind != IClasspathEntry.CPE_SOURCE || !Util.isExcluded(currentOutput, inclusionPatterns, exclusionPatterns, true)) {
return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH, Util.bind("classpath.cannotNestOutputInEntry", currentOutput.makeRelative().toString(), entryPath.makeRelative().toString())); //$NON-NLS-1$
}
}
}
// prevent nesting entry inside output location - when distinct from project or a source folder
for (int j = 0; j < outputCount; j++){
if (allowNestingInOutputLocations[j]) continue;
IPath currentOutput = outputLocations[j];
if (currentOutput.isPrefixOf(entryPath)) {
return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH, Util.bind("classpath.cannotNestEntryInOutput", entryPath.makeRelative().toString(), currentOutput.makeRelative().toString())); //$NON-NLS-1$
}
}
}
// ensure that no specific output is coincidating with another source folder (only allowed if matching current source folder)
// 36465 - for 2.0 backward compatibility, only check specific output locations (the default can still coincidate)
// perform one separate iteration so as to not take precedence over previously checked scenarii (in particular should
// diagnose nesting source folder issue before this one, for example, [src]"Project/", [src]"Project/source/" and output="Project/" should
// first complain about missing exclusion pattern
for (int i = 0 ; i < length; i++) {
IClasspathEntry entry = classpath[i];
if (entry == null) continue;
IPath entryPath = entry.getPath();
int kind = entry.getEntryKind();
// Build some common strings for status message
boolean isProjectRelative = entryPath.segment(0).toString().equals(projectName);
String entryPathMsg = isProjectRelative ? entryPath.removeFirstSegments(1).toString() : entryPath.makeRelative().toString();
if (kind == IClasspathEntry.CPE_SOURCE) {
IPath output = entry.getOutputLocation();
if (output == null) continue; // 36465 - for 2.0 backward compatibility, only check specific output locations (the default can still coincidate)
// if (output == null) output = projectOutputLocation; // if no specific output, still need to check using default output (this line would check default output)
for (int j = 0; j < length; j++) {
IClasspathEntry otherEntry = classpath[j];
if (otherEntry == entry) continue;
// Build some common strings for status message
boolean opStartsWithProject = otherEntry.getPath().segment(0).toString().equals(projectName);
String otherPathMsg = opStartsWithProject ? otherEntry.getPath().removeFirstSegments(1).toString() : otherEntry.getPath().makeRelative().toString();
switch (otherEntry.getEntryKind()) {
case IClasspathEntry.CPE_SOURCE :
if (otherEntry.getPath().equals(output)) {
return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH, Util.bind("classpath.cannotUseDistinctSourceFolderAsOutput", new String[] {entryPathMsg, otherPathMsg, projectName})); //$NON-NLS-1$
}
break;
case IClasspathEntry.CPE_LIBRARY :
if (otherEntry.getPath().equals(output)) {
return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH, Util.bind("classpath.cannotUseLibraryAsOutput", new String[] {entryPathMsg, otherPathMsg, projectName})); //$NON-NLS-1$
}
}
}
}
}
return JavaModelStatus.VERIFIED_OK;
}
/**
* Returns a Java model status describing the problem related to this classpath entry if any,
* a status object with code <code>IStatus.OK</code> if the entry is fine (that is, if the
* given classpath entry denotes a valid element to be referenced onto a classpath).
*
* @param project the given java project
* @param entry the given classpath entry
* @param checkSourceAttachment a flag to determine if source attachement should be checked
* @param recurseInContainers flag indicating whether validation should be applied to container entries recursively
* @return a java model status describing the problem related to this classpath entry if any, a status object with code <code>IStatus.OK</code> if the entry is fine
*/
public static IJavaModelStatus validateClasspathEntry(IJavaProject project, IClasspathEntry entry, boolean checkSourceAttachment, boolean recurseInContainers){
IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot();
IPath path = entry.getPath();
// Build some common strings for status message
String projectName = project.getElementName();
boolean pathStartsWithProject = path.segment(0).toString().equals(projectName);
String entryPathMsg = pathStartsWithProject ? path.removeFirstSegments(1).toString() : path.makeRelative().toString();
switch(entry.getEntryKind()){
// container entry check
case IClasspathEntry.CPE_CONTAINER :
if (path != null && path.segmentCount() >= 1){
try {
IClasspathContainer container = JavaModelManager.getJavaModelManager().getClasspathContainer(path, project);
// container retrieval is performing validation check on container entry kinds.
if (container == null){
return new JavaModelStatus(IJavaModelStatusConstants.CP_CONTAINER_PATH_UNBOUND, project, path);
} else if (container == JavaModelManager.CONTAINER_INITIALIZATION_IN_PROGRESS) {
// don't create a marker if initialization is in progress (case of cp initialization batching)
return JavaModelStatus.VERIFIED_OK;
}
IClasspathEntry[] containerEntries = container.getClasspathEntries();
if (containerEntries != null){
for (int i = 0, length = containerEntries.length; i < length; i++){
IClasspathEntry containerEntry = containerEntries[i];
int kind = containerEntry == null ? 0 : containerEntry.getEntryKind();
if (containerEntry == null
|| kind == IClasspathEntry.CPE_SOURCE
|| kind == IClasspathEntry.CPE_VARIABLE
|| kind == IClasspathEntry.CPE_CONTAINER){
String description = container.getDescription();
if (description == null) description = path.makeRelative().toString();
return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CP_CONTAINER_ENTRY, project, path);
}
if (recurseInContainers) {
IJavaModelStatus containerEntryStatus = validateClasspathEntry(project, containerEntry, checkSourceAttachment, recurseInContainers);
if (!containerEntryStatus.isOK()){
return containerEntryStatus;
}
}
}
}
} catch(JavaModelException e){
return new JavaModelStatus(e);
}
} else {
return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH, Util.bind("classpath.illegalContainerPath", entryPathMsg, projectName)); //$NON-NLS-1$
}
break;
// variable entry check
case IClasspathEntry.CPE_VARIABLE :
if (path != null && path.segmentCount() >= 1){
try {
entry = JavaCore.getResolvedClasspathEntry(entry);
} catch (Assert.AssertionFailedException e) {
// Catch the assertion failure and throw java model exception instead
// see bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=55992
return new JavaModelStatus(IJavaModelStatusConstants.INVALID_PATH, e.getMessage());
}
if (entry == null){
return new JavaModelStatus(IJavaModelStatusConstants.CP_VARIABLE_PATH_UNBOUND, project, path);
}
return validateClasspathEntry(project, entry, checkSourceAttachment, recurseInContainers);
} else {
return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH, Util.bind("classpath.illegalVariablePath", path.makeRelative().toString(), projectName)); //$NON-NLS-1$
}
// library entry check
case IClasspathEntry.CPE_LIBRARY :
if (path != null && path.isAbsolute() && !path.isEmpty()) {
IPath sourceAttachment = entry.getSourceAttachmentPath();
Object target = JavaModel.getTarget(workspaceRoot, path, true);
if (target != null && project.getOption(JavaCore.CORE_INCOMPATIBLE_JDK_LEVEL, true) != JavaCore.IGNORE) {
long projectTargetJDK = CompilerOptions.versionToJdkLevel(project.getOption(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM, true));
long libraryJDK = Util.getJdkLevel(target);
if (libraryJDK != 0 && libraryJDK > projectTargetJDK) {
return new JavaModelStatus(IJavaModelStatusConstants.INCOMPATIBLE_JDK_LEVEL, project, path, CompilerOptions.versionFromJdkLevel(libraryJDK));
}
}
if (target instanceof IResource){
IResource resolvedResource = (IResource) target;
switch(resolvedResource.getType()){
case IResource.FILE :
if (org.eclipse.jdt.internal.compiler.util.Util.isArchiveFileName(resolvedResource.getFileExtension())) {
if (checkSourceAttachment
&& sourceAttachment != null
&& !sourceAttachment.isEmpty()
&& JavaModel.getTarget(workspaceRoot, sourceAttachment, true) == null){
return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH, Util.bind("classpath.unboundSourceAttachment", new String [] {sourceAttachment.makeRelative().toString(), path.makeRelative().toString(), projectName})); //$NON-NLS-1$
}
}
break;
case IResource.FOLDER : // internal binary folder
if (checkSourceAttachment
&& sourceAttachment != null
&& !sourceAttachment.isEmpty()
&& JavaModel.getTarget(workspaceRoot, sourceAttachment, true) == null){
return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH, Util.bind("classpath.unboundSourceAttachment", new String [] {sourceAttachment.makeRelative().toString(), path.makeRelative().toString(), projectName})); //$NON-NLS-1$
}
}
} else if (target instanceof File){
if (checkSourceAttachment
&& sourceAttachment != null
&& !sourceAttachment.isEmpty()
&& JavaModel.getTarget(workspaceRoot, sourceAttachment, true) == null){
return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH, Util.bind("classpath.unboundSourceAttachment", new String [] {sourceAttachment.toString(), path.makeRelative().toString(), projectName})); //$NON-NLS-1$
}
} else {
return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH, Util.bind("classpath.unboundLibrary", path.makeRelative().toString(), projectName)); //$NON-NLS-1$
}
} else {
return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH, Util.bind("classpath.illegalLibraryPath", path.makeRelative().toString(), projectName)); //$NON-NLS-1$
}
break;
// project entry check
case IClasspathEntry.CPE_PROJECT :
if (path != null && path.isAbsolute() && !path.isEmpty()) {
IProject prereqProjectRsc = workspaceRoot.getProject(path.segment(0));
IJavaProject prereqProject = JavaCore.create(prereqProjectRsc);
try {
if (!prereqProjectRsc.exists() || !prereqProjectRsc.hasNature(JavaCore.NATURE_ID)){
return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH, Util.bind("classpath.unboundProject", path.makeRelative().segment(0).toString(), projectName)); //$NON-NLS-1$
}
if (!prereqProjectRsc.isOpen()){
return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH, Util.bind("classpath.closedProject", path.segment(0).toString())); //$NON-NLS-1$
}
if (project.getOption(JavaCore.CORE_INCOMPATIBLE_JDK_LEVEL, true) != JavaCore.IGNORE) {
long projectTargetJDK = CompilerOptions.versionToJdkLevel(project.getOption(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM, true));
long prereqProjectTargetJDK = CompilerOptions.versionToJdkLevel(prereqProject.getOption(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM, true));
if (prereqProjectTargetJDK > projectTargetJDK) {
return new JavaModelStatus(IJavaModelStatusConstants.INCOMPATIBLE_JDK_LEVEL, project, path, CompilerOptions.versionFromJdkLevel(prereqProjectTargetJDK));
}
}
} catch (CoreException e){
return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH, Util.bind("classpath.unboundProject", path.segment(0).toString(), projectName)); //$NON-NLS-1$
}
} else {
return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH, Util.bind("classpath.illegalProjectPath", path.segment(0).toString(), projectName)); //$NON-NLS-1$
}
break;
// project source folder
case IClasspathEntry.CPE_SOURCE :
if (((entry.getInclusionPatterns() != null && entry.getInclusionPatterns().length > 0)
|| (entry.getExclusionPatterns() != null && entry.getExclusionPatterns().length > 0))
&& JavaCore.DISABLED.equals(project.getOption(JavaCore.CORE_ENABLE_CLASSPATH_EXCLUSION_PATTERNS, true))) {
return new JavaModelStatus(IJavaModelStatusConstants.DISABLED_CP_EXCLUSION_PATTERNS, project, path);
}
if (entry.getOutputLocation() != null && JavaCore.DISABLED.equals(project.getOption(JavaCore.CORE_ENABLE_CLASSPATH_MULTIPLE_OUTPUT_LOCATIONS, true))) {
return new JavaModelStatus(IJavaModelStatusConstants.DISABLED_CP_MULTIPLE_OUTPUT_LOCATIONS, project, path);
}
if (path != null && path.isAbsolute() && !path.isEmpty()) {
IPath projectPath= project.getProject().getFullPath();
if (!projectPath.isPrefixOf(path) || JavaModel.getTarget(workspaceRoot, path, true) == null){
return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH, Util.bind("classpath.unboundSourceFolder", entryPathMsg, projectName)); //$NON-NLS-1$
}
} else {
return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH, Util.bind("classpath.illegalSourceFolderPath", entryPathMsg, projectName)); //$NON-NLS-1$
}
break;
}
return JavaModelStatus.VERIFIED_OK;
}
}