blob: 3a631f553012e8484a01886e46c9487c62285d13 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2009, 2015 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.search;
import java.util.Set;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.pde.api.tools.internal.AntFilterStore;
import org.eclipse.pde.api.tools.internal.builder.AbstractProblemDetector;
import org.eclipse.pde.api.tools.internal.builder.ProblemDetectorBuilder;
import org.eclipse.pde.api.tools.internal.builder.Reference;
import org.eclipse.pde.api.tools.internal.builder.ReferenceAnalyzer;
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.IApiFilterStore;
import org.eclipse.pde.api.tools.internal.provisional.VisibilityModifiers;
import org.eclipse.pde.api.tools.internal.provisional.builder.IApiProblemDetector;
import org.eclipse.pde.api.tools.internal.provisional.builder.IReference;
import org.eclipse.pde.api.tools.internal.provisional.comparator.ApiScope;
import org.eclipse.pde.api.tools.internal.provisional.model.IApiComponent;
import org.eclipse.pde.api.tools.internal.provisional.model.IApiElement;
import org.eclipse.pde.api.tools.internal.provisional.model.IApiMember;
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.problems.IApiProblem;
import org.eclipse.pde.api.tools.internal.provisional.search.ApiSearchEngine;
import org.eclipse.pde.api.tools.internal.provisional.search.IApiSearchRequestor;
/**
* Default implementation of an {@link IApiSearchRequestor} to use with the
* {@link ApiSearchEngine}. This requestor returns a search scope composed of
* the dependent (visible) {@link IApiComponent}s for the given
* {@link IApiElement}
*
* <p>
* The references are filtered based on api filter stores. The filters may come
* from a .api_filters found in the component or a separate filter location set
* in the ant task via {@link #setFilterRoot(String)}. If filter files are found
* in both locations, the filters at both will be applied.
* </p>
*
* @since 1.0.0
*/
public class UseSearchRequestor implements IApiSearchRequestor {
/**
* The backing elements to search with
*/
private Set<String> fComponentIds = null;
/**
* The current {@link IApiFilterStore} from the current
* {@link IApiComponent} context we are visiting.
*/
private IApiFilterStore currentStore = null;
/**
* The current {@link IApiFilterStore} for the current {@link IApiComponent}
* context that we are visiting. The filter store will be created by finding
* each component's filter file in the root filter location
* {@link #antFilterRoot}.
*/
private IApiFilterStore antStore = null;
/**
* The root directory of the .api_filters files that should be used to
* filter references.
*
* The .api_filters files specify specific problems to ignore during api
* analysis. During the use scan, the problem filters will be used to filter
* the use scan results.
*
* The root is specified using an absolute path. The root needs to contain
* the following structure:
*
* <pre>
* root
* |
* +-- component name (i.e. org.eclipse.jface)
* |
* +--- .api_filters
* </pre>
*/
private String antFilterRoot = null;
/**
* The mask to use while searching
*/
private int fSearchMask = 0;
/**
* The search scope for this requestor
*/
private IApiScope fScope = null;
/**
* Patterns for jar API type roots to not scan
*/
private String[] jarPatterns = null;
/**
* The default {@link ReferenceAnalyzer} for detecting illegal API use
*
* @see #includesIllegalUse()
*/
ReferenceAnalyzer fAnalyzer = null;
/**
* Constructor
*
* @param elements an array of {@link IApiElement}s for the search engine to
* use
* @param scope the raw list of {@link IApiElement}s to extract references
* from
* @param searchkinds the kinds of references to search for. <br>
* Options include:
* <ol>
* <li>{@link #INCLUDE_API}</li>
* <li>{@link #INCLUDE_INTERNAL}</li>
* </ol>
*/
public UseSearchRequestor(Set<String> elementnames, IApiElement[] scope, int searchkinds) {
fSearchMask = searchkinds;
fComponentIds = elementnames;
fAnalyzer = new ReferenceAnalyzer();
prepareScope(scope);
}
@Override
public boolean acceptComponent(IApiComponent component) {
try {
if (!component.isSystemComponent() && getScope().encloses(component)) {
if (includesIllegalUse()) {
fAnalyzer.buildProblemDetectors(component, ProblemDetectorBuilder.K_USE, null);
}
currentStore = component.getFilterStore();
antStore = antFilterRoot != null ? new AntFilterStore(antFilterRoot, component.getSymbolicName()) : null;
return true;
}
} catch (CoreException ce) {
// do nothing, return false
}
currentStore = null;
return false;
}
@Override
public boolean acceptContainer(IApiTypeContainer container) {
return considerTypeContainer(container);
}
@Override
public boolean acceptMember(IApiMember member) {
// don't consider inner types, as they are considered with the root type
switch (member.getType()) {
case IApiElement.TYPE: {
IApiType type = (IApiType) member;
return !(type.isMemberType() || type.isLocal());
}
default: {
return true;
}
}
}
/**
* Returns if the given {@link IApiTypeContainer} should be processed
*
* @param container
* @return true if the container should be processed false otherwise
*/
boolean considerTypeContainer(IApiTypeContainer container) {
if (jarPatterns != null && container != null) {
if (container.getContainerType() == IApiTypeContainer.ARCHIVE) {
String[] pparts = null;
for (String jarPattern : jarPatterns) {
pparts = jarPattern.split(":"); //$NON-NLS-1$
if (pparts.length != 2) {
continue;
}
if (container.getApiComponent().getSymbolicName().equals(pparts[0])) {
if (container.getName().endsWith(pparts[1])) {
return false;
}
}
}
}
}
return true;
}
@Override
public boolean acceptReference(IReference reference, IProgressMonitor monitor) {
try {
IApiMember member = reference.getResolvedReference();
if (member != null) {
IApiComponent component = member.getApiComponent();
if (!fComponentIds.contains(component.getSymbolicName()) || component.equals(reference.getMember().getApiComponent())) {
return false;
}
if (isIllegalUse(reference, monitor) || (includesAPI() && includesInternal())) {
return true;
}
IApiAnnotations annots = component.getApiDescription().resolveAnnotations(member.getHandle());
if (annots != null) {
int vis = annots.getVisibility();
if (VisibilityModifiers.isAPI(vis) && includesAPI()) {
return true;
} else if (VisibilityModifiers.isPrivate(vis) && includesInternal()) {
return true;
}
}
}
} catch (CoreException ce) {
ApiPlugin.log(ce);
AbstractProblemDetector.checkIfDisposed(reference.getMember().getApiComponent(), monitor);
}
return false;
}
/**
* Returns true if the given reference is an illegal usage reference iff
* illegal use is part of the search mask.
*
* @param reference
* @return true if the reference is illegal use false otherwise
* @since 1.1
*/
boolean isIllegalUse(IReference reference, IProgressMonitor monitor) {
IApiProblemDetector[] detectors = fAnalyzer.getProblemDetectors(reference.getReferenceKind());
for (IApiProblemDetector detector : detectors) {
if (monitor.isCanceled()) {
break;
}
if (detector.considerReference(reference, monitor)) {
Reference ref = (Reference) reference;
ref.setFlags(IReference.F_ILLEGAL);
try {
IApiProblem pb = ((AbstractProblemDetector) detector).checkAndCreateProblem(reference, monitor);
if (pb != null && !isFiltered(pb)) {
ref.addProblems(pb);
} else {
return false;
}
} catch (CoreException e) {
ApiPlugin.log(e);
AbstractProblemDetector.checkIfDisposed(reference.getMember().getApiComponent(), monitor);
}
return true;
}
}
return false;
}
/**
* Returns if the given problem is filtered
*
* @param problem
* @return <code>true</code> is filtered, false otherwise
*/
boolean isFiltered(IApiProblem problem) {
return (currentStore != null && currentStore.isFiltered(problem)) || (antStore != null && antStore.isFiltered(problem));
}
@Override
public int getReferenceKinds() {
return IReference.MASK_REF_ALL & ~IReference.REF_CONSTANTPOOL;
}
/**
* Prepares the search scope based on the available entries in the
* constructor
*
* @param elements
*/
private void prepareScope(IApiElement[] elements) {
if (elements != null) {
fScope = new ApiScope();
for (IApiElement element : elements) {
fScope.addElement(element.getApiComponent());
}
}
}
@Override
public IApiScope getScope() {
return fScope;
}
@Override
public boolean includesAPI() {
return (fSearchMask & INCLUDE_API) > 0;
}
@Override
public boolean includesInternal() {
return (fSearchMask & INCLUDE_INTERNAL) > 0;
}
@Override
public boolean includesIllegalUse() {
return (fSearchMask & INCLUDE_ILLEGAL_USE) > 0;
}
/**
* The patterns for jar names to exclude from the search
*
* @param patterns
*/
public void setJarPatterns(String[] patterns) {
jarPatterns = patterns;
}
/**
* Sets the root directory of the .api_filters files that should be used to
* filter references.
*
* The .api_filters files specify specific problems to ignore during api
* analysis. During the use scan, the problem filters will be used to filter
* the use scan results. If .api_filter files are found inside the component
* those filters will be applied in addition to any found at this filter
* root.
*
* The root is specified using an absolute path. The root needs to contain
* the following structure:
*
* <pre>
* root
* |
* +-- component name (i.e. org.eclipse.jface)
* |
* +--- .api_filters
* </pre>
*
* @param filterRoot the absolute string path to the root of the filters
*/
public void setFilterRoot(String filterRoot) {
antFilterRoot = filterRoot;
}
}