blob: 2cce59ce84d731d6e2923c35d724d64de23b4c48 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2008 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.pde.api.tools.internal.builder;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.LineNumberReader;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.jar.JarFile;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IMember;
import org.eclipse.jdt.core.ISourceRange;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.ITypeRoot;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.osgi.util.NLS;
import org.eclipse.pde.api.tools.internal.ApiProfileManager;
import org.eclipse.pde.api.tools.internal.IApiCoreConstants;
import org.eclipse.pde.api.tools.internal.PluginProjectApiComponent;
import org.eclipse.pde.api.tools.internal.comparator.Delta;
import org.eclipse.pde.api.tools.internal.problems.ApiProblemFactory;
import org.eclipse.pde.api.tools.internal.provisional.ApiPlugin;
import org.eclipse.pde.api.tools.internal.provisional.ClassFileContainerVisitor;
import org.eclipse.pde.api.tools.internal.provisional.Factory;
import org.eclipse.pde.api.tools.internal.provisional.IApiComponent;
import org.eclipse.pde.api.tools.internal.provisional.IApiFilterStore;
import org.eclipse.pde.api.tools.internal.provisional.IApiMarkerConstants;
import org.eclipse.pde.api.tools.internal.provisional.IApiProfile;
import org.eclipse.pde.api.tools.internal.provisional.IApiProfileManager;
import org.eclipse.pde.api.tools.internal.provisional.IClassFile;
import org.eclipse.pde.api.tools.internal.provisional.IClassFileContainer;
import org.eclipse.pde.api.tools.internal.provisional.RestrictionModifiers;
import org.eclipse.pde.api.tools.internal.provisional.VisibilityModifiers;
import org.eclipse.pde.api.tools.internal.provisional.builder.IApiAnalyzer;
import org.eclipse.pde.api.tools.internal.provisional.comparator.ApiComparator;
import org.eclipse.pde.api.tools.internal.provisional.comparator.DeltaProcessor;
import org.eclipse.pde.api.tools.internal.provisional.comparator.IDelta;
import org.eclipse.pde.api.tools.internal.provisional.descriptors.IElementDescriptor;
import org.eclipse.pde.api.tools.internal.provisional.descriptors.IReferenceTypeDescriptor;
import org.eclipse.pde.api.tools.internal.provisional.problems.IApiProblem;
import org.eclipse.pde.api.tools.internal.provisional.problems.IApiProblemTypes;
import org.eclipse.pde.api.tools.internal.provisional.search.IApiSearchScope;
import org.eclipse.pde.api.tools.internal.util.SinceTagVersion;
import org.eclipse.pde.api.tools.internal.util.Util;
import org.osgi.framework.Constants;
import org.osgi.framework.Version;
import com.ibm.icu.text.MessageFormat;
/**
* Base implementation of the analyzer used in the {@link ApiAnalysisBuilder}
*
* @since 1.0.0
*/
public class BaseApiAnalyzer implements IApiAnalyzer {
/**
* Visitor for validating Javadoc tags in {@link IClassFile}s
*/
class ClassFileVisitor extends ClassFileContainerVisitor {
/* (non-Javadoc)
* @see org.eclipse.pde.api.tools.internal.provisional.ClassFileContainerVisitor#visit(java.lang.String, org.eclipse.pde.api.tools.internal.provisional.IClassFile)
*/
public void visit(String packageName, IClassFile classFile) {
processType(classFile.getTypeName());
}
}
/**
* Constant used for controlling tracing in the API tool builder
*/
private static boolean DEBUG = Util.DEBUG;
/**
* The backing list of problems found so far
*/
private HashSet fProblems = new HashSet(25);
/**
* List of pending deltas for which the @since tags should be checked
*/
private List fPendingDeltaInfos = new ArrayList(3);
/**
* The current build state to use
*/
private BuildState fBuildState = null;
/**
* The associated {@link IJavaProject}, if there is one
*/
private IJavaProject fJavaProject = null;
/**
* Method used for initializing tracing in the API tool builder
*/
public static void setDebug(boolean debugValue) {
DEBUG = debugValue || Util.DEBUG;
}
/**
* Constructs an API analyzer
*/
public BaseApiAnalyzer() {
}
/* (non-Javadoc)
* @see org.eclipse.pde.api.tools.internal.provisional.builder.IApiAnalyzer#analyzeComponent(org.eclipse.pde.api.tools.internal.builder.BuildState, org.eclipse.pde.api.tools.internal.provisional.IApiProfile, org.eclipse.pde.api.tools.internal.provisional.IApiComponent, java.lang.String[], java.lang.String[], org.eclipse.core.runtime.IProgressMonitor)
*/
public void analyzeComponent(final BuildState state, final IApiProfile baseline, final IApiComponent component, final String[] typenames, final String[] changedtypes, IProgressMonitor monitor) {
IProgressMonitor localMonitor = SubMonitor.convert(monitor, BuilderMessages.BaseApiAnalyzer_analyzing_api, 3 + (typenames == null ? 0 : typenames.length));
try {
fJavaProject = getJavaProject(component);
if(baseline == null) {
//check default baseline
checkDefaultBaselineSet();
updateMonitor(localMonitor, 3);
return;
}
IApiComponent reference = baseline.getApiComponent(component.getId());
this.fBuildState = state;
if(fBuildState == null) {
fBuildState = getBuildState();
}
//compatibility checks
if(reference != null) {
localMonitor.subTask(NLS.bind(BuilderMessages.BaseApiAnalyzer_comparing_api_profiles, reference.getId()));
if(typenames != null) {
for(int i = 0; i < typenames.length; i++) {
checkCompatibility(typenames[i], reference, component);
updateMonitor(localMonitor);
}
}
else {
checkCompatibility(reference, component);
updateMonitor(localMonitor);
}
}
//usage checks
checkApiUsage(component, getSearchScope(component, typenames), localMonitor);
updateMonitor(localMonitor);
//version checks
checkApiComponentVersion(reference, component);
updateMonitor(localMonitor);
//tag validation
checkTagValidation(changedtypes, component, localMonitor);
updateMonitor(localMonitor);
}
finally {
localMonitor.done();
}
}
private CompilationUnit createAST(ITypeRoot root, int offset) {
if(fJavaProject == null) {
return null;
}
ASTParser parser = ASTParser.newParser(AST.JLS3);
parser.setFocalPosition(offset);
parser.setResolveBindings(false);
parser.setSource(root);
Map options = fJavaProject.getOptions(true);
options.put(JavaCore.COMPILER_DOC_COMMENT_SUPPORT, JavaCore.ENABLED);
parser.setCompilerOptions(options);
return (CompilationUnit) parser.createAST(new NullProgressMonitor());
}
/**
* @return the build state to use.
*/
private BuildState getBuildState() {
IProject project = null;
if (fJavaProject != null) {
project = fJavaProject.getProject();
}
if(project == null) {
return new BuildState();
}
try {
BuildState state = ApiAnalysisBuilder.getLastBuiltState(project);
if(state != null) {
return state;
}
}
catch (CoreException e) {}
return new BuildState();
}
/**
* Returns an {@link IApiSearchScope} given the component and type names context
* @param component
* @param types
* @return a new {@link IApiSearchScope} for the component and type names context
*/
private IApiSearchScope getSearchScope(final IApiComponent component, final String[] typenames) {
if(typenames == null) {
return Factory.newScope(new IApiComponent[]{component});
}
else {
return Factory.newTypeScope(component, getScopedElements(typenames));
}
}
/**
* Returns a listing of {@link IReferenceTypeDescriptor}s given the listing of type names
* @param typenames
* @return
*/
private IReferenceTypeDescriptor[] getScopedElements(final String[] typenames) {
ArrayList types = new ArrayList(typenames.length);
for(int i = 0; i < typenames.length; i++) {
types.add(Util.getType(typenames[i]));
}
return (IReferenceTypeDescriptor[]) types.toArray(new IReferenceTypeDescriptor[types.size()]);
}
/* (non-Javadoc)
* @see org.eclipse.pde.api.tools.internal.provisional.builder.IApiAnalyzer#getProblems()
*/
public IApiProblem[] getProblems() {
if(fProblems == null) {
return new IApiProblem[0];
}
return (IApiProblem[]) fProblems.toArray(new IApiProblem[fProblems.size()]);
}
/* (non-Javadoc)
* @see org.eclipse.pde.api.tools.internal.provisional.builder.IApiAnalyzer#dispose()
*/
public void dispose() {
if(fProblems != null) {
fProblems.clear();
fProblems = null;
}
if(fPendingDeltaInfos != null) {
fPendingDeltaInfos.clear();
fPendingDeltaInfos = null;
}
if(fBuildState != null) {
fBuildState = null;
}
}
/**
* @return if the API usage scan should be ignored
*/
private boolean ignoreApiUsageScan() {
if (fJavaProject == null) {
// do the API use scan for binary bundles in non-OSGi mode
return false;
}
IProject project = fJavaProject.getProject();
boolean ignore = true;
ApiPlugin plugin = ApiPlugin.getDefault();
ignore &= plugin.getSeverityLevel(IApiProblemTypes.ILLEGAL_EXTEND, project) == ApiPlugin.SEVERITY_IGNORE;
ignore &= plugin.getSeverityLevel(IApiProblemTypes.ILLEGAL_IMPLEMENT, project) == ApiPlugin.SEVERITY_IGNORE;
ignore &= plugin.getSeverityLevel(IApiProblemTypes.ILLEGAL_INSTANTIATE, project) == ApiPlugin.SEVERITY_IGNORE;
ignore &= plugin.getSeverityLevel(IApiProblemTypes.ILLEGAL_REFERENCE, project) == ApiPlugin.SEVERITY_IGNORE;
ignore &= plugin.getSeverityLevel(IApiProblemTypes.LEAK_EXTEND, project) == ApiPlugin.SEVERITY_IGNORE;
ignore &= plugin.getSeverityLevel(IApiProblemTypes.LEAK_FIELD_DECL, project) == ApiPlugin.SEVERITY_IGNORE;
ignore &= plugin.getSeverityLevel(IApiProblemTypes.LEAK_IMPLEMENT, project) == ApiPlugin.SEVERITY_IGNORE;
ignore &= plugin.getSeverityLevel(IApiProblemTypes.LEAK_METHOD_PARAM, project) == ApiPlugin.SEVERITY_IGNORE;
ignore &= plugin.getSeverityLevel(IApiProblemTypes.LEAK_METHOD_RETURN_TYPE, project) == ApiPlugin.SEVERITY_IGNORE;
return ignore;
}
/**
* @return if the default API baseline check should be ignored or not
*/
private boolean ignoreDefaultBaselineCheck() {
if(fJavaProject == null) {
return true;
}
return ApiPlugin.getDefault().getSeverityLevel(IApiProblemTypes.MISSING_DEFAULT_API_BASELINE, fJavaProject.getProject().getProject()) == ApiPlugin.SEVERITY_IGNORE;
}
/**
* Whether to ignore since tag checks. If <code>null</code> is passed in we are asking if all since tag checks should be ignored,
* if a pref is specified we only want to know if that kind should be ignored
* @param pref
* @return
*/
private boolean ignoreSinceTagCheck(String pref) {
if (fJavaProject == null) {
return true;
}
IProject project = fJavaProject.getProject();
ApiPlugin plugin = ApiPlugin.getDefault();
if(pref == null) {
boolean ignore = plugin.getSeverityLevel(IApiProblemTypes.MALFORMED_SINCE_TAG, project) == ApiPlugin.SEVERITY_IGNORE;
ignore &= plugin.getSeverityLevel(IApiProblemTypes.INVALID_SINCE_TAG_VERSION, project) == ApiPlugin.SEVERITY_IGNORE;
ignore &= plugin.getSeverityLevel(IApiProblemTypes.MISSING_SINCE_TAG, project) == ApiPlugin.SEVERITY_IGNORE;
return ignore;
}
else {
return plugin.getSeverityLevel(pref, project) == ApiPlugin.SEVERITY_IGNORE;
}
}
/**
* @return if the component version checks should be ignored or not
*/
private boolean ignoreComponentVersionCheck() {
if (fJavaProject == null) {
// still do version checks for non-OSGi case
return false;
}
return ApiPlugin.getDefault().getSeverityLevel(IApiProblemTypes.INCOMPATIBLE_API_COMPONENT_VERSION, fJavaProject.getProject().getProject()) == ApiPlugin.SEVERITY_IGNORE;
}
/**
* @return if the invalid tag check should be ignored
*/
private boolean ignoreInvalidTagCheck() {
if(fJavaProject == null) {
return true;
}
return ApiPlugin.getDefault().getSeverityLevel(IApiProblemTypes.INVALID_JAVADOC_TAG, fJavaProject.getProject()) == ApiPlugin.SEVERITY_IGNORE;
}
/**
* Checks the validation of tags for the given {@link IApiComponent}
* @param typenames
* @param component
* @param monitor
*/
private void checkTagValidation(String[] typenames, IApiComponent component, IProgressMonitor monitor) {
if(ignoreInvalidTagCheck()) {
return;
}
IProgressMonitor localMonitor = SubMonitor.convert(monitor, BuilderMessages.BaseApiAnalyzer_validating_javadoc_tags, 1 + (typenames == null ? component.getClassFileContainers().length : typenames.length));
try {
if(typenames == null) {
IClassFileContainer[] containers = component.getClassFileContainers();
ClassFileVisitor visitor = new ClassFileVisitor();
for(int i = 0; i < containers.length; i++) {
try {
localMonitor.subTask(NLS.bind(BuilderMessages.BaseApiAnalyzer_scanning_0, containers[i].getOrigin()));
containers[i].accept(visitor);
updateMonitor(localMonitor);
}
catch(CoreException ce) {}
}
}
else {
for(int i = 0; i < typenames.length; i++) {
localMonitor.subTask(NLS.bind(BuilderMessages.BaseApiAnalyzer_scanning_0, typenames[i]));
processType(typenames[i]);
updateMonitor(localMonitor);
}
}
updateMonitor(localMonitor);
}
finally {
localMonitor.done();
}
}
/**
* Processes the given type name for invalid Javadoc tags
* @param typename
*/
private void processType(String typename) {
try {
IMember type = fJavaProject.findType(typename);
if(type != null) {
ICompilationUnit cunit = type.getCompilationUnit();
if(cunit != null) {
TagValidator tv = new TagValidator(cunit);
CompilationUnit comp = createAST(cunit, 0);
if(comp == null) {
return;
}
comp.accept(tv);
IApiProblem[] tagProblems = tv.getTagProblems();
for (int i = 0; i < tagProblems.length; i++) {
IApiProblem apiProblem = tagProblems[i];
this.addProblem(apiProblem);
}
}
}
}
catch (JavaModelException e) {
e.printStackTrace();
}
}
/**
* Checks for illegal API usage in the specified component, creating problem
* markers as required.
*
* @param profile profile being analyzed
* @param component component being built
* @param scope scope being built
* @param monitor progress monitor
*/
private void checkApiUsage(final IApiComponent component, final IApiSearchScope scope, IProgressMonitor monitor) {
if(ignoreApiUsageScan()) {
if(DEBUG) {
System.out.println("Ignoring API usage scan"); //$NON-NLS-1$
}
return;
}
IProgressMonitor localMonitor = SubMonitor.convert(monitor, MessageFormat.format(BuilderMessages.checking_api_usage, new String[] {component.getId()}), 2);
ApiUseAnalyzer analyzer = new ApiUseAnalyzer();
try {
long start = System.currentTimeMillis();
IApiProblem[] illegal = analyzer.findIllegalApiUse(component, scope, monitor);
updateMonitor(localMonitor);
long end = System.currentTimeMillis();
if (DEBUG) {
System.out.println("API usage scan: " + (end- start) + " ms\t" + illegal.length + " problems"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
if (illegal.length > 0) {
for (int i = 0; i < illegal.length; i++) {
this.addProblem(illegal[i]);
}
}
updateMonitor(localMonitor);
}
catch (CoreException e) {
ApiPlugin.log(e.getStatus());
}
finally {
localMonitor.done();
}
}
/**
* Compares the given type between the two API components
* @param typeName the type to check in each component
* @param reference
* @param component
*/
private void checkCompatibility(final String typeName, final IApiComponent reference, final IApiComponent component) {
if (DEBUG) {
System.out.println("comparing profiles ["+reference.getId()+"] and ["+component.getId()+"] for type ["+typeName+"]"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
}
IClassFile classFile = null;
try {
classFile = component.findClassFile(typeName);
} catch (CoreException e) {
ApiPlugin.log(e);
}
if (classFile == null) {
if (DEBUG) {
System.err.println("Could not retrieve class file for " + typeName + " in " + component.getId()); //$NON-NLS-1$ //$NON-NLS-2$
}
return;
}
fBuildState.cleanup(typeName);
IDelta delta = null;
long time = System.currentTimeMillis();
try {
delta = ApiComparator.compare(classFile, reference, component, reference.getProfile(), component.getProfile(), VisibilityModifiers.API);
} catch(Exception e) {
ApiPlugin.log(e);
} finally {
if (DEBUG) {
System.out.println("Time spent for " + typeName + " : " + (System.currentTimeMillis() - time) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
fPendingDeltaInfos.clear();
}
if (delta == null) {
return;
}
if (delta != ApiComparator.NO_DELTA) {
List allDeltas = Util.collectAllDeltas(delta);
for (Iterator iterator = allDeltas.iterator(); iterator.hasNext();) {
processDelta((IDelta) iterator.next(), reference, component);
}
if (!fPendingDeltaInfos.isEmpty()) {
for (Iterator iterator = fPendingDeltaInfos.iterator(); iterator.hasNext();) {
checkSinceTags((Delta) iterator.next(), component);
}
}
}
}
/**
* Compares the two given profiles and generates an {@link IDelta}
*
* @param jproject
* @param reference
* @param component
*/
private void checkCompatibility(final IApiComponent reference, final IApiComponent component) {
long time = System.currentTimeMillis();
IDelta delta = null;
if (reference == null) {
delta =
new Delta(
null,
IDelta.API_PROFILE_ELEMENT_TYPE,
IDelta.ADDED,
IDelta.API_COMPONENT,
null,
component.getId(),
component.getId());
} else {
try {
delta = ApiComparator.compare(reference, component, VisibilityModifiers.API);
} catch(Exception e) {
ApiPlugin.log(e);
} finally {
if (DEBUG) {
System.out.println("Time spent for " + component.getId() + " : " + (System.currentTimeMillis() - time) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
fPendingDeltaInfos.clear();
}
}
if (delta == null) {
return;
}
if (delta != ApiComparator.NO_DELTA) {
List allDeltas = Util.collectAllDeltas(delta);
if (allDeltas.size() != 0) {
for (Iterator iterator = allDeltas.iterator(); iterator.hasNext();) {
processDelta((IDelta) iterator.next(), reference, component);
}
if (!fPendingDeltaInfos.isEmpty()) {
for (Iterator iterator = fPendingDeltaInfos.iterator(); iterator.hasNext();) {
checkSinceTags((Delta) iterator.next(), component);
}
}
}
}
}
/**
* Processes delta to determine if it needs an @since tag. If it does and one
* is not present or the version of the tag is incorrect, a marker is created
* @param jproject
* @param delta
* @param component
*/
private void checkSinceTags(final Delta delta, final IApiComponent component) {
if(ignoreSinceTagCheck(null)) {
return;
}
IMember member = Util.getIMember(delta, fJavaProject);
if (member == null) {
return;
}
ICompilationUnit cunit = member.getCompilationUnit();
if (cunit == null) {
return;
}
try {
if (! cunit.isConsistent()) {
cunit.makeConsistent(null);
}
} catch (JavaModelException e) {
e.printStackTrace();
}
IApiProblem problem = null;
ISourceRange nameRange = null;
try {
nameRange = member.getNameRange();
} catch (JavaModelException e) {
ApiPlugin.log(e);
return;
}
if (nameRange == null) {
return;
}
try {
int offset = nameRange.getOffset();
CompilationUnit comp = createAST(cunit, offset);
if(comp == null) {
return;
}
SinceTagChecker visitor = new SinceTagChecker(offset);
comp.accept(visitor);
String componentVersionString = component.getVersion();
try {
if (visitor.hasNoComment() || visitor.isMissing()) {
if(ignoreSinceTagCheck(IApiProblemTypes.MISSING_SINCE_TAG)) {
if(DEBUG) {
System.out.println("Ignoring missing since tag problem"); //$NON-NLS-1$
}
return;
}
StringBuffer buffer = new StringBuffer();
Version componentVersion = new Version(componentVersionString);
buffer.append(componentVersion.getMajor()).append('.').append(componentVersion.getMinor());
problem = createSinceTagProblem(IApiProblem.SINCE_TAG_MISSING, null, delta, member, String.valueOf(buffer));
} else if (visitor.hasJavadocComment()) {
// we don't want to flag block comment
String sinceVersion = visitor.getSinceVersion();
if (sinceVersion != null) {
SinceTagVersion tagVersion = new SinceTagVersion(sinceVersion);
if (Util.getFragmentNumber(sinceVersion) > 2 || tagVersion.getVersion() == null) {
if(ignoreSinceTagCheck(IApiProblemTypes.MALFORMED_SINCE_TAG)) {
if(DEBUG) {
System.out.println("Ignoring malformed since tag problem"); //$NON-NLS-1$
}
return;
}
StringBuffer buffer = new StringBuffer();
if (tagVersion.pluginName() != null) {
buffer.append(tagVersion.pluginName()).append(' ');
}
Version componentVersion = new Version(componentVersionString);
buffer.append(componentVersion.getMajor()).append('.').append(componentVersion.getMinor());
problem = createSinceTagProblem(IApiProblem.SINCE_TAG_MALFORMED, new String[] {sinceVersion}, delta, member, String.valueOf(buffer));
} else {
if(ignoreSinceTagCheck(IApiProblemTypes.INVALID_SINCE_TAG_VERSION)) {
if(DEBUG) {
System.out.println("Ignoring invalid tag version problem"); //$NON-NLS-1$
}
return;
}
StringBuffer accurateVersionBuffer = new StringBuffer();
Version componentVersion = new Version(componentVersionString);
accurateVersionBuffer.append(componentVersion.getMajor()).append('.').append(componentVersion.getMinor());
String accurateVersion = String.valueOf(accurateVersionBuffer);
if (Util.isDifferentVersion(sinceVersion, accurateVersion)) {
// report invalid version number
StringBuffer buffer = new StringBuffer();
if (tagVersion.pluginName() != null) {
buffer.append(tagVersion.pluginName()).append(' ');
}
Version version = new Version(accurateVersion);
buffer.append(version.getMajor()).append('.').append(version.getMinor());
String accurateSinceTagValue = String.valueOf(buffer);
problem = createSinceTagProblem(IApiProblem.SINCE_TAG_INVALID, new String[] {sinceVersion, accurateSinceTagValue}, delta, member, accurateSinceTagValue);
}
}
}
}
} catch (IllegalArgumentException e) {
ApiPlugin.log(e);
}
} catch (RuntimeException e) {
ApiPlugin.log(e);
}
if(problem != null) {
this.addProblem(problem);
}
}
/**
* Creates a marker to denote a problem with the since tag (existence or correctness) for a member
* and returns it, or <code>null</code>
* @param kind
* @param messageargs
* @param compilationUnit
* @param member
* @param version
* @return a new {@link IApiProblem} or <code>null</code>
*/
private IApiProblem createSinceTagProblem(int kind, final String[] messageargs, final Delta info, final IMember member, final String version) {
try {
// create a marker on the member for missing @since tag
IResource resource = null;
ICompilationUnit unit = null;
try {
unit = member.getCompilationUnit();
if (unit != null) {
resource = unit.getCorrespondingResource();
}
} catch (JavaModelException e) {
ApiPlugin.log(e);
}
if (resource == null) {
return null;
}
int lineNumber = 1;
int charStart = 0;
int charEnd = 1;
ISourceRange range = member.getNameRange();
charStart = range.getOffset();
charEnd = charStart + range.getLength();
try {
// unit cannot be null
IDocument document = Util.getDocument(unit);
lineNumber = document.getLineOfOffset(charStart);
} catch (BadLocationException e) {
ApiPlugin.log(e);
}
return ApiProblemFactory.newApiSinceTagProblem(resource.getProjectRelativePath().toPortableString(),
messageargs,
new String[] {IApiMarkerConstants.MARKER_ATTR_VERSION, IApiMarkerConstants.API_MARKER_ATTR_ID, IApiMarkerConstants.MARKER_ATTR_HANDLE_ID},
new Object[] {version, new Integer(IApiMarkerConstants.SINCE_TAG_MARKER_ID), member.getHandleIdentifier()},
lineNumber,
charStart,
charEnd,
info.getElementType(),
kind);
} catch (CoreException e) {
ApiPlugin.log(e);
}
return null;
}
/**
* Creates an {@link IApiProblem} for the given compatibility delta
* @param delta
* @param jproject
* @param reference
* @param component
* @return a new compatibility problem or <code>null</code>
*/
private IApiProblem createCompatibilityProblem(final IDelta delta, final IApiComponent reference, final IApiComponent component) {
try {
Version referenceVersion = new Version(reference.getVersion());
Version componentVersion = new Version(component.getVersion());
if (referenceVersion.getMajor() < componentVersion.getMajor()) {
// API breakage are ok in this case
fBuildState.addBreakingChange(delta);
return null;
}
IResource resource = null;
IType type = null;
// retrieve line number, char start and char end
int lineNumber = 1;
int charStart = -1;
int charEnd = 1;
IMember member = null;
if (fJavaProject != null) {
try {
type = fJavaProject.findType(delta.getTypeName().replace('$', '.'));
} catch (JavaModelException e) {
ApiPlugin.log(e);
}
if (type == null) {
IResource manifestFile = Util.getManifestFile(fJavaProject.getProject());
if (manifestFile == null) {
// Cannot retrieve the manifest.mf file
return null;
}
resource = manifestFile;
} else {
ICompilationUnit unit = type.getCompilationUnit();
if (unit != null) {
resource = unit.getCorrespondingResource();
if (resource == null) {
return null;
}
} else {
IResource manifestFile = Util.getManifestFile(fJavaProject.getProject());
if (manifestFile == null) {
// Cannot retrieve the manifest.mf file
return null;
}
resource = manifestFile;
}
}
member = Util.getIMember(delta, fJavaProject);
if (member != null) {
ISourceRange range = member.getNameRange();
charStart = range.getOffset();
charEnd = charStart + range.getLength();
try {
IDocument document = Util.getDocument(member.getCompilationUnit());
lineNumber = document.getLineOfOffset(charStart);
} catch (BadLocationException e) {
// ignore
}
}
}
String path = null;
if (resource == null) {
path = delta.getTypeName();
} else {
path = resource.getProjectRelativePath().toPortableString();
}
IApiProblem apiProblem = ApiProblemFactory.newApiProblem(path,
delta.getArguments(),
new String[] {
IApiMarkerConstants.MARKER_ATTR_HANDLE_ID,
IApiMarkerConstants.API_MARKER_ATTR_ID
},
new Object[] {
member == null ? null : member.getHandleIdentifier(),
new Integer(IApiMarkerConstants.COMPATIBILITY_MARKER_ID),
},
lineNumber,
charStart,
charEnd,
IApiProblem.CATEGORY_COMPATIBILITY,
delta.getElementType(),
delta.getKind(),
delta.getFlags());
return apiProblem;
} catch (CoreException e) {
ApiPlugin.log(e);
}
return null;
}
/**
* Processes a delta to know if we need to check for since tag or version numbering problems
* @param jproject
* @param delta
* @param reference
* @param component
*/
private void processDelta(final IDelta delta, final IApiComponent reference, final IApiComponent component) {
int flags = delta.getFlags();
int kind = delta.getKind();
if (DeltaProcessor.isCompatible(delta)) {
if (flags != IDelta.EXECUTION_ENVIRONMENT) {
// we filter EXECUTION ENVIRONMENT deltas
fBuildState.addCompatibleChange(delta);
}
if (kind == IDelta.ADDED) {
int modifiers = delta.getModifiers();
if (Util.isPublic(modifiers)) {
// if public, we always want to check @since tags
switch(flags) {
case IDelta.TYPE_MEMBER :
case IDelta.METHOD :
case IDelta.CONSTRUCTOR :
case IDelta.ENUM_CONSTANT :
case IDelta.METHOD_WITH_DEFAULT_VALUE :
case IDelta.METHOD_WITHOUT_DEFAULT_VALUE :
case IDelta.FIELD :
case IDelta.TYPE :
if (DEBUG) {
String deltaDetails = "Delta : " + Util.getDetail(delta); //$NON-NLS-1$
System.out.println(deltaDetails + " is compatible"); //$NON-NLS-1$
}
fPendingDeltaInfos.add(delta);
break;
}
} else if (Util.isProtected(modifiers) && !RestrictionModifiers.isExtendRestriction(delta.getRestrictions())) {
// if protected, we only want to check @since tags if the enclosing class can be subclassed
switch(flags) {
case IDelta.TYPE_MEMBER :
case IDelta.METHOD :
case IDelta.CONSTRUCTOR :
case IDelta.ENUM_CONSTANT :
case IDelta.FIELD :
case IDelta.TYPE :
if (DEBUG) {
String deltaDetails = "Delta : " + Util.getDetail(delta); //$NON-NLS-1$
System.out.println(deltaDetails + " is compatible"); //$NON-NLS-1$
}
fPendingDeltaInfos.add(delta);
break;
}
}
}
} else {
switch(kind) {
case IDelta.ADDED :
// if public, we always want to check @since tags
switch(flags) {
case IDelta.TYPE_MEMBER :
case IDelta.METHOD :
case IDelta.CONSTRUCTOR :
case IDelta.ENUM_CONSTANT :
case IDelta.METHOD_WITH_DEFAULT_VALUE :
case IDelta.METHOD_WITHOUT_DEFAULT_VALUE :
case IDelta.FIELD :
// ensure that there is a @since tag for the corresponding member
if (Util.isVisible(delta)) {
if (DEBUG) {
String deltaDetails = "Delta : " + Util.getDetail(delta); //$NON-NLS-1$
System.err.println(deltaDetails + " is not compatible"); //$NON-NLS-1$
}
fPendingDeltaInfos.add(delta);
}
}
break;
case IDelta.CHANGED :
if (flags == IDelta.RESTRICTIONS) {
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=228424
return;
}
}
IApiProblem problem = createCompatibilityProblem(delta, reference, component);
if(this.addProblem(problem)) {
fBuildState.addBreakingChange(delta);
}
}
}
/**
* Checks the version number of the API component and creates a problem markers as needed
* @param reference
* @param component
*/
private void checkApiComponentVersion(final IApiComponent reference, final IApiComponent component) {
if(ignoreComponentVersionCheck() || reference == null || component == null) {
if(DEBUG) {
System.out.println("Ignoring component version check"); //$NON-NLS-1$
}
return;
}
IApiProblem problem = null;
String refversionval = reference.getVersion();
String compversionval = component.getVersion();
Version refversion = new Version(refversionval);
Version compversion = new Version(compversionval);
Version newversion = null;
if (DEBUG) {
System.out.println("reference version of " + reference.getId() + " : " + refversion); //$NON-NLS-1$ //$NON-NLS-2$
System.out.println("component version of " + component.getId() + " : " + compversion); //$NON-NLS-1$ //$NON-NLS-2$
}
IDelta[] breakingChanges = fBuildState.getBreakingChanges();
if (breakingChanges.length != 0) {
// make sure that the major version has been incremented
if (compversion.getMajor() <= refversion.getMajor()) {
newversion = new Version(compversion.getMajor() + 1, 0, 0, compversion.getQualifier());
problem = createVersionProblem(
IApiProblem.MAJOR_VERSION_CHANGE,
new String[] {
compversionval,
refversionval
},
String.valueOf(newversion),
collectDetails(breakingChanges));
}
} else {
IDelta[] compatibleChanges = fBuildState.getCompatibleChanges();
if (compatibleChanges.length != 0) {
// only new API have been added
if (compversion.getMajor() != refversion.getMajor()) {
// major version should be identical
newversion = new Version(refversion.getMajor(), compversion.getMinor() + 1, 0, compversion.getQualifier());
problem = createVersionProblem(
IApiProblem.MAJOR_VERSION_CHANGE_NO_BREAKAGE,
new String[] {
compversionval,
refversionval
},
String.valueOf(newversion),
collectDetails(compatibleChanges));
} else if (compversion.getMinor() <= refversion.getMinor()) {
// the minor version should be incremented
newversion = new Version(compversion.getMajor(), compversion.getMinor() + 1, 0, compversion.getQualifier());
problem = createVersionProblem(
IApiProblem.MINOR_VERSION_CHANGE,
new String[] {
compversionval,
refversionval
},
String.valueOf(newversion),
collectDetails(compatibleChanges));
}
}
}
if(problem != null) {
this.addProblem(problem);
}
}
/**
* Collects details from the given delta listing for version problems
* @param deltas
* @return
*/
private String collectDetails(final IDelta[] deltas) {
StringBuffer buffer = new StringBuffer();
for (int i = 0, max = deltas.length; i < max ; i++) {
buffer.append("- "); //$NON-NLS-1$
buffer.append(deltas[i].getMessage());
}
return buffer.toString();
}
/**
* Creates a marker on a manifest file for a version numbering problem and returns it
* or <code>null</code>
* @param kind
* @param messageargs
* @param version
* @param description the description of details
* @return a new {@link IApiProblem} or <code>null</code>
*/
private IApiProblem createVersionProblem(int kind, final String[] messageargs, String version, String description) {
IResource manifestFile = null;
String path = JarFile.MANIFEST_NAME;
if (fJavaProject != null) {
manifestFile = Util.getManifestFile(fJavaProject.getProject());
}
// this error should be located on the manifest.mf file
// first of all we check how many api breakage marker are there
int lineNumber = -1;
int charStart = 0;
int charEnd = 1;
char[] contents = null;
if (manifestFile!= null && manifestFile.getType() == IResource.FILE) {
path = manifestFile.getProjectRelativePath().toPortableString();
IFile file = (IFile) manifestFile;
InputStream inputStream = null;
LineNumberReader reader = null;
try {
inputStream = file.getContents(true);
contents = Util.getInputStreamAsCharArray(inputStream, -1, IApiCoreConstants.UTF_8);
reader = new LineNumberReader(new BufferedReader(new StringReader(new String(contents))));
int lineCounter = 0;
String line = null;
loop: while ((line = reader.readLine()) != null) {
lineCounter++;
if (line.startsWith(Constants.BUNDLE_VERSION)) {
lineNumber = lineCounter;
break loop;
}
}
} catch (CoreException e) {
// ignore
} catch (IOException e) {
// ignore
} finally {
try {
if (inputStream != null) {
inputStream.close();
}
if (reader != null) {
reader.close();
}
} catch (IOException e) {
// ignore
}
}
}
if (lineNumber != -1 && contents != null) {
// initialize char start, char end
int index = CharOperation.indexOf(Constants.BUNDLE_VERSION.toCharArray(), contents, true);
loop: for (int i = index + Constants.BUNDLE_VERSION.length() + 1, max = contents.length; i < max; i++) {
char currentCharacter = contents[i];
if (CharOperation.isWhitespace(currentCharacter)) {
continue;
}
charStart = i;
break loop;
}
loop: for (int i = charStart + 1, max = contents.length; i < max; i++) {
switch(contents[i]) {
case '\r' :
case '\n' :
charEnd = i;
break loop;
}
}
} else {
lineNumber = 1;
}
return ApiProblemFactory.newApiVersionNumberProblem(path,
messageargs,
new String[] {
IApiMarkerConstants.MARKER_ATTR_VERSION,
IApiMarkerConstants.API_MARKER_ATTR_ID,
IApiMarkerConstants.VERSION_NUMBERING_ATTR_DESCRIPTION,
},
new Object[] {
version,
new Integer(IApiMarkerConstants.VERSION_NUMBERING_MARKER_ID),
description
},
lineNumber,
charStart,
charEnd,
IElementDescriptor.T_RESOURCE,
kind);
}
/**
* Checks to see if there is a default API profile set in the workspace,
* if not create a marker
*/
private void checkDefaultBaselineSet() {
if(ignoreDefaultBaselineCheck()) {
if(DEBUG) {
System.out.println("Ignoring check for default API baseline"); //$NON-NLS-1$
}
return;
}
if(DEBUG) {
System.out.println("Checking if the default api baseline is set"); //$NON-NLS-1$
}
IApiProblem problem = ApiProblemFactory.newApiProfileProblem(Path.EMPTY.toPortableString(),
null,
new String[] {IApiMarkerConstants.API_MARKER_ATTR_ID},
new Object[] {new Integer(IApiMarkerConstants.DEFAULT_API_PROFILE_MARKER_ID)},
-1,
-1,
-1,
IElementDescriptor.T_RESOURCE,
IApiProblem.API_PROFILE_MISSING);
this.addProblem(problem);
}
/**
* Updates the work done on the monitor by 1 tick and polls to see if the monitor has been cancelled
* @param monitor
* @throws OperationCanceledException if the monitor has been cancelled
*/
private void updateMonitor(IProgressMonitor monitor) throws OperationCanceledException {
updateMonitor(monitor, 1);
}
/**
* Updates the work done on the monitor by 1 tick and polls to see if the monitor has been cancelled
* @param monitor
* @param work
* @throws OperationCanceledException if the monitor has been cancelled
*/
private void updateMonitor(IProgressMonitor monitor, int work) throws OperationCanceledException {
if(monitor != null) {
monitor.worked(work);
monitor.setTaskName(""); //$NON-NLS-1$
if (monitor.isCanceled()) {
throw new OperationCanceledException();
}
}
}
/**
* Returns the Java project associated with the given API component, or <code>null</code>
* if none.
*
*@param component API component
* @return Java project or <code>null</code>
*/
private IJavaProject getJavaProject(IApiComponent component) {
if (component instanceof PluginProjectApiComponent) {
PluginProjectApiComponent pp = (PluginProjectApiComponent) component;
return pp.getJavaProject();
}
return null;
}
private boolean addProblem(IApiProblem problem) {
if (problem == null || isProblemFiltered(problem)) return false;
return this.fProblems.add(problem);
}
/**
* Returns if the given {@link IApiProblem} should be filtered from having a problem marker created for it
*
* @param problem the problem that may or may not be filtered
* @return true if the {@link IApiProblem} should not have a marker created, false otherwise
*/
private boolean isProblemFiltered(IApiProblem problem) {
if (fJavaProject == null) {
return false;
}
IProject project = fJavaProject.getProject();
// first the severity is checked
if (ApiPlugin.getDefault().getSeverityLevel(ApiProblemFactory.getProblemSeverityId(problem), project) == ApiPlugin.SEVERITY_IGNORE) {
return true;
}
IApiProfileManager manager = ApiProfileManager.getManager();
IApiProfile profile = manager.getWorkspaceProfile();
if(profile == null) {
return false;
}
IApiComponent component = profile.getApiComponent(project.getName());
if(component != null) {
try {
IApiFilterStore filterStore = component.getFilterStore();
if (filterStore != null) {
return filterStore.isFiltered(problem);
}
}
catch(CoreException e) {}
}
return false;
}
}