blob: e92148f92f922802142290bb79f5d5217ee32ced [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2007, 2019 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.pde.api.tools.internal.provisional.comparator;
import java.util.HashSet;
import java.util.Set;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.jdt.core.Flags;
import org.eclipse.pde.api.tools.internal.builder.AbstractProblemDetector;
import org.eclipse.pde.api.tools.internal.comparator.ClassFileComparator;
import org.eclipse.pde.api.tools.internal.comparator.Delta;
import org.eclipse.pde.api.tools.internal.provisional.ApiPlugin;
import org.eclipse.pde.api.tools.internal.provisional.IApiAnnotations;
import org.eclipse.pde.api.tools.internal.provisional.IApiDescription;
import org.eclipse.pde.api.tools.internal.provisional.IRequiredComponentDescription;
import org.eclipse.pde.api.tools.internal.provisional.RestrictionModifiers;
import org.eclipse.pde.api.tools.internal.provisional.VisibilityModifiers;
import org.eclipse.pde.api.tools.internal.provisional.model.ApiTypeContainerVisitor;
import org.eclipse.pde.api.tools.internal.provisional.model.IApiBaseline;
import org.eclipse.pde.api.tools.internal.provisional.model.IApiComponent;
import org.eclipse.pde.api.tools.internal.provisional.model.IApiScope;
import org.eclipse.pde.api.tools.internal.provisional.model.IApiType;
import org.eclipse.pde.api.tools.internal.provisional.model.IApiTypeContainer;
import org.eclipse.pde.api.tools.internal.provisional.model.IApiTypeRoot;
import org.eclipse.pde.api.tools.internal.util.Util;
import org.osgi.framework.Version;
/**
* This class defines a comparator to get a IDelta out of the comparison of two
* elements.
*
* @since 1.0
*/
public class ApiComparator {
/**
* Default empty delta
*/
public static final IDelta NO_DELTA = new Delta();
/**
* Returns a delta for a API component version change
*
* @param apiComponent2
* @param id
* @param apiComponentVersion
* @param apiComponentVersion2
*/
static IDelta checkBundleVersionChanges(IApiComponent apiComponent2, String id, String apiComponentVersion, String apiComponentVersion2) {
Version version = null;
try {
version = new Version(apiComponentVersion);
} catch (IllegalArgumentException e) {
// ignore
}
Version version2 = null;
try {
version2 = new Version(apiComponentVersion2);
} catch (IllegalArgumentException e) {
// ignore
}
if (version != null && version2 != null) {
// add check for bundle versions
if (version.getMajor() != version2.getMajor()) {
return new Delta(Util.getDeltaComponentVersionsId(apiComponent2), IDelta.API_COMPONENT_ELEMENT_TYPE, IDelta.CHANGED, IDelta.MAJOR_VERSION, RestrictionModifiers.NO_RESTRICTIONS, RestrictionModifiers.NO_RESTRICTIONS, 0, 0, null, id, new String[] {
id, apiComponentVersion, apiComponentVersion2 });
} else if (version.getMinor() != version2.getMinor()) {
return new Delta(Util.getDeltaComponentVersionsId(apiComponent2), IDelta.API_COMPONENT_ELEMENT_TYPE, IDelta.CHANGED, IDelta.MINOR_VERSION, RestrictionModifiers.NO_RESTRICTIONS, RestrictionModifiers.NO_RESTRICTIONS, 0, 0, null, id, new String[] {
id, apiComponentVersion, apiComponentVersion2 });
}
}
return null;
}
/**
* Returns a delta that corresponds to the difference between the given
* baseline and the reference.
*
* @param referenceBaseline the given API baseline which is used as the
* reference
* @param baseline the given API baseline to compare with
* @param visibilityModifiers the given visibility that triggers what
* visibility should be used for the comparison
* @param force a flag to force the comparison of nested API components with
* the same versions
* @param monitor
*
* @return a delta, an empty delta if no difference is found or null if the
* delta detection failed
* @throws IllegalArgumentException if one of the two baselines is null
*/
public static IDelta compare(final IApiBaseline referenceBaseline, final IApiBaseline baseline, final int visibilityModifiers, final boolean force, final IProgressMonitor monitor) {
SubMonitor localmonitor = SubMonitor.convert(monitor, 2);
try {
if (referenceBaseline == null || baseline == null) {
throw new IllegalArgumentException("None of the baselines must be null"); //$NON-NLS-1$
}
IApiComponent[] apiComponents = referenceBaseline.getApiComponents();
IApiComponent[] apiComponents2 = baseline.getApiComponents();
Set<String> apiComponentsIds = new HashSet<>();
final Delta globalDelta = new Delta();
SubMonitor apiLoopMonitor = localmonitor.split(1).setWorkRemaining(apiComponents.length);
for (IApiComponent apiComponentMainLoop : apiComponents) {
apiLoopMonitor.split(1);
IApiComponent apiComponent = apiComponentMainLoop;
if (!apiComponent.isSystemComponent()) {
String id = apiComponent.getSymbolicName();
IApiComponent apiComponentBaseline = baseline.getApiComponent(id);
IDelta delta = null;
if (apiComponentBaseline == null) {
// report removal of an API component
delta = new Delta(null, IDelta.API_BASELINE_ELEMENT_TYPE, IDelta.REMOVED, IDelta.API_COMPONENT, null, id, id);
} else {
apiComponentsIds.add(id);
String versionString = apiComponent.getVersion();
String versionString2 = apiComponentBaseline.getVersion();
IDelta bundleVersionChangesDelta = checkBundleVersionChanges(apiComponentBaseline, id, versionString, versionString2);
if (bundleVersionChangesDelta != null) {
globalDelta.add(bundleVersionChangesDelta);
}
if (!versionString.equals(versionString2) || force) {
long time = System.currentTimeMillis();
try {
delta = compare(apiComponent, apiComponentBaseline, referenceBaseline, baseline, visibilityModifiers, localmonitor.split(1));
} finally {
if (ApiPlugin.DEBUG_API_COMPARATOR) {
System.out.println("Time spent for " + id + " " + versionString + " : " + (System.currentTimeMillis() - time) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
}
}
}
}
if (delta != null && delta != NO_DELTA) {
globalDelta.add(delta);
}
}
}
SubMonitor elementLoopMonitor = localmonitor.split(1).setWorkRemaining(apiComponents2.length);
for (IApiComponent element : apiComponents2) {
elementLoopMonitor.split(1);
IApiComponent apiComponent = element;
if (!apiComponent.isSystemComponent()) {
String id = apiComponent.getSymbolicName();
if (!apiComponentsIds.contains(id)) {
// addition of an API component
globalDelta.add(new Delta(null, IDelta.API_BASELINE_ELEMENT_TYPE, IDelta.ADDED, IDelta.API_COMPONENT, null, id, id));
}
}
}
return globalDelta.isEmpty() ? NO_DELTA : globalDelta;
} finally {
SubMonitor.done(monitor);
}
}
/**
* Returns a delta that corresponds to the difference between the given
* component and the reference baseline.
*
* @param component the given component to compare with the given reference
* baseline
* @param referenceBaseline the given API baseline which is used as the
* reference
* @param visibilityModifiers the given visibility that triggers what
* visibility should be used for the comparison
* @param force a flag to force the comparison of nested API components with
* the same versions
* @param monitor
*
* @return a delta, an empty delta if no difference is found or null if the
* delta detection failed
* @exception IllegalArgumentException if:
* <ul>
* <li>the given component is null</li>
* <li>the reference baseline is null</li>
* </ul>
*/
public static IDelta compare(final IApiComponent component, final IApiBaseline referenceBaseline, final int visibilityModifiers, final boolean force, final IProgressMonitor monitor) {
SubMonitor localmonitor = SubMonitor.convert(monitor, 2);
try {
if (component == null) {
throw new IllegalArgumentException("The composent cannot be null"); //$NON-NLS-1$
}
if (referenceBaseline == null) {
throw new IllegalArgumentException("The reference baseline cannot be null"); //$NON-NLS-1$
}
localmonitor.split(1);
IDelta delta = null;
if (!component.isSystemComponent()) {
String id = component.getSymbolicName();
IApiComponent apiComponent2 = referenceBaseline.getApiComponent(id);
if (apiComponent2 == null) {
// report addition of an API component
delta = new Delta(null, IDelta.API_BASELINE_ELEMENT_TYPE, IDelta.ADDED, IDelta.API_COMPONENT, null, id, id);
} else {
if (!component.getVersion().equals(apiComponent2.getVersion()) || force) {
long time = System.currentTimeMillis();
try {
delta = compare(apiComponent2, component, visibilityModifiers, localmonitor.split(1));
} finally {
if (ApiPlugin.DEBUG_API_COMPARATOR) {
System.out.println("Time spent for " + id + " " + component.getVersion() + " : " + (System.currentTimeMillis() - time) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
}
}
}
}
if (delta != null && delta != NO_DELTA) {
return delta;
}
}
return NO_DELTA;
} finally {
localmonitor.done();
}
}
/**
* Returns a delta that corresponds to the comparison of the two given API
* components. The two components are compared even if their versions are
* identical.
*
* @param referenceComponent the given API component
* @param component2 the given API component to compare with
* @param referenceBaseline the given API baseline from which the given
* component <code>component</code> is coming from
* @param baseline the given API baseline from which the given component
* <code>component2</code> is coming from
* @param visibilityModifiers the given visibility that triggers what
* visibility should be used for the comparison
* @param monitor
*
* @return a delta, an empty delta if no difference is found or null if the
* delta detection failed
* @exception IllegalArgumentException if:
* <ul>
* <li>both given components are null</li>
* <li>one of the baselines is null</li>
* </ul>
*/
public static IDelta compare(final IApiComponent referenceComponent, final IApiComponent component2, final IApiBaseline referenceBaseline, final IApiBaseline baseline, final int visibilityModifiers, final IProgressMonitor monitor) {
SubMonitor localmonitor = SubMonitor.convert(monitor, 3);
try {
if (referenceComponent == null) {
if (component2 == null) {
throw new IllegalArgumentException("Both components cannot be null"); //$NON-NLS-1$
}
return new Delta(null, IDelta.API_BASELINE_ELEMENT_TYPE, IDelta.ADDED, IDelta.API_COMPONENT, null, component2.getSymbolicName(), Util.getComponentVersionsId(component2));
} else if (component2 == null) {
String referenceComponentId = referenceComponent.getSymbolicName();
return new Delta(null, IDelta.API_BASELINE_ELEMENT_TYPE, IDelta.REMOVED, IDelta.API_COMPONENT, null, referenceComponentId, Util.getComponentVersionsId(referenceComponent));
}
if (referenceBaseline == null || baseline == null) {
throw new IllegalArgumentException("The baselines cannot be null"); //$NON-NLS-1$
}
String referenceComponentId = referenceComponent.getSymbolicName();
final Delta globalDelta = new Delta();
// check the EE first
Set<String> referenceEEs = Util.convertAsSet(referenceComponent.getExecutionEnvironments());
Set<String> componentsEEs = Util.convertAsSet(component2.getExecutionEnvironments());
SubMonitor referencesLoopMonitor = localmonitor.split(1).setWorkRemaining(referenceEEs.size());
for (String currentEE : referenceEEs) {
referencesLoopMonitor.split(1);
if (!componentsEEs.remove(currentEE)) {
globalDelta.add(new Delta(Util.getDeltaComponentVersionsId(referenceComponent), IDelta.API_COMPONENT_ELEMENT_TYPE, IDelta.REMOVED, IDelta.EXECUTION_ENVIRONMENT, RestrictionModifiers.NO_RESTRICTIONS, RestrictionModifiers.NO_RESTRICTIONS, 0, 0, null, referenceComponentId, new String[] {
currentEE,
Util.getComponentVersionsId(referenceComponent) }));
}
}
SubMonitor componentsLoopMonitor = localmonitor.split(1).setWorkRemaining(componentsEEs.size());
for (String currentEE : componentsEEs) {
componentsLoopMonitor.split(1);
globalDelta.add(new Delta(Util.getDeltaComponentVersionsId(referenceComponent), IDelta.API_COMPONENT_ELEMENT_TYPE, IDelta.ADDED, IDelta.EXECUTION_ENVIRONMENT, RestrictionModifiers.NO_RESTRICTIONS, RestrictionModifiers.NO_RESTRICTIONS, 0, 0, null, referenceComponentId, new String[] {
currentEE,
Util.getComponentVersionsId(referenceComponent) }));
}
return internalCompare(referenceComponent, component2, referenceBaseline, baseline, visibilityModifiers, globalDelta, localmonitor.split(1));
} catch (CoreException e) {
// null means an error case
return null;
}
}
/**
* Returns a delta that corresponds to the difference between the given
* component and the given reference component. The given component cannot
* be null.
*
* @param referenceComponent the given API component that is used as the
* reference
* @param component the given component to compare with
* @param visibilityModifiers the given visibility that triggers what
* visibility should be used for the comparison
* @param force a flag to force the comparison of nested API components with
* the same versions
* @param monitor
*
* @return a delta, an empty delta if no difference is found or null if the
* delta detection failed
*/
public static IDelta compare(final IApiComponent referenceComponent, final IApiComponent component, final int visibilityModifiers, final IProgressMonitor monitor) {
try {
return compare(referenceComponent, component, referenceComponent == null ? null : referenceComponent.getBaseline(), component.getBaseline(), visibilityModifiers, monitor);
} catch (CoreException e) {
ApiPlugin.log(e);
}
return null;
}
/**
* Returns a delta that corresponds to the comparison of the given class
* file with the reference.
*
* @param typeRoot2 the given class file that comes from the
* <code>component2</code>
* @param component the given API component from the reference
* @param component2 the given API component to compare with
* @param reexporter the API component re-exporting component2, or
* <code>null</code> if none
* @param referenceBaseline the given API baseline from which the given
* component <code>component</code> is coming from
* @param baseline the given API baseline from which the given component
* <code>component2</code> is coming from
* @param visibilityModifiers the given visibility that triggers what
* visibility should be used for the comparison
* @param monitor
*
* @return a delta, an empty delta if no difference is found or null if the
* delta detection failed
* @exception IllegalArgumentException if:
* <ul>
* <li>the given class file is null</li>
* <li>one of the given components is null</li>
* <li>one of the given baselines is null</li>
* </ul>
*/
public static IDelta compare(final IApiTypeRoot typeRoot2, final IApiComponent component, final IApiComponent component2, final IApiComponent reexporter, final IApiBaseline referenceBaseline, final IApiBaseline baseline, final int visibilityModifiers, final IProgressMonitor monitor) {
if (typeRoot2 == null) {
throw new IllegalArgumentException("The given class file is null"); //$NON-NLS-1$
}
if (component == null || component2 == null) {
throw new IllegalArgumentException("One of the given components is null"); //$NON-NLS-1$
}
if (referenceBaseline == null || baseline == null) {
throw new IllegalArgumentException("One of the given baselines is null"); //$NON-NLS-1$
}
SubMonitor localmonitor = SubMonitor.convert(monitor, 6);
try {
IApiType typeDescriptor2 = typeRoot2.getStructure();
if (typeDescriptor2.isMemberType() || typeDescriptor2.isAnonymous() || typeDescriptor2.isLocal()) {
// we skip nested types (member, local and anonymous)
return NO_DELTA;
}
String typeName = typeRoot2.getTypeName();
IApiTypeRoot typeRoot = null;
String id = component.getSymbolicName();
if (Util.ORG_ECLIPSE_SWT.equals(id)) {
typeRoot = component.findTypeRoot(typeName);
} else {
typeRoot = component.findTypeRoot(typeName, id);
}
final IApiDescription apiDescription2 = component2.getApiDescription();
IApiAnnotations elementDescription2 = apiDescription2.resolveAnnotations(typeDescriptor2.getHandle());
int visibility = 0;
if (elementDescription2 != null) {
visibility = elementDescription2.getVisibility();
}
localmonitor.split(1);
final IApiDescription referenceApiDescription = component.getApiDescription();
IApiAnnotations refElementDescription = referenceApiDescription.resolveAnnotations(typeDescriptor2.getHandle());
int refVisibility = 0;
if (refElementDescription != null) {
refVisibility = refElementDescription.getVisibility();
}
localmonitor.split(1);
String deltaComponentID = Util.getDeltaComponentVersionsId(component2);
if (typeRoot == null) {
if (Util.isAPI(visibility, typeDescriptor2)) {
return new Delta(deltaComponentID, IDelta.API_COMPONENT_ELEMENT_TYPE, IDelta.ADDED, reexporter == null ? IDelta.TYPE : IDelta.REEXPORTED_TYPE, elementDescription2 != null ? elementDescription2.getRestrictions() : RestrictionModifiers.NO_RESTRICTIONS, RestrictionModifiers.NO_RESTRICTIONS, 0, typeDescriptor2.getModifiers(), typeName, typeName, new String[] {
typeName, Util.getComponentVersionsId(component2) });
}
return NO_DELTA;
}
localmonitor.split(1);
IApiType typeDescriptor = typeRoot.getStructure();
if ((visibility & visibilityModifiers) == 0) {
if ((refVisibility & visibilityModifiers) == 0) {
// no delta
return NO_DELTA;
}
if (Util.isAPI(refVisibility, typeDescriptor)) {
return new Delta(deltaComponentID, IDelta.API_COMPONENT_ELEMENT_TYPE, IDelta.REMOVED, IDelta.API_TYPE, elementDescription2 != null ? elementDescription2.getRestrictions() : RestrictionModifiers.NO_RESTRICTIONS, RestrictionModifiers.NO_RESTRICTIONS, typeDescriptor.getModifiers(), typeDescriptor2.getModifiers(), typeName, typeName, new String[] {
typeName, Util.getComponentVersionsId(component2) });
}
} else if (!Util.isAPI(refVisibility, typeDescriptor) && Util.isAPI(visibility, typeDescriptor2)) {
return new Delta(deltaComponentID, IDelta.API_COMPONENT_ELEMENT_TYPE, IDelta.ADDED, IDelta.TYPE, elementDescription2 != null ? elementDescription2.getRestrictions() : RestrictionModifiers.NO_RESTRICTIONS, RestrictionModifiers.NO_RESTRICTIONS, typeDescriptor.getModifiers(), typeDescriptor2.getModifiers(), typeName, typeName, new String[] {
typeName, Util.getComponentVersionsId(component2) });
}
localmonitor.split(1);
if (visibilityModifiers == VisibilityModifiers.API) {
// if the visibility is API, we only consider public and
// protected types
if (Util.isDefault(typeDescriptor2.getModifiers()) || Flags.isPrivate(typeDescriptor2.getModifiers())) {
// we need to check if the reference contains the type to
// report a reduced visibility
if (Flags.isPublic(typeDescriptor.getModifiers()) || Flags.isProtected(typeDescriptor.getModifiers())) {
return new Delta(deltaComponentID, IDelta.API_COMPONENT_ELEMENT_TYPE, IDelta.REMOVED, IDelta.API_TYPE, elementDescription2 != null ? elementDescription2.getRestrictions() : RestrictionModifiers.NO_RESTRICTIONS, RestrictionModifiers.NO_RESTRICTIONS, typeDescriptor.getModifiers(), typeDescriptor2.getModifiers(), typeName, typeName, new String[] {
typeName,
Util.getComponentVersionsId(component2) });
} else {
return NO_DELTA;
}
}
}
localmonitor.split(1);
ClassFileComparator comparator = new ClassFileComparator(typeDescriptor, typeRoot2, component, component2, referenceBaseline, baseline, visibilityModifiers);
IDelta delta = comparator.getDelta(localmonitor.split(1));
if (ApiPlugin.DEBUG_API_COMPARATOR) {
IStatus status = comparator.getStatus();
if (status != null) {
ApiPlugin.log(status);
}
}
return delta;
} catch (CoreException e) {
return null;
} finally {
localmonitor.done();
}
}
/**
* Returns a delta that corresponds to the comparison of the given class
* file.
*
* @param typeRoot the given class file
* @param typeRoot2 the given class file to compare with
* @param component the given API component from which the given class file
* is coming from
* @param component2 the given API component to compare with
* @param referenceBaseline the given API baseline from which the given
* component <code>component</code> is coming from
* @param baseline the given API baseline from which the given component
* <code>component2</code> is coming from
* @param visibilityModifiers the given visibility that triggers what
* visibility should be used for the comparison
* @param monitor
*
* @return a delta, an empty delta if no difference is found or
* <code>null</code> if the delta detection failed
* @exception IllegalArgumentException if:
* <ul>
* <li>one of the given components is null</li>
* <li>one of the given baselines is null</li>
* </ul>
*/
public static IDelta compare(final IApiTypeRoot typeRoot, final IApiTypeRoot typeRoot2, final IApiComponent component, final IApiComponent component2, final IApiBaseline referenceBaseline, final IApiBaseline baseline, final int visibilityModifiers, final IProgressMonitor monitor) {
if (typeRoot == null || typeRoot2 == null) {
throw new IllegalArgumentException("One of the given class files is null"); //$NON-NLS-1$
}
if (component == null || component2 == null) {
throw new IllegalArgumentException("One of the given components is null"); //$NON-NLS-1$
}
if (referenceBaseline == null || baseline == null) {
throw new IllegalArgumentException("One of the given baselines is null"); //$NON-NLS-1$
}
IDelta delta = null;
try {
ClassFileComparator comparator = new ClassFileComparator(typeRoot, typeRoot2, component, component2, referenceBaseline, baseline, visibilityModifiers);
delta = comparator.getDelta(SubMonitor.convert(monitor));
if (ApiPlugin.DEBUG_API_COMPARATOR) {
IStatus status = comparator.getStatus();
if (status != null) {
ApiPlugin.log(status);
}
}
} catch (CoreException e) {
ApiPlugin.log(e);
}
return delta;
}
/**
* Returns a delta that corresponds to the comparison of the two given API
* baselines. Nested API components with the same versions are not compared.
* <p>
* Equivalent to: compare(baseline, baseline2, visibilityModifiers, false,
* false, monitor);
* </p>
*
* @param scope the given scope for the comparison
* @param baseline the given API baseline to compare with
* @param visibilityModifiers the given visibility that triggers what
* visibility should be used for the comparison
* @param force a flag to force the comparison of nested API components with
* the same versions
* @param monitor the given progress monitor to report progress
*
* @return a delta, an empty delta if no difference is found or null if the
* delta detection failed
* @throws IllegalArgumentException if one of the two baselines is null
* CoreException if one of the element in the scope cannot be
* visited
*/
public static IDelta compare(final IApiScope scope, final IApiBaseline baseline, final int visibilityModifiers, final boolean force, final IProgressMonitor monitor) throws CoreException {
return compare(scope, baseline, visibilityModifiers, force, false, monitor);
}
/**
* Returns a delta that corresponds to the comparison of the two given API
* baselines. Nested API components with the same versions are not compared.
* <p>
* Equivalent to: compare(baseline, baseline2, visibilityModifiers, false);
* </p>
*
* @param scope the given scope for the comparison
* @param baseline the given API baseline to compare with
* @param visibilityModifiers the given visibility that triggers what
* visibility should be used for the comparison
* @param force a flag to force the comparison of nested API components with
* the same versions
* @param continueOnResolverError if <code>true</code> the comparison will
* continue even if a component in the scope has a resolver error
* @param monitor the given progress monitor to report progress
*
* @return a delta, an empty delta if no difference is found or null if the
* delta detection failed. If set to continue on resolver error a
* delta, possibly empty, will always be returned
* @throws IllegalArgumentException if one of the two baselines is null
* CoreException if one of the element in the scope cannot be
* visited
*/
public static IDelta compare(final IApiScope scope, final IApiBaseline baseline, final int visibilityModifiers, final boolean force, final boolean continueOnResolverError, final IProgressMonitor monitor) throws CoreException {
if (scope == null || baseline == null) {
throw new IllegalArgumentException("None of the scope or the baseline must be null"); //$NON-NLS-1$
}
SubMonitor localmonitor = SubMonitor.convert(monitor, 2);
try {
final Set<IDelta> deltas = new HashSet<>();
final CompareApiScopeVisitor visitor = new CompareApiScopeVisitor(deltas, baseline, force, visibilityModifiers, continueOnResolverError, localmonitor.split(1));
scope.accept(visitor);
// If set to continue on error, return whatever deltas were
// collected
if (!continueOnResolverError && visitor.containsError()) {
return null;
}
if (deltas.isEmpty()) {
return NO_DELTA;
}
final Delta globalDelta = new Delta();
for (IDelta iDelta : deltas) {
iDelta.accept(new DeltaVisitor() {
@Override
public void endVisit(IDelta localDelta) {
if (localDelta.getChildren().length == 0) {
switch (localDelta.getElementType()) {
case IDelta.ANNOTATION_ELEMENT_TYPE:
case IDelta.ENUM_ELEMENT_TYPE:
case IDelta.CONSTRUCTOR_ELEMENT_TYPE:
case IDelta.METHOD_ELEMENT_TYPE:
case IDelta.INTERFACE_ELEMENT_TYPE:
case IDelta.CLASS_ELEMENT_TYPE:
case IDelta.FIELD_ELEMENT_TYPE:
case IDelta.API_COMPONENT_ELEMENT_TYPE:
case IDelta.API_BASELINE_ELEMENT_TYPE: {
globalDelta.add(localDelta);
break;
}
default:
break;
}
}
}
});
}
localmonitor.split(1);
return globalDelta.isEmpty() ? NO_DELTA : globalDelta;
} finally {
localmonitor.done();
}
}
/**
* Returns true, if the given type descriptor should be skipped, false
* otherwise.
*
* @param visibilityModifiers
* @param elementDescription
* @param typeDescriptor
* @return
*/
static boolean filterType(final int visibilityModifiers, IApiAnnotations elementDescription, IApiType typeDescriptor) {
if (elementDescription != null && (elementDescription.getVisibility() & visibilityModifiers) == 0) {
// we skip the class file according to their visibility
return true;
}
if (visibilityModifiers == VisibilityModifiers.API) {
// if the visibility is API, we only consider public and protected
// types or types
// without element description
if (elementDescription == null || Util.isDefault(typeDescriptor.getModifiers()) || Flags.isPrivate(typeDescriptor.getModifiers())) {
return true;
}
}
return false;
}
/**
* Performs the internal compare of the given {@link IApiComponent}s using
* their type containers
*
* @param component
* @param component2
* @param referenceBaseline
* @param baseline
* @param visibilityModifiers
* @param globalDelta
* @param monitor
*
* @return a delta of changed API elements
* @throws CoreException
*/
private static IDelta internalCompare(final IApiComponent component, final IApiComponent component2, final IApiBaseline referenceBaseline, final IApiBaseline baseline, final int visibilityModifiers, final Delta globalDelta, final IProgressMonitor monitor) throws CoreException {
final Set<String> typeRootBaseLineNames = new HashSet<>();
final String id = component.getSymbolicName();
IApiTypeContainer[] typeRootContainers = null;
IApiTypeContainer[] typeRootContainers2 = null;
final SubMonitor localmonitor = SubMonitor.convert(monitor, 4);
final boolean isSWT = Util.ORG_ECLIPSE_SWT.equals(id);
if (isSWT) {
typeRootContainers = component.getApiTypeContainers();
typeRootContainers2 = component2.getApiTypeContainers();
} else {
typeRootContainers = component.getApiTypeContainers(id);
typeRootContainers2 = component2.getApiTypeContainers(id);
}
final IApiDescription apiDescription = component.getApiDescription();
final IApiDescription apiDescription2 = component2.getApiDescription();
if (typeRootContainers != null) {
SubMonitor loopMonitor = localmonitor.split(1).setWorkRemaining(typeRootContainers.length);
for (IApiTypeContainer container : typeRootContainers) {
SubMonitor iterationMonitor = loopMonitor.split(1);
try {
container.accept(new ApiTypeContainerVisitor() {
@Override
public void visit(String packageName, IApiTypeRoot typeRoot) {
SubMonitor visitMonitor = iterationMonitor.setWorkRemaining(50).split(1).setWorkRemaining(2);
String typeName = typeRoot.getTypeName();
try {
IApiType typeDescriptor = typeRoot.getStructure();
IApiAnnotations elementDescription = apiDescription.resolveAnnotations(typeDescriptor.getHandle());
if (typeDescriptor.isMemberType() || typeDescriptor.isAnonymous() || typeDescriptor.isLocal()) {
// we skip nested types (member, local and
// anonymous)
return;
}
int visibility = 0;
if (elementDescription != null) {
visibility = elementDescription.getVisibility();
}
IApiTypeRoot typeRoot2 = null;
if (isSWT) {
typeRoot2 = component2.findTypeRoot(typeName);
} else {
typeRoot2 = component2.findTypeRoot(typeName, id);
}
String deltaComponentID = null;
IApiComponent provider = null;
IApiDescription providerApiDesc = null;
boolean reexported = false;
if (typeRoot2 == null) {
// check if the type is provided by a
// required component (it could have been
// moved/re-exported)
IApiComponent[] providers = component2.getBaseline().resolvePackage(component2, packageName);
SubMonitor providerMonitor = visitMonitor.split(1).setWorkRemaining(providers.length);
int index = 0;
while (typeRoot2 == null && index < providers.length) {
providerMonitor.split(1);
IApiComponent p = providers[index];
if (!p.equals(component2)) {
String id2 = p.getSymbolicName();
if (Util.ORG_ECLIPSE_SWT.equals(id2)) {
typeRoot2 = p.findTypeRoot(typeName);
} else {
typeRoot2 = p.findTypeRoot(typeName, id2);
}
if (typeRoot2 != null) {
provider = p;
providerApiDesc = p.getApiDescription();
IRequiredComponentDescription[] required = component2.getRequiredComponents();
for (IRequiredComponentDescription description : required) {
if (description.getId().equals(id2)) {
reexported = description.isExported();
break;
}
}
}
}
index++;
}
} else {
provider = component2;
providerApiDesc = apiDescription2;
}
visitMonitor.setWorkRemaining(1).split(1);
deltaComponentID = Util.getDeltaComponentVersionsId(component2);
if (typeRoot2 == null) {
if ((visibility & visibilityModifiers) == 0) {
// we skip the class file according to
// their visibility
return;
}
if (visibilityModifiers == VisibilityModifiers.API) {
// if the visibility is API, we only
// consider public and protected types
if (Util.isDefault(typeDescriptor.getModifiers()) || Flags.isPrivate(typeDescriptor.getModifiers())) {
return;
}
}
globalDelta.add(new Delta(deltaComponentID, IDelta.API_COMPONENT_ELEMENT_TYPE, IDelta.REMOVED, IDelta.TYPE, RestrictionModifiers.NO_RESTRICTIONS, RestrictionModifiers.NO_RESTRICTIONS, typeDescriptor.getModifiers(), 0, typeName, typeName, new String[] {
typeName,
component2.isFragment()
? Util.getComponentVersionsId(component2.getHost())
: Util.getComponentVersionsId(component2) }));
} else {
if ((visibility & visibilityModifiers) == 0) {
// we skip the class file according to
// their visibility
return;
}
IApiType typeDescriptor2 = typeRoot2.getStructure();
IApiAnnotations elementDescription2 = providerApiDesc.resolveAnnotations(typeDescriptor2.getHandle());
int visibility2 = 0;
if (elementDescription2 != null) {
visibility2 = elementDescription2.getVisibility();
}
if (visibilityModifiers == VisibilityModifiers.API) {
// if the visibility is API, we only
// consider public and protected types
if (Util.isDefault(typeDescriptor.getModifiers()) || Flags.isPrivate(typeDescriptor.getModifiers())) {
return;
}
}
if (Util.isAPI(visibility, typeDescriptor)) {
if (!Util.isAPI(visibility2, typeDescriptor2)) {
globalDelta.add(new Delta(deltaComponentID, IDelta.API_COMPONENT_ELEMENT_TYPE, IDelta.REMOVED, reexported ? IDelta.REEXPORTED_API_TYPE : IDelta.API_TYPE, elementDescription2 != null ? elementDescription2.getRestrictions() : RestrictionModifiers.NO_RESTRICTIONS, RestrictionModifiers.NO_RESTRICTIONS, typeDescriptor.getModifiers(), typeDescriptor2.getModifiers(), typeName, typeName, new String[] {
typeName,
Util.getComponentVersionsId(component2) }));
return;
}
}
if ((visibility2 & visibilityModifiers) == 0) {
// we simply report a changed visibility
globalDelta.add(new Delta(deltaComponentID, IDelta.API_COMPONENT_ELEMENT_TYPE, IDelta.CHANGED, IDelta.TYPE_VISIBILITY, elementDescription2 != null ? elementDescription2.getRestrictions() : RestrictionModifiers.NO_RESTRICTIONS, RestrictionModifiers.NO_RESTRICTIONS, typeDescriptor.getModifiers(), typeDescriptor2.getModifiers(), typeName, typeName, new String[] {
typeName,
Util.getComponentVersionsId(component2) }));
}
typeRootBaseLineNames.add(typeName);
ClassFileComparator comparator = new ClassFileComparator(typeDescriptor, typeRoot2, component, provider, referenceBaseline, baseline, visibilityModifiers);
IDelta delta = comparator.getDelta(visitMonitor.split(1));
if (ApiPlugin.DEBUG_API_COMPARATOR) {
IStatus status = comparator.getStatus();
if (status != null) {
ApiPlugin.log(status);
}
}
if (delta != null && delta != NO_DELTA) {
globalDelta.add(delta);
}
}
} catch (CoreException e) {
ApiPlugin.log(e);
AbstractProblemDetector.checkIfDisposed(container.getApiComponent(), iterationMonitor);
}
}
});
} catch (CoreException e) {
ApiPlugin.log(e);
AbstractProblemDetector.checkIfDisposed(container.getApiComponent(), loopMonitor);
}
}
}
localmonitor.setWorkRemaining(3);
IRequiredComponentDescription[] requiredComponents = component.getRequiredComponents();
int length = requiredComponents.length;
if (length != 0) {
SubMonitor loopMonitor = localmonitor.split(1).setWorkRemaining(length);
for (int j = 0; j < length; j++) {
SubMonitor iterationMonitor = loopMonitor.split(1).setWorkRemaining(1);
IRequiredComponentDescription description = requiredComponents[j];
if (description.isExported()) {
final String currentComponentID = Util.getDeltaComponentVersionsId(component);
String descriptionID = description.getId();
IApiComponent currentRequiredApiComponent = referenceBaseline.getApiComponent(descriptionID);
if (currentRequiredApiComponent == null) {
continue;
}
final IApiDescription reexportedApiDescription = currentRequiredApiComponent.getApiDescription();
IApiTypeContainer[] apiTypeContainers = currentRequiredApiComponent.getApiTypeContainers();
if (apiTypeContainers != null) {
SubMonitor apiContainerLoopMonitor = iterationMonitor.split(1).setWorkRemaining(apiTypeContainers.length);
for (IApiTypeContainer container : apiTypeContainers) {
SubMonitor apiContainerIterationMonitor = apiContainerLoopMonitor.split(1);
try {
container.accept(new ApiTypeContainerVisitor() {
@Override
public void visit(String packageName, IApiTypeRoot typeRoot) {
apiContainerIterationMonitor.setWorkRemaining(50).split(1);
String typeName = typeRoot.getTypeName();
try {
IApiType typeDescriptor = typeRoot.getStructure();
IApiAnnotations elementDescription = reexportedApiDescription.resolveAnnotations(typeDescriptor.getHandle());
if (typeDescriptor.isMemberType() || typeDescriptor.isAnonymous() || typeDescriptor.isLocal()) {
// we skip nested types (member,
// local and anonymous)
return;
}
int visibility = 0;
if (elementDescription != null) {
visibility = elementDescription.getVisibility();
}
IApiTypeRoot typeRoot2 = null;
if (isSWT) {
typeRoot2 = component2.findTypeRoot(typeName);
} else {
typeRoot2 = component2.findTypeRoot(typeName, id);
}
IApiDescription providerApiDesc = null;
if (typeRoot2 == null) {
// check if the type is provided
// by a required component (it
// could have been
// moved/re-exported)
IApiComponent[] providers = component2.getBaseline().resolvePackage(component2, packageName);
int index = 0;
while (typeRoot2 == null && index < providers.length) {
IApiComponent p = providers[index];
if (!p.equals(component2)) {
String id2 = p.getSymbolicName();
if (Util.ORG_ECLIPSE_SWT.equals(id2)) {
typeRoot2 = p.findTypeRoot(typeName);
} else {
typeRoot2 = p.findTypeRoot(typeName, id2);
}
if (typeRoot2 != null) {
providerApiDesc = p.getApiDescription();
}
}
index++;
}
} else {
providerApiDesc = apiDescription2;
}
if (typeRoot2 == null) {
if ((visibility & visibilityModifiers) == 0) {
// we skip the class file
// according to their
// visibility
return;
}
if (visibilityModifiers == VisibilityModifiers.API) {
// if the visibility is API,
// we only consider public
// and protected types
if (Util.isDefault(typeDescriptor.getModifiers()) || Flags.isPrivate(typeDescriptor.getModifiers())) {
return;
}
}
globalDelta.add(new Delta(currentComponentID, IDelta.API_COMPONENT_ELEMENT_TYPE, IDelta.REMOVED, IDelta.REEXPORTED_TYPE, RestrictionModifiers.NO_RESTRICTIONS, RestrictionModifiers.NO_RESTRICTIONS, typeDescriptor.getModifiers(), 0, typeName, typeName, new String[] {
typeName,
Util.getComponentVersionsId(component) }));
} else {
typeRootBaseLineNames.add(typeName);
IApiType typeDescriptor2 = typeRoot2.getStructure();
IApiAnnotations elementDescription2 = providerApiDesc.resolveAnnotations(typeDescriptor2.getHandle());
int visibility2 = 0;
if (elementDescription2 != null) {
visibility2 = elementDescription2.getVisibility();
}
// if the visibility is API, we
// only consider public and
// protected types
if (Util.isDefault(typeDescriptor.getModifiers()) || Flags.isPrivate(typeDescriptor.getModifiers())) {
return;
}
if (Util.isAPI(visibility, typeDescriptor)) {
if (!Util.isAPI(visibility2, typeDescriptor2)) {
globalDelta.add(new Delta(currentComponentID, IDelta.API_COMPONENT_ELEMENT_TYPE, IDelta.REMOVED, IDelta.REEXPORTED_API_TYPE, elementDescription2 != null ? elementDescription2.getRestrictions() : RestrictionModifiers.NO_RESTRICTIONS, RestrictionModifiers.NO_RESTRICTIONS, typeDescriptor.getModifiers(), typeDescriptor2.getModifiers(), typeName, typeName, new String[] {
typeName,
Util.getComponentVersionsId(component) }));
return;
}
}
}
} catch (CoreException e) {
ApiPlugin.log(e);
AbstractProblemDetector.checkIfDisposed(container.getApiComponent(), apiContainerIterationMonitor);
}
}
});
} catch (CoreException e) {
ApiPlugin.log(e);
AbstractProblemDetector.checkIfDisposed(container.getApiComponent(), apiContainerIterationMonitor);
}
}
}
}
}
}
localmonitor.setWorkRemaining(2);
if (typeRootContainers2 != null) {
SubMonitor loopMonitor = localmonitor.split(1).setWorkRemaining(typeRootContainers2.length);
for (IApiTypeContainer container : typeRootContainers2) {
SubMonitor iterationMonitor = loopMonitor.split(1);
try {
container.accept(new ApiTypeContainerVisitor() {
@Override
public void visit(String packageName, IApiTypeRoot typeRoot) {
iterationMonitor.setWorkRemaining(50).split(1);
String typeName = typeRoot.getTypeName();
try {
IApiType type = typeRoot.getStructure();
IApiAnnotations elementDescription = apiDescription2.resolveAnnotations(type.getHandle());
if (type.isMemberType() || type.isLocal() || type.isAnonymous()) {
// we skip nested types (member, local and
// anonymous)
return;
}
if (filterType(visibilityModifiers, elementDescription, type)) {
return;
}
if (typeRootBaseLineNames.contains(typeName)) {
// already processed
return;
}
typeRootBaseLineNames.add(typeName);
String deltaComponentID = Util.getDeltaComponentVersionsId(component2);
globalDelta.add(new Delta(deltaComponentID, IDelta.API_COMPONENT_ELEMENT_TYPE, IDelta.ADDED, IDelta.TYPE, elementDescription != null ? elementDescription.getRestrictions() : RestrictionModifiers.NO_RESTRICTIONS, RestrictionModifiers.NO_RESTRICTIONS, 0, type.getModifiers(), typeName, typeName, new String[] {
typeName,
Util.getComponentVersionsId(component2) }));
} catch (CoreException e) {
ApiPlugin.log(e);
AbstractProblemDetector.checkIfDisposed(container.getApiComponent(), iterationMonitor);
}
}
});
} catch (CoreException e) {
ApiPlugin.log(e);
AbstractProblemDetector.checkIfDisposed(container.getApiComponent(), iterationMonitor);
}
}
}
localmonitor.setWorkRemaining(1);
requiredComponents = component2.getRequiredComponents();
length = requiredComponents.length;
if (length != 0) {
SubMonitor loopMonitor = localmonitor.split(1).setWorkRemaining(length);
for (int j = 0; j < length; j++) {
SubMonitor iterationMonitor = loopMonitor.split(1).setWorkRemaining(1);
IRequiredComponentDescription description = requiredComponents[j];
if (description.isExported()) {
final String currentComponentID = Util.getDeltaComponentVersionsId(component);
String descriptionID = description.getId();
IApiComponent currentRequiredApiComponent = baseline.getApiComponent(descriptionID);
if (currentRequiredApiComponent == null) {
continue;
}
IApiTypeContainer[] apiTypeContainers = currentRequiredApiComponent.getApiTypeContainers();
final IApiDescription reexportedApiDescription = currentRequiredApiComponent.getApiDescription();
if (apiTypeContainers != null) {
SubMonitor typeContainerLoopMonitor = iterationMonitor.split(1).setWorkRemaining(apiTypeContainers.length);
for (IApiTypeContainer container : apiTypeContainers) {
SubMonitor typeContainerIterationMonitor = typeContainerLoopMonitor.split(1);
try {
container.accept(new ApiTypeContainerVisitor() {
@Override
public void visit(String packageName, IApiTypeRoot typeRoot) {
typeContainerIterationMonitor.setWorkRemaining(50).split(1);
String typeName = typeRoot.getTypeName();
try {
IApiType typeDescriptor = typeRoot.getStructure();
IApiAnnotations elementDescription = reexportedApiDescription.resolveAnnotations(typeDescriptor.getHandle());
if (typeDescriptor.isMemberType() || typeDescriptor.isAnonymous() || typeDescriptor.isLocal()) {
// we skip nested types (member,
// local and anonymous)
return;
}
if (filterType(visibilityModifiers, elementDescription, typeDescriptor)) {
return;
}
if (typeRootBaseLineNames.contains(typeName)) {
// already processed
return;
}
typeRootBaseLineNames.add(typeName);
globalDelta.add(new Delta(currentComponentID, IDelta.API_COMPONENT_ELEMENT_TYPE, IDelta.ADDED, IDelta.REEXPORTED_TYPE, elementDescription != null ? elementDescription.getRestrictions() : RestrictionModifiers.NO_RESTRICTIONS, RestrictionModifiers.NO_RESTRICTIONS, 0, typeDescriptor.getModifiers(), typeName, typeName, new String[] {
typeName,
Util.getComponentVersionsId(component) }));
} catch (CoreException e) {
ApiPlugin.log(e);
AbstractProblemDetector.checkIfDisposed(container.getApiComponent(), typeContainerIterationMonitor);
}
}
});
} catch (CoreException e) {
ApiPlugin.log(e);
AbstractProblemDetector.checkIfDisposed(container.getApiComponent(), typeContainerIterationMonitor);
}
}
}
}
}
}
return globalDelta.isEmpty() ? NO_DELTA : globalDelta;
}
}