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