| /******************************************************************************* |
| * 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; |
| boolean unsupported = unsupportedByteCodes(); |
| if (baseline != null && !unsupported) { |
| 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 { |
| if (unsupported) { |
| reportUnsupportedBytecodes(); |
| } 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(); |
| } |
| } |
| |
| /** |
| * Returns if the component is using bytecodes that are not supported by the |
| * current version of ASM. Checks the JDT core settings for class file + |
| * compiler options<br> |
| * <br> |
| * For Luna there are no unsupported bytecodes, for Kepler, Java 8 is |
| * unsupported |
| * |
| * @return true if the backing project is configured to create bytecodes |
| * that are not supported |
| */ |
| private boolean unsupportedByteCodes() { |
| return false; |
| // fJavaProject != null && |
| // JavaCore.VERSION_1_8.equals(fJavaProject.getOption(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM, |
| // true)) && |
| // JavaCore.VERSION_1_8.equals(fJavaProject.getOption(JavaCore.COMPILER_COMPLIANCE, |
| // true)); |
| } |
| |
| /** |
| * Creates a new problem marker on the project using the unsupported |
| * bytecodes |
| */ |
| private void reportUnsupportedBytecodes() { |
| if (fJavaProject != null && (ApiPlugin.getDefault().getSeverityLevel(IApiProblemTypes.UNSUPPORTED_BYTECODES, fJavaProject.getProject()) != ApiPlugin.SEVERITY_IGNORE)) { |
| IApiProblem pb = ApiProblemFactory.newApiComponentResolutionProblem(Path.EMPTY.toString(), new String[] { fJavaProject.getElementName() }, new String[] { IApiMarkerConstants.API_MARKER_ATTR_ID }, new Object[] { new Integer(IApiMarkerConstants.API_COMPONENT_RESOLUTION_MARKER_ID) }, IElementDescriptor.RESOURCE, IApiProblem.UNSUPPORTED_BYTECODES); |
| addProblem(pb); |
| } |
| } |
| |
| /** |
| * 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() + " (" + apiComponent.getVersion() + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ |
| } 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) { |
| IType ptype = cunit.findPrimaryType(); |
| if (type.equals(ptype)) { |
| // outer types are not member types but are processed |
| // with the compilation unit |
| 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 + "] for type [" + typeName + "]"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ |
| } |
| 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() + " : " + (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); |
| 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; |
| } |
| } |