Java tutorial
/******************************************************************************* * Copyright (c) 2008, 2014 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.PrintWriter; import java.io.StringReader; import java.io.StringWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Properties; 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.resources.IWorkspace; import org.eclipse.core.resources.IWorkspaceRunnable; import org.eclipse.core.resources.ResourcesPlugin; 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.core.runtime.preferences.IEclipsePreferences; import org.eclipse.core.runtime.preferences.InstanceScope; import org.eclipse.jdt.core.Flags; import org.eclipse.jdt.core.ICompilationUnit; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.IMember; import org.eclipse.jdt.core.IPackageFragmentRoot; import org.eclipse.jdt.core.IParent; 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.service.resolver.ResolverError; import org.eclipse.osgi.service.resolver.VersionConstraint; import org.eclipse.osgi.service.resolver.VersionRange; import org.eclipse.osgi.util.NLS; import org.eclipse.pde.api.tools.internal.ApiBaselineManager; import org.eclipse.pde.api.tools.internal.ApiFilterStore; import org.eclipse.pde.api.tools.internal.IApiCoreConstants; import org.eclipse.pde.api.tools.internal.comparator.Delta; import org.eclipse.pde.api.tools.internal.model.ProjectComponent; import org.eclipse.pde.api.tools.internal.model.StubApiComponent; import org.eclipse.pde.api.tools.internal.problems.ApiProblemFactory; import org.eclipse.pde.api.tools.internal.problems.ApiProblemFilter; import org.eclipse.pde.api.tools.internal.provisional.ApiPlugin; import org.eclipse.pde.api.tools.internal.provisional.Factory; import org.eclipse.pde.api.tools.internal.provisional.IApiAnnotations; import org.eclipse.pde.api.tools.internal.provisional.IApiBaselineManager; import org.eclipse.pde.api.tools.internal.provisional.IApiDescription; 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.IRequiredComponentDescription; import org.eclipse.pde.api.tools.internal.provisional.IVersionRange; 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.builder.IBuildContext; import org.eclipse.pde.api.tools.internal.provisional.builder.IReference; 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.IMemberDescriptor; import org.eclipse.pde.api.tools.internal.provisional.descriptors.IMethodDescriptor; import org.eclipse.pde.api.tools.internal.provisional.descriptors.IReferenceTypeDescriptor; import org.eclipse.pde.api.tools.internal.provisional.model.IApiBaseline; import org.eclipse.pde.api.tools.internal.provisional.model.IApiComponent; import org.eclipse.pde.api.tools.internal.provisional.model.IApiType; import org.eclipse.pde.api.tools.internal.provisional.model.IApiTypeContainer; import org.eclipse.pde.api.tools.internal.provisional.model.IApiTypeRoot; import org.eclipse.pde.api.tools.internal.provisional.problems.IApiProblem; import org.eclipse.pde.api.tools.internal.provisional.problems.IApiProblemFilter; import org.eclipse.pde.api.tools.internal.provisional.problems.IApiProblemTypes; import org.eclipse.pde.api.tools.internal.search.IReferenceDescriptor; import org.eclipse.pde.api.tools.internal.search.UseScanManager; import org.eclipse.pde.api.tools.internal.util.Signatures; 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 { private static final String QUALIFIER = "qualifier"; //$NON-NLS-1$ /** * @since 1.1 */ static final String[] NO_TYPES = new String[0]; private static class ReexportedBundleVersionInfo { String componentID; int kind; ReexportedBundleVersionInfo(String componentID, int kind) { this.componentID = componentID; this.kind = kind; } } /** * The backing list of problems found so far */ private ArrayList<IApiProblem> fProblems = new ArrayList<IApiProblem>(25); /** * List of pending deltas for which the @since tags should be checked */ private List<IDelta> fPendingDeltaInfos = new ArrayList<IDelta>(3); /** * The current build state to use */ private BuildState fBuildState = null; /** * The current filter store to use */ private IApiFilterStore fFilterStore = null; /** * The associated {@link IJavaProject}, if there is one */ private IJavaProject fJavaProject = null; /** * The current preferences to use when the platform is not running. */ private Properties fPreferences = null; /** * Boolean setting to continue analyzing a component even if it has * resolution errors. In the workspace builder we fail fast, but when * running the * {@link org.eclipse.pde.api.tools.internal.tasks.APIToolsAnalysisTask} we * want to still be able to produce results with resolver errors. */ private boolean fContinueOnResolutionError = false; /** * Constructs an API analyzer */ public BaseApiAnalyzer() { } /* * (non-Javadoc) * @see org.eclipse.pde.api.tools.internal.provisional.builder.IApiAnalyzer# * analyzeComponent(..) */ @Override public void analyzeComponent(final BuildState state, final IApiFilterStore filterStore, final Properties preferences, final IApiBaseline baseline, final IApiComponent component, final IBuildContext context, IProgressMonitor monitor) { SubMonitor localMonitor = SubMonitor.convert(monitor, BuilderMessages.BaseApiAnalyzer_analyzing_api, 8); try { fJavaProject = getJavaProject(component); this.fFilterStore = filterStore; this.fPreferences = preferences; if (!ignoreUnusedProblemFilterCheck()) { ((ApiFilterStore) component.getFilterStore()).recordFilterUsage(); } ResolverError[] errors = component.getErrors(); if (errors != null) { // check if all errors have a constraint StringBuffer buffer = null; for (int i = 0, max = errors.length; i < max; i++) { ResolverError error = errors[i]; VersionConstraint constraint = error.getUnsatisfiedConstraint(); if (constraint == null) { continue; } VersionRange versionRange = constraint.getVersionRange(); if (buffer == null) { buffer = new StringBuffer(); } if (i > 0) { buffer.append(',').append(' '); } buffer.append(NLS.bind(BuilderMessages.reportUnsatisfiedConstraint, new String[] { constraint.getName(), versionRange != null ? versionRange.toString() : BuilderMessages.undefinedRange })); } if (buffer != null) { // API component has errors that should be reported createApiComponentResolutionProblem(component, String.valueOf(buffer)); if (baseline == null) { checkDefaultBaselineSet(); } // If run from the builder, quit now and report the resolver // error. // If run from the task, continue processing the component if (!fContinueOnResolutionError) { return; } } } IBuildContext bcontext = context; if (bcontext == null) { bcontext = new BuildContext(); } boolean checkfilters = false; if (baseline != null) { IApiComponent reference = baseline.getApiComponent(component.getSymbolicName()); this.fBuildState = state; if (fBuildState == null) { fBuildState = getBuildState(); } // compatibility checks if (reference != null) { localMonitor.subTask(NLS.bind(BuilderMessages.BaseApiAnalyzer_comparing_api_profiles, new String[] { reference.getSymbolicName(), baseline.getName() })); if (bcontext.hasTypes()) { String[] changedtypes = bcontext.getStructurallyChangedTypes(); checkCompatibility(changedtypes, reference, component, localMonitor); } else { // store re-exported bundle into the build state checkCompatibility(reference, component, localMonitor.newChild(1)); Util.updateMonitor(localMonitor); } this.fBuildState.setReexportedComponents(Util.getReexportedComponents(component)); } else { localMonitor.subTask(NLS.bind(BuilderMessages.BaseApiAnalyzer_comparing_api_profiles, new String[] { component.getSymbolicName(), baseline.getName() })); checkCompatibility(null, component, localMonitor.newChild(1)); Util.updateMonitor(localMonitor); } // version checks checkApiComponentVersion(reference, component); Util.updateMonitor(localMonitor); checkfilters = true; } else { // check default baseline checkDefaultBaselineSet(); Util.updateMonitor(localMonitor); } // check EE description status checkEEDescriptions(); // usage checks checkApiUsage(bcontext, component, localMonitor.newChild(1)); Util.updateMonitor(localMonitor); // tag validation checkTagValidation(bcontext, component, localMonitor.newChild(1)); Util.updateMonitor(localMonitor); if (checkfilters) { // check for unused filters only if the scans have been done checkUnusedProblemFilters(bcontext, component, localMonitor.newChild(1)); } Util.updateMonitor(localMonitor); if (component instanceof ProjectComponent) { checkExternalDependencies(component, bcontext, null, localMonitor.newChild(1)); } } catch (CoreException e) { ApiPlugin.log(e); } catch (OperationCanceledException oce) { // do nothing, but don't forward it // https://bugs.eclipse.org/bugs/show_bug.cgi?id=304315 if (ApiPlugin.DEBUG_API_ANALYZER) { System.out.println("Trapped OperationCanceledException"); //$NON-NLS-1$ } } finally { localMonitor.done(); } } /** * Sets whether to continue analyzing a component even if it has resolution * errors. By default this is false. The workspace builder should not * analyze components with errors to avoid polluting the project with * markers. When running the the API tools analysis task the analyzer should * continue to process the component to produce some results (the task * should warn that the results may not be accurate). * * @param continueOnError whether to continue processing a component if it * has resolution errors */ public void setContinueOnResolverError(boolean continueOnError) { fContinueOnResolutionError = continueOnError; } /** * Returns whether this analyzer will continue analyzing a component even if * it has resolution errors. By default this is false. The workspace builder * should not analyze components with errors to avoid polluting the project * with markers. When running the the API tools analysis task the analyzer * should continue to process the component to produce some results (the * task should warn that the results may not be accurate). * * @return whether this analyzer will continue analyzing a component if it * has resolution errors */ public boolean isContinueOnResolverError() { return fContinueOnResolutionError; } /** * Checks if the setting to scan for invalid references is not set to be * ignored AND there are no descriptions installed * * @param component * @param monitor * @since 1.0.400 */ void checkEEDescriptions() { if (ignoreEEDescriptionCheck()) { if (ApiPlugin.DEBUG_API_ANALYZER) { System.out.println("Ignoring check for API EE descriptions"); //$NON-NLS-1$ } return; } if (ApiPlugin.DEBUG_API_ANALYZER) { System.out.println( "Checking if there are any API EE descriptions installed if the preference is set to not be 'ignore'"); //$NON-NLS-1$ } String[] ees = StubApiComponent.getInstalledMetadata(); if (ees.length < 1) { IApiProblem problem = ApiProblemFactory.newApiUsageProblem(Path.EMPTY.toString(), null, new String[] { fJavaProject.getElementName() }, new String[] { IApiMarkerConstants.API_MARKER_ATTR_ID }, new Object[] { new Integer(IApiMarkerConstants.API_USAGE_MARKER_ID) }, -1, -1, -1, IElementDescriptor.RESOURCE, IApiProblem.MISSING_EE_DESCRIPTIONS); addProblem(problem); } } /** * @return if the API EE description check should be ignored or not */ private boolean ignoreEEDescriptionCheck() { if (fJavaProject == null) { return true; } return ApiPlugin.getDefault().getSeverityLevel(IApiProblemTypes.INVALID_REFERENCE_IN_SYSTEM_LIBRARIES, fJavaProject.getProject().getProject()) == ApiPlugin.SEVERITY_IGNORE; } /** * Processes the API Use Scan report for the given API Component * * @param apiComponent * @param bcontext * @param monitor * @throws CoreException */ public void checkExternalDependencies(IApiComponent apiComponent, IBuildContext bcontext, Properties properties, IProgressMonitor monitor) throws CoreException { if (!isSeverityEnabled(properties)) { return; } String[] apiUseTypes = getApiUseTypes(bcontext); if (ApiPlugin.DEBUG_API_ANALYZER) { if (apiUseTypes.length < 1) { System.out.println("Checking use scan dependencies for: " + apiComponent.getSymbolicName() + " (" //$NON-NLS-1$//$NON-NLS-2$ + apiComponent.getVersion() + ")"); //$NON-NLS-1$ } else { System.out.println("Checking use scan dependencies for: " + Arrays.asList(apiUseTypes)); //$NON-NLS-1$ } } SubMonitor localmonitor = SubMonitor.convert(monitor, BuilderMessages.checking_external_dependencies, 10); IReferenceDescriptor[] externalDependencies = UseScanManager.getInstance() .getExternalDependenciesFor(apiComponent, apiUseTypes, localmonitor.newChild(10)); try { if (externalDependencies != null) { localmonitor.setWorkRemaining(externalDependencies.length); HashMap<String, IApiProblem> problems = new HashMap<String, IApiProblem>(); for (int i = 0; i < externalDependencies.length; i++) { Util.updateMonitor(localmonitor, 1); Reference externalReference = null; IApiTypeRoot type = null; IMemberDescriptor referencedMember = externalDependencies[i].getReferencedMember(); IReferenceTypeDescriptor referenceMemberType = referencedMember.getEnclosingType(); if (referenceMemberType != null) { type = apiComponent.findTypeRoot(referenceMemberType.getQualifiedName()); } switch (referencedMember.getElementType()) { case IElementDescriptor.TYPE: { referenceMemberType = (IReferenceTypeDescriptor) referencedMember; type = apiComponent.findTypeRoot(referenceMemberType.getQualifiedName()); if (type != null) { externalReference = Reference.typeReference(type.getStructure(), referenceMemberType.getQualifiedName(), externalDependencies[i].getReferenceKind()); } break; } case IElementDescriptor.METHOD: { if (type != null) { externalReference = Reference.methodReference(type.getStructure(), referenceMemberType.getQualifiedName(), referencedMember.getName(), ((IMethodDescriptor) referencedMember).getSignature(), externalDependencies[i].getReferenceKind()); } break; } case IElementDescriptor.FIELD: { if (type != null) { externalReference = Reference.fieldReference(type.getStructure(), referenceMemberType.getQualifiedName(), referencedMember.getName(), externalDependencies[i].getReferenceKind()); } break; } default: break; } if (type == null) { createExternalDependenciesProblem(problems, externalDependencies[i], referenceMemberType.getQualifiedName(), referencedMember, externalDependencies[i].getReferencedMember().getElementType(), IApiProblem.API_USE_SCAN_DELETED); } else { externalReference.resolve(); if (externalReference.getResolvedReference() == null) { createExternalDependenciesProblem(problems, externalDependencies[i], referenceMemberType.getQualifiedName(), referencedMember, externalDependencies[i].getReferencedMember().getElementType(), IApiProblem.API_USE_SCAN_UNRESOLVED); } } } for (IApiProblem apiProblem : problems.values()) { addProblem(apiProblem); } } } finally { localmonitor.done(); } } public boolean isSeverityEnabled(Properties properties) { IEclipsePreferences node = InstanceScope.INSTANCE.getNode(ApiPlugin.PLUGIN_ID); if (properties == null) { if (!isIgnore(node.get(IApiProblemTypes.API_USE_SCAN_TYPE_SEVERITY, ApiPlugin.VALUE_IGNORE))) { return true; } if (!isIgnore(node.get(IApiProblemTypes.API_USE_SCAN_METHOD_SEVERITY, ApiPlugin.VALUE_IGNORE))) { return true; } if (isIgnore(node.get(IApiProblemTypes.API_USE_SCAN_FIELD_SEVERITY, ApiPlugin.VALUE_IGNORE))) { return true; } return false; } else { if (properties.isEmpty()) { return true; // preferences parameter not provided } if (!isIgnore(properties.get(IApiProblemTypes.API_USE_SCAN_TYPE_SEVERITY))) { return true; } if (!isIgnore(properties.get(IApiProblemTypes.API_USE_SCAN_METHOD_SEVERITY))) { return true; } if (!isIgnore(properties.get(IApiProblemTypes.API_USE_SCAN_FIELD_SEVERITY))) { return true; } return false; } } private boolean isIgnore(Object value) { if (value != null && (value.toString().equalsIgnoreCase(ApiPlugin.VALUE_ERROR) || value.toString().equalsIgnoreCase(ApiPlugin.VALUE_WARNING))) { return false; } return true; } /** * Creates an {@link IApiProblem} for the broken external dependency * * @param problems * @param dependency * @param referenceType * @param referencedMember * @param elementType * @param flag * @return */ protected IApiProblem createExternalDependenciesProblem(HashMap<String, IApiProblem> problems, IReferenceDescriptor dependency, String referenceTypeName, IMemberDescriptor referencedMember, int elementType, int flag) { String resource = referenceTypeName; String primaryTypeName = referenceTypeName.replace('$', '.'); int charStart = -1, charEnd = -1, lineNumber = -1; if (fJavaProject != null) { try { IType type = fJavaProject.findType(primaryTypeName); IResource res = Util.getResource(fJavaProject.getProject(), type); if (res == null) { return null; } if (!Util.isManifest(res.getProjectRelativePath())) { resource = res.getProjectRelativePath().toString(); } else { resource = "."; //$NON-NLS-1$ } if (type != null) { ISourceRange range = type.getNameRange(); charStart = range.getOffset(); charEnd = charStart + range.getLength(); try { IDocument document = Util.getDocument(type.getCompilationUnit()); lineNumber = document.getLineOfOffset(charStart); } catch (BadLocationException e) { // ignore } catch (CoreException ce) { } } } catch (JavaModelException e) { } } String[] msgArgs = new String[] { referenceTypeName, referencedMember.getName(), dependency.getComponent().getId() }; int kind = 0; switch (elementType) { case IElementDescriptor.TYPE: { kind = IApiProblem.API_USE_SCAN_TYPE_PROBLEM; break; } case IElementDescriptor.METHOD: { kind = IApiProblem.API_USE_SCAN_METHOD_PROBLEM; msgArgs[1] = BuilderMessages.BaseApiAnalyzer_Method + ' ' + msgArgs[1]; if ((dependency.getReferenceKind() & IReference.REF_CONSTRUCTORMETHOD) > 0) { msgArgs[1] = BuilderMessages.BaseApiAnalyzer_Constructor + ' ' + msgArgs[1]; } break; } case IElementDescriptor.FIELD: { kind = IApiProblem.API_USE_SCAN_FIELD_PROBLEM; break; } default: break; } int dependencyNameIndex = 2; // the comma separated list of dependent // plugins int problemId = ApiProblemFactory.createProblemId(IApiProblem.CATEGORY_API_USE_SCAN_PROBLEM, elementType, kind, flag); String problemKey = referenceTypeName + problemId; IApiProblem similarProblem = problems.get(problemKey); if (similarProblem != null) { String[] existingMsgArgs = similarProblem.getMessageArguments()[dependencyNameIndex].split(", "); //$NON-NLS-1$ if (!Arrays.asList(existingMsgArgs).contains(msgArgs[dependencyNameIndex])) { msgArgs[dependencyNameIndex] = similarProblem.getMessageArguments()[dependencyNameIndex] + ',' + ' ' + msgArgs[dependencyNameIndex]; } else { return similarProblem; } } IApiProblem problem = ApiProblemFactory.newApiUseScanProblem(resource, primaryTypeName, msgArgs, new String[] { IApiMarkerConstants.API_USESCAN_TYPE }, new String[] { primaryTypeName }, lineNumber, charStart, charEnd, elementType, kind, flag); problems.put(problemKey, problem); return problem; } /** * Checks the compatibility of each type. * * @param changedtypes type names, may have <code>null</code> entries * @param reference API component in the reference baseline * @param component API component being checked for compatibility * @param localMonitor * @throws CoreException */ private void checkCompatibility(String[] changedtypes, IApiComponent reference, IApiComponent component, SubMonitor localMonitor) throws CoreException { for (int i = 0; i < changedtypes.length; i++) { if (changedtypes[i] == null) { continue; } checkCompatibility(changedtypes[i], reference, component, localMonitor.newChild(1)); Util.updateMonitor(localMonitor); } } /** * Checks for unused API problem filters * * @param context the current build context * @param reference * @param monitor */ private void checkUnusedProblemFilters(final IBuildContext context, IApiComponent reference, IProgressMonitor monitor) { if (ignoreUnusedProblemFilterCheck()) { if (ApiPlugin.DEBUG_API_ANALYZER) { System.out.println("Ignoring unused problem filter check"); //$NON-NLS-1$ } Util.updateMonitor(monitor, 1); return; } try { ApiFilterStore store = (ApiFilterStore) reference.getFilterStore(); IProject project = fJavaProject.getProject(); boolean autoremove = ApiPlugin.getDefault() .getEnableState(IApiProblemTypes.AUTOMATICALLY_REMOVE_UNUSED_PROBLEM_FILTERS, project); ArrayList<IApiProblemFilter> toremove = null; if (autoremove) { toremove = new ArrayList<IApiProblemFilter>(8); } IApiProblemFilter[] filters = null; if (context.hasTypes()) { IResource resource = null; String[] types = getApiUseTypes(context); for (int i = 0; i < types.length; i++) { if (types[i] == null) { continue; } resource = Util.getResource(project, fJavaProject.findType(Signatures.getPrimaryTypeName(types[i]))); if (resource != null) { filters = store.getUnusedFilters(resource, types[i], null); if (autoremove) { for (int j = 0; j < filters.length; j++) { toremove.add(filters[j]); } continue; } createUnusedApiFilterProblems(filters); } } if (autoremove) { removeUnusedProblemFilters(store, toremove, monitor); } } else { filters = store.getUnusedFilters(null, null, null); if (autoremove) { for (int i = 0; i < filters.length; i++) { toremove.add(filters[i]); } removeUnusedProblemFilters(store, toremove, monitor); } else { // full build, clean up all old markers createUnusedApiFilterProblems(filters); } } } catch (CoreException ce) { // ignore, just don't create problems } finally { Util.updateMonitor(monitor, 1); } } /** * Removes the given set of {@link IApiProblemFilter}s from the given * {@link IApiFilterStore} using a workspace runnable to avoid resource * notifications * * @param store the store to remove from * @param filterlist list of filters to batch remove * @param monitor * @throws CoreException * @since 1.1 */ void removeUnusedProblemFilters(final IApiFilterStore store, final List<IApiProblemFilter> filterlist, final IProgressMonitor monitor) throws CoreException { if (filterlist.size() > 0) { IWorkspaceRunnable runner = new IWorkspaceRunnable() { /* * (non-Javadoc) * @see * org.eclipse.core.resources.IWorkspaceRunnable#run(org.eclipse * .core.runtime.IProgressMonitor) */ @Override public void run(IProgressMonitor lmonitor) throws CoreException { store.removeFilters(filterlist.toArray(new IApiProblemFilter[filterlist.size()])); } }; ResourcesPlugin.getWorkspace().run(runner, null, IWorkspace.AVOID_UPDATE, monitor); } } /** * Creates a new unused {@link IApiProblemFilter} problem * * @param filters the filters to create the problems for * @return a new {@link IApiProblem} for unused problem filters or * <code>null</code> */ private void createUnusedApiFilterProblems(IApiProblemFilter[] filters) { if (fJavaProject == null) { return; } IApiProblemFilter filter = null; IApiProblem problem = null; for (int i = 0; i < filters.length; i++) { filter = filters[i]; problem = filter.getUnderlyingProblem(); if (problem == null) { return; } IResource resource = null; IType type = null; // retrieve line number, char start and char end int lineNumber = 0; int charStart = -1; int charEnd = -1; if (fJavaProject != null) { try { String typeName = problem.getTypeName(); if (typeName != null) { type = fJavaProject.findType(typeName.replace('$', '.')); } IProject project = fJavaProject.getProject(); resource = Util.getResource(project, type); if (resource == null) { return; } if (!Util.isManifest(resource.getProjectRelativePath()) && !type.isBinary()) { ISourceRange range = type.getNameRange(); charStart = range.getOffset(); charEnd = charStart + range.getLength(); try { IDocument document = Util.getDocument(type.getCompilationUnit()); lineNumber = document.getLineOfOffset(charStart); } catch (BadLocationException e) { // ignore } } } catch (JavaModelException e) { ApiPlugin.log(e); } catch (CoreException e) { ApiPlugin.log(e); } } String path = null; if (resource != null) { path = resource.getProjectRelativePath().toPortableString(); } addProblem(ApiProblemFactory.newApiUsageProblem(path, problem.getTypeName(), new String[] { filter.getUnderlyingProblem().getMessage() }, // message // args new String[] { IApiMarkerConstants.MARKER_ATTR_FILTER_HANDLE_ID, IApiMarkerConstants.API_MARKER_ATTR_ID }, new Object[] { ((ApiProblemFilter) filter).getHandle(), new Integer(IApiMarkerConstants.UNUSED_PROBLEM_FILTER_MARKER_ID) }, lineNumber, charStart, charEnd, problem.getElementKind(), IApiProblem.UNUSED_PROBLEM_FILTERS)); } } /** * Check the version changes of re-exported bundles to make sure that the * given component version is modified accordingly. * * @param reference the given reference API component * @param component the given component */ private ReexportedBundleVersionInfo checkBundleVersionsOfReexportedBundles(IApiComponent reference, IApiComponent component) throws CoreException { IRequiredComponentDescription[] requiredComponents = component.getRequiredComponents(); int length = requiredComponents.length; ReexportedBundleVersionInfo info = null; if (length != 0) { loop: for (int i = 0; i < length; i++) { IRequiredComponentDescription description = requiredComponents[i]; if (description.isExported()) { // get the corresponding IRequiredComponentDescription for // the component from the reference baseline String id = description.getId(); IRequiredComponentDescription[] requiredComponents2 = reference.getRequiredComponents(); // get the corresponding exported bundle IRequiredComponentDescription referenceDescription = null; int length2 = requiredComponents2.length; loop2: for (int j = 0; j < length2; j++) { IRequiredComponentDescription description2 = requiredComponents2[j]; if (description2.getId().equals(id)) { if (description2.isExported()) { referenceDescription = description2; break loop2; } } } if (referenceDescription == null) { continue loop; } IVersionRange versionRange = description.getVersionRange(); IVersionRange versionRange2 = referenceDescription.getVersionRange(); Version currentLowerBound = new Version(versionRange.getMinimumVersion()); Version referenceLowerBound = new Version(versionRange2.getMinimumVersion()); int currentLowerMajorVersion = currentLowerBound.getMajor(); int referenceLowerMajorVersion = referenceLowerBound.getMajor(); int currentLowerMinorVersion = currentLowerBound.getMinor(); int referenceLowerMinorVersion = referenceLowerBound.getMinor(); if (currentLowerMajorVersion < referenceLowerMajorVersion || currentLowerMinorVersion < referenceLowerMinorVersion) { return new ReexportedBundleVersionInfo(id, IApiProblem.REEXPORTED_MAJOR_VERSION_CHANGE); } if (currentLowerMajorVersion > referenceLowerMajorVersion) { return new ReexportedBundleVersionInfo(id, IApiProblem.REEXPORTED_MAJOR_VERSION_CHANGE); } if (currentLowerMinorVersion > referenceLowerMinorVersion) { info = new ReexportedBundleVersionInfo(id, IApiProblem.REEXPORTED_MINOR_VERSION_CHANGE); } } } } return info; } /** * Creates and AST for the given {@link ITypeRoot} at the given offset * * @param root * @param offset * @return */ private CompilationUnit createAST(ITypeRoot root, int offset) { if (fJavaProject == null) { return null; } ASTParser parser = ASTParser.newParser(AST.JLS8); parser.setFocalPosition(offset); parser.setResolveBindings(false); parser.setSource(root); Map<String, String> 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 = BuildState.getLastBuiltState(project); if (state != null) { return state; } } catch (CoreException e) { } return new BuildState(); } /** * Returns an {@link IApiTypeContainer} given the component and type names * context * * @param component * @param types * @return a new {@link IApiTypeContainer} for the component and type names * context */ private IApiTypeContainer getSearchScope(final IApiComponent component, final String[] typenames) { if (typenames == null) { return component; } if (typenames.length == 0) { return 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<IReferenceTypeDescriptor> types = new ArrayList<IReferenceTypeDescriptor>(typenames.length); for (int i = 0; i < typenames.length; i++) { if (typenames[i] == null) { continue; } types.add(Util.getType(typenames[i])); } return types.toArray(new IReferenceTypeDescriptor[types.size()]); } /* * (non-Javadoc) * @see org.eclipse.pde.api.tools.internal.provisional.builder.IApiAnalyzer# * getProblems() */ @Override public IApiProblem[] getProblems() { if (fProblems == null) { return new IApiProblem[0]; } return fProblems.toArray(new IApiProblem[fProblems.size()]); } /* * (non-Javadoc) * @see * org.eclipse.pde.api.tools.internal.provisional.builder.IApiAnalyzer#dispose * () */ @Override 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.ILLEGAL_OVERRIDE, 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; ignore &= plugin.getSeverityLevel(IApiProblemTypes.INVALID_REFERENCE_IN_SYSTEM_LIBRARIES, project) == ApiPlugin.SEVERITY_IGNORE; return ignore; } /** * @return if the API usage scan should be ignored */ private boolean reportApiBreakageWhenMajorVersionIncremented() { if (fJavaProject == null) { // we ignore it for non-OSGi case return false; } return ApiPlugin.getDefault().getEnableState( IApiProblemTypes.REPORT_API_BREAKAGE_WHEN_MAJOR_VERSION_INCREMENTED, fJavaProject.getProject().getProject()); } /** * @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; } private boolean ignoreMinorVersionCheckWithoutApiChange() { if (fJavaProject == null) { // we ignore it for non-OSGi case return true; } return !ApiPlugin.getDefault().getEnableState( IApiProblemTypes.INCOMPATIBLE_API_COMPONENT_VERSION_INCLUDE_INCLUDE_MINOR_WITHOUT_API_CHANGE, fJavaProject.getProject().getProject()); } private boolean ignoreMajorVersionCheckWithoutBreakingChange() { if (fJavaProject == null) { // we ignore it for non-OSGi case return true; } return !ApiPlugin.getDefault().getEnableState( IApiProblemTypes.INCOMPATIBLE_API_COMPONENT_VERSION_INCLUDE_INCLUDE_MAJOR_WITHOUT_BREAKING_CHANGE, fJavaProject.getProject().getProject()); } /** * @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; } /** * @return if the invalid annotation check should be ignored * * @since 1.0.600 */ private boolean ignoreInvalidAnnotationCheck() { if (fJavaProject == null) { return true; } return ApiPlugin.getDefault().getSeverityLevel(IApiProblemTypes.INVALID_ANNOTATION, fJavaProject.getProject()) == ApiPlugin.SEVERITY_IGNORE; } /** * @return if the unused problem filter check should be ignored or not */ private boolean ignoreUnusedProblemFilterCheck() { if (fJavaProject == null) { return true; } return ApiPlugin.getDefault().getSeverityLevel(IApiProblemTypes.UNUSED_PROBLEM_FILTERS, fJavaProject.getProject()) == ApiPlugin.SEVERITY_IGNORE; } /** * Checks the validation of tags for the given {@link IApiComponent} * * @param context * @param component * @param monitor */ private void checkTagValidation(final IBuildContext context, final IApiComponent component, IProgressMonitor monitor) { boolean tags = ignoreInvalidTagCheck(); boolean annotations = ignoreInvalidAnnotationCheck(); if (tags && annotations) { return; } SubMonitor localMonitor = null; try { localMonitor = SubMonitor.convert(monitor, BuilderMessages.BaseApiAnalyzer_validating_javadoc_tags, 1 + component.getApiTypeContainers().length); if (context.hasTypes()) { String[] typenames = context.getStructurallyChangedTypes(); for (int i = 0; i < typenames.length; i++) { if (typenames[i] == null) { continue; } localMonitor.subTask(NLS.bind(BuilderMessages.BaseApiAnalyzer_scanning_0, typenames[i])); processType(typenames[i], !tags, !annotations); Util.updateMonitor(localMonitor); } } else { try { IPackageFragmentRoot[] roots = fJavaProject.getPackageFragmentRoots(); for (int i = 0; i < roots.length; i++) { if (roots[i].getKind() == IPackageFragmentRoot.K_SOURCE) { localMonitor.subTask(NLS.bind(BuilderMessages.BaseApiAnalyzer_scanning_0, roots[i].getPath().toOSString())); scanSource(roots[i], !tags, !annotations, localMonitor.newChild(1)); Util.updateMonitor(localMonitor); } } } catch (JavaModelException jme) { ApiPlugin.log(jme); } } Util.updateMonitor(localMonitor); } catch (CoreException e) { ApiPlugin.log(e); } finally { if (localMonitor != null) { localMonitor.done(); } } } /** * Recursively finds all source in the given project and scans it for * invalid tags * * @param element * @param monitor * @throws JavaModelException */ private void scanSource(IJavaElement element, boolean tags, boolean annotations, IProgressMonitor monitor) throws JavaModelException { try { switch (element.getElementType()) { case IJavaElement.PACKAGE_FRAGMENT_ROOT: case IJavaElement.PACKAGE_FRAGMENT: { IParent parent = (IParent) element; IJavaElement[] children = parent.getChildren(); for (int i = 0; i < children.length; i++) { scanSource(children[i], tags, annotations, monitor); Util.updateMonitor(monitor, 0); } break; } case IJavaElement.COMPILATION_UNIT: { ICompilationUnit unit = (ICompilationUnit) element; processType(unit, tags, annotations); Util.updateMonitor(monitor, 0); break; } default: break; } } finally { if (monitor != null) { Util.updateMonitor(monitor); monitor.done(); } } } /** * Processes the given type name for invalid Javadoc tags * * @param typename */ private void processType(String typename, boolean tags, boolean annotations) { try { IType type = fJavaProject.findType(typename); if (type != null && !type.isMember()) { // member types are processed while processing the compilation // unit ICompilationUnit cunit = type.getCompilationUnit(); if (cunit != null) { processType(cunit, tags, annotations); } } } catch (JavaModelException e) { ApiPlugin.log(e); } } /** * Processes the given {@link ICompilationUnit} for invalid tags * * @param cunit */ private void processType(ICompilationUnit cunit, boolean tags, boolean annotations) { CompilationUnit comp = createAST(cunit, 0); if (comp == null) { return; } TagValidator tv = new TagValidator(cunit, tags, annotations); comp.accept(tv); IApiProblem[] tagProblems = tv.getProblems(); for (int i = 0; i < tagProblems.length; i++) { addProblem(tagProblems[i]); } } /** * Checks for illegal API usage in the specified component, creating problem * markers as required. * * @param context the current build context * @param component component being built * @param monitor progress monitor */ private void checkApiUsage(final IBuildContext context, final IApiComponent component, IProgressMonitor monitor) { if (ignoreApiUsageScan()) { if (ApiPlugin.DEBUG_API_ANALYZER) { System.out.println("Ignoring API usage scan"); //$NON-NLS-1$ } return; } IApiTypeContainer scope = null; if (context.hasTypes()) { String[] typenames = getApiUseTypes(context); if (typenames.length < 1) { monitor.done(); return; } scope = getSearchScope(component, typenames); } else { scope = getSearchScope(component, null); // entire component } SubMonitor localMonitor = SubMonitor.convert(monitor, MessageFormat .format(BuilderMessages.checking_api_usage, new Object[] { component.getSymbolicName() }), 2); ReferenceAnalyzer analyzer = new ReferenceAnalyzer(); try { long start = System.currentTimeMillis(); IApiProblem[] illegal = analyzer.analyze(component, scope, localMonitor.newChild(2)); Util.updateMonitor(localMonitor); long end = System.currentTimeMillis(); if (ApiPlugin.DEBUG_API_ANALYZER) { System.out.println("API usage scan: " + (end - start) + " ms\t" + illegal.length + " problems"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } for (int i = 0; i < illegal.length; i++) { addProblem(illegal[i]); } Util.updateMonitor(localMonitor); } catch (CoreException ce) { if (ApiPlugin.DEBUG_API_ANALYZER) { ApiPlugin.log(ce); } } finally { if (monitor != null) { monitor.done(); } } } /** * Returns the collection of type names to be built * * @param context * @return the complete listing of type names to build or an empty array, * never <code>null</code> * @since 1.1 */ String[] getApiUseTypes(IBuildContext context) { if (context.hasTypes()) { String[] deptypes = null; int size = 0; if (context.hasDescriptionDependents()) { // only check dependents if there were description changes deptypes = context.getDescriptionDependentTypes(); size += deptypes.length; } String[] structtypes = context.getStructurallyChangedTypes(); HashSet<String> typenames = new HashSet<String>(size + structtypes.length); if (deptypes != null) { for (int i = 0; i < deptypes.length; i++) { if (deptypes[i] == null) { continue; } typenames.add(deptypes[i]); } } for (int i = 0; i < structtypes.length; i++) { if (structtypes[i] == null) { continue; } typenames.add(structtypes[i]); } return typenames.toArray(new String[typenames.size()]); } return NO_TYPES; } /** * Compares the given type between the two API components * * @param typeName the type to check in each component * @param reference * @param component * @param monitor */ private void checkCompatibility(final String typeName, final IApiComponent reference, final IApiComponent component, IProgressMonitor monitor) throws CoreException { String id = component.getSymbolicName(); if (ApiPlugin.DEBUG_API_ANALYZER) { System.out.println("comparing components [" + reference.getSymbolicName() + "] and [" + id //$NON-NLS-1$//$NON-NLS-2$ + "] for type [" + typeName + "]"); //$NON-NLS-1$ //$NON-NLS-2$ } IApiTypeRoot classFile = null; try { if (Util.ORG_ECLIPSE_SWT.equals(id)) { classFile = component.findTypeRoot(typeName); } else { classFile = component.findTypeRoot(typeName, id); } } catch (CoreException e) { ApiPlugin.log(e); } SubMonitor localmonitor = SubMonitor.convert(monitor, BuilderMessages.BaseApiAnalyzer_checking_compat, 4); try { IDelta delta = null; IApiComponent provider = null; boolean reexported = false; if (classFile == null) { String packageName = Signatures.getPackageName(typeName); // check if the type is provided by a required component (it // could have been moved/re-exported) IApiComponent[] providers = component.getBaseline().resolvePackage(component, packageName); int index = 0; while (classFile == null && index < providers.length) { IApiComponent p = providers[index]; if (!p.equals(component)) { String id2 = p.getSymbolicName(); if (Util.ORG_ECLIPSE_SWT.equals(id2)) { classFile = p.findTypeRoot(typeName); } else { classFile = p.findTypeRoot(typeName, id2); } if (classFile != null) { IRequiredComponentDescription[] components = component.getRequiredComponents(); for (int i = 0; i < components.length; i++) { IRequiredComponentDescription description = components[i]; if (description.getId().equals(p.getSymbolicName()) && description.isExported()) { reexported = true; break; } } provider = p; } } index++; } } else { provider = component; } Util.updateMonitor(localmonitor, 1); if (classFile == null) { // this indicates a removed type // we should try to get the class file from the reference IApiTypeRoot referenceClassFile = null; try { referenceClassFile = reference.findTypeRoot(typeName); } catch (CoreException e) { ApiPlugin.log(e); } if (referenceClassFile != null) { try { IApiType type = referenceClassFile.getStructure(); if (type == null) { return; } final IApiDescription referenceApiDescription = reference.getApiDescription(); IApiAnnotations elementDescription = referenceApiDescription .resolveAnnotations(type.getHandle()); int restrictions = RestrictionModifiers.NO_RESTRICTIONS; if (!type.isMemberType() && !type.isAnonymous() && !type.isLocal()) { int visibility = VisibilityModifiers.ALL_VISIBILITIES; // we skip nested types (member, local and // anonymous) if (elementDescription != null) { restrictions = elementDescription.getRestrictions(); visibility = elementDescription.getVisibility(); } // if the visibility is API, we only consider public // and protected types if (Util.isDefault(type.getModifiers()) || Flags.isPrivate(type.getModifiers())) { return; } if (VisibilityModifiers.isAPI(visibility)) { String deltaComponentID = Util.getDeltaComponentVersionsId(reference); delta = new Delta(deltaComponentID, IDelta.API_COMPONENT_ELEMENT_TYPE, IDelta.REMOVED, IDelta.TYPE, restrictions, RestrictionModifiers.NO_RESTRICTIONS, type.getModifiers(), 0, typeName, typeName, new String[] { typeName, Util.getComponentVersionsId(reference) }); } } } catch (CoreException e) { ApiPlugin.log(e); } } Util.updateMonitor(localmonitor, 1); } else { fBuildState.cleanup(typeName); long time = System.currentTimeMillis(); try { IApiComponent exporter = null; if (reexported) { exporter = component; } delta = ApiComparator.compare(classFile, reference, provider, exporter, reference.getBaseline(), provider.getBaseline(), VisibilityModifiers.API, localmonitor.newChild(1)); } catch (OperationCanceledException oce) { // do nothing, but don't forward it // https://bugs.eclipse.org/bugs/show_bug.cgi?id=304315 if (ApiPlugin.DEBUG_API_ANALYZER) { System.out.println("Trapped OperationCanceledException"); //$NON-NLS-1$ } } catch (Exception e) { ApiPlugin.log(e); } finally { if (ApiPlugin.DEBUG_API_ANALYZER) { 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<IDelta> allDeltas = Util.collectAllDeltas(delta); localmonitor.subTask(BuilderMessages.BaseApiAnalyzer_processing_deltas); for (IDelta d : allDeltas) { processDelta(d, reference, component); Util.updateMonitor(localmonitor); } Util.updateMonitor(localmonitor, 1); if (!fPendingDeltaInfos.isEmpty()) { localmonitor.subTask(BuilderMessages.BaseApiAnalyzer_checking_since_tags); for (IDelta d : fPendingDeltaInfos) { checkSinceTags((Delta) d, component); } } Util.updateMonitor(localmonitor, 1); } else { Util.updateMonitor(localmonitor, 2); } } finally { localmonitor.done(); } } /** * Compares the two given components and generates an {@link IDelta} * * @param jproject * @param reference * @param component * @param monitor */ private void checkCompatibility(final IApiComponent reference, final IApiComponent component, IProgressMonitor monitor) { long time = System.currentTimeMillis(); SubMonitor localmonitor = SubMonitor.convert(monitor, BuilderMessages.BaseApiAnalyzer_checking_compat, 3); try { IDelta delta = null; if (reference == null) { delta = new Delta(null, IDelta.API_BASELINE_ELEMENT_TYPE, IDelta.ADDED, IDelta.API_COMPONENT, null, component.getSymbolicName(), component.getSymbolicName()); Util.updateMonitor(localmonitor, 5); } else { try { delta = ApiComparator.compare(reference, component, VisibilityModifiers.API, localmonitor.newChild(1)); } finally { if (ApiPlugin.DEBUG_API_ANALYZER) { System.out.println("Time spent for " + component.getSymbolicName() + " : " //$NON-NLS-1$//$NON-NLS-2$ + (System.currentTimeMillis() - time) + "ms"); //$NON-NLS-1$ } fPendingDeltaInfos.clear(); } } if (delta == null) { return; } if (delta != ApiComparator.NO_DELTA) { List<IDelta> allDeltas = Util.collectAllDeltas(delta); if (allDeltas.size() != 0) { localmonitor.subTask(BuilderMessages.BaseApiAnalyzer_processing_deltas); for (IDelta d : allDeltas) { processDelta(d, reference, component); Util.updateMonitor(localmonitor); } Util.updateMonitor(localmonitor, 1); localmonitor.subTask(BuilderMessages.BaseApiAnalyzer_checking_since_tags); if (!fPendingDeltaInfos.isEmpty()) { for (IDelta d : fPendingDeltaInfos) { checkSinceTags((Delta) d, component); } } Util.updateMonitor(localmonitor, 1); } } else { Util.updateMonitor(localmonitor, 2); } } finally { localmonitor.done(); } } /** * 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 || member.isBinary()) { 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); // we must retrieve the component version from the delta component // id String componentVersionId = delta.getComponentVersionId(); String componentVersionString = null; if (componentVersionId == null) { componentVersionString = component.getVersion(); } else { componentVersionString = extractVersion(componentVersionId); } try { if (visitor.hasNoComment() || visitor.isMissing()) { if (ignoreSinceTagCheck(IApiProblemTypes.MISSING_SINCE_TAG)) { if (ApiPlugin.DEBUG_API_ANALYZER) { 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, new String[] { Util.getDeltaArgumentString(delta) }, 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); String postfixString = tagVersion.postfixString(); if (tagVersion.getVersion() == null || Util.getFragmentNumber(tagVersion.getVersionString()) > 2) { if (ignoreSinceTagCheck(IApiProblemTypes.MALFORMED_SINCE_TAG)) { if (ApiPlugin.DEBUG_API_ANALYZER) { System.out.println("Ignoring malformed since tag problem"); //$NON-NLS-1$ } return; } StringBuffer buffer = new StringBuffer(); if (tagVersion.prefixString() != null) { buffer.append(tagVersion.prefixString()); } Version componentVersion = new Version(componentVersionString); buffer.append(componentVersion.getMajor()).append('.') .append(componentVersion.getMinor()); if (postfixString != null) { buffer.append(postfixString); } problem = createSinceTagProblem(IApiProblem.SINCE_TAG_MALFORMED, new String[] { sinceVersion, Util.getDeltaArgumentString(delta) }, delta, member, String.valueOf(buffer)); } else { if (ignoreSinceTagCheck(IApiProblemTypes.INVALID_SINCE_TAG_VERSION)) { if (ApiPlugin.DEBUG_API_ANALYZER) { 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.prefixString() != null) { buffer.append(tagVersion.prefixString()); } Version version = new Version(accurateVersion); buffer.append(version.getMajor()).append('.').append(version.getMinor()); if (postfixString != null) { buffer.append(postfixString); } String accurateSinceTagValue = String.valueOf(buffer); problem = createSinceTagProblem(IApiProblem.SINCE_TAG_INVALID, new String[] { sinceVersion, accurateSinceTagValue, Util.getDeltaArgumentString(delta) }, delta, member, accurateSinceTagValue); } } } } } catch (IllegalArgumentException e) { ApiPlugin.log(e); } } catch (RuntimeException e) { ApiPlugin.log(e); } if (problem != null) { addProblem(problem); } } private String extractVersion(String componentVersionId) { // extract the version from the delta component id. It is located // between parenthesis int indexOfOpen = componentVersionId.lastIndexOf('('); return componentVersionId.substring(indexOfOpen + 1, componentVersionId.length() - 1); } /** * 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 IType declaringType = null; if (member.getElementType() == IJavaElement.TYPE) { declaringType = (IType) member; } else { declaringType = member.getDeclaringType(); } IResource resource = Util.getResource(this.fJavaProject.getProject(), declaringType); if (resource == null) { return null; } int lineNumber = 1; int charStart = 0; int charEnd = 1; String qtn = null; if (member instanceof IType) { qtn = ((IType) member).getFullyQualifiedName(); } else { qtn = declaringType.getFullyQualifiedName(); } String[] messageArguments = null; if (!Util.isManifest(resource.getProjectRelativePath())) { messageArguments = messageargs; ICompilationUnit unit = member.getCompilationUnit(); 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); } } else { // update the last entry in the message arguments if (!(member instanceof IType)) { // insert the declaring type int length = messageargs.length; messageArguments = new String[length]; System.arraycopy(messageargs, 0, messageArguments, 0, length); StringBuffer buffer = new StringBuffer(); buffer.append(qtn).append('.').append(messageargs[length - 1]); messageArguments[length - 1] = String.valueOf(buffer); } else { messageArguments = messageargs; } } return ApiProblemFactory.newApiSinceTagProblem(resource.getProjectRelativePath().toPortableString(), qtn, messageArguments, 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()) && !reportApiBreakageWhenMajorVersionIncremented()) { // API breakage are ok in this case and we don't want them to be // reported fBuildState.addBreakingChange(delta); return null; } IResource resource = null; IType type = null; // retrieve line number, char start and char end int lineNumber = 0; 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); } IProject project = fJavaProject.getProject(); resource = Util.getResource(project, type); if (resource == null) { return null; } if (!Util.isManifest(resource.getProjectRelativePath())) { member = Util.getIMember(delta, fJavaProject); } if (member != null && !member.isBinary() && member.exists()) { 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 = resource.getProjectRelativePath().toPortableString(); } IApiProblem apiProblem = ApiProblemFactory.newApiProblem(path, delta.getTypeName(), 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; } /** * Creates an {@link IApiProblem} for the given API component * * @param component * @return a new API component resolution problem or <code>null</code> */ private void createApiComponentResolutionProblem(final IApiComponent component, final String message) { IApiProblem problem = ApiProblemFactory.newApiComponentResolutionProblem(Path.EMPTY.toString(), new String[] { component.getSymbolicName(), message }, new String[] { IApiMarkerConstants.API_MARKER_ATTR_ID }, new Object[] { new Integer(IApiMarkerConstants.API_COMPONENT_RESOLUTION_MARKER_ID) }, IElementDescriptor.RESOURCE, IApiProblem.API_COMPONENT_RESOLUTION); addProblem(problem); } /** * 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(); int modifiers = delta.getNewModifiers(); if (DeltaProcessor.isCompatible(delta)) { if (!RestrictionModifiers.isReferenceRestriction(delta.getCurrentRestrictions())) { if (Util.isVisible(modifiers)) { if (Flags.isProtected(modifiers)) { String typeName = delta.getTypeName(); if (typeName != null) { IApiTypeRoot typeRoot = null; IApiType type = null; try { String id = component.getSymbolicName(); if (Util.ORG_ECLIPSE_SWT.equals(id)) { typeRoot = component.findTypeRoot(typeName); } else { typeRoot = component.findTypeRoot(typeName, id); } if (typeRoot == null) { String packageName = Signatures.getPackageName(typeName); // check if the type is provided by a // required component (it could have been // moved/re-exported) IApiComponent[] providers = component.getBaseline().resolvePackage(component, packageName); int index = 0; while (typeRoot == null && index < providers.length) { IApiComponent p = providers[index]; if (!p.equals(component)) { String id2 = p.getSymbolicName(); if (Util.ORG_ECLIPSE_SWT.equals(id2)) { typeRoot = p.findTypeRoot(typeName); } else { typeRoot = p.findTypeRoot(typeName, id2); } } index++; } } if (typeRoot == null) { return; } type = typeRoot.getStructure(); } catch (CoreException e) { // ignore } if (type == null || Flags.isFinal(type.getModifiers())) { // no @since tag to report for new protected // methods inside a final class return; } } } // if protected, we only want to check @since tags if the // enclosing class can be sub-classed 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: case IDelta.TYPE: { if (ApiPlugin.DEBUG_API_ANALYZER) { String deltaDetails = "Delta : " + Util.getDetail(delta); //$NON-NLS-1$ System.out.println(deltaDetails + " is compatible"); //$NON-NLS-1$ } this.fBuildState.addCompatibleChange(delta); fPendingDeltaInfos.add(delta); break; } default: break; } break; } case IDelta.CHANGED: { if (flags == IDelta.INCREASE_ACCESS) { if (ApiPlugin.DEBUG_API_ANALYZER) { String deltaDetails = "Delta : " + Util.getDetail(delta); //$NON-NLS-1$ System.out.println(deltaDetails + " is compatible"); //$NON-NLS-1$ } this.fBuildState.addCompatibleChange(delta); fPendingDeltaInfos.add(delta); } break; } default: 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(modifiers)) { if (ApiPlugin.DEBUG_API_ANALYZER) { String deltaDetails = "Delta : " + Util.getDetail(delta); //$NON-NLS-1$ System.err.println(deltaDetails + " is not compatible"); //$NON-NLS-1$ } fPendingDeltaInfos.add(delta); } break; } default: break; } break; } default: break; } IApiProblem problem = createCompatibilityProblem(delta, reference, component); if (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) throws CoreException { if (ignoreComponentVersionCheck() || reference == null || component == null) { if (ApiPlugin.DEBUG_API_ANALYZER) { 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 (ApiPlugin.DEBUG_API_ANALYZER) { System.out.println("reference version of " + reference.getSymbolicName() + " : " + refversion); //$NON-NLS-1$ //$NON-NLS-2$ System.out.println("component version of " + component.getSymbolicName() + " : " + 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() != null ? QUALIFIER : null); 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()) { if (!ignoreMajorVersionCheckWithoutBreakingChange()) { // major version should be identical newversion = new Version(refversion.getMajor(), refversion.getMinor() + 1, 0, compversion.getQualifier() != null ? QUALIFIER : null); 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() != null ? QUALIFIER : null); problem = createVersionProblem(IApiProblem.MINOR_VERSION_CHANGE, new String[] { compversionval, refversionval }, String.valueOf(newversion), collectDetails(compatibleChanges)); } } else if (compversion.getMajor() != refversion.getMajor()) { if (!ignoreMajorVersionCheckWithoutBreakingChange()) { // major version should be identical newversion = new Version(refversion.getMajor(), refversion.getMinor(), refversion.getMicro(), refversion.getQualifier() != null ? QUALIFIER : null); problem = createVersionProblem(IApiProblem.MAJOR_VERSION_CHANGE_NO_BREAKAGE, new String[] { compversionval, refversionval }, String.valueOf(newversion), Util.EMPTY_STRING); } } else if (compversion.getMinor() > refversion.getMinor()) { // the minor version should not be incremented if (!ignoreMinorVersionCheckWithoutApiChange()) { newversion = new Version(refversion.getMajor(), refversion.getMinor(), refversion.getMicro(), refversion.getQualifier() != null ? QUALIFIER : null); problem = createVersionProblem(IApiProblem.MINOR_VERSION_CHANGE_NO_NEW_API, new String[] { compversionval, refversionval }, String.valueOf(newversion), Util.EMPTY_STRING); } } // analyze version of required components ReexportedBundleVersionInfo info = null; if (problem != null) { switch (problem.getKind()) { case IApiProblem.MAJOR_VERSION_CHANGE_NO_BREAKAGE: { // check if there is a version change required due to // re-exported bundles info = checkBundleVersionsOfReexportedBundles(reference, component); if (info != null) { switch (info.kind) { case IApiProblem.REEXPORTED_MAJOR_VERSION_CHANGE: { /* * we don't do anything since the major * version is already incremented we cancel * the previous issue. No need to report * that the major version should not be * incremented */ problem = null; break; } case IApiProblem.REEXPORTED_MINOR_VERSION_CHANGE: { // we should reset the major version and // increment only the minor version newversion = new Version(refversion.getMajor(), refversion.getMinor() + 1, 0, compversion.getQualifier() != null ? QUALIFIER : null); problem = createVersionProblem(IApiProblem.REEXPORTED_MAJOR_VERSION_CHANGE, new String[] { compversionval, info.componentID, }, String.valueOf(newversion), Util.EMPTY_STRING); break; } default: break; } } break; } case IApiProblem.MINOR_VERSION_CHANGE: { // check if there is a version change required due to // re-exported bundles info = checkBundleVersionsOfReexportedBundles(reference, component); if (info != null) { switch (info.kind) { case IApiProblem.REEXPORTED_MAJOR_VERSION_CHANGE: { // we keep this problem newversion = new Version(compversion.getMajor() + 1, 0, 0, compversion.getQualifier() != null ? QUALIFIER : null); problem = createVersionProblem(info.kind, new String[] { compversionval, info.componentID, }, String.valueOf(newversion), Util.EMPTY_STRING); break; } default: break; } } break; } case IApiProblem.MINOR_VERSION_CHANGE_NO_NEW_API: { // check if there is a version change required due to // re-exported bundles info = checkBundleVersionsOfReexportedBundles(reference, component); if (info != null) { switch (info.kind) { case IApiProblem.REEXPORTED_MAJOR_VERSION_CHANGE: { // we return this one newversion = new Version(compversion.getMajor() + 1, 0, 0, compversion.getQualifier() != null ? QUALIFIER : null); problem = createVersionProblem(info.kind, new String[] { compversionval, info.componentID, }, String.valueOf(newversion), Util.EMPTY_STRING); break; } case IApiProblem.REEXPORTED_MINOR_VERSION_CHANGE: { // we don't do anything since we already // incremented the minor version // we get rid of the previous problem problem = null; break; } default: break; } } break; } default: break; } } else { info = checkBundleVersionsOfReexportedBundles(reference, component); if (info != null) { switch (info.kind) { case IApiProblem.REEXPORTED_MAJOR_VERSION_CHANGE: { // major version change if (compversion.getMajor() <= refversion.getMajor()) { newversion = new Version(compversion.getMajor() + 1, 0, 0, compversion.getQualifier() != null ? QUALIFIER : null); problem = createVersionProblem(info.kind, new String[] { compversionval, info.componentID, }, String.valueOf(newversion), Util.EMPTY_STRING); } break; } case IApiProblem.REEXPORTED_MINOR_VERSION_CHANGE: { // minor version change if (compversion.getMinor() <= refversion.getMinor()) { newversion = new Version(compversion.getMajor(), compversion.getMinor() + 1, 0, compversion.getQualifier()); problem = createVersionProblem(info.kind, new String[] { compversionval, info.componentID, }, String.valueOf(newversion), Util.EMPTY_STRING); } break; } default: break; } } } } if (problem != null) { addProblem(problem); } } /** * Collects details from the given delta listing for version problems * * @param deltas * @return a {@link String} of the details why the version number should be * changed */ private String collectDetails(final IDelta[] deltas) { StringWriter writer = new StringWriter(); PrintWriter printWriter = new PrintWriter(writer); // TODO contrived default for // https://bugs.eclipse.org/bugs/show_bug.cgi?id=251313 int max = Math.min(20, deltas.length); for (int i = 0; i < max; i++) { printWriter.print("- "); //$NON-NLS-1$ printWriter.println(deltas[i].getMessage()); if (i == max - 1 && max < deltas.length) { printWriter.println(NLS.bind(BuilderMessages.BaseApiAnalyzer_more_version_problems, new Integer(deltas.length - max))); } } printWriter.flush(); printWriter.close(); return String.valueOf(writer.getBuffer()); } /** * 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; default: continue; } } } else { lineNumber = 1; } return ApiProblemFactory.newApiVersionNumberProblem(path, null, 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.RESOURCE, kind); } /** * Checks to see if there is a default API baseline set in the workspace, if * not create a marker */ private void checkDefaultBaselineSet() { if (ignoreDefaultBaselineCheck()) { if (ApiPlugin.DEBUG_API_ANALYZER) { System.out.println("Ignoring check for default API baseline"); //$NON-NLS-1$ } return; } if (ApiPlugin.DEBUG_API_ANALYZER) { System.out.println("Checking if the default API baseline is set"); //$NON-NLS-1$ } IApiProblem problem = ApiProblemFactory.newApiBaselineProblem(Path.EMPTY.toString(), new String[] { IApiMarkerConstants.API_MARKER_ATTR_ID }, new Object[] { new Integer(IApiMarkerConstants.DEFAULT_API_BASELINE_MARKER_ID) }, IElementDescriptor.RESOURCE, IApiProblem.API_BASELINE_MISSING); addProblem(problem); } /** * 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 ProjectComponent) { ProjectComponent pp = (ProjectComponent) component; return pp.getJavaProject(); } return null; } /** * Adds the problem to the list of problems iff it is not <code>null</code> * and not filtered * * @param problem * @return */ private boolean addProblem(IApiProblem problem) { if (problem == null || isProblemFiltered(problem)) { return false; } return 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) { if (this.fFilterStore != null) { boolean filtered = this.fFilterStore.isFiltered(problem); if (filtered) { return true; } } if (this.fPreferences != null) { String key = ApiProblemFactory.getProblemSeverityId(problem); if (key != null) { String value = this.fPreferences.getProperty(key, null); return ApiPlugin.VALUE_IGNORE.equals(value); } } return false; } IProject project = fJavaProject.getProject(); // first the severity is checked if (ApiPlugin.getDefault().getSeverityLevel(ApiProblemFactory.getProblemSeverityId(problem), project) == ApiPlugin.SEVERITY_IGNORE) { return true; } IApiBaselineManager manager = ApiBaselineManager.getManager(); IApiBaseline baseline = manager.getWorkspaceBaseline(); if (baseline == null) { return false; } IApiComponent component = baseline.getApiComponent(project); if (component != null) { try { IApiFilterStore filterStore = component.getFilterStore(); if (filterStore != null) { return filterStore.isFiltered(problem); } } catch (CoreException e) { } } return false; } }