* 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).resolveClasspath(rawClasspath);
} catch(JavaModelException e){
return e.getJavaModelStatus();
}
int length = classpath.length;
int outputCount = 1;
IPath[] outputLocations = new IPath[length+1];
boolean[] allowNestingInOutputLocations = new boolean[length+1];
outputLocations[0] = projectOutputLocation;
// retrieve and check output locations
IPath potentialNestedOutput = null; // for error reporting purpose
int sourceEntryCount = 0;
boolean disableExclusionPatterns = JavaCore.DISABLED.equals(javaProject.getOption(JavaCore.CORE_ENABLE_CLASSPATH_EXCLUSION_PATTERNS, true));
boolean disableCustomOutputLocations = JavaCore.DISABLED.equals(javaProject.getOption(JavaCore.CORE_ENABLE_CLASSPATH_MULTIPLE_OUTPUT_LOCATIONS, true));
for (int i = 0 ; i < length; i++) {
IClasspathEntry resolvedEntry = classpath[i];
if (disableExclusionPatterns &&
((resolvedEntry.getInclusionPatterns() != null && resolvedEntry.getInclusionPatterns().length > 0)
|| (resolvedEntry.getExclusionPatterns() != null && resolvedEntry.getExclusionPatterns().length > 0))) {
return new JavaModelStatus(IJavaModelStatusConstants.DISABLED_CP_EXCLUSION_PATTERNS, javaProject, resolvedEntry.getPath());
}
switch(resolvedEntry.getEntryKind()){
case IClasspathEntry.CPE_SOURCE :
sourceEntryCount++;
IPath customOutput;
if ((customOutput = resolvedEntry.getOutputLocation()) != null) {
if (disableCustomOutputLocations) {
return new JavaModelStatus(IJavaModelStatusConstants.DISABLED_CP_MULTIPLE_OUTPUT_LOCATIONS, javaProject, resolvedEntry.getPath());
}
// ensure custom output is in project
if (customOutput.isAbsolute()) {
if (!javaProject.getPath().isPrefixOf(customOutput)) {
return new JavaModelStatus(IJavaModelStatusConstants.PATH_OUTSIDE_PROJECT, javaProject, customOutput.toString());
}
} else {
return new JavaModelStatus(IJavaModelStatusConstants.RELATIVE_PATH, customOutput);
}
// ensure custom output doesn't conflict with other outputs
// check exact match
if (Util.indexOfMatchingPath(customOutput, outputLocations, outputCount) != -1) {
continue; // already found
}
// accumulate all outputs, will check nesting once all available (to handle ordering issues)
outputLocations[outputCount++] = customOutput;
}
}
}
// check nesting across output locations
for (int i = 1 /*no check for default output*/ ; i < outputCount; i++) {
IPath customOutput = outputLocations[i];
int index;
// check nesting
if ((index = Util.indexOfEnclosingPath(customOutput, outputLocations, outputCount)) != -1 && index != i) {
if (index == 0) {
// custom output is nested in project's output: need to check if all source entries have a custom
// output before complaining
if (potentialNestedOutput == null) potentialNestedOutput = customOutput;
} else {
return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH, Messages.bind(Messages.classpath_cannotNestOutputInOutput, new String[] {customOutput.makeRelative().toString(), outputLocations[index].makeRelative().toString()}));
}
}
}
// allow custom output nesting in project's output if all source entries have a custom output
if (sourceEntryCount <= outputCount-1) {
allowNestingInOutputLocations[0] = true;
} else if (potentialNestedOutput != null) {
return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH, Messages.bind(Messages.classpath_cannotNestOutputInOutput, new String[] {potentialNestedOutput.makeRelative().toString(), outputLocations[0].makeRelative().toString()}));
}
for (int i = 0 ; i < length; i++) {
IClasspathEntry resolvedEntry = classpath[i];
IPath path = resolvedEntry.getPath();
int index;
switch(resolvedEntry.getEntryKind()){
case IClasspathEntry.CPE_SOURCE :
hasSource = true;
if ((index = Util.indexOfMatchingPath(path, outputLocations, outputCount)) != -1){
allowNestingInOutputLocations[index] = true;
}
break;
case IClasspathEntry.CPE_LIBRARY:
hasLibFolder |= !org.aspectj.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 = projectName.equals(entryPath.segment(0));
String entryPathMsg = isProjectRelative ? entryPath.removeFirstSegments(1).toString() : entryPath.makeRelative().toString();
// complain if duplicate path
if (!pathes.add(entryPath)){
return new JavaModelStatus(IJavaModelStatusConstants.NAME_COLLISION, Messages.bind(Messages.classpath_duplicateEntryPath, new String[] {entryPathMsg, projectName}));
}
// no further check if entry coincidates with project or output location
if (entryPath.equals(projectPath)){
// complain if self-referring project entry
if (kind == IClasspathEntry.CPE_PROJECT){
return new JavaModelStatus(IJavaModelStatusConstants.INVALID_PATH, Messages.bind(Messages.classpath_cannotReferToItself, entryPath.makeRelative().toString()));
}
// tolerate nesting output in src if src==prj
continue;
}
// allow nesting source entries in each other as long as the outer entry excludes the inner one
if (kind == IClasspathEntry.CPE_SOURCE
|| (kind == IClasspathEntry.CPE_LIBRARY && !org.aspectj.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.aspectj.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, Messages.bind(Messages.classpath_mustEndWithSlash, new String[] {exclusionPattern, entryPath.makeRelative().toString()}));
} else {
if (otherKind == IClasspathEntry.CPE_SOURCE) {
exclusionPattern += '/';
if (!disableExclusionPatterns) {
return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH, Messages.bind(Messages.classpath_cannotNestEntryInEntry, new String[] {entryPath.makeRelative().toString(), otherEntry.getPath().makeRelative().toString(), exclusionPattern}));
} else {
return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH, Messages.bind(Messages.classpath_cannotNestEntryInEntryNoExclusion, new String[] {entryPath.makeRelative().toString(), otherEntry.getPath().makeRelative().toString(), exclusionPattern}));
}
} else {
return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH, Messages.bind(Messages.classpath_cannotNestEntryInLibrary, new String[] {entryPath.makeRelative().toString(), otherEntry.getPath().makeRelative().toString()}));
}
}
}
}
}
}
// prevent nesting output location inside entry unless enclosing is a source entry which explicitly exclude the output location
char[][] inclusionPatterns = ((ClasspathEntry)entry).fullInclusionPatternChars();
char[][] exclusionPatterns = ((ClasspathEntry)entry).fullExclusionPatternChars();
for (int j = 0; j < outputCount; j++){
IPath currentOutput = outputLocations[j];
if (entryPath.equals(currentOutput)) continue;
if (entryPath.isPrefixOf(currentOutput)) {
if (kind != IClasspathEntry.CPE_SOURCE || !Util.isExcluded(currentOutput, inclusionPatterns, exclusionPatterns, true)) {
return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH, Messages.bind(Messages.classpath_cannotNestOutputInEntry, new String[] {currentOutput.makeRelative().toString(), entryPath.makeRelative().toString()}));
}
}
}
// prevent nesting entry inside output location - when distinct from project or a source folder
for (int j = 0; j < outputCount; j++){
if (allowNestingInOutputLocations[j]) continue;
IPath currentOutput = outputLocations[j];
if (currentOutput.isPrefixOf(entryPath)) {
return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CLASSPATH, Messages.bind(Messages.classpath_cannotNestEntryInOutput, new String[] {entryPath.makeRelative().toString(), currentOutput.makeRelative().toString()}));
}
}
}
// ensure that no specific output is coincidating with another source folder (only allowed if matching current source folder)
// 36465 - for 2.0 backward compatibility, only check specific output locations (the default can still coincidate)
// perform one separate iteration so as to not take precedence over previously checked scenarii (in particular should
// diagnose nesting source folder issue before this one, for example, [src]"Project/", [src]"Project/source/" and output="Project/" should
// first complain about missing exclusion pattern
for (int i = 0 ; i < length; i++) {
IClasspathEntry entry = classpath[i];
if (entry == null) continue;
IPath entryPath = entry.getPath();
int kind = entry.getEntryKind();
// Build some common strings for status message
boolean isProjectRelative = projectName.equals(entryPath.segment(0));
String entryPathMsg = isProjectRelative ? entryPath.removeFirstSegments(1).toString() : entryPath.makeRelative().toString();
if (kind == IClasspathEntry.CPE_SOURCE) {
IPath output = entry.getOutputLocation();
if (output == null) 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;