1 /******************************************************************************* 2 * Copyright (c) 2008, 2020 IBM Corporation and others. 3 * 4 * This program and the accompanying materials 5 * are made available under the terms of the Eclipse Public License 2.0 6 * which accompanies this distribution, and is available at 7 * https://www.eclipse.org/legal/epl-2.0/ 8 * 9 * SPDX-License-Identifier: EPL-2.0 10 * 11 * Contributors: 12 * IBM Corporation - initial API and implementation 13 *******************************************************************************/ 14 package org.eclipse.pde.api.tools.internal.builder; 15 16 import java.io.BufferedReader; 17 import java.io.IOException; 18 import java.io.InputStream; 19 import java.io.LineNumberReader; 20 import java.io.PrintWriter; 21 import java.io.StringReader; 22 import java.io.StringWriter; 23 import java.nio.charset.StandardCharsets; 24 import java.text.MessageFormat; 25 import java.util.ArrayList; 26 import java.util.Arrays; 27 import java.util.Collections; 28 import java.util.HashMap; 29 import java.util.HashSet; 30 import java.util.List; 31 import java.util.Map; 32 import java.util.Properties; 33 import java.util.Set; 34 import java.util.jar.JarFile; 35 36 import org.eclipse.core.resources.IFile; 37 import org.eclipse.core.resources.IMarker; 38 import org.eclipse.core.resources.IProject; 39 import org.eclipse.core.resources.IResource; 40 import org.eclipse.core.resources.IWorkspace; 41 import org.eclipse.core.resources.IWorkspaceRoot; 42 import org.eclipse.core.resources.IWorkspaceRunnable; 43 import org.eclipse.core.resources.ResourcesPlugin; 44 import org.eclipse.core.runtime.CoreException; 45 import org.eclipse.core.runtime.IProgressMonitor; 46 import org.eclipse.core.runtime.NullProgressMonitor; 47 import org.eclipse.core.runtime.OperationCanceledException; 48 import org.eclipse.core.runtime.Path; 49 import org.eclipse.core.runtime.SubMonitor; 50 import org.eclipse.core.runtime.preferences.IEclipsePreferences; 51 import org.eclipse.core.runtime.preferences.InstanceScope; 52 import org.eclipse.jdt.core.Flags; 53 import org.eclipse.jdt.core.ICompilationUnit; 54 import org.eclipse.jdt.core.IJavaElement; 55 import org.eclipse.jdt.core.IJavaProject; 56 import org.eclipse.jdt.core.IMember; 57 import org.eclipse.jdt.core.IPackageFragmentRoot; 58 import org.eclipse.jdt.core.IParent; 59 import org.eclipse.jdt.core.ISourceRange; 60 import org.eclipse.jdt.core.IType; 61 import org.eclipse.jdt.core.ITypeRoot; 62 import org.eclipse.jdt.core.JavaCore; 63 import org.eclipse.jdt.core.JavaModelException; 64 import org.eclipse.jdt.core.compiler.CharOperation; 65 import org.eclipse.jdt.core.dom.AST; 66 import org.eclipse.jdt.core.dom.ASTParser; 67 import org.eclipse.jdt.core.dom.CompilationUnit; 68 import org.eclipse.jdt.internal.core.BinaryType; 69 import org.eclipse.jface.text.BadLocationException; 70 import org.eclipse.jface.text.IDocument; 71 import org.eclipse.osgi.service.resolver.ResolverError; 72 import org.eclipse.osgi.service.resolver.VersionConstraint; 73 import org.eclipse.osgi.service.resolver.VersionRange; 74 import org.eclipse.osgi.util.NLS; 75 import org.eclipse.pde.api.tools.internal.ApiBaselineManager; 76 import org.eclipse.pde.api.tools.internal.ApiFilterStore; 77 import org.eclipse.pde.api.tools.internal.IApiCoreConstants; 78 import org.eclipse.pde.api.tools.internal.comparator.Delta; 79 import org.eclipse.pde.api.tools.internal.model.ProjectComponent; 80 import org.eclipse.pde.api.tools.internal.model.StubApiComponent; 81 import org.eclipse.pde.api.tools.internal.problems.ApiProblemFactory; 82 import org.eclipse.pde.api.tools.internal.problems.ApiProblemFilter; 83 import org.eclipse.pde.api.tools.internal.provisional.ApiPlugin; 84 import org.eclipse.pde.api.tools.internal.provisional.Factory; 85 import org.eclipse.pde.api.tools.internal.provisional.IApiAnnotations; 86 import org.eclipse.pde.api.tools.internal.provisional.IApiBaselineManager; 87 import org.eclipse.pde.api.tools.internal.provisional.IApiDescription; 88 import org.eclipse.pde.api.tools.internal.provisional.IApiFilterStore; 89 import org.eclipse.pde.api.tools.internal.provisional.IApiMarkerConstants; 90 import org.eclipse.pde.api.tools.internal.provisional.IRequiredComponentDescription; 91 import org.eclipse.pde.api.tools.internal.provisional.IVersionRange; 92 import org.eclipse.pde.api.tools.internal.provisional.RestrictionModifiers; 93 import org.eclipse.pde.api.tools.internal.provisional.VisibilityModifiers; 94 import org.eclipse.pde.api.tools.internal.provisional.builder.IApiAnalyzer; 95 import org.eclipse.pde.api.tools.internal.provisional.builder.IBuildContext; 96 import org.eclipse.pde.api.tools.internal.provisional.builder.IReference; 97 import org.eclipse.pde.api.tools.internal.provisional.comparator.ApiComparator; 98 import org.eclipse.pde.api.tools.internal.provisional.comparator.DeltaProcessor; 99 import org.eclipse.pde.api.tools.internal.provisional.comparator.IDelta; 100 import org.eclipse.pde.api.tools.internal.provisional.descriptors.IElementDescriptor; 101 import org.eclipse.pde.api.tools.internal.provisional.descriptors.IMemberDescriptor; 102 import org.eclipse.pde.api.tools.internal.provisional.descriptors.IMethodDescriptor; 103 import org.eclipse.pde.api.tools.internal.provisional.descriptors.IReferenceTypeDescriptor; 104 import org.eclipse.pde.api.tools.internal.provisional.model.IApiBaseline; 105 import org.eclipse.pde.api.tools.internal.provisional.model.IApiComponent; 106 import org.eclipse.pde.api.tools.internal.provisional.model.IApiType; 107 import org.eclipse.pde.api.tools.internal.provisional.model.IApiTypeContainer; 108 import org.eclipse.pde.api.tools.internal.provisional.model.IApiTypeRoot; 109 import org.eclipse.pde.api.tools.internal.provisional.problems.IApiProblem; 110 import org.eclipse.pde.api.tools.internal.provisional.problems.IApiProblemFilter; 111 import org.eclipse.pde.api.tools.internal.provisional.problems.IApiProblemTypes; 112 import org.eclipse.pde.api.tools.internal.search.IReferenceDescriptor; 113 import org.eclipse.pde.api.tools.internal.search.UseScanManager; 114 import org.eclipse.pde.api.tools.internal.util.Signatures; 115 import org.eclipse.pde.api.tools.internal.util.SinceTagVersion; 116 import org.eclipse.pde.api.tools.internal.util.Util; 117 import org.osgi.framework.Constants; 118 import org.osgi.framework.Version; 119 120 /** 121 * Base implementation of the analyzer used in the {@link ApiAnalysisBuilder} 122 * 123 * @since 1.0.0 124 */ 125 public class BaseApiAnalyzer implements IApiAnalyzer { 126 private static final String QUALIFIER = "qualifier"; //$NON-NLS-1$ 127 /** 128 * @since 1.1 129 */ 130 static final String[] NO_TYPES = new String[0]; 131 132 private static class ReexportedBundleVersionInfo { 133 String componentID; 134 int kind; 135 ReexportedBundleVersionInfo(String componentID, int kind)136 ReexportedBundleVersionInfo(String componentID, int kind) { 137 this.componentID = componentID; 138 this.kind = kind; 139 } 140 } 141 142 /** 143 * The backing list of problems found so far 144 */ 145 private ArrayList<IApiProblem> fProblems = new ArrayList<>(25); 146 147 /** 148 * List of pending deltas for which the @since tags should be checked 149 */ 150 private List<IDelta> fPendingDeltaInfos = new ArrayList<>(3); 151 152 /** 153 * The current build state to use 154 */ 155 private BuildState fBuildState = null; 156 /** 157 * The current filter store to use 158 */ 159 private IApiFilterStore fFilterStore = null; 160 /** 161 * The associated {@link IJavaProject}, if there is one 162 */ 163 private IJavaProject fJavaProject = null; 164 /** 165 * The current preferences to use when the platform is not running. 166 */ 167 private Properties fPreferences = null; 168 169 /** 170 * Boolean setting to continue analyzing a component even if it has 171 * resolution errors. In the workspace builder we fail fast, but when 172 * running the 173 * {@link org.eclipse.pde.api.tools.internal.tasks.APIToolsAnalysisTask} we 174 * want to still be able to produce results with resolver errors. 175 */ 176 private boolean fContinueOnResolutionError = false; 177 178 /** 179 * Constructs an API analyzer 180 */ BaseApiAnalyzer()181 public BaseApiAnalyzer() { 182 } 183 184 @Override analyzeComponent(final BuildState state, final IApiFilterStore filterStore, final Properties preferences, final IApiBaseline baseline, final IApiComponent component, final IBuildContext context, IProgressMonitor monitor)185 public void analyzeComponent(final BuildState state, final IApiFilterStore filterStore, final Properties preferences, final IApiBaseline baseline, final IApiComponent component, final IBuildContext context, IProgressMonitor monitor) { 186 SubMonitor localMonitor = SubMonitor.convert(monitor, BuilderMessages.BaseApiAnalyzer_analyzing_api, 6); 187 try { 188 fJavaProject = getJavaProject(component); 189 this.fFilterStore = filterStore; 190 this.fPreferences = preferences; 191 if (!ignoreUnusedProblemFilterCheck()) { 192 ((ApiFilterStore) component.getFilterStore()).recordFilterUsage(); 193 } 194 ResolverError[] errors = component.getErrors(); 195 if (errors != null) { 196 // check if all errors have a constraint 197 StringBuilder buffer = null; 198 for (int i = 0, max = errors.length; i < max; i++) { 199 ResolverError error = errors[i]; 200 VersionConstraint constraint = error.getUnsatisfiedConstraint(); 201 if (constraint == null) { 202 continue; 203 } 204 VersionRange versionRange = constraint.getVersionRange(); 205 if (buffer == null) { 206 buffer = new StringBuilder(); 207 } 208 if (i > 0) { 209 buffer.append(',').append(' '); 210 } 211 buffer.append(NLS.bind(BuilderMessages.reportUnsatisfiedConstraint, new String[] { 212 constraint.getName(), 213 versionRange != null ? versionRange.toString() : BuilderMessages.undefinedRange })); 214 } 215 if (buffer != null) { 216 // API component has errors that should be reported 217 createApiComponentResolutionProblem(component, String.valueOf(buffer)); 218 if (baseline == null) { 219 checkDefaultBaselineSet(); 220 } 221 // If run from the builder, quit now and report the resolver 222 // error. 223 // If run from the task, continue processing the component 224 if (!fContinueOnResolutionError) { 225 return; 226 } 227 } 228 } 229 IBuildContext bcontext = context; 230 if (bcontext == null) { 231 bcontext = new BuildContext(); 232 } 233 boolean isNested = checkIfNested(component); 234 if (isNested) { 235 return; 236 } 237 boolean checkfilters = false; 238 if (baseline != null) { 239 IApiComponent reference = baseline.getApiComponent(component.getSymbolicName()); 240 if (reference != null) { 241 // if there are more than 1 versions in the baseline, select 242 // the best match 243 Set<IApiComponent> baselineAllComponents = baseline.getAllApiComponents(component.getSymbolicName()); 244 if (!baselineAllComponents.isEmpty()) { 245 IApiComponent bestMatchReference = getBestMatchFromMultipleComponents(baselineAllComponents, component); 246 if (bestMatchReference != null) { 247 reference = bestMatchReference; 248 } 249 } 250 } 251 252 this.fBuildState = state; 253 if (fBuildState == null) { 254 fBuildState = getBuildState(); 255 } 256 // compatibility checks 257 if (reference != null) { 258 localMonitor.subTask(NLS.bind(BuilderMessages.BaseApiAnalyzer_comparing_api_profiles, new String[] { 259 reference.getSymbolicName(), baseline.getName() })); 260 if (bcontext.hasTypes()) { 261 String[] changedtypes = bcontext.getStructurallyChangedTypes(); 262 checkCompatibility(changedtypes, reference, component, localMonitor.split(1)); 263 } else { 264 // store re-exported bundle into the build state 265 checkCompatibility(reference, component, localMonitor.split(1)); 266 } 267 this.fBuildState.setReexportedComponents(Util.getReexportedComponents(component)); 268 } else { 269 localMonitor.subTask(NLS.bind(BuilderMessages.BaseApiAnalyzer_comparing_api_profiles, new String[] { 270 component.getSymbolicName(), baseline.getName() })); 271 checkCompatibility(null, component, localMonitor.split(1)); 272 } 273 // version checks 274 checkApiComponentVersion(reference, component); 275 localMonitor.split(1); 276 checkfilters = true; 277 } else { 278 279 // check default baseline 280 checkDefaultBaselineSet(); 281 localMonitor.split(2); 282 } 283 284 // check EE description status 285 checkEEDescriptions(); 286 287 // usage checks 288 checkApiUsage(bcontext, component, localMonitor.split(1)); 289 // tag validation 290 checkTagValidation(bcontext, component, localMonitor.split(1)); 291 if (checkfilters) { 292 // check for unused filters only if the scans have been done 293 checkUnusedProblemFilters(bcontext, component, localMonitor.split(1)); 294 } 295 localMonitor.setWorkRemaining(1); 296 297 if (component instanceof ProjectComponent) { 298 checkExternalDependencies(component, bcontext, null, localMonitor.split(1)); 299 } 300 } catch (CoreException e) { 301 ApiPlugin.log(e); 302 } catch (OperationCanceledException oce) { 303 // do nothing, but don't forward it 304 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=304315 305 if (ApiPlugin.DEBUG_API_ANALYZER) { 306 System.out.println("Trapped OperationCanceledException"); //$NON-NLS-1$ 307 } 308 } finally { 309 SubMonitor.done(monitor); 310 } 311 } 312 313 checkIfNested(IApiComponent component)314 private boolean checkIfNested(IApiComponent component) { 315 if (fJavaProject != null) { 316 return false; 317 } 318 IWorkspace workspace = ResourcesPlugin.getWorkspace(); 319 IWorkspaceRoot root = workspace.getRoot(); 320 IProject[] projects = root.getProjects(); 321 Path componentLocation = new Path(component.getLocation()); 322 for (IProject project : projects) { 323 if (project.getLocation().isPrefixOf(componentLocation)) { 324 // if same project, skipped here 325 if (componentLocation.segmentCount() == project.getLocation().segmentCount() 326 + 1) { 327 return true; 328 } 329 } 330 } 331 return false; 332 } 333 getBestMatchFromMultipleComponents(Set<IApiComponent> baselineAllComponents, IApiComponent component)334 private IApiComponent getBestMatchFromMultipleComponents(Set<IApiComponent> baselineAllComponents, IApiComponent component) { 335 // baseline already sorted from higher to lower version. see 336 // ApiBaseline::fAllComponentsById 337 IApiComponent bestMatchReference = null; 338 Version compVer = new Version(component.getVersion()); 339 for (IApiComponent baselineComp : baselineAllComponents) { 340 Version verBaseline = new Version(baselineComp.getVersion()); 341 if (compVer.compareTo(verBaseline) >= 0) { 342 bestMatchReference = baselineComp; 343 break; 344 } 345 } 346 return bestMatchReference; 347 } 348 349 /** 350 * Sets whether to continue analyzing a component even if it has resolution 351 * errors. By default this is false. The workspace builder should not 352 * analyze components with errors to avoid polluting the project with 353 * markers. When running the the API tools analysis task the analyzer should 354 * continue to process the component to produce some results (the task 355 * should warn that the results may not be accurate). 356 * 357 * @param continueOnError whether to continue processing a component if it 358 * has resolution errors 359 */ setContinueOnResolverError(boolean continueOnError)360 public void setContinueOnResolverError(boolean continueOnError) { 361 fContinueOnResolutionError = continueOnError; 362 } 363 364 /** 365 * Returns whether this analyzer will continue analyzing a component even if 366 * it has resolution errors. By default this is false. The workspace builder 367 * should not analyze components with errors to avoid polluting the project 368 * with markers. When running the the API tools analysis task the analyzer 369 * should continue to process the component to produce some results (the 370 * task should warn that the results may not be accurate). 371 * 372 * @return whether this analyzer will continue analyzing a component if it 373 * has resolution errors 374 */ isContinueOnResolverError()375 public boolean isContinueOnResolverError() { 376 return fContinueOnResolutionError; 377 } 378 379 /** 380 * Checks if the setting to scan for invalid references is not set to be 381 * ignored AND there are no descriptions installed 382 * 383 * @param component 384 * @param monitor 385 * @since 1.0.400 386 */ checkEEDescriptions()387 void checkEEDescriptions() { 388 if (ignoreEEDescriptionCheck()) { 389 if (ApiPlugin.DEBUG_API_ANALYZER) { 390 System.out.println("Ignoring check for API EE descriptions"); //$NON-NLS-1$ 391 } 392 return; 393 } 394 if (ApiPlugin.DEBUG_API_ANALYZER) { 395 System.out.println("Checking if there are any API EE descriptions installed if the preference is set to not be 'ignore'"); //$NON-NLS-1$ 396 } 397 String[] ees = StubApiComponent.getInstalledMetadata(); 398 if (ees.length < 1) { 399 IApiProblem problem = ApiProblemFactory.newApiUsageProblem(Path.EMPTY.toString(), null, new String[] { fJavaProject.getElementName() }, new String[] { IApiMarkerConstants.API_MARKER_ATTR_ID }, new Object[] { Integer.valueOf(IApiMarkerConstants.API_USAGE_MARKER_ID) }, -1, -1, -1, IElementDescriptor.RESOURCE, IApiProblem.MISSING_EE_DESCRIPTIONS); 400 addProblem(problem); 401 } 402 } 403 404 /** 405 * @return if the API EE description check should be ignored or not 406 */ ignoreEEDescriptionCheck()407 private boolean ignoreEEDescriptionCheck() { 408 if (fJavaProject == null) { 409 return true; 410 } 411 return ApiPlugin.getDefault().getSeverityLevel(IApiProblemTypes.INVALID_REFERENCE_IN_SYSTEM_LIBRARIES, fJavaProject.getProject().getProject()) == ApiPlugin.SEVERITY_IGNORE; 412 } 413 414 /** 415 * Processes the API Use Scan report for the given API Component 416 * 417 * @param apiComponent 418 * @param bcontext 419 * @param monitor 420 * @throws CoreException 421 */ checkExternalDependencies(IApiComponent apiComponent, IBuildContext bcontext, Properties properties, IProgressMonitor monitor)422 public void checkExternalDependencies(IApiComponent apiComponent, IBuildContext bcontext, Properties properties, IProgressMonitor monitor) throws CoreException { 423 if (!isSeverityEnabled(properties)) { 424 return; 425 } 426 String[] apiUseTypes = getApiUseTypes(bcontext); 427 if (ApiPlugin.DEBUG_API_ANALYZER) { 428 if (apiUseTypes.length < 1) { 429 System.out.println("Checking use scan dependencies for: " + apiComponent.getSymbolicName() + " (" + apiComponent.getVersion() + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ 430 } else { 431 System.out.println("Checking use scan dependencies for: " + Arrays.asList(apiUseTypes)); //$NON-NLS-1$ 432 } 433 } 434 boolean checkState = hasCheckedAPIScanLocation(); 435 if (checkState == false) { 436 return; 437 } 438 SubMonitor localmonitor = SubMonitor.convert(monitor, BuilderMessages.checking_external_dependencies, 10); 439 IReferenceDescriptor[] externalDependencies = UseScanManager.getInstance().getExternalDependenciesFor(apiComponent, apiUseTypes, localmonitor.split(10)); 440 try { 441 if (externalDependencies != null) { 442 localmonitor.setWorkRemaining(externalDependencies.length); 443 HashMap<String, IApiProblem> problems = new HashMap<>(); 444 for (IReferenceDescriptor externalDependency : externalDependencies) { 445 localmonitor.split(1); 446 Reference externalReference = null; 447 IApiTypeRoot type = null; 448 IMemberDescriptor referencedMember = externalDependency.getReferencedMember(); 449 IReferenceTypeDescriptor referenceMemberType = referencedMember.getEnclosingType(); 450 if (referenceMemberType != null) { 451 type = apiComponent.findTypeRoot(referenceMemberType.getQualifiedName()); 452 } 453 switch (referencedMember.getElementType()) { 454 case IElementDescriptor.TYPE: { 455 referenceMemberType = (IReferenceTypeDescriptor) referencedMember; 456 type = apiComponent.findTypeRoot(referenceMemberType.getQualifiedName()); 457 if (type != null) { 458 externalReference = Reference.typeReference(type.getStructure(), referenceMemberType.getQualifiedName(), externalDependency.getReferenceKind()); 459 } 460 break; 461 } 462 case IElementDescriptor.METHOD: { 463 if (type != null) { 464 externalReference = Reference.methodReference(type.getStructure(), referenceMemberType.getQualifiedName(), referencedMember.getName(), ((IMethodDescriptor) referencedMember).getSignature(), externalDependency.getReferenceKind()); 465 } 466 break; 467 } 468 case IElementDescriptor.FIELD: { 469 if (type != null) { 470 externalReference = Reference.fieldReference(type.getStructure(), referenceMemberType.getQualifiedName(), referencedMember.getName(), externalDependency.getReferenceKind()); 471 } 472 break; 473 } 474 default: 475 break; 476 } 477 if (type == null) { 478 createExternalDependenciesProblem(problems, externalDependency, referenceMemberType.getQualifiedName(), referencedMember, externalDependency.getReferencedMember().getElementType(), IApiProblem.API_USE_SCAN_DELETED); 479 } else { 480 externalReference.resolve(); 481 if (externalReference.getResolvedReference() == null) { 482 createExternalDependenciesProblem(problems, externalDependency, referenceMemberType.getQualifiedName(), referencedMember, externalDependency.getReferencedMember().getElementType(), IApiProblem.API_USE_SCAN_UNRESOLVED); 483 } 484 } 485 } 486 for (IApiProblem apiProblem : problems.values()) { 487 addProblem(apiProblem); 488 } 489 } 490 } finally { 491 localmonitor.done(); 492 } 493 } 494 hasCheckedAPIScanLocation()495 private boolean hasCheckedAPIScanLocation() { 496 IEclipsePreferences node = InstanceScope.INSTANCE.getNode(ApiPlugin.PLUGIN_ID); 497 String location = node.get(IApiCoreConstants.API_USE_SCAN_LOCATION, null); 498 if (location == null || location.length() == 0) { 499 return false; 500 } 501 ArrayList<String> checkedLocations = new ArrayList<>(); 502 if (location != null && location.length() > 0) { 503 String[] locations = location.split(UseScanManager.ESCAPE_REGEX + UseScanManager.LOCATION_DELIM); 504 for (String locationString : locations) { 505 String values[] = locationString.split(UseScanManager.ESCAPE_REGEX + UseScanManager.STATE_DELIM); 506 if (Boolean.parseBoolean(values[1])) { 507 checkedLocations.add(values[0]); 508 } 509 } 510 } 511 return checkedLocations.size() > 0; 512 } 513 isSeverityEnabled(Properties properties)514 public boolean isSeverityEnabled(Properties properties) { 515 IEclipsePreferences node = InstanceScope.INSTANCE.getNode(ApiPlugin.PLUGIN_ID); 516 if (properties == null) { 517 if (!isIgnore(node.get(IApiProblemTypes.API_USE_SCAN_TYPE_SEVERITY, ApiPlugin.VALUE_IGNORE))) { 518 return true; 519 } 520 if (!isIgnore(node.get(IApiProblemTypes.API_USE_SCAN_METHOD_SEVERITY, ApiPlugin.VALUE_IGNORE))) { 521 return true; 522 } 523 if (isIgnore(node.get(IApiProblemTypes.API_USE_SCAN_FIELD_SEVERITY, ApiPlugin.VALUE_IGNORE))) { 524 return true; 525 } 526 return false; 527 } else { 528 if (properties.isEmpty()) { 529 return true; // preferences parameter not provided 530 } 531 if (!isIgnore(properties.get(IApiProblemTypes.API_USE_SCAN_TYPE_SEVERITY))) { 532 return true; 533 } 534 if (!isIgnore(properties.get(IApiProblemTypes.API_USE_SCAN_METHOD_SEVERITY))) { 535 return true; 536 } 537 if (!isIgnore(properties.get(IApiProblemTypes.API_USE_SCAN_FIELD_SEVERITY))) { 538 return true; 539 } 540 return false; 541 } 542 } 543 isIgnore(Object value)544 private boolean isIgnore(Object value) { 545 if (value != null && (value.toString().equalsIgnoreCase(ApiPlugin.VALUE_ERROR) || value.toString().equalsIgnoreCase(ApiPlugin.VALUE_WARNING))) { 546 return false; 547 } 548 return true; 549 } 550 551 /** 552 * Creates an {@link IApiProblem} for the broken external dependency 553 * 554 * @param problems 555 * @param dependency 556 * @param referenceType 557 * @param referencedMember 558 * @param elementType 559 * @param flag 560 * @return 561 */ createExternalDependenciesProblem(HashMap<String, IApiProblem> problems, IReferenceDescriptor dependency, String referenceTypeName, IMemberDescriptor referencedMember, int elementType, int flag)562 protected IApiProblem createExternalDependenciesProblem(HashMap<String, IApiProblem> problems, IReferenceDescriptor dependency, String referenceTypeName, IMemberDescriptor referencedMember, int elementType, int flag) { 563 String resource = referenceTypeName; 564 String primaryTypeName = referenceTypeName.replace('$', '.'); 565 int charStart = -1, charEnd = -1, lineNumber = -1; 566 if (fJavaProject != null) { 567 try { 568 569 IType type = fJavaProject.findType(primaryTypeName); 570 IType typeInProject = Util.getTypeInSameJavaProject(type, primaryTypeName, fJavaProject); 571 if (typeInProject != null) { 572 type = typeInProject; 573 } 574 IResource res = Util.getResource(fJavaProject.getProject(), type); 575 if (res == null) { 576 return null; 577 } 578 if (!Util.isManifest(res.getProjectRelativePath())) { 579 resource = res.getProjectRelativePath().toString(); 580 } else { 581 resource = "."; //$NON-NLS-1$ 582 } 583 if (type != null) { 584 ISourceRange range = type.getNameRange(); 585 charStart = range.getOffset(); 586 charEnd = charStart + range.getLength(); 587 try { 588 IDocument document = Util.getDocument(type.getCompilationUnit()); 589 lineNumber = document.getLineOfOffset(charStart); 590 } catch (BadLocationException e) { 591 // ignore 592 } 593 } 594 } catch (CoreException e) { 595 ApiPlugin.log("Failed to resolve problem details for " + referenceTypeName + " in " + fJavaProject.getElementName(), e); //$NON-NLS-1$ //$NON-NLS-2$ 596 } 597 } 598 String[] msgArgs = new String[] { 599 referenceTypeName, referencedMember.getName(), 600 dependency.getComponent().getId() }; 601 int kind = 0; 602 switch (elementType) { 603 case IElementDescriptor.TYPE: { 604 kind = IApiProblem.API_USE_SCAN_TYPE_PROBLEM; 605 break; 606 } 607 case IElementDescriptor.METHOD: { 608 kind = IApiProblem.API_USE_SCAN_METHOD_PROBLEM; 609 msgArgs[1] = BuilderMessages.BaseApiAnalyzer_Method + ' ' + msgArgs[1]; 610 if ((dependency.getReferenceKind() & IReference.REF_CONSTRUCTORMETHOD) > 0) { 611 msgArgs[1] = BuilderMessages.BaseApiAnalyzer_Constructor + ' ' + msgArgs[1]; 612 } 613 break; 614 } 615 case IElementDescriptor.FIELD: { 616 kind = IApiProblem.API_USE_SCAN_FIELD_PROBLEM; 617 break; 618 } 619 default: 620 break; 621 } 622 623 int dependencyNameIndex = 2; // the comma separated list of dependent 624 // plugins 625 int problemId = ApiProblemFactory.createProblemId(IApiProblem.CATEGORY_API_USE_SCAN_PROBLEM, elementType, kind, flag); 626 String problemKey = referenceTypeName + problemId; 627 IApiProblem similarProblem = problems.get(problemKey); 628 if (similarProblem != null) { 629 String[] existingMsgArgs = similarProblem.getMessageArguments()[dependencyNameIndex].split(", "); //$NON-NLS-1$ 630 if (!Arrays.asList(existingMsgArgs).contains(msgArgs[dependencyNameIndex])) { 631 msgArgs[dependencyNameIndex] = similarProblem.getMessageArguments()[dependencyNameIndex] + ',' + ' ' + msgArgs[dependencyNameIndex]; 632 } else { 633 return similarProblem; 634 } 635 } 636 IApiProblem problem = ApiProblemFactory.newApiUseScanProblem(resource, primaryTypeName, msgArgs, new String[] { IApiMarkerConstants.API_USESCAN_TYPE }, new String[] { primaryTypeName }, lineNumber, charStart, charEnd, elementType, kind, flag); 637 problems.put(problemKey, problem); 638 return problem; 639 } 640 641 /** 642 * Checks the compatibility of each type. 643 * 644 * @param changedtypes type names, may have <code>null</code> entries 645 * @param reference API component in the reference baseline 646 * @param component API component being checked for compatibility 647 * @param localMonitor 648 * @throws CoreException 649 */ checkCompatibility(String[] changedtypes, IApiComponent reference, IApiComponent component, SubMonitor localMonitor)650 private void checkCompatibility(String[] changedtypes, IApiComponent reference, IApiComponent component, SubMonitor localMonitor) throws CoreException { 651 localMonitor.setWorkRemaining(changedtypes.length); 652 for (String changedtype : changedtypes) { 653 if (changedtype == null) { 654 continue; 655 } 656 checkCompatibility(changedtype, reference, component, localMonitor.split(1)); 657 } 658 } 659 660 /** 661 * Checks for unused API problem filters 662 * 663 * @param context the current build context 664 * @param reference 665 * @param monitor 666 */ checkUnusedProblemFilters(final IBuildContext context, IApiComponent reference, IProgressMonitor monitor)667 private void checkUnusedProblemFilters(final IBuildContext context, IApiComponent reference, IProgressMonitor monitor) { 668 if (ignoreUnusedProblemFilterCheck()) { 669 if (ApiPlugin.DEBUG_API_ANALYZER) { 670 System.out.println("Ignoring unused problem filter check"); //$NON-NLS-1$ 671 } 672 return; 673 } 674 try { 675 ApiFilterStore store = (ApiFilterStore) reference.getFilterStore(); 676 IProject project = fJavaProject.getProject(); 677 boolean autoremove = ApiPlugin.getDefault().getEnableState(IApiProblemTypes.AUTOMATICALLY_REMOVE_UNUSED_PROBLEM_FILTERS, project); 678 ArrayList<IApiProblemFilter> toremove = null; 679 if (autoremove) { 680 toremove = new ArrayList<>(8); 681 } 682 IApiProblemFilter[] filters = null; 683 if (context.hasTypes()) { 684 IResource resource = null; 685 String[] types = getApiUseTypes(context); 686 for (String type : types) { 687 if (type == null) { 688 continue; 689 } 690 IType type2 = fJavaProject.findType(Signatures.getPrimaryTypeName(type)); 691 IType typeInProject = Util.getTypeInSameJavaProject(type2, Signatures.getPrimaryTypeName(type), fJavaProject); 692 if (typeInProject != null) { 693 type2 = typeInProject; 694 } 695 resource = Util.getResource(project, type2); 696 if (resource != null) { 697 filters = store.getUnusedFilters(resource, type, null); 698 if (autoremove) { 699 Collections.addAll(toremove, filters); 700 continue; 701 } 702 createUnusedApiFilterProblems(filters); 703 } 704 } 705 if (autoremove) { 706 removeUnusedProblemFilters(store, toremove, monitor); 707 } 708 } else { 709 filters = store.getUnusedFilters(null, null, null); 710 if (autoremove) { 711 Collections.addAll(toremove, filters); 712 removeUnusedProblemFilters(store, toremove, monitor); 713 } else { 714 // full build, clean up all old markers 715 createUnusedApiFilterProblems(filters); 716 } 717 } 718 } catch (CoreException ce) { 719 // ignore, just don't create problems 720 } 721 } 722 723 /** 724 * Removes the given set of {@link IApiProblemFilter}s from the given 725 * {@link IApiFilterStore} using a workspace runnable to avoid resource 726 * notifications 727 * 728 * @param store the store to remove from 729 * @param filterlist list of filters to batch remove 730 * @param monitor 731 * @throws CoreException 732 * @since 1.1 733 */ removeUnusedProblemFilters(final IApiFilterStore store, final List<IApiProblemFilter> filterlist, final IProgressMonitor monitor)734 void removeUnusedProblemFilters(final IApiFilterStore store, final List<IApiProblemFilter> filterlist, final IProgressMonitor monitor) throws CoreException { 735 if (filterlist.size() > 0) { 736 IWorkspaceRunnable runner = lmonitor -> store.removeFilters(filterlist.toArray(new IApiProblemFilter[filterlist.size()])); 737 ResourcesPlugin.getWorkspace().run(runner, null, IWorkspace.AVOID_UPDATE, monitor); 738 } 739 } 740 741 /** 742 * Creates a new unused {@link IApiProblemFilter} problem 743 * 744 * @param filters the filters to create the problems for 745 * @return a new {@link IApiProblem} for unused problem filters or 746 * <code>null</code> 747 */ createUnusedApiFilterProblems(IApiProblemFilter[] filters)748 private void createUnusedApiFilterProblems(IApiProblemFilter[] filters) { 749 if (fJavaProject == null) { 750 return; 751 } 752 IApiProblemFilter filter = null; 753 IApiProblem problem = null; 754 for (IApiProblemFilter f : filters) { 755 filter = f; 756 problem = filter.getUnderlyingProblem(); 757 if (problem == null) { 758 return; 759 } 760 IResource resource = null; 761 IType type = null; 762 // retrieve line number, char start and char end 763 int lineNumber = 0; 764 int charStart = -1; 765 int charEnd = -1; 766 if (fJavaProject != null) { 767 try { 768 String typeName = problem.getTypeName(); 769 if (typeName != null) { 770 type = fJavaProject.findType(typeName.replace('$', '.')); 771 } 772 if (type instanceof BinaryType) { 773 IType sourceType = Util.findSourceTypeinJavaProject(fJavaProject, typeName.replace('$', '.')); 774 if (sourceType != null) { 775 type = sourceType; 776 } 777 } 778 IProject project = fJavaProject.getProject(); 779 resource = Util.getResource(project, type); 780 if (resource == null) { 781 return; 782 } 783 if (!Util.isManifest(resource.getProjectRelativePath()) && !type.isBinary()) { 784 ISourceRange range = type.getNameRange(); 785 charStart = range.getOffset(); 786 charEnd = charStart + range.getLength(); 787 try { 788 IDocument document = Util.getDocument(type.getCompilationUnit()); 789 lineNumber = document.getLineOfOffset(charStart); 790 } catch (BadLocationException e) { 791 // ignore 792 } 793 } 794 } catch (CoreException e) { 795 ApiPlugin.log(e); 796 } 797 } 798 String path = null; 799 if (resource != null) { 800 path = resource.getProjectRelativePath().toPortableString(); 801 } 802 addProblem(ApiProblemFactory.newApiUsageProblem(path, problem.getTypeName(), new String[] { filter.getUnderlyingProblem().getMessage() }, // message 803 // args 804 new String[] { 805 IApiMarkerConstants.MARKER_ATTR_FILTER_HANDLE_ID, 806 IApiMarkerConstants.API_MARKER_ATTR_ID }, new Object[] { 807 ((ApiProblemFilter) filter).getHandle(), 808 Integer.valueOf(IApiMarkerConstants.UNUSED_PROBLEM_FILTER_MARKER_ID) }, lineNumber, charStart, charEnd, problem.getElementKind(), IApiProblem.UNUSED_PROBLEM_FILTERS)); 809 } 810 } 811 812 /** 813 * Check the version changes of re-exported bundles to make sure that the 814 * given component version is modified accordingly. 815 * 816 * @param reference the given reference API component 817 * @param component the given component 818 */ checkBundleVersionsOfReexportedBundles(IApiComponent reference, IApiComponent component)819 private ReexportedBundleVersionInfo checkBundleVersionsOfReexportedBundles(IApiComponent reference, IApiComponent component) throws CoreException { 820 IRequiredComponentDescription[] requiredComponents = component.getRequiredComponents(); 821 int length = requiredComponents.length; 822 ReexportedBundleVersionInfo info = null; 823 if (length != 0) { 824 loop: for (int i = 0; i < length; i++) { 825 IRequiredComponentDescription description = requiredComponents[i]; 826 if (description.isExported()) { 827 // get the corresponding IRequiredComponentDescription for 828 // the component from the reference baseline 829 String id = description.getId(); 830 IRequiredComponentDescription[] requiredComponents2 = reference.getRequiredComponents(); 831 // get the corresponding exported bundle 832 IRequiredComponentDescription referenceDescription = null; 833 int length2 = requiredComponents2.length; 834 loop2: for (int j = 0; j < length2; j++) { 835 IRequiredComponentDescription description2 = requiredComponents2[j]; 836 IRequiredComponentDescription bestMatch = getBestMatchFromMultipleReqComponents(requiredComponents2, description); 837 if(bestMatch !=null) { 838 description2 = bestMatch; 839 } 840 if (description2.getId().equals(id)) { 841 if (description2.isExported()) { 842 referenceDescription = description2; 843 break loop2; 844 } 845 } 846 } 847 if (referenceDescription == null) { 848 continue loop; 849 } 850 IVersionRange versionRange = description.getVersionRange(); 851 IVersionRange versionRange2 = referenceDescription.getVersionRange(); 852 853 Version currentLowerBound = new Version(versionRange.getMinimumVersion()); 854 Version referenceLowerBound = new Version(versionRange2.getMinimumVersion()); 855 856 int currentLowerMajorVersion = currentLowerBound.getMajor(); 857 int referenceLowerMajorVersion = referenceLowerBound.getMajor(); 858 int currentLowerMinorVersion = currentLowerBound.getMinor(); 859 int referenceLowerMinorVersion = referenceLowerBound.getMinor(); 860 861 if (currentLowerMajorVersion < referenceLowerMajorVersion || currentLowerMinorVersion < referenceLowerMinorVersion) { 862 return new ReexportedBundleVersionInfo(id, IApiProblem.REEXPORTED_MAJOR_VERSION_CHANGE); 863 } 864 865 if (currentLowerMajorVersion > referenceLowerMajorVersion) { 866 return new ReexportedBundleVersionInfo(id, IApiProblem.REEXPORTED_MAJOR_VERSION_CHANGE); 867 } 868 if (currentLowerMinorVersion > referenceLowerMinorVersion) { 869 info = new ReexportedBundleVersionInfo(id, IApiProblem.REEXPORTED_MINOR_VERSION_CHANGE); 870 } 871 } 872 } 873 } 874 return info; 875 } 876 getBestMatchFromMultipleReqComponents(IRequiredComponentDescription[] requiredComponents2, IRequiredComponentDescription description)877 private IRequiredComponentDescription getBestMatchFromMultipleReqComponents(IRequiredComponentDescription[] requiredComponents2, IRequiredComponentDescription description) { 878 IVersionRange versionRange = description.getVersionRange(); 879 Version currentLowerBound = new Version(versionRange.getMinimumVersion()); 880 int major = currentLowerBound.getMajor(); 881 for (IRequiredComponentDescription iRequiredComponentDescription : requiredComponents2) { 882 if (!description.getId().equals(iRequiredComponentDescription.getId())) { 883 continue; 884 } 885 IVersionRange versionRange2 = iRequiredComponentDescription.getVersionRange(); 886 Version currentLowerBound2 = new Version(versionRange2.getMinimumVersion()); 887 if (currentLowerBound2.getMajor() == major) { 888 return iRequiredComponentDescription; 889 } 890 } 891 return null; 892 } 893 894 /** 895 * Creates and AST for the given {@link ITypeRoot} at the given offset 896 * 897 * @param root 898 * @param offset 899 * @return 900 */ createAST(ITypeRoot root, int offset)901 private CompilationUnit createAST(ITypeRoot root, int offset) { 902 if (fJavaProject == null) { 903 return null; 904 } 905 ASTParser parser = ASTParser.newParser(AST.JLS14); 906 parser.setFocalPosition(offset); 907 parser.setResolveBindings(false); 908 parser.setSource(root); 909 Map<String, String> options = fJavaProject.getOptions(true); 910 options.put(JavaCore.COMPILER_DOC_COMMENT_SUPPORT, JavaCore.ENABLED); 911 parser.setCompilerOptions(options); 912 return (CompilationUnit) parser.createAST(new NullProgressMonitor()); 913 } 914 915 /** 916 * @return the build state to use. 917 */ getBuildState()918 private BuildState getBuildState() { 919 IProject project = null; 920 if (fJavaProject != null) { 921 project = fJavaProject.getProject(); 922 } 923 if (project == null) { 924 return new BuildState(); 925 } 926 try { 927 BuildState state = BuildState.getLastBuiltState(project); 928 if (state != null) { 929 return state; 930 } 931 } catch (CoreException e) { 932 ApiPlugin.log("Failed to read last build state for " + project, e); //$NON-NLS-1$ 933 } 934 return new BuildState(); 935 } 936 937 /** 938 * Returns an {@link IApiTypeContainer} given the component and type names 939 * context 940 * 941 * @param component 942 * @param types 943 * @return a new {@link IApiTypeContainer} for the component and type names 944 * context 945 */ getSearchScope(final IApiComponent component, final String[] typenames)946 private IApiTypeContainer getSearchScope(final IApiComponent component, final String[] typenames) { 947 if (typenames == null) { 948 return component; 949 } 950 if (typenames.length == 0) { 951 return component; 952 } else { 953 return Factory.newTypeScope(component, getScopedElements(typenames)); 954 } 955 } 956 957 /** 958 * Returns a listing of {@link IReferenceTypeDescriptor}s given the listing 959 * of type names 960 * 961 * @param typenames 962 * @return 963 */ getScopedElements(final String[] typenames)964 private IReferenceTypeDescriptor[] getScopedElements(final String[] typenames) { 965 ArrayList<IReferenceTypeDescriptor> types = new ArrayList<>(typenames.length); 966 for (String typename : typenames) { 967 if (typename == null) { 968 continue; 969 } 970 types.add(Util.getType(typename)); 971 } 972 return types.toArray(new IReferenceTypeDescriptor[types.size()]); 973 } 974 975 @Override getProblems()976 public IApiProblem[] getProblems() { 977 if (fProblems == null) { 978 return new IApiProblem[0]; 979 } 980 return fProblems.toArray(new IApiProblem[fProblems.size()]); 981 } 982 983 @Override dispose()984 public void dispose() { 985 if (fProblems != null) { 986 fProblems.clear(); 987 fProblems = null; 988 } 989 if (fPendingDeltaInfos != null) { 990 fPendingDeltaInfos.clear(); 991 fPendingDeltaInfos = null; 992 } 993 if (fBuildState != null) { 994 fBuildState = null; 995 } 996 } 997 998 /** 999 * @return if the API usage scan should be ignored 1000 */ ignoreApiUsageScan()1001 private boolean ignoreApiUsageScan() { 1002 if (fJavaProject == null) { 1003 // do the API use scan for binary bundles in non-OSGi mode 1004 return false; 1005 } 1006 IProject project = fJavaProject.getProject(); 1007 boolean ignore = true; 1008 ApiPlugin plugin = ApiPlugin.getDefault(); 1009 ignore &= plugin.getSeverityLevel(IApiProblemTypes.ILLEGAL_EXTEND, project) == ApiPlugin.SEVERITY_IGNORE; 1010 ignore &= plugin.getSeverityLevel(IApiProblemTypes.ILLEGAL_IMPLEMENT, project) == ApiPlugin.SEVERITY_IGNORE; 1011 ignore &= plugin.getSeverityLevel(IApiProblemTypes.ILLEGAL_INSTANTIATE, project) == ApiPlugin.SEVERITY_IGNORE; 1012 ignore &= plugin.getSeverityLevel(IApiProblemTypes.ILLEGAL_REFERENCE, project) == ApiPlugin.SEVERITY_IGNORE; 1013 ignore &= plugin.getSeverityLevel(IApiProblemTypes.ILLEGAL_OVERRIDE, project) == ApiPlugin.SEVERITY_IGNORE; 1014 ignore &= plugin.getSeverityLevel(IApiProblemTypes.LEAK_EXTEND, project) == ApiPlugin.SEVERITY_IGNORE; 1015 ignore &= plugin.getSeverityLevel(IApiProblemTypes.LEAK_FIELD_DECL, project) == ApiPlugin.SEVERITY_IGNORE; 1016 ignore &= plugin.getSeverityLevel(IApiProblemTypes.LEAK_IMPLEMENT, project) == ApiPlugin.SEVERITY_IGNORE; 1017 ignore &= plugin.getSeverityLevel(IApiProblemTypes.LEAK_METHOD_PARAM, project) == ApiPlugin.SEVERITY_IGNORE; 1018 ignore &= plugin.getSeverityLevel(IApiProblemTypes.LEAK_METHOD_RETURN_TYPE, project) == ApiPlugin.SEVERITY_IGNORE; 1019 ignore &= plugin.getSeverityLevel(IApiProblemTypes.INVALID_REFERENCE_IN_SYSTEM_LIBRARIES, project) == ApiPlugin.SEVERITY_IGNORE; 1020 return ignore; 1021 } 1022 1023 /** 1024 * @return if the API usage scan should be ignored 1025 */ reportApiBreakageWhenMajorVersionIncremented()1026 private boolean reportApiBreakageWhenMajorVersionIncremented() { 1027 if (fJavaProject == null) { 1028 // we ignore it for non-OSGi case 1029 return false; 1030 } 1031 return ApiPlugin.getDefault().getEnableState(IApiProblemTypes.REPORT_API_BREAKAGE_WHEN_MAJOR_VERSION_INCREMENTED, fJavaProject.getProject().getProject()); 1032 } 1033 1034 /** 1035 * @return if the default API baseline check should be ignored or not 1036 */ ignoreDefaultBaselineCheck()1037 private boolean ignoreDefaultBaselineCheck() { 1038 if (fJavaProject == null) { 1039 return true; 1040 } 1041 return ApiPlugin.getDefault().getSeverityLevel(IApiProblemTypes.MISSING_DEFAULT_API_BASELINE, fJavaProject.getProject().getProject()) == ApiPlugin.SEVERITY_IGNORE; 1042 } 1043 1044 /** 1045 * Whether to ignore since tag checks. If <code>null</code> is passed in we 1046 * are asking if all since tag checks should be ignored, if a pref is 1047 * specified we only want to know if that kind should be ignored 1048 * 1049 * @param pref 1050 * @return 1051 */ ignoreSinceTagCheck(String pref)1052 private boolean ignoreSinceTagCheck(String pref) { 1053 if (fJavaProject == null) { 1054 return true; 1055 } 1056 IProject project = fJavaProject.getProject(); 1057 ApiPlugin plugin = ApiPlugin.getDefault(); 1058 if (pref == null) { 1059 boolean ignore = plugin.getSeverityLevel(IApiProblemTypes.MALFORMED_SINCE_TAG, project) == ApiPlugin.SEVERITY_IGNORE; 1060 ignore &= plugin.getSeverityLevel(IApiProblemTypes.INVALID_SINCE_TAG_VERSION, project) == ApiPlugin.SEVERITY_IGNORE; 1061 ignore &= plugin.getSeverityLevel(IApiProblemTypes.MISSING_SINCE_TAG, project) == ApiPlugin.SEVERITY_IGNORE; 1062 return ignore; 1063 } else { 1064 return plugin.getSeverityLevel(pref, project) == ApiPlugin.SEVERITY_IGNORE; 1065 } 1066 } 1067 1068 /** 1069 * @return if the component version checks should be ignored or not 1070 */ ignoreComponentVersionCheck()1071 private boolean ignoreComponentVersionCheck() { 1072 if (fJavaProject == null) { 1073 // still do version checks for non-OSGi case 1074 return false; 1075 } 1076 return ApiPlugin.getDefault().getSeverityLevel(IApiProblemTypes.INCOMPATIBLE_API_COMPONENT_VERSION, fJavaProject.getProject().getProject()) == ApiPlugin.SEVERITY_IGNORE; 1077 } 1078 reportUnnecessaryMinorMicroVersionCheck()1079 private boolean reportUnnecessaryMinorMicroVersionCheck() { 1080 if (fJavaProject == null) { 1081 // we ignore it for non-OSGi case 1082 return true; 1083 } 1084 return !(ApiPlugin.getDefault().getSeverityLevel(IApiProblemTypes.INCOMPATIBLE_API_COMPONENT_VERSION_REPORT_MINOR_WITHOUT_API_CHANGE, fJavaProject.getProject().getProject()) == ApiPlugin.SEVERITY_IGNORE); 1085 } 1086 reportMajorVersionCheckWithoutBreakingChange()1087 private boolean reportMajorVersionCheckWithoutBreakingChange() { 1088 if (fJavaProject == null) { 1089 // we ignore it for non-OSGi case 1090 return true; 1091 } 1092 return !(ApiPlugin.getDefault().getSeverityLevel(IApiProblemTypes.INCOMPATIBLE_API_COMPONENT_VERSION_REPORT_MAJOR_WITHOUT_BREAKING_CHANGE, fJavaProject.getProject().getProject()) == ApiPlugin.SEVERITY_IGNORE); 1093 } 1094 1095 /** 1096 * @return if the invalid tag check should be ignored 1097 */ ignoreInvalidTagCheck()1098 private boolean ignoreInvalidTagCheck() { 1099 if (fJavaProject == null) { 1100 return true; 1101 } 1102 return ApiPlugin.getDefault().getSeverityLevel(IApiProblemTypes.INVALID_JAVADOC_TAG, fJavaProject.getProject()) == ApiPlugin.SEVERITY_IGNORE; 1103 } 1104 1105 /** 1106 * @return if the invalid annotation check should be ignored 1107 * 1108 * @since 1.0.600 1109 */ ignoreInvalidAnnotationCheck()1110 private boolean ignoreInvalidAnnotationCheck() { 1111 if (fJavaProject == null) { 1112 return true; 1113 } 1114 return ApiPlugin.getDefault().getSeverityLevel(IApiProblemTypes.INVALID_ANNOTATION, fJavaProject.getProject()) == ApiPlugin.SEVERITY_IGNORE; 1115 } 1116 1117 /** 1118 * @return if the unused problem filter check should be ignored or not 1119 */ ignoreUnusedProblemFilterCheck()1120 private boolean ignoreUnusedProblemFilterCheck() { 1121 if (fJavaProject == null) { 1122 return true; 1123 } 1124 return ApiPlugin.getDefault().getSeverityLevel(IApiProblemTypes.UNUSED_PROBLEM_FILTERS, fJavaProject.getProject()) == ApiPlugin.SEVERITY_IGNORE; 1125 } 1126 1127 /** 1128 * Checks the validation of tags for the given {@link IApiComponent} 1129 * 1130 * @param context 1131 * @param component 1132 * @param monitor 1133 */ checkTagValidation(final IBuildContext context, final IApiComponent component, IProgressMonitor monitor)1134 private void checkTagValidation(final IBuildContext context, final IApiComponent component, IProgressMonitor monitor) { 1135 boolean tags = ignoreInvalidTagCheck(); 1136 boolean annotations = ignoreInvalidAnnotationCheck(); 1137 if (tags && annotations) { 1138 return; 1139 } 1140 SubMonitor localMonitor = SubMonitor.convert(monitor, BuilderMessages.BaseApiAnalyzer_validating_javadoc_tags, 1); 1141 if (context.hasTypes()) { 1142 String[] typenames = context.getStructurallyChangedTypes(); 1143 localMonitor.setWorkRemaining(typenames.length); 1144 for (String typename : typenames) { 1145 if (typename == null) { 1146 continue; 1147 } 1148 localMonitor.subTask(NLS.bind(BuilderMessages.BaseApiAnalyzer_scanning_0, typename)); 1149 processType(typename, !tags, !annotations); 1150 localMonitor.split(1); 1151 } 1152 } else { 1153 try { 1154 IPackageFragmentRoot[] roots = fJavaProject.getPackageFragmentRoots(); 1155 1156 localMonitor.setWorkRemaining(roots.length); 1157 for (IPackageFragmentRoot root : roots) { 1158 if (root.getKind() == IPackageFragmentRoot.K_SOURCE) { 1159 localMonitor.subTask(NLS.bind(BuilderMessages.BaseApiAnalyzer_scanning_0, root.getPath().toOSString())); 1160 scanSource(root, !tags, !annotations, localMonitor.split(1)); 1161 } 1162 } 1163 } catch (JavaModelException jme) { 1164 ApiPlugin.log(jme); 1165 } 1166 } 1167 } 1168 1169 /** 1170 * Recursively finds all source in the given project and scans it for 1171 * invalid tags 1172 * 1173 * @param element 1174 * @param monitor 1175 * @throws JavaModelException 1176 */ scanSource(IJavaElement element, boolean tags, boolean annotations, IProgressMonitor monitor)1177 private void scanSource(IJavaElement element, boolean tags, boolean annotations, IProgressMonitor monitor) throws JavaModelException { 1178 SubMonitor subMonitor = SubMonitor.convert(monitor); 1179 switch (element.getElementType()) { 1180 case IJavaElement.PACKAGE_FRAGMENT_ROOT: 1181 case IJavaElement.PACKAGE_FRAGMENT: { 1182 IParent parent = (IParent) element; 1183 IJavaElement[] children = parent.getChildren(); 1184 subMonitor.setWorkRemaining(children.length); 1185 for (IJavaElement javaElement : children) { 1186 scanSource(javaElement, tags, annotations, subMonitor.split(1)); 1187 } 1188 break; 1189 } 1190 case IJavaElement.COMPILATION_UNIT: { 1191 ICompilationUnit unit = (ICompilationUnit) element; 1192 processType(unit, tags, annotations); 1193 break; 1194 } 1195 default: 1196 break; 1197 } 1198 } 1199 1200 /** 1201 * Processes the given type name for invalid Javadoc tags 1202 * 1203 * @param typename 1204 */ processType(String typename, boolean tags, boolean annotations)1205 private void processType(String typename, boolean tags, boolean annotations) { 1206 try { 1207 IType type = fJavaProject.findType(typename); 1208 IType typeInProject = Util.getTypeInSameJavaProject(type, typename, fJavaProject); 1209 if (typeInProject != null) { 1210 type = typeInProject; 1211 } 1212 if (type != null && !type.isMember()) { 1213 // member types are processed while processing the compilation 1214 // unit 1215 ICompilationUnit cunit = type.getCompilationUnit(); 1216 if (cunit != null) { 1217 processType(cunit, tags, annotations); 1218 } 1219 } 1220 } catch (JavaModelException e) { 1221 ApiPlugin.log(e); 1222 } 1223 } 1224 1225 /** 1226 * Processes the given {@link ICompilationUnit} for invalid tags 1227 * 1228 * @param cunit 1229 */ processType(ICompilationUnit cunit, boolean tags, boolean annotations)1230 private void processType(ICompilationUnit cunit, boolean tags, boolean annotations) { 1231 CompilationUnit comp = createAST(cunit, 0); 1232 if (comp == null) { 1233 return; 1234 } 1235 TagValidator tv = new TagValidator(cunit, tags, annotations); 1236 comp.accept(tv); 1237 IApiProblem[] tagProblems = tv.getProblems(); 1238 for (IApiProblem tagProblem : tagProblems) { 1239 addProblem(tagProblem); 1240 } 1241 } 1242 1243 /** 1244 * Checks for illegal API usage in the specified component, creating problem 1245 * markers as required. 1246 * 1247 * @param context the current build context 1248 * @param component component being built 1249 * @param monitor progress monitor 1250 */ checkApiUsage(final IBuildContext context, final IApiComponent component, IProgressMonitor monitor)1251 private void checkApiUsage(final IBuildContext context, final IApiComponent component, IProgressMonitor monitor) { 1252 if (ignoreApiUsageScan()) { 1253 if (ApiPlugin.DEBUG_API_ANALYZER) { 1254 System.out.println("Ignoring API usage scan"); //$NON-NLS-1$ 1255 } 1256 return; 1257 } 1258 IApiTypeContainer scope = null; 1259 if (context.hasTypes()) { 1260 String[] typenames = getApiUseTypes(context); 1261 if (typenames.length < 1) { 1262 return; 1263 } 1264 scope = getSearchScope(component, typenames); 1265 } else { 1266 scope = getSearchScope(component, null); // entire component 1267 } 1268 SubMonitor localMonitor = SubMonitor.convert(monitor, MessageFormat.format(BuilderMessages.checking_api_usage, component.getSymbolicName()), 2); 1269 ReferenceAnalyzer analyzer = new ReferenceAnalyzer(); 1270 try { 1271 long start = System.currentTimeMillis(); 1272 IApiProblem[] illegal = analyzer.analyze(component, scope, localMonitor.split(2)); 1273 long end = System.currentTimeMillis(); 1274 if (ApiPlugin.DEBUG_API_ANALYZER) { 1275 System.out.println("API usage scan: " + (end - start) + " ms\t" + illegal.length + " problems"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ 1276 } 1277 for (IApiProblem element : illegal) { 1278 addProblem(element); 1279 } 1280 } catch (CoreException ce) { 1281 if (ApiPlugin.DEBUG_API_ANALYZER) { 1282 ApiPlugin.log(ce); 1283 } 1284 } 1285 } 1286 1287 /** 1288 * Returns the collection of type names to be built 1289 * 1290 * @param context 1291 * @return the complete listing of type names to build or an empty array, 1292 * never <code>null</code> 1293 * @since 1.1 1294 */ getApiUseTypes(IBuildContext context)1295 String[] getApiUseTypes(IBuildContext context) { 1296 if (context.hasTypes()) { 1297 String[] deptypes = null; 1298 int size = 0; 1299 if (context.hasDescriptionDependents()) { 1300 // only check dependents if there were description changes 1301 deptypes = context.getDescriptionDependentTypes(); 1302 size += deptypes.length; 1303 } 1304 String[] structtypes = context.getStructurallyChangedTypes(); 1305 HashSet<String> typenames = new HashSet<>(size + structtypes.length); 1306 if (deptypes != null) { 1307 for (String deptype : deptypes) { 1308 if (deptype == null) { 1309 continue; 1310 } 1311 typenames.add(deptype); 1312 } 1313 } 1314 for (String structtype : structtypes) { 1315 if (structtype == null) { 1316 continue; 1317 } 1318 typenames.add(structtype); 1319 } 1320 return typenames.toArray(new String[typenames.size()]); 1321 } 1322 return NO_TYPES; 1323 } 1324 1325 /** 1326 * Compares the given type between the two API components 1327 * 1328 * @param typeName the type to check in each component 1329 * @param reference 1330 * @param component 1331 * @param monitor 1332 */ checkCompatibility(final String typeName, final IApiComponent reference, final IApiComponent component, IProgressMonitor monitor)1333 private void checkCompatibility(final String typeName, final IApiComponent reference, final IApiComponent component, IProgressMonitor monitor) throws CoreException { 1334 String id = component.getSymbolicName(); 1335 if (ApiPlugin.DEBUG_API_ANALYZER) { 1336 System.out.println("comparing components [" + reference.getSymbolicName() + "] and [" + id + "] for type [" + typeName + "]"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ 1337 } 1338 IApiTypeRoot classFile = null; 1339 try { 1340 if (Util.ORG_ECLIPSE_SWT.equals(id)) { 1341 classFile = component.findTypeRoot(typeName); 1342 } else { 1343 classFile = component.findTypeRoot(typeName, id); 1344 } 1345 } catch (CoreException e) { 1346 ApiPlugin.log(e); 1347 } 1348 SubMonitor subMonitor = SubMonitor.convert(monitor, BuilderMessages.BaseApiAnalyzer_checking_compat, 4); 1349 IDelta delta = null; 1350 IApiComponent provider = null; 1351 boolean reexported = false; 1352 if (classFile == null) { 1353 String packageName = Signatures.getPackageName(typeName); 1354 // check if the type is provided by a required component (it 1355 // could have been moved/re-exported) 1356 IApiComponent[] providers = component.getBaseline().resolvePackage(component, packageName); 1357 int index = 0; 1358 while (classFile == null && index < providers.length) { 1359 IApiComponent p = providers[index]; 1360 if (!p.equals(component)) { 1361 String id2 = p.getSymbolicName(); 1362 if (Util.ORG_ECLIPSE_SWT.equals(id2)) { 1363 classFile = p.findTypeRoot(typeName); 1364 } else { 1365 classFile = p.findTypeRoot(typeName, id2); 1366 } 1367 if (classFile != null) { 1368 IRequiredComponentDescription[] components = component.getRequiredComponents(); 1369 for (IRequiredComponentDescription description : components) { 1370 if (description.getId().equals(p.getSymbolicName()) && description.isExported()) { 1371 reexported = true; 1372 break; 1373 } 1374 } 1375 provider = p; 1376 } 1377 } 1378 index++; 1379 } 1380 } else { 1381 provider = component; 1382 } 1383 subMonitor.split(1); 1384 if (classFile == null) { 1385 // this indicates a removed type 1386 // we should try to get the class file from the reference 1387 IApiTypeRoot referenceClassFile = null; 1388 try { 1389 referenceClassFile = reference.findTypeRoot(typeName); 1390 } catch (CoreException e) { 1391 ApiPlugin.log(e); 1392 } 1393 if (referenceClassFile != null) { 1394 try { 1395 IApiType type = referenceClassFile.getStructure(); 1396 if (type == null) { 1397 return; 1398 } 1399 final IApiDescription referenceApiDescription = reference.getApiDescription(); 1400 IApiAnnotations elementDescription = referenceApiDescription.resolveAnnotations(type.getHandle()); 1401 int restrictions = RestrictionModifiers.NO_RESTRICTIONS; 1402 if (!type.isMemberType() && !type.isAnonymous() && !type.isLocal()) { 1403 int visibility = VisibilityModifiers.ALL_VISIBILITIES; 1404 // we skip nested types (member, local and 1405 // anonymous) 1406 if (elementDescription != null) { 1407 restrictions = elementDescription.getRestrictions(); 1408 visibility = elementDescription.getVisibility(); 1409 } 1410 // if the visibility is API, we only consider public 1411 // and protected types 1412 if (Util.isDefault(type.getModifiers()) || Flags.isPrivate(type.getModifiers())) { 1413 return; 1414 } 1415 if (VisibilityModifiers.isAPI(visibility)) { 1416 String deltaComponentID = Util.getDeltaComponentVersionsId(reference); 1417 delta = new Delta(deltaComponentID, IDelta.API_COMPONENT_ELEMENT_TYPE, IDelta.REMOVED, IDelta.TYPE, restrictions, RestrictionModifiers.NO_RESTRICTIONS, type.getModifiers(), 0, typeName, typeName, new String[] { 1418 typeName, 1419 Util.getComponentVersionsId(reference) }); 1420 } 1421 } 1422 } catch (CoreException e) { 1423 ApiPlugin.log(e); 1424 } 1425 } 1426 subMonitor.split(1); 1427 } else { 1428 fBuildState.cleanup(typeName); 1429 long time = System.currentTimeMillis(); 1430 try { 1431 IApiComponent exporter = null; 1432 if (reexported) { 1433 exporter = component; 1434 } 1435 delta = ApiComparator.compare(classFile, reference, provider, exporter, reference.getBaseline(), provider.getBaseline(), VisibilityModifiers.API, subMonitor.split(1)); 1436 } catch (OperationCanceledException oce) { 1437 // do nothing, but don't forward it 1438 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=304315 1439 if (ApiPlugin.DEBUG_API_ANALYZER) { 1440 System.out.println("Trapped OperationCanceledException"); //$NON-NLS-1$ 1441 } 1442 } catch (Exception e) { 1443 ApiPlugin.log(e); 1444 } finally { 1445 if (ApiPlugin.DEBUG_API_ANALYZER) { 1446 System.out.println("Time spent for " + typeName + " : " + (System.currentTimeMillis() - time) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ 1447 } 1448 fPendingDeltaInfos.clear(); 1449 } 1450 } 1451 if (delta == null) { 1452 return; 1453 } 1454 if (delta != ApiComparator.NO_DELTA) { 1455 List<IDelta> allDeltas = Util.collectAllDeltas(delta); 1456 subMonitor.subTask(BuilderMessages.BaseApiAnalyzer_processing_deltas); 1457 SubMonitor deltaLoopMonitor = subMonitor.split(1).setWorkRemaining(allDeltas.size()); 1458 for (IDelta d : allDeltas) { 1459 deltaLoopMonitor.split(1); 1460 processDelta(d, reference, component); 1461 } 1462 if (!fPendingDeltaInfos.isEmpty()) { 1463 SubMonitor checkLoopMonitor = subMonitor.split(1).setWorkRemaining(fPendingDeltaInfos.size()); 1464 subMonitor.subTask(BuilderMessages.BaseApiAnalyzer_checking_since_tags); 1465 for (IDelta d : fPendingDeltaInfos) { 1466 checkLoopMonitor.split(1); 1467 checkSinceTags((Delta) d, component); 1468 } 1469 } 1470 } 1471 } 1472 1473 /** 1474 * Compares the two given components and generates an {@link IDelta} 1475 * 1476 * @param jproject 1477 * @param reference 1478 * @param component 1479 * @param monitor 1480 */ checkCompatibility(final IApiComponent reference, final IApiComponent component, IProgressMonitor monitor)1481 private void checkCompatibility(final IApiComponent reference, final IApiComponent component, IProgressMonitor monitor) { 1482 long time = System.currentTimeMillis(); 1483 SubMonitor localmonitor = SubMonitor.convert(monitor, BuilderMessages.BaseApiAnalyzer_checking_compat, 3); 1484 IDelta delta = null; 1485 if (reference == null) { 1486 delta = new Delta(null, IDelta.API_BASELINE_ELEMENT_TYPE, IDelta.ADDED, IDelta.API_COMPONENT, null, component.getSymbolicName(), component.getSymbolicName()); 1487 localmonitor.split(1); 1488 } else { 1489 try { 1490 delta = ApiComparator.compare(reference, component, VisibilityModifiers.API, localmonitor.split(1)); 1491 } finally { 1492 if (ApiPlugin.DEBUG_API_ANALYZER) { 1493 System.out.println("Time spent for " + component.getSymbolicName() + " : " + (System.currentTimeMillis() - time) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ 1494 } 1495 fPendingDeltaInfos.clear(); 1496 } 1497 } 1498 if (delta == null) { 1499 return; 1500 } 1501 if (delta != ApiComparator.NO_DELTA) { 1502 List<IDelta> allDeltas = Util.collectAllDeltas(delta); 1503 if (allDeltas.size() != 0) { 1504 localmonitor.subTask(BuilderMessages.BaseApiAnalyzer_processing_deltas); 1505 SubMonitor processLoopMonitor = localmonitor.split(1).setWorkRemaining(allDeltas.size()); 1506 for (IDelta d : allDeltas) { 1507 processLoopMonitor.split(1); 1508 processDelta(d, reference, component); 1509 } 1510 localmonitor.subTask(BuilderMessages.BaseApiAnalyzer_checking_since_tags); 1511 SubMonitor checkLoopMonitor = localmonitor.split(1).setWorkRemaining(fPendingDeltaInfos.size()); 1512 if (!fPendingDeltaInfos.isEmpty()) { 1513 for (IDelta d : fPendingDeltaInfos) { 1514 checkLoopMonitor.split(1); 1515 checkSinceTags((Delta) d, component); 1516 } 1517 } 1518 } 1519 } 1520 } 1521 ignoreExecutionEnvChanges()1522 private boolean ignoreExecutionEnvChanges() { 1523 if (fJavaProject == null) { 1524 return true; 1525 } 1526 IProject project = fJavaProject.getProject(); 1527 ApiPlugin plugin = ApiPlugin.getDefault(); 1528 boolean ignore = plugin.getSeverityLevel(IApiProblemTypes.CHANGED_EXECUTION_ENV, project) == ApiPlugin.SEVERITY_IGNORE; 1529 return ignore; 1530 1531 } 1532 1533 /** 1534 * Compares the two given components, checks for Execution Env changes and 1535 * generates true if minor version has to be increased 1536 * 1537 * @param reference 1538 * @param component 1539 */ 1540 shouldVersionChangeForExecutionEnvChanges(IApiComponent reference, IApiComponent component)1541 private boolean shouldVersionChangeForExecutionEnvChanges(IApiComponent reference, IApiComponent component) { 1542 1543 if (ignoreExecutionEnvChanges()) { 1544 return false; 1545 } 1546 1547 String refversionval = reference.getVersion(); 1548 String compversionval = component.getVersion(); 1549 Version refversion = new Version(refversionval); 1550 Version compversion = new Version(compversionval); 1551 1552 // prerequisite for minor version change for execution env changes 1553 if (compversion.getMajor() > refversion.getMajor()) { 1554 return false; 1555 } 1556 if ((compversion.getMajor() == refversion.getMajor()) && (compversion.getMinor() > refversion.getMinor())) { 1557 return false; 1558 } 1559 return hasExecutionEnvironmentChanged(reference, component); 1560 } 1561 hasExecutionEnvironmentChanged(IApiComponent reference, IApiComponent component)1562 private boolean hasExecutionEnvironmentChanged(IApiComponent reference, IApiComponent component) { 1563 String[] refExecutionEnv = null; 1564 String[] compExecutionEnv = null; 1565 try { 1566 refExecutionEnv = reference.getExecutionEnvironments(); 1567 } catch (CoreException e) { 1568 ApiPlugin.log(e); 1569 } 1570 try { 1571 compExecutionEnv = component.getExecutionEnvironments(); 1572 } catch (CoreException e) { 1573 ApiPlugin.log(e); 1574 } 1575 if (refExecutionEnv == null && compExecutionEnv != null) { 1576 return true; 1577 } 1578 if (refExecutionEnv != null && compExecutionEnv == null) { 1579 return true; 1580 } 1581 // any change in BREE list would generate a minor version increase 1582 if (refExecutionEnv != null && compExecutionEnv != null) { 1583 List<String> refExecutionEnvList = new ArrayList<>(Arrays.asList(refExecutionEnv)); 1584 List<String> compExecutionEnvList = new ArrayList<>(Arrays.asList(compExecutionEnv)); 1585 if (!(refExecutionEnvList.containsAll(compExecutionEnvList) && compExecutionEnvList.containsAll(refExecutionEnvList))) { 1586 return true; 1587 } 1588 } 1589 return false; 1590 } 1591 1592 /** 1593 * Processes delta to determine if it needs an @since tag. If it does and 1594 * one is not present or the version of the tag is incorrect, a marker is 1595 * created 1596 * 1597 * @param jproject 1598 * @param delta 1599 * @param component 1600 */ checkSinceTags(final Delta delta, final IApiComponent component)1601 private void checkSinceTags(final Delta delta, final IApiComponent component) { 1602 if (ignoreSinceTagCheck(null)) { 1603 return; 1604 } 1605 IMember member = Util.getIMember(delta, fJavaProject); 1606 if (member == null || member.isBinary()) { 1607 return; 1608 } 1609 ICompilationUnit cunit = member.getCompilationUnit(); 1610 if (cunit == null) { 1611 return; 1612 } 1613 try { 1614 if (!cunit.isConsistent()) { 1615 cunit.makeConsistent(null); 1616 } 1617 } catch (JavaModelException e) { 1618 e.printStackTrace(); 1619 } 1620 IApiProblem problem = null; 1621 ISourceRange nameRange = null; 1622 try { 1623 nameRange = member.getNameRange(); 1624 } catch (JavaModelException e) { 1625 ApiPlugin.log(e); 1626 return; 1627 } 1628 if (nameRange == null) { 1629 return; 1630 } 1631 try { 1632 int offset = nameRange.getOffset(); 1633 CompilationUnit comp = createAST(cunit, offset); 1634 if (comp == null) { 1635 return; 1636 } 1637 SinceTagChecker visitor = new SinceTagChecker(offset); 1638 comp.accept(visitor); 1639 // we must retrieve the component version from the delta component 1640 // id 1641 String componentVersionId = delta.getComponentVersionId(); 1642 String componentVersionString = null; 1643 if (componentVersionId == null) { 1644 componentVersionString = component.getVersion(); 1645 } else { 1646 componentVersionString = extractVersion(componentVersionId); 1647 } 1648 try { 1649 if (visitor.hasNoComment() || visitor.isMissing()) { 1650 if (ignoreSinceTagCheck(IApiProblemTypes.MISSING_SINCE_TAG)) { 1651 if (ApiPlugin.DEBUG_API_ANALYZER) { 1652 System.out.println("Ignoring missing since tag problem"); //$NON-NLS-1$ 1653 } 1654 return; 1655 } 1656 StringBuilder buffer = new StringBuilder(); 1657 Version componentVersion = new Version(componentVersionString); 1658 buffer.append(componentVersion.getMajor()).append('.').append(componentVersion.getMinor()); 1659 problem = createSinceTagProblem(IApiProblem.SINCE_TAG_MISSING, new String[] { Util.getDeltaArgumentString(delta) }, delta, member, String.valueOf(buffer)); 1660 } else if (visitor.hasJavadocComment()) { 1661 // we don't want to flag block comment 1662 String sinceVersion = visitor.getSinceVersion(); 1663 if (sinceVersion != null) { 1664 SinceTagVersion tagVersion = new SinceTagVersion(sinceVersion); 1665 String postfixString = tagVersion.postfixString(); 1666 if (tagVersion.getVersion() == null || Util.getFragmentNumber(tagVersion.getVersionString()) > 2) { 1667 if (ignoreSinceTagCheck(IApiProblemTypes.MALFORMED_SINCE_TAG)) { 1668 if (ApiPlugin.DEBUG_API_ANALYZER) { 1669 System.out.println("Ignoring malformed since tag problem"); //$NON-NLS-1$ 1670 } 1671 return; 1672 } 1673 StringBuilder buffer = new StringBuilder(); 1674 if (tagVersion.prefixString() != null) { 1675 buffer.append(tagVersion.prefixString()); 1676 } 1677 Version componentVersion = new Version(componentVersionString); 1678 buffer.append(componentVersion.getMajor()).append('.').append(componentVersion.getMinor()); 1679 if (postfixString != null) { 1680 buffer.append(postfixString); 1681 } 1682 problem = createSinceTagProblem(IApiProblem.SINCE_TAG_MALFORMED, new String[] { 1683 sinceVersion, 1684 Util.getDeltaArgumentString(delta) }, delta, member, String.valueOf(buffer)); 1685 } else { 1686 if (ignoreSinceTagCheck(IApiProblemTypes.INVALID_SINCE_TAG_VERSION)) { 1687 if (ApiPlugin.DEBUG_API_ANALYZER) { 1688 System.out.println("Ignoring invalid tag version problem"); //$NON-NLS-1$ 1689 } 1690 return; 1691 } 1692 StringBuilder accurateVersionBuffer = new StringBuilder(); 1693 Version componentVersion = new Version(componentVersionString); 1694 accurateVersionBuffer.append(componentVersion.getMajor()).append('.').append(componentVersion.getMinor()); 1695 String accurateVersion = String.valueOf(accurateVersionBuffer); 1696 if (Util.isDifferentVersion(sinceVersion, accurateVersion)) { 1697 // report invalid version number 1698 StringBuilder buffer = new StringBuilder(); 1699 if (tagVersion.prefixString() != null) { 1700 buffer.append(tagVersion.prefixString()); 1701 } 1702 Version version = new Version(accurateVersion); 1703 buffer.append(version.getMajor()).append('.').append(version.getMinor()); 1704 if (postfixString != null) { 1705 buffer.append(postfixString); 1706 } 1707 String accurateSinceTagValue = String.valueOf(buffer); 1708 problem = createSinceTagProblem(IApiProblem.SINCE_TAG_INVALID, new String[] { 1709 sinceVersion, accurateSinceTagValue, 1710 Util.getDeltaArgumentString(delta) }, delta, member, accurateSinceTagValue); 1711 } 1712 } 1713 } 1714 } 1715 } catch (IllegalArgumentException e) { 1716 ApiPlugin.log(e); 1717 } 1718 } catch (RuntimeException e) { 1719 ApiPlugin.log(e); 1720 } 1721 if (problem != null) { 1722 addProblem(problem); 1723 } 1724 } 1725 extractVersion(String componentVersionId)1726 private String extractVersion(String componentVersionId) { 1727 // extract the version from the delta component id. It is located 1728 // between parenthesis 1729 int indexOfOpen = componentVersionId.lastIndexOf('('); 1730 return componentVersionId.substring(indexOfOpen + 1, componentVersionId.length() - 1); 1731 } 1732 1733 /** 1734 * Creates a marker to denote a problem with the since tag (existence or 1735 * correctness) for a member and returns it, or <code>null</code> 1736 * 1737 * @param kind 1738 * @param messageargs 1739 * @param compilationUnit 1740 * @param member 1741 * @param version 1742 * @return a new {@link IApiProblem} or <code>null</code> 1743 */ createSinceTagProblem(int kind, final String[] messageargs, final Delta info, final IMember member, final String version)1744 private IApiProblem createSinceTagProblem(int kind, final String[] messageargs, final Delta info, final IMember member, final String version) { 1745 try { 1746 // create a marker on the member for missing @since tag 1747 IType declaringType = null; 1748 if (member.getElementType() == IJavaElement.TYPE) { 1749 declaringType = (IType) member; 1750 } else { 1751 declaringType = member.getDeclaringType(); 1752 } 1753 IResource resource = Util.getResource(this.fJavaProject.getProject(), declaringType); 1754 if (resource == null) { 1755 return null; 1756 } 1757 int lineNumber = 1; 1758 int charStart = 0; 1759 int charEnd = 1; 1760 String qtn = null; 1761 if (member instanceof IType) { 1762 qtn = ((IType) member).getFullyQualifiedName(); 1763 } else { 1764 qtn = declaringType.getFullyQualifiedName(); 1765 } 1766 String[] messageArguments = null; 1767 if (!Util.isManifest(resource.getProjectRelativePath())) { 1768 messageArguments = messageargs; 1769 ICompilationUnit unit = member.getCompilationUnit(); 1770 ISourceRange range = member.getNameRange(); 1771 charStart = range.getOffset(); 1772 charEnd = charStart + range.getLength(); 1773 try { 1774 // unit cannot be null 1775 IDocument document = Util.getDocument(unit); 1776 lineNumber = document.getLineOfOffset(charStart); 1777 } catch (BadLocationException e) { 1778 ApiPlugin.log(e); 1779 } 1780 } else { 1781 // update the last entry in the message arguments 1782 if (!(member instanceof IType)) { 1783 // insert the declaring type 1784 int length = messageargs.length; 1785 messageArguments = new String[length]; 1786 System.arraycopy(messageargs, 0, messageArguments, 0, length); 1787 StringBuilder buffer = new StringBuilder(); 1788 buffer.append(qtn).append('.').append(messageargs[length - 1]); 1789 messageArguments[length - 1] = String.valueOf(buffer); 1790 } else { 1791 messageArguments = messageargs; 1792 } 1793 } 1794 return ApiProblemFactory.newApiSinceTagProblem(resource.getProjectRelativePath().toPortableString(), qtn, messageArguments, new String[] { 1795 IApiMarkerConstants.MARKER_ATTR_VERSION, 1796 IApiMarkerConstants.API_MARKER_ATTR_ID, 1797 IApiMarkerConstants.MARKER_ATTR_HANDLE_ID }, new Object[] { 1798 version, 1799 Integer.valueOf(IApiMarkerConstants.SINCE_TAG_MARKER_ID), 1800 member.getHandleIdentifier() }, lineNumber, charStart, charEnd, info.getElementType(), kind); 1801 } catch (CoreException e) { 1802 ApiPlugin.log(e); 1803 } 1804 return null; 1805 } 1806 1807 /** 1808 * Creates an {@link IApiProblem} for the given compatibility delta 1809 * 1810 * @param delta 1811 * @param jproject 1812 * @param reference 1813 * @param component 1814 * @return a new compatibility problem or <code>null</code> 1815 */ createCompatibilityProblem(final IDelta delta, final IApiComponent reference, final IApiComponent component)1816 private IApiProblem createCompatibilityProblem(final IDelta delta, final IApiComponent reference, final IApiComponent component) { 1817 try { 1818 Version referenceVersion = new Version(reference.getVersion()); 1819 Version componentVersion = new Version(component.getVersion()); 1820 if ((referenceVersion.getMajor() < componentVersion.getMajor()) && !reportApiBreakageWhenMajorVersionIncremented()) { 1821 // API breakage are ok in this case and we don't want them to be 1822 // reported 1823 fBuildState.addBreakingChange(delta); 1824 return null; 1825 } 1826 IResource resource = null; 1827 IType type = null; 1828 // retrieve line number, char start and char end 1829 int lineNumber = 0; 1830 int charStart = -1; 1831 int charEnd = 1; 1832 IMember member = null; 1833 if (fJavaProject != null) { 1834 try { 1835 type = fJavaProject.findType(delta.getTypeName().replace('$', '.')); 1836 IType typeInProject = Util.getTypeInSameJavaProject(type, delta.getTypeName(), fJavaProject); 1837 if (typeInProject != null) { 1838 type = typeInProject; 1839 } 1840 } catch (JavaModelException e) { 1841 ApiPlugin.log(e); 1842 } 1843 if (type instanceof BinaryType) { 1844 IType sourceType = Util.findSourceTypeinJavaProject(fJavaProject, delta.getTypeName().replace('$', '.')); 1845 if (sourceType != null) { 1846 type = sourceType; 1847 } 1848 } 1849 IProject project = fJavaProject.getProject(); 1850 resource = Util.getResource(project, type); 1851 IProject project2 = resource.getProject(); 1852 if (!project2.equals(project)) { 1853 // marker should be on the same project as fJavaProject 1854 int flag = delta.getFlags(); 1855 if (flag == IDelta.REEXPORTED_API_TYPE) { 1856 resource = project.findMember(new Path(JarFile.MANIFEST_NAME)); 1857 charStart = 0; 1858 charEnd = 0; 1859 lineNumber = 1; 1860 } 1861 } 1862 if (resource == null) { 1863 return null; 1864 } 1865 if (!Util.isManifest(resource.getProjectRelativePath())) { 1866 member = Util.getIMember(delta, fJavaProject); 1867 } 1868 if (member != null && !member.isBinary() && member.exists()) { 1869 ISourceRange range = member.getNameRange(); 1870 charStart = range.getOffset(); 1871 charEnd = charStart + range.getLength(); 1872 try { 1873 IDocument document = Util.getDocument(member.getCompilationUnit()); 1874 lineNumber = document.getLineOfOffset(charStart); 1875 } catch (BadLocationException e) { 1876 // ignore 1877 } 1878 } 1879 } 1880 String path = null; 1881 if (resource != null) { 1882 path = resource.getProjectRelativePath().toPortableString(); 1883 } 1884 IApiProblem apiProblem = ApiProblemFactory.newApiProblem(path, delta.getTypeName(), delta.getArguments(), new String[] { 1885 IApiMarkerConstants.MARKER_ATTR_HANDLE_ID, 1886 IApiMarkerConstants.API_MARKER_ATTR_ID }, new Object[] { 1887 member == null ? null : member.getHandleIdentifier(), 1888 Integer.valueOf(IApiMarkerConstants.COMPATIBILITY_MARKER_ID), }, lineNumber, charStart, charEnd, IApiProblem.CATEGORY_COMPATIBILITY, delta.getElementType(), delta.getKind(), delta.getFlags()); 1889 return apiProblem; 1890 1891 } catch (CoreException e) { 1892 ApiPlugin.log(e); 1893 } 1894 return null; 1895 } 1896 1897 /** 1898 * Creates an {@link IApiProblem} for the given API component 1899 * 1900 * @param component 1901 * @return a new API component resolution problem or <code>null</code> 1902 */ createApiComponentResolutionProblem(final IApiComponent component, final String message)1903 private void createApiComponentResolutionProblem(final IApiComponent component, final String message) { 1904 IApiProblem problem = ApiProblemFactory.newApiComponentResolutionProblem(Path.EMPTY.toString(), new String[] { 1905 component.getSymbolicName(), message }, new String[] { IApiMarkerConstants.API_MARKER_ATTR_ID }, new Object[] { Integer.valueOf(IApiMarkerConstants.API_COMPONENT_RESOLUTION_MARKER_ID) }, IElementDescriptor.RESOURCE, IApiProblem.API_COMPONENT_RESOLUTION); 1906 addProblem(problem); 1907 } 1908 1909 /** 1910 * Processes a delta to know if we need to check for since tag or version 1911 * numbering problems 1912 * 1913 * @param jproject 1914 * @param delta 1915 * @param reference 1916 * @param component 1917 */ processDelta(final IDelta delta, final IApiComponent reference, final IApiComponent component)1918 private void processDelta(final IDelta delta, final IApiComponent reference, final IApiComponent component) { 1919 int flags = delta.getFlags(); 1920 int kind = delta.getKind(); 1921 int modifiers = delta.getNewModifiers(); 1922 if (DeltaProcessor.isCompatible(delta)) { 1923 if (!RestrictionModifiers.isReferenceRestriction(delta.getCurrentRestrictions())) { 1924 if (Util.isVisible(modifiers)) { 1925 if (Flags.isProtected(modifiers)) { 1926 String typeName = delta.getTypeName(); 1927 if (typeName != null) { 1928 IApiTypeRoot typeRoot = null; 1929 IApiType type = null; 1930 try { 1931 String id = component.getSymbolicName(); 1932 if (Util.ORG_ECLIPSE_SWT.equals(id)) { 1933 typeRoot = component.findTypeRoot(typeName); 1934 } else { 1935 typeRoot = component.findTypeRoot(typeName, id); 1936 } 1937 if (typeRoot == null) { 1938 String packageName = Signatures.getPackageName(typeName); 1939 // check if the type is provided by a 1940 // required component (it could have been 1941 // moved/re-exported) 1942 IApiComponent[] providers = component.getBaseline().resolvePackage(component, packageName); 1943 int index = 0; 1944 while (typeRoot == null && index < providers.length) { 1945 IApiComponent p = providers[index]; 1946 if (!p.equals(component)) { 1947 String id2 = p.getSymbolicName(); 1948 if (Util.ORG_ECLIPSE_SWT.equals(id2)) { 1949 typeRoot = p.findTypeRoot(typeName); 1950 } else { 1951 typeRoot = p.findTypeRoot(typeName, id2); 1952 } 1953 } 1954 index++; 1955 } 1956 } 1957 if (typeRoot == null) { 1958 return; 1959 } 1960 type = typeRoot.getStructure(); 1961 } catch (CoreException e) { 1962 // ignore 1963 } 1964 if (type == null || Flags.isFinal(type.getModifiers())) { 1965 // no @since tag to report for new protected 1966 // methods inside a final class 1967 return; 1968 } 1969 } 1970 } 1971 // if protected, we only want to check @since tags if the 1972 // enclosing class can be sub-classed 1973 switch (kind) { 1974 case IDelta.ADDED: { 1975 // if public, we always want to check @since tags 1976 switch (flags) { 1977 case IDelta.TYPE_MEMBER: 1978 case IDelta.METHOD: 1979 case IDelta.DEFAULT_METHOD: 1980 case IDelta.CONSTRUCTOR: 1981 case IDelta.ENUM_CONSTANT: 1982 case IDelta.METHOD_WITH_DEFAULT_VALUE: 1983 case IDelta.METHOD_WITHOUT_DEFAULT_VALUE: 1984 case IDelta.FIELD: 1985 case IDelta.TYPE: { 1986 if (ApiPlugin.DEBUG_API_ANALYZER) { 1987 String deltaDetails = "Delta : " + Util.getDetail(delta); //$NON-NLS-1$ 1988 System.out.println(deltaDetails + " is compatible"); //$NON-NLS-1$ 1989 } 1990 this.fBuildState.addCompatibleChange(delta); 1991 fPendingDeltaInfos.add(delta); 1992 break; 1993 } 1994 case IDelta.SUPERCLASS: { 1995 // add to build state but dont add marker 1996 this.fBuildState.addCompatibleChange(delta); 1997 break; 1998 } 1999 2000 default: 2001 break; 2002 } 2003 break; 2004 } 2005 case IDelta.CHANGED: { 2006 if (flags == IDelta.EXPANDED_SUPERINTERFACES_SET) { 2007 // add to build state but dont add marker 2008 this.fBuildState.addCompatibleChange(delta); 2009 } 2010 if (flags == IDelta.INCREASE_ACCESS) { 2011 if (ApiPlugin.DEBUG_API_ANALYZER) { 2012 String deltaDetails = "Delta : " + Util.getDetail(delta); //$NON-NLS-1$ 2013 System.out.println(deltaDetails + " is compatible"); //$NON-NLS-1$ 2014 } 2015 this.fBuildState.addCompatibleChange(delta); 2016 fPendingDeltaInfos.add(delta); 2017 } 2018 break; 2019 } 2020 case IDelta.REMOVED:{ 2021 this.fBuildState.addCompatibleChange(delta); 2022 break; 2023 } 2024 default: 2025 break; 2026 } 2027 } 2028 } 2029 } else { 2030 switch (kind) { 2031 case IDelta.ADDED: { 2032 // if public, we always want to check @since tags 2033 switch (flags) { 2034 case IDelta.TYPE_MEMBER: 2035 case IDelta.METHOD: 2036 case IDelta.DEFAULT_METHOD: 2037 case IDelta.CONSTRUCTOR: 2038 case IDelta.ENUM_CONSTANT: 2039 case IDelta.METHOD_WITH_DEFAULT_VALUE: 2040 case IDelta.METHOD_WITHOUT_DEFAULT_VALUE: 2041 case IDelta.FIELD: { 2042 // ensure that there is a @since tag for the 2043 // corresponding member 2044 if (Util.isVisible(modifiers)) { 2045 if (ApiPlugin.DEBUG_API_ANALYZER) { 2046 String deltaDetails = "Delta : " + Util.getDetail(delta); //$NON-NLS-1$ 2047 System.err.println(deltaDetails + " is not compatible"); //$NON-NLS-1$ 2048 } 2049 fPendingDeltaInfos.add(delta); 2050 } 2051 break; 2052 } 2053 case IDelta.EXPANDED_SUPERINTERFACES_SET_BREAKING: 2054 case IDelta.SUPERCLASS_BREAKING:{ 2055 //just add in state but dont add as marker 2056 fBuildState.addBreakingChange(delta); 2057 break; 2058 } 2059 default: 2060 break; 2061 } 2062 break; 2063 } 2064 case IDelta.CHANGED: { 2065 // if public, we always want to check @since tags 2066 switch (flags) { 2067 case IDelta.EXPANDED_SUPERINTERFACES_SET_BREAKING:{ 2068 //just add in state but dont add as marker 2069 fBuildState.addBreakingChange(delta); 2070 break; 2071 } 2072 default: 2073 break; 2074 } 2075 break; 2076 } 2077 default: 2078 break; 2079 } 2080 IApiProblem problem = createCompatibilityProblem(delta, reference, component); 2081 if (addProblem(problem)) { 2082 fBuildState.addBreakingChange(delta); 2083 } 2084 } 2085 } 2086 2087 /** 2088 * Checks the version number of the API component and creates a problem 2089 * markers as needed 2090 * 2091 * @param reference 2092 * @param component 2093 */ checkApiComponentVersion(final IApiComponent reference, final IApiComponent component)2094 private void checkApiComponentVersion(final IApiComponent reference, final IApiComponent component) throws CoreException { 2095 if (reference == null || component == null) { 2096 if (ApiPlugin.DEBUG_API_ANALYZER) { 2097 System.out.println("Ignoring component version check"); //$NON-NLS-1$ 2098 } 2099 return; 2100 } 2101 IApiProblem problem = null; 2102 String refversionval = reference.getVersion(); 2103 String compversionval = component.getVersion(); 2104 Version refversion = new Version(refversionval); 2105 Version compversion = new Version(compversionval); 2106 Version newversion = null; 2107 if (ignoreComponentVersionCheck()) { 2108 if (ignoreExecutionEnvChanges() == false) { 2109 if (shouldVersionChangeForExecutionEnvChanges(reference, component)) { 2110 newversion = new Version(compversion.getMajor(), compversion.getMinor() + 1, 0, compversion.getQualifier() != null ? QUALIFIER : null); 2111 problem = createVersionProblem(IApiProblem.MINOR_VERSION_CHANGE_EXECUTION_ENV_CHANGED, new String[] { 2112 compversionval, 2113 refversionval }, String.valueOf(newversion), Util.EMPTY_STRING); 2114 } 2115 } 2116 if (problem != null) { 2117 addProblem(problem); 2118 } 2119 if (ApiPlugin.DEBUG_API_ANALYZER) { 2120 System.out.println("Ignoring component version check"); //$NON-NLS-1$ 2121 } 2122 return; 2123 } 2124 2125 if (ApiPlugin.DEBUG_API_ANALYZER) { 2126 System.out.println("reference version of " + reference.getSymbolicName() + " : " + refversion); //$NON-NLS-1$ //$NON-NLS-2$ 2127 System.out.println("component version of " + component.getSymbolicName() + " : " + compversion); //$NON-NLS-1$ //$NON-NLS-2$ 2128 } 2129 IDelta[] breakingChanges = fBuildState.getBreakingChanges(); 2130 IDelta[] compatibleChanges = fBuildState.getCompatibleChanges(); 2131 if (breakingChanges.length != 0) { 2132 // make sure that the major version has been incremented 2133 if (compversion.getMajor() <= refversion.getMajor()) { 2134 newversion = new Version(compversion.getMajor() + 1, 0, 0, compversion.getQualifier() != null ? QUALIFIER : null); 2135 problem = createVersionProblem(IApiProblem.MAJOR_VERSION_CHANGE, new String[] { 2136 compversionval, refversionval }, String.valueOf(newversion), collectDetails(breakingChanges)); 2137 } 2138 } else { 2139 if (compatibleChanges.length != 0) { 2140 // only new API have been added 2141 if (compversion.getMajor() != refversion.getMajor()) { 2142 if (reportMajorVersionCheckWithoutBreakingChange()) { 2143 // major version should be identical 2144 newversion = new Version(refversion.getMajor(), refversion.getMinor() + 1, 0, compversion.getQualifier() != null ? QUALIFIER : null); 2145 problem = createVersionProblem(IApiProblem.MAJOR_VERSION_CHANGE_NO_BREAKAGE, new String[] { 2146 compversionval, refversionval }, String.valueOf(newversion), collectDetails(compatibleChanges)); 2147 } 2148 } else if (compversion.getMinor() <= refversion.getMinor()) { 2149 // the minor version should be incremented 2150 newversion = new Version(compversion.getMajor(), compversion.getMinor() + 1, 0, compversion.getQualifier() != null ? QUALIFIER : null); 2151 problem = createVersionProblem(IApiProblem.MINOR_VERSION_CHANGE, new String[] { 2152 compversionval, refversionval }, String.valueOf(newversion), collectDetails(compatibleChanges)); 2153 } 2154 } else if (compversion.getMajor() != refversion.getMajor()) { 2155 if (reportMajorVersionCheckWithoutBreakingChange()) { 2156 // major version should be identical 2157 newversion = new Version(refversion.getMajor(), refversion.getMinor(), refversion.getMicro(), refversion.getQualifier() != null ? QUALIFIER : null); 2158 problem = createVersionProblem(IApiProblem.MAJOR_VERSION_CHANGE_NO_BREAKAGE, new String[] { 2159 compversionval, refversionval }, String.valueOf(newversion), Util.EMPTY_STRING); 2160 } 2161 } else if (compversion.getMinor() != refversion.getMinor()) { 2162 // the minor version should not be incremented 2163 if (reportUnnecessaryMinorMicroVersionCheck() && !hasExecutionEnvironmentChanged(reference, component)) { 2164 newversion = new Version(refversion.getMajor(), refversion.getMinor(), refversion.getMicro(), refversion.getQualifier() != null ? QUALIFIER : null); 2165 problem = createVersionProblem(IApiProblem.MINOR_VERSION_CHANGE_NO_NEW_API, new String[] { 2166 compversionval, refversionval }, String.valueOf(newversion), Util.EMPTY_STRING); 2167 } 2168 } else if (shouldVersionChangeForExecutionEnvChanges(reference, component)) { 2169 newversion = new Version(compversion.getMajor(), compversion.getMinor() + 1, 0, compversion.getQualifier() != null ? QUALIFIER : null); 2170 problem = createVersionProblem(IApiProblem.MINOR_VERSION_CHANGE_EXECUTION_ENV_CHANGED, new String[] { 2171 compversionval, 2172 refversionval }, String.valueOf(newversion), Util.EMPTY_STRING); 2173 2174 } 2175 // analyze version of required components 2176 ReexportedBundleVersionInfo info = null; 2177 if (problem != null) { 2178 switch (problem.getKind()) { 2179 case IApiProblem.MAJOR_VERSION_CHANGE_NO_BREAKAGE: { 2180 // check if there is a version change required due to 2181 // re-exported bundles 2182 info = checkBundleVersionsOfReexportedBundles(reference, component); 2183 if (info != null) { 2184 switch (info.kind) { 2185 case IApiProblem.REEXPORTED_MAJOR_VERSION_CHANGE: { 2186 /* 2187 * we don't do anything since the major 2188 * version is already incremented we cancel 2189 * the previous issue. No need to report 2190 * that the major version should not be 2191 * incremented 2192 */ 2193 problem = null; 2194 break; 2195 } 2196 case IApiProblem.REEXPORTED_MINOR_VERSION_CHANGE: { 2197 /* 2198 * we don't do anything since the major 2199 * version is already incremented. Just 2200 * report that the major version should not 2201 * be incremented 2202 */ 2203 break; 2204 } 2205 default: 2206 break; 2207 } 2208 } 2209 break; 2210 } 2211 case IApiProblem.MINOR_VERSION_CHANGE: { 2212 // check if there is a version change required due to 2213 // re-exported bundles 2214 info = checkBundleVersionsOfReexportedBundles(reference, component); 2215 if (info != null) { 2216 switch (info.kind) { 2217 case IApiProblem.REEXPORTED_MAJOR_VERSION_CHANGE: { 2218 // we keep this problem 2219 newversion = new Version(compversion.getMajor() + 1, 0, 0, compversion.getQualifier() != null ? QUALIFIER : null); 2220 problem = createVersionProblem(info.kind, new String[] { 2221 compversionval, info.componentID, }, String.valueOf(newversion), Util.EMPTY_STRING); 2222 break; 2223 } 2224 default: 2225 break; 2226 } 2227 } 2228 break; 2229 } 2230 case IApiProblem.MINOR_VERSION_CHANGE_NO_NEW_API: { 2231 // check if there is a version change required due to 2232 // re-exported bundles 2233 info = checkBundleVersionsOfReexportedBundles(reference, component); 2234 if (info != null) { 2235 switch (info.kind) { 2236 case IApiProblem.REEXPORTED_MAJOR_VERSION_CHANGE: { 2237 // we return this one 2238 newversion = new Version(compversion.getMajor() + 1, 0, 0, compversion.getQualifier() != null ? QUALIFIER : null); 2239 problem = createVersionProblem(info.kind, new String[] { 2240 compversionval, info.componentID, }, String.valueOf(newversion), Util.EMPTY_STRING); 2241 break; 2242 } 2243 case IApiProblem.REEXPORTED_MINOR_VERSION_CHANGE: { 2244 // we don't do anything since we already 2245 // incremented the minor version 2246 // we get rid of the previous problem 2247 problem = null; 2248 break; 2249 } 2250 default: 2251 break; 2252 } 2253 } 2254 break; 2255 } 2256 default: 2257 break; 2258 } 2259 } else { 2260 info = checkBundleVersionsOfReexportedBundles(reference, component); 2261 if (info != null) { 2262 switch (info.kind) { 2263 case IApiProblem.REEXPORTED_MAJOR_VERSION_CHANGE: { 2264 // major version change 2265 if (compversion.getMajor() <= refversion.getMajor()) { 2266 newversion = new Version(compversion.getMajor() + 1, 0, 0, compversion.getQualifier() != null ? QUALIFIER : null); 2267 problem = createVersionProblem(info.kind, new String[] { 2268 compversionval, info.componentID, }, String.valueOf(newversion), Util.EMPTY_STRING); 2269 } 2270 break; 2271 } 2272 case IApiProblem.REEXPORTED_MINOR_VERSION_CHANGE: { 2273 // minor version change if the component's major 2274 // version is not greater than ref's major version 2275 if (compversion.getMinor() <= refversion.getMinor() && !(compversion.getMajor() > refversion.getMajor())) { 2276 newversion = new Version(compversion.getMajor(), compversion.getMinor() + 1, 0, compversion.getQualifier()); 2277 problem = createVersionProblem(info.kind, new String[] { 2278 compversionval, info.componentID, }, String.valueOf(newversion), Util.EMPTY_STRING); 2279 } 2280 break; 2281 } 2282 default: 2283 break; 2284 } 2285 } 2286 } 2287 } 2288 if (problem != null) { 2289 addProblem(problem); 2290 } 2291 if (problem == null) { 2292 if (breakingChanges.length > 0 || compatibleChanges.length > 0) { 2293 // check if major or minor version is increased 2294 if (reportUnnecessaryMinorMicroVersionCheck() 2295 && checkIfMajorOrMinorVersionIncreased(compversion, refversion)) { 2296 if (hasMicroVersionIncreased(compversion)) { 2297 newversion = new Version(compversion.getMajor(), compversion.getMinor(), 0, 2298 compversion.getQualifier() != null ? QUALIFIER : null); 2299 problem = createVersionProblem(IApiProblem.MICRO_VERSION_CHANGE_UNNECESSARILY, 2300 new String[] { compversionval, refversionval }, String.valueOf(newversion), 2301 Util.EMPTY_STRING); 2302 if (problem != null) { 2303 addProblem(problem); 2304 } 2305 } 2306 } 2307 } 2308 if (breakingChanges.length > 0) { 2309 // check if major version is increased 2310 if (reportUnnecessaryMinorMicroVersionCheck() 2311 && checkIfMajorVersionIncreased(compversion, refversion)) { 2312 if (hasMinorVersionIncreased(compversion)) { 2313 newversion = new Version(compversion.getMajor(), 0, 0, 2314 compversion.getQualifier() != null ? QUALIFIER : null); 2315 problem = createVersionProblem(IApiProblem.MINOR_VERSION_CHANGE_UNNECESSARILY, 2316 new String[] { compversionval, refversionval }, String.valueOf(newversion), 2317 Util.EMPTY_STRING); 2318 if (problem != null) { 2319 addProblem(problem); 2320 } 2321 } 2322 } 2323 } 2324 } 2325 } 2326 hasMinorVersionIncreased(Version compversion)2327 private boolean hasMinorVersionIncreased(Version compversion) { 2328 if (compversion.getMinor() > 0) { 2329 return true; 2330 } 2331 return false; 2332 } 2333 checkIfMajorVersionIncreased(Version compversion, Version refversion)2334 private boolean checkIfMajorVersionIncreased(Version compversion, Version refversion) { 2335 if (compversion.getMajor() > refversion.getMajor()) { 2336 return true; 2337 } 2338 2339 return false; 2340 } 2341 hasMicroVersionIncreased(Version compversion)2342 private boolean hasMicroVersionIncreased(Version compversion) { 2343 if (compversion.getMicro() > 0) { 2344 return true; 2345 } 2346 return false; 2347 } 2348 checkIfMajorOrMinorVersionIncreased(Version compversion, Version refversion)2349 private boolean checkIfMajorOrMinorVersionIncreased(Version compversion, Version refversion) { 2350 if (compversion.getMajor() > refversion.getMajor()) { 2351 return true; 2352 } 2353 if (compversion.getMinor() > refversion.getMinor()) { 2354 return true; 2355 } 2356 return false; 2357 2358 } 2359 2360 /** 2361 * Collects details from the given delta listing for version problems 2362 * 2363 * @param deltas 2364 * @return a {@link String} of the details why the version number should be 2365 * changed 2366 */ collectDetails(final IDelta[] deltas)2367 private String collectDetails(final IDelta[] deltas) { 2368 StringWriter writer = new StringWriter(); 2369 PrintWriter printWriter = new PrintWriter(writer); 2370 // TODO contrived default for 2371 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=251313 2372 int max = Math.min(20, deltas.length); 2373 for (int i = 0; i < max; i++) { 2374 printWriter.print("- "); //$NON-NLS-1$ 2375 printWriter.println(deltas[i].getMessage()); 2376 if (i == max - 1 && max < deltas.length) { 2377 printWriter.println(NLS.bind(BuilderMessages.BaseApiAnalyzer_more_version_problems, Integer.valueOf(deltas.length - max))); 2378 } 2379 } 2380 printWriter.flush(); 2381 printWriter.close(); 2382 return String.valueOf(writer.getBuffer()); 2383 } 2384 2385 /** 2386 * Creates a marker on a manifest file for a version numbering problem and 2387 * returns it or <code>null</code> 2388 * 2389 * @param kind 2390 * @param messageargs 2391 * @param version 2392 * @param description the description of details 2393 * @return a new {@link IApiProblem} or <code>null</code> 2394 */ createVersionProblem(int kind, final String[] messageargs, String version, String description)2395 private IApiProblem createVersionProblem(int kind, final String[] messageargs, String version, String description) { 2396 IResource manifestFile = null; 2397 String path = JarFile.MANIFEST_NAME; 2398 if (fJavaProject != null) { 2399 manifestFile = Util.getManifestFile(fJavaProject.getProject()); 2400 } 2401 // this error should be located on the manifest.mf file 2402 // first of all we check how many API breakage marker are there 2403 int lineNumber = -1; 2404 int charStart = 0; 2405 int charEnd = 1; 2406 char[] contents = null; 2407 if (manifestFile != null && manifestFile.getType() == IResource.FILE) { 2408 path = manifestFile.getProjectRelativePath().toPortableString(); 2409 IFile file = (IFile) manifestFile; 2410 InputStream inputStream = null; 2411 LineNumberReader reader = null; 2412 try { 2413 inputStream = file.getContents(true); 2414 contents = Util.getInputStreamAsCharArray(inputStream, -1, StandardCharsets.UTF_8); 2415 reader = new LineNumberReader(new BufferedReader(new StringReader(new String(contents)))); 2416 int lineCounter = 0; 2417 String line = null; 2418 loop: while ((line = reader.readLine()) != null) { 2419 lineCounter++; 2420 if (line.startsWith(Constants.BUNDLE_VERSION)) { 2421 lineNumber = lineCounter; 2422 break loop; 2423 } 2424 } 2425 } catch (CoreException | IOException e) { 2426 // ignore 2427 } finally { 2428 try { 2429 if (inputStream != null) { 2430 inputStream.close(); 2431 } 2432 if (reader != null) { 2433 reader.close(); 2434 } 2435 } catch (IOException e) { 2436 // ignore 2437 } 2438 } 2439 } 2440 if (lineNumber != -1 && contents != null) { 2441 // initialize char start, char end 2442 int index = CharOperation.indexOf(Constants.BUNDLE_VERSION.toCharArray(), contents, true); 2443 loop: for (int i = index + Constants.BUNDLE_VERSION.length() + 1, max = contents.length; i < max; i++) { 2444 char currentCharacter = contents[i]; 2445 if (CharOperation.isWhitespace(currentCharacter)) { 2446 continue; 2447 } 2448 charStart = i; 2449 break loop; 2450 } 2451 loop: for (int i = charStart + 1, max = contents.length; i < max; i++) { 2452 switch (contents[i]) { 2453 case '\r': 2454 case '\n': 2455 charEnd = i; 2456 break loop; 2457 default: 2458 continue; 2459 } 2460 } 2461 } else { 2462 lineNumber = 1; 2463 } 2464 2465 return ApiProblemFactory.newApiVersionNumberProblem(path, null, messageargs, new String[] { 2466 IApiMarkerConstants.MARKER_ATTR_VERSION, 2467 IApiMarkerConstants.API_MARKER_ATTR_ID, 2468 IApiMarkerConstants.VERSION_NUMBERING_ATTR_DESCRIPTION, }, new Object[] { 2469 version, 2470 Integer.valueOf(IApiMarkerConstants.VERSION_NUMBERING_MARKER_ID), 2471 description }, lineNumber, charStart, charEnd, IElementDescriptor.RESOURCE, kind); 2472 } 2473 2474 /** 2475 * Checks to see if there is a default API baseline set in the workspace, if 2476 * not create a marker 2477 */ checkDefaultBaselineSet()2478 private void checkDefaultBaselineSet() { 2479 if (ignoreDefaultBaselineCheck()) { 2480 if (ApiPlugin.DEBUG_API_ANALYZER) { 2481 System.out.println("Ignoring check for default API baseline"); //$NON-NLS-1$ 2482 } 2483 return; 2484 } 2485 if (ApiPlugin.DEBUG_API_ANALYZER) { 2486 System.out.println("Checking if the default API baseline is set"); //$NON-NLS-1$ 2487 } 2488 IApiProblem problem = ApiProblemFactory.newApiBaselineProblem(Path.EMPTY.toString(), new String[] { IApiMarkerConstants.API_MARKER_ATTR_ID }, new Object[] { Integer.valueOf(IApiMarkerConstants.DEFAULT_API_BASELINE_MARKER_ID) }, IElementDescriptor.RESOURCE, IApiProblem.API_BASELINE_MISSING); 2489 addProblem(problem); 2490 } 2491 2492 /** 2493 * Checks to see if the baseline set in the workspace has at least 1 matching 2494 * project in the workspace 2495 */ checkBaselineMismatch(IApiBaseline baseline, IApiBaseline workspaceBaseline)2496 public void checkBaselineMismatch(IApiBaseline baseline, IApiBaseline workspaceBaseline) { 2497 IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot(); 2498 try { 2499 IMarker[] findMarkers = root.findMarkers(IApiMarkerConstants.DEFAULT_API_BASELINE_PROBLEM_MARKER, false, 2500 IResource.DEPTH_ZERO); 2501 for (IMarker iMarker : findMarkers) { 2502 iMarker.delete(); 2503 } 2504 } catch (CoreException e) { 2505 ApiPlugin.log(e); 2506 } 2507 if (baseline == null || workspaceBaseline == null) { 2508 return; 2509 } 2510 int severityLevel = ApiPlugin.getDefault().getSeverityLevel(IApiProblemTypes.MISSING_DEFAULT_API_BASELINE, 2511 null); 2512 if (severityLevel == ApiPlugin.SEVERITY_IGNORE) { 2513 return; 2514 } 2515 IApiComponent[] workspacesComponents = workspaceBaseline.getApiComponents(); 2516 boolean found = false; 2517 for (IApiComponent iApiComponent : workspacesComponents) { 2518 if (iApiComponent instanceof ProjectComponent) { 2519 IApiComponent apiComponent = baseline.getApiComponent(iApiComponent.getSymbolicName()); 2520 if (apiComponent != null) { 2521 found = true; 2522 break; 2523 } 2524 } 2525 } 2526 if (found) { 2527 return; 2528 } 2529 2530 IApiProblem problem = ApiProblemFactory.newApiBaselineProblem(Path.EMPTY.toString(), 2531 new String[] { IApiMarkerConstants.API_MARKER_ATTR_ID }, 2532 new Object[] { Integer.valueOf(IApiMarkerConstants.DEFAULT_API_BASELINE_MARKER_ID) }, 2533 IElementDescriptor.RESOURCE, IApiProblem.API_BASELINE_MISMATCH); 2534 addProblem(problem); 2535 } 2536 2537 /** 2538 * Returns the Java project associated with the given API component, or 2539 * <code>null</code> if none. 2540 * 2541 * @param component API component 2542 * @return Java project or <code>null</code> 2543 */ getJavaProject(IApiComponent component)2544 private IJavaProject getJavaProject(IApiComponent component) { 2545 if (component instanceof ProjectComponent) { 2546 ProjectComponent pp = (ProjectComponent) component; 2547 return pp.getJavaProject(); 2548 } 2549 return null; 2550 } 2551 2552 /** 2553 * Adds the problem to the list of problems iff it is not <code>null</code> 2554 * and not filtered 2555 * 2556 * @param problem 2557 * @return 2558 */ addProblem(IApiProblem problem)2559 private boolean addProblem(IApiProblem problem) { 2560 if (problem == null || isProblemFiltered(problem)) { 2561 return false; 2562 } 2563 return fProblems.add(problem); 2564 } 2565 2566 /** 2567 * Returns if the given {@link IApiProblem} should be filtered from having a 2568 * problem marker created for it 2569 * 2570 * @param problem the problem that may or may not be filtered 2571 * @return true if the {@link IApiProblem} should not have a marker created, 2572 * false otherwise 2573 */ isProblemFiltered(IApiProblem problem)2574 private boolean isProblemFiltered(IApiProblem problem) { 2575 if (fJavaProject == null) { 2576 if (this.fFilterStore != null) { 2577 boolean filtered = this.fFilterStore.isFiltered(problem); 2578 if (filtered) { 2579 return true; 2580 } 2581 } 2582 if (this.fPreferences != null) { 2583 String key = ApiProblemFactory.getProblemSeverityId(problem); 2584 if (key != null) { 2585 String value = this.fPreferences.getProperty(key, null); 2586 return ApiPlugin.VALUE_IGNORE.equals(value); 2587 } 2588 } 2589 return false; 2590 } 2591 2592 IProject project = fJavaProject.getProject(); 2593 // first the severity is checked 2594 if (ApiPlugin.getDefault().getSeverityLevel(ApiProblemFactory.getProblemSeverityId(problem), project) == ApiPlugin.SEVERITY_IGNORE) { 2595 return true; 2596 } 2597 2598 IApiBaselineManager manager = ApiBaselineManager.getManager(); 2599 IApiBaseline baseline = manager.getWorkspaceBaseline(); 2600 if (baseline == null) { 2601 return false; 2602 } 2603 IApiComponent component = baseline.getApiComponent(project); 2604 if (component != null) { 2605 try { 2606 IApiFilterStore filterStore = component.getFilterStore(); 2607 if (filterStore != null) { 2608 return filterStore.isFiltered(problem); 2609 } 2610 } catch (CoreException e) { 2611 ApiPlugin.log("Failed to get filter store for " + component.getName(), e); //$NON-NLS-1$ 2612 } 2613 } 2614 return false; 2615 } 2616 } 2617