blob: 8acaec4ef3ffc6a209a2d9e32258d43576e70662 [file] [log] [blame]
package org.eclipse.pde.internal.core.builders;
import java.io.File;
import java.util.ArrayList;
import java.util.regex.Pattern;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.osgi.service.resolver.BundleDescription;
import org.eclipse.osgi.util.NLS;
import org.eclipse.pde.core.build.IBuild;
import org.eclipse.pde.core.build.IBuildEntry;
import org.eclipse.pde.core.plugin.IPluginLibrary;
import org.eclipse.pde.core.plugin.IPluginModelBase;
import org.eclipse.pde.internal.core.ClasspathUtilCore;
import org.eclipse.pde.internal.core.ModelEntry;
import org.eclipse.pde.internal.core.PDECore;
import org.eclipse.pde.internal.core.PDECoreMessages;
import org.eclipse.pde.internal.core.PluginModelManager;
import org.eclipse.pde.internal.core.build.WorkspaceBuildModel;
import org.eclipse.pde.internal.core.ibundle.IBundleFragmentModel;
import org.eclipse.pde.internal.core.ibundle.IBundleModel;
import org.eclipse.pde.internal.core.ibundle.IBundlePluginModelBase;
import org.eclipse.pde.internal.core.ibundle.IManifestHeader;
import org.eclipse.pde.internal.core.text.build.BuildEntry;
import org.eclipse.pde.internal.core.text.build.BuildModel;
import org.eclipse.pde.internal.core.util.CoreUtility;
import org.eclipse.pde.internal.core.util.PatternConstructor;
import org.osgi.framework.Constants;
public class BuildErrorReporter extends ErrorReporter {
private static final int NO_RES = PDEMarkerFactory.NO_RESOLUTION;
private static final String BIN_INCLUDES = "bin.includes"; //$NON-NLS-1$
private static final String SRC_INCLUDES = "src.includes"; //$NON-NLS-1$
private static final String CUSTOM = "custom"; //$NON-NLS-1$
public static final String SOURCE = "source."; //$NON-NLS-1$
private static final String DEF_SOURCE_ENTRY = SOURCE + '.';
private class BuildProblem {
String fEntryToken;
String fEntryName;
String fMessage;
int fFixId;
BuildProblem(String name, String token, String message, int fixId) {
fEntryName = name;
fEntryToken = token;
fMessage = message;
fFixId = fixId;
}
public boolean equals(Object obj) {
if (!(obj instanceof BuildProblem))
return false;
BuildProblem bp = (BuildProblem)obj;
if (!fEntryName.equals(bp.fEntryName))
return false;
if (fEntryToken != null && !fEntryToken.equals(bp.fEntryToken))
return false;
if (fFixId != bp.fFixId)
return false;
return true;
}
}
private ArrayList fProblemList = new ArrayList();
private int fSeverity;
public BuildErrorReporter(IFile buildFile, int severity) {
super(buildFile);
fSeverity = severity;
}
public void validate(IProgressMonitor monitor) {
if (fSeverity == CompilerFlags.IGNORE)
return;
WorkspaceBuildModel wbm = new WorkspaceBuildModel(fFile);
wbm.load();
if (!wbm.isLoaded())
return;
// check build and store all found errors
validateBuild(wbm.getBuild(true));
// if there are any errors report using the text model
if (fProblemList.size() > 0)
reportErrors(prepareTextBuildModel(monitor));
}
private void validateBuild(IBuild build) {
IBuildEntry binIncludes = null;
IBuildEntry srcIncludes = null;
ArrayList sourceEntries = new ArrayList();
ArrayList sourceEntryKeys = new ArrayList();
IBuildEntry[] entries = build.getBuildEntries();
for (int i = 0; i < entries.length; i++) {
String name = entries[i].getName();
if (entries[i].getTokens().length == 0) {
prepareError(name, null,
PDECoreMessages.BuildErrorReporter_emptyEntry,
PDEMarkerFactory.B_REMOVAL);
} else if (name.equals(BIN_INCLUDES))
binIncludes = entries[i];
else if (name.equals(SRC_INCLUDES))
srcIncludes = entries[i];
else if (name.startsWith(SOURCE)) {
sourceEntries.add(entries[i]);
sourceEntryKeys.add(entries[i].getName());
} else if (name.equals(CUSTOM)) {
String[] tokens = entries[i].getTokens();
if (tokens.length == 1 && tokens[0].equalsIgnoreCase("true")) //$NON-NLS-1$
// nothing to validate in custom builds
return;
}
}
validateIncludes(binIncludes, sourceEntryKeys);
validateIncludes(srcIncludes, sourceEntryKeys);
try {
if (fProject.hasNature(JavaCore.NATURE_ID)) {
IJavaProject jp = JavaCore.create(fProject);
IClasspathEntry[] cpes = jp.getRawClasspath();
validateMissingLibraries(sourceEntryKeys, cpes);
validateSourceEntries(sourceEntries, cpes);
}
} catch (JavaModelException e) {
} catch (CoreException e) {
}
validateSourceEntries(sourceEntries);
validateMissingSourceInBinIncludes(binIncludes, sourceEntryKeys);
}
private void validateMissingSourceInBinIncludes(IBuildEntry binIncludes, ArrayList sourceEntryKeys) {
if (binIncludes == null)
return;
for (int i = 0; i < sourceEntryKeys.size(); i++) {
String key = (String)sourceEntryKeys.get(i);
key = key.substring(SOURCE.length());
boolean found = false;
String[] binIncludesTokens = binIncludes.getTokens();
for (int j = 0; j < binIncludesTokens.length; j++) {
Pattern pattern = PatternConstructor.createPattern(binIncludesTokens[j], false);
if (pattern.matcher(key).matches())
found = true;
}
if (!found)
prepareError(BIN_INCLUDES, key,
NLS.bind(PDECoreMessages.BuildErrorReporter_binIncludesMissing, key),
PDEMarkerFactory.B_ADDDITION);
}
}
private void validateSourceEntries(ArrayList sourceEntries) {
for (int i = 0; i < sourceEntries.size(); i++) {
String name = ((IBuildEntry)sourceEntries.get(i)).getName();
String[] tokens = ((IBuildEntry)sourceEntries.get(i)).getTokens();
for (int j = 0; j < tokens.length; j++) {
IResource folderEntry = fProject.findMember(tokens[j]);
if (folderEntry == null
|| !folderEntry.exists()
|| !(folderEntry instanceof IFolder))
prepareError(name, tokens[j],
NLS.bind(PDECoreMessages.BuildErrorReporter_missingFolder, tokens[j]),
PDEMarkerFactory.B_REMOVAL);
}
}
}
private void validateMissingLibraries(ArrayList sourceEntryKeys, IClasspathEntry[] cpes) {
PluginModelManager manager = PDECore.getDefault().getModelManager();
IPluginModelBase model = manager.findModel(fProject);
if (model == null)
return;
if (model instanceof IBundlePluginModelBase && !(model instanceof IBundleFragmentModel)) {
IBundleModel bm = ((IBundlePluginModelBase)model).getBundleModel();
IManifestHeader mh = bm.getBundle().getManifestHeader(Constants.BUNDLE_CLASSPATH);
if ((mh == null || mh.getValue() == null)) {
for (int i = 0; i < cpes.length; i++) {
if (cpes[i].getEntryKind() == IClasspathEntry.CPE_SOURCE) {
if (!sourceEntryKeys.contains(DEF_SOURCE_ENTRY))
prepareError(DEF_SOURCE_ENTRY, null,
PDECoreMessages.BuildErrorReporter_sourceMissing,
PDEMarkerFactory.B_SOURCE_ADDITION);
break;
}
}
}
}
IPluginLibrary[] libraries = model.getPluginBase().getLibraries();
for (int i = 0; i < libraries.length; i++) {
String libname = libraries[i].getName();
if (libname.equals(".")) { //$NON-NLS-1$
// no need to flag anything if the project contains no source folders.
for (int j = 0; j < cpes.length; j++) {
if (cpes[j].getEntryKind() == IClasspathEntry.CPE_SOURCE) {
if (!sourceEntryKeys.contains(DEF_SOURCE_ENTRY))
prepareError(DEF_SOURCE_ENTRY, null,
PDECoreMessages.BuildErrorReporter_sourceMissing,
PDEMarkerFactory.B_SOURCE_ADDITION);
break;
}
}
continue;
} else if (fProject.findMember(libname) != null) {//$NON-NLS-1$
// non "." library entries that exist in the workspace
// don't have to be referenced in the build properties
continue;
}
String sourceEntryKey = SOURCE + libname;
if (!sourceEntryKeys.contains(sourceEntryKey)
&& !containedInFragment(manager, model.getBundleDescription().getFragments(), libname))
prepareError(sourceEntryKey, null,
NLS.bind(PDECoreMessages.BuildErrorReporter_missingEntry, sourceEntryKey),
PDEMarkerFactory.B_SOURCE_ADDITION);
}
}
private boolean containedInFragment(PluginModelManager manager, BundleDescription[] fragments, String libname) {
if (fragments == null)
return false;
for (int j = 0; j < fragments.length; j++) {
ModelEntry entry = manager.findEntry(fragments[j].getSymbolicName());
IPluginModelBase fragmentModel = entry.getWorkspaceModel();
if (fragmentModel != null) {
IProject project = fragmentModel.getUnderlyingResource().getProject();
if (project.findMember(libname) != null)
return true;
try {
IBuild build = ClasspathUtilCore.getBuild(fragmentModel);
if (build != null) {
IBuildEntry[] entries = build.getBuildEntries();
for (int i = 0; i < entries.length; i++)
if (entries[i].getName().equals(SOURCE + libname))
return true;
return false;
}
} catch (CoreException e) {
}
} else {
String location = fragments[j].getLocation();
File external = new File(location);
if (external.exists()) {
if (external.isDirectory()) {
IPath p = new Path(location).addTrailingSeparator().append(libname);
return new File(p.toOSString()).exists();
}
return CoreUtility.jarContainsResource(external, libname, false);
}
}
}
return false;
}
private void validateSourceEntries(ArrayList sourceEntries, IClasspathEntry[] cpes) {
String[] unlisted = getUnlistedClasspaths(sourceEntries, fProject, cpes);
StringBuffer sb = new StringBuffer();
for (int i = 0; i < unlisted.length; i++) {
if (unlisted[i] == null)
break;
if (sb.length() > 0)
sb.append(", "); //$NON-NLS-1$
sb.append(unlisted[i]);
}
String unlistedEntries = sb.toString();
if (sb.length() == 0)
return;
if (sourceEntries.size() == 1) {
String name = ((IBuildEntry)sourceEntries.get(0)).getName();
prepareError(name, null,
NLS.bind(PDECoreMessages.BuildErrorReporter_classpathEntryMissing1, unlistedEntries, name),
PDEMarkerFactory.B_SOURCE_ADDITION);
} else
prepareError(DEF_SOURCE_ENTRY, null,
NLS.bind(PDECoreMessages.BuildErrorReporter_classpathEntryMissing, unlistedEntries),
PDEMarkerFactory.B_SOURCE_ADDITION);
}
public static String[] getUnlistedClasspaths(ArrayList sourceEntries, IProject project, IClasspathEntry[] cpes) {
String[] unlisted = new String[cpes.length];
int index = 0;
for (int i = 0; i < cpes.length; i++) {
if (cpes[i].getEntryKind() != IClasspathEntry.CPE_SOURCE)
continue;
IPath path = cpes[i].getPath();
boolean found = false;
for (int j = 0; j < sourceEntries.size(); j++) {
IBuildEntry be = (IBuildEntry)sourceEntries.get(j);
String[] tokens = be.getTokens();
for (int k = 0; k < tokens.length; k++) {
IResource res = project.findMember(tokens[k]);
if (res == null)
continue;
IPath ipath = res.getFullPath();
if (ipath.equals(path))
found = true;
}
}
if (!found)
unlisted[index++] = path.removeFirstSegments(1).addTrailingSeparator().toString();
}
return unlisted;
}
private void validateIncludes(IBuildEntry includes, ArrayList sourceIncludes) {
if (includes == null)
return;
String[] tokens = includes.getTokens();
for (int i = 0; i < tokens.length; i++) {
String token = tokens[i].trim();
if (token.indexOf("*") != -1) //$NON-NLS-1$
// skip entries with wildcards
continue;
if (token.equals(".")) //$NON-NLS-1$
// skip . since we know it exists
continue;
int varStart = token.indexOf("${"); //$NON-NLS-1$
if (varStart != -1 && varStart < token.indexOf("}")) //$NON-NLS-1$
// skip '${x}' variables
continue;
IResource member = fProject.findMember(token);
String message = null;
int fixId = NO_RES;
if (member == null) {
if (sourceIncludes.contains(SOURCE + token))
continue;
if (token.endsWith("/")) //$NON-NLS-1$
message = NLS.bind(PDECoreMessages.BuildErrorReporter_missingFolder, token);
else
message = NLS.bind(PDECoreMessages.BuildErrorReporter_missingFile, token);
fixId = PDEMarkerFactory.B_REMOVAL;
} else if (token.endsWith("/") && !(member instanceof IFolder)) { //$NON-NLS-1$
message = NLS.bind(PDECoreMessages.BuildErrorReporter_entiresMustRefDirs, token);
fixId = PDEMarkerFactory.B_REMOVE_SLASH_FILE_ENTRY;
} else if (!token.endsWith("/") && !(member instanceof IFile)) { //$NON-NLS-1$
message = NLS.bind(PDECoreMessages.BuildErrorReporter_dirsMustEndSlash, token);
fixId = PDEMarkerFactory.B_APPEND_SLASH_FOLDER_ENTRY;
}
if (message != null)
prepareError(includes.getName(), token, message, fixId);
}
}
private BuildModel prepareTextBuildModel(IProgressMonitor monitor) {
try {
IDocument doc = createDocument(fFile);
if (doc == null)
return null;
BuildModel bm = new BuildModel(doc, true);
bm.load();
if (!bm.isLoaded())
return null;
return bm;
} catch (CoreException e) {
PDECore.log(e);
return null;
}
}
private void reportErrors(BuildModel bm) {
if (bm == null)
return;
for (int i = 0; i < fProblemList.size(); i++) {
BuildProblem bp = (BuildProblem)fProblemList.get(i);
int lineNum;
IBuildEntry buildEntry = bm.getBuild().getEntry(bp.fEntryName);
if (buildEntry == null || bp.fEntryName == null)
// general file case (eg. missing source.* entry)
lineNum = 1;
else
// issue with a particular entry
lineNum = getLineNumber(buildEntry, bp.fEntryToken);
if (lineNum > 0)
report(bp.fMessage, lineNum, bp.fFixId, bp.fEntryName, bp.fEntryToken);
}
}
private int getLineNumber(IBuildEntry ibe, String tokenString) {
if (!(ibe instanceof BuildEntry))
return 0;
BuildEntry be = (BuildEntry)ibe;
IDocument doc = ((BuildModel)be.getModel()).getDocument();
try {
int buildEntryLineNumber = doc.getLineOfOffset(be.getOffset()) + 1;
if (tokenString == null)
// we are interested in the build entry name
// (getLineOfOffset is 0-indexed, need 1-indexed)
return buildEntryLineNumber;
// extract the full entry
String entry = doc.get(be.getOffset(), be.getLength());
int valueIndex = entry.indexOf('=') + 1;
if (valueIndex == 0 || valueIndex == entry.length())
return buildEntryLineNumber;
// remove the entry name
entry = entry.substring(valueIndex);
int entryTokenOffset = entry.indexOf(tokenString);
if (entryTokenOffset == -1)
return buildEntryLineNumber;
// skip ahead to 1st occurence
entry = entry.substring(entryTokenOffset);
int currOffset = be.getOffset() + valueIndex + entryTokenOffset;
while (true) {
// tokenize string using ',' as a delimiter, trim out whitespace and '\' characters
// during comparison
if (entry.charAt(0) == '\\') {
currOffset++;
entry = entry.substring(1);
}
int cci = entry.indexOf(',');
if (cci == -1) {
if (entry.trim().equals(tokenString))
return doc.getLineOfOffset(currOffset + entry.indexOf(tokenString)) + 1;
return buildEntryLineNumber;
}
String ct = entry.substring(0, cci).trim();
if (ct.equals(tokenString))
return doc.getLineOfOffset(currOffset) + 1;
entry = entry.substring(++cci);
currOffset += cci;
}
} catch (BadLocationException e) {
}
return 0;
}
private void prepareError(String name, String token, String message, int fixId) {
BuildProblem bp = new BuildProblem(name, token, message, fixId);
for (int i = 0; i < fProblemList.size(); i++) {
BuildProblem listed = (BuildProblem)fProblemList.get(i);
if (listed.equals(bp))
return;
}
fProblemList.add(bp);
}
private void report(String message, int line, int problemID, String buildEntry, String buildToken) {
IMarker marker = report(message, line, fSeverity, problemID);
if (marker == null)
return;
try {
marker.setAttribute(PDEMarkerFactory.BK_BUILD_ENTRY, buildEntry);
marker.setAttribute(PDEMarkerFactory.BK_BUILD_TOKEN, buildToken);
} catch (CoreException e) {
}
}
}