blob: 7fe2a2ce68d3b77673f8418ae6e532eca8763b8a [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2007, 2013 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.model;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.osgi.service.resolver.BundleDescription;
import org.eclipse.osgi.util.NLS;
import org.eclipse.pde.api.tools.internal.ApiDescriptionManager;
import org.eclipse.pde.api.tools.internal.ApiFilterStore;
import org.eclipse.pde.api.tools.internal.CoreMessages;
import org.eclipse.pde.api.tools.internal.provisional.ApiPlugin;
import org.eclipse.pde.api.tools.internal.provisional.IApiDescription;
import org.eclipse.pde.api.tools.internal.provisional.IApiFilterStore;
import org.eclipse.pde.api.tools.internal.provisional.model.IApiBaseline;
import org.eclipse.pde.api.tools.internal.provisional.model.IApiTypeContainer;
import org.eclipse.pde.api.tools.internal.util.Util;
import org.eclipse.pde.core.build.IBuild;
import org.eclipse.pde.core.build.IBuildEntry;
import org.eclipse.pde.core.build.IBuildModel;
import org.eclipse.pde.core.plugin.IPluginModelBase;
import org.eclipse.pde.core.plugin.PluginRegistry;
import org.osgi.framework.BundleException;
/**
* An API component for a plug-in project in the workspace.
* <p>
* Note: this class requires a running workspace to be instantiated.
* </p>
*
* @since 1.0.0
*/
public class ProjectComponent extends BundleComponent {
/**
* Constant used to describe the custom build.properties entry
*
* @since 1.0.3
*/
public static final String ENTRY_CUSTOM = "custom"; //$NON-NLS-1$
/**
* Constant used to describe build.properties that start with
* <code>extra.</code>
*
* @since 1.0.3
*/
public static final String EXTRA_PREFIX = "extra."; //$NON-NLS-1$
/**
* Associated Java project
*/
private IJavaProject fProject;
/**
* Associated IPluginModelBase object
*/
private IPluginModelBase fModel;
/**
* A cache of bundle class path entries to class file containers.
*/
private volatile Map<String, IApiTypeContainer> fPathToOutputContainers;
/**
* A cache of output location paths to corresponding class file containers.
*/
private volatile Map<IPath, IApiTypeContainer> fOutputLocationToContainer;
/**
* Constructs an API component for the given Java project in the specified
* baseline.
*
* @param baseline the owning API baseline
* @param location the given location of the component
* @param model the given model
* @param bundleid
* @throws CoreException if unable to create the API component
*/
public ProjectComponent(IApiBaseline baseline, String location, IPluginModelBase model, long bundleid) throws CoreException {
super(baseline, location, bundleid);
IPath path = new Path(location);
IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(path.lastSegment());
this.fProject = JavaCore.create(project);
this.fModel = model;
}
@Override
protected void setName(String newname) {
// Override to use the translated name from the plug-in model
super.setName(fModel.getResourceString(newname));
}
@Override
protected boolean isBinary() {
return false;
}
@Override
protected BundleDescription getBundleDescription(Map<String, String> manifest, String location, long id) throws BundleException {
try {
BundleDescription result = getModel().getBundleDescription();
if (result == null) {
throw new BundleException("Cannot find manifest for bundle at " + location); //$NON-NLS-1$
}
return result;
} catch (CoreException ce) {
throw new BundleException(ce.getMessage());
}
}
/**
* Returns the {@link IPluginModelBase} backing this component
*
* @return the {@link IPluginModelBase} or throws and exception, never
* retruns <code>null</code>
* @throws CoreException
*/
IPluginModelBase getModel() throws CoreException {
if (fModel == null) {
fModel = PluginRegistry.findModel(fProject.getProject());
if (fModel == null) {
abort(NLS.bind(CoreMessages.ProjectComponent_could_not_locate_model, fProject.getElementName()), null);
}
}
return fModel;
}
@Override
protected boolean isApiEnabled() {
return Util.isApiProject(fProject);
}
@Override
public void dispose() {
if (isDisposed()) {
return;
}
try {
if (hasApiFilterStore()) {
getFilterStore().dispose();
}
fModel = null;
if (fOutputLocationToContainer != null) {
fOutputLocationToContainer.clear();
fOutputLocationToContainer = null;
}
if (fPathToOutputContainers != null) {
fPathToOutputContainers.clear();
fPathToOutputContainers = null;
}
} catch (CoreException ce) {
ApiPlugin.log(ce);
} finally {
super.dispose();
}
}
@Override
protected IApiDescription createLocalApiDescription() throws CoreException {
long time = System.currentTimeMillis();
if (Util.isApiProject(getJavaProject())) {
setHasApiDescription(true);
}
IApiDescription apiDesc = ApiDescriptionManager.getManager().getApiDescription(this, getBundleDescription());
if (ApiPlugin.DEBUG_PROJECT_COMPONENT) {
System.out.println("Time to create api description for: [" + fProject.getElementName() + "] " + (System.currentTimeMillis() - time) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
return apiDesc;
}
@Override
protected IApiFilterStore createApiFilterStore() throws CoreException {
long time = System.currentTimeMillis();
IApiFilterStore store = new ApiFilterStore(getJavaProject());
if (ApiPlugin.DEBUG_PROJECT_COMPONENT) {
System.out.println("Time to create api filter store for: [" + fProject.getElementName() + "] " + (System.currentTimeMillis() - time) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
return store;
}
@Override
protected List<IApiTypeContainer> createApiTypeContainers() throws CoreException {
if (isDisposed()) {
baselineDisposed(getBaseline());
}
// first populate build.properties cache so we can create class file
// containers
// from bundle classpath entries
fPathToOutputContainers = new ConcurrentHashMap<>(4);
fOutputLocationToContainer = new ConcurrentHashMap<>(4);
if (fProject.exists() && fProject.getProject().isOpen()) {
IPluginModelBase model = PluginRegistry.findModel(fProject.getProject());
if (model != null) {
createContainersFromProjectModel(model, this, fPathToOutputContainers, fOutputLocationToContainer);
}
return super.createApiTypeContainers();
}
return Collections.emptyList();
}
private static void createContainersFromProjectModel(IPluginModelBase model, ProjectComponent project,
Map<String, IApiTypeContainer> pathToOutputContainers,
Map<IPath, IApiTypeContainer> outputLocationToContainer) throws CoreException {
IBuildModel buildModel = PluginRegistry.createBuildModel(model);
if (buildModel == null) {
return;
}
IBuild build = buildModel.getBuild();
IBuildEntry entry = build.getEntry(ENTRY_CUSTOM);
if (entry != null) {
String[] tokens = entry.getTokens();
if (tokens.length == 1 && tokens[0].equals("true")) { //$NON-NLS-1$
// hack : add the current output location for each
// classpath entries
IClasspathEntry[] classpathEntries = project.fProject.getRawClasspath();
List<IApiTypeContainer> containers = new ArrayList<>();
for (IClasspathEntry classpathEntrie : classpathEntries) {
IClasspathEntry classpathEntry = classpathEntrie;
switch (classpathEntry.getEntryKind())
{
case IClasspathEntry.CPE_SOURCE:
String containerPath = classpathEntry.getPath().removeFirstSegments(1).toString();
IApiTypeContainer container = getApiTypeContainer(containerPath, project,
outputLocationToContainer);
if (container != null && !containers.contains(container)) {
containers.add(container);
}
break;
case IClasspathEntry.CPE_VARIABLE:
classpathEntry = JavaCore.getResolvedClasspathEntry(classpathEntry);
//$FALL-THROUGH$
case IClasspathEntry.CPE_LIBRARY:
IPath path = classpathEntry.getPath();
if (Util.isArchive(path.lastSegment())) {
IResource resource = ResourcesPlugin.getWorkspace().getRoot().findMember(path);
if (resource != null) {
// jar inside the workspace
containers.add(
new ArchiveApiTypeContainer(project, resource.getLocation().toOSString()));
} else {
// external jar
containers.add(new ArchiveApiTypeContainer(project, path.toOSString()));
}
}
break;
default:
break;
}
}
if (!containers.isEmpty()) {
IApiTypeContainer cfc = null;
if (containers.size() == 1) {
cfc = containers.get(0);
} else {
cfc = new CompositeApiTypeContainer(project, containers);
}
pathToOutputContainers.put(".", cfc); //$NON-NLS-1$
}
}
} else {
IBuildEntry[] entries = build.getBuildEntries();
int length = entries.length;
for (int i = 0; i < length; i++) {
IBuildEntry buildEntry = entries[i];
String name = buildEntry.getName();
if (name.startsWith(IBuildEntry.JAR_PREFIX)) {
retrieveContainers(name, IBuildEntry.JAR_PREFIX, buildEntry, project, pathToOutputContainers,
outputLocationToContainer);
} else if (name.startsWith(EXTRA_PREFIX)) {
retrieveContainers(name, EXTRA_PREFIX, buildEntry, project, pathToOutputContainers,
outputLocationToContainer);
}
}
}
}
private static void retrieveContainers(String name, String prefix, IBuildEntry buildEntry, ProjectComponent project,
Map<String, IApiTypeContainer> pathToOutputContainers,
Map<IPath, IApiTypeContainer> outputLocationToContainer) throws CoreException {
String jar = name.substring(prefix.length());
String[] tokens = buildEntry.getTokens();
if (tokens.length == 1) {
IApiTypeContainer container = getApiTypeContainer(tokens[0], project, outputLocationToContainer);
if (container != null) {
IApiTypeContainer existingContainer = pathToOutputContainers.get(jar);
if (existingContainer != null) {
// concat both containers
List<IApiTypeContainer> allContainers = new ArrayList<>();
allContainers.add(existingContainer);
allContainers.add(container);
IApiTypeContainer apiTypeContainer = new CompositeApiTypeContainer(project, allContainers);
pathToOutputContainers.put(jar, apiTypeContainer);
} else {
pathToOutputContainers.put(jar, container);
}
}
} else {
List<IApiTypeContainer> containers = new ArrayList<>();
for (String currentToken : tokens) {
IApiTypeContainer container = getApiTypeContainer(currentToken, project, outputLocationToContainer);
if (container != null && !containers.contains(container)) {
containers.add(container);
}
}
if (!containers.isEmpty()) {
IApiTypeContainer existingContainer = pathToOutputContainers.get(jar);
if (existingContainer != null) {
// concat both containers
containers.add(existingContainer);
}
IApiTypeContainer cfc = null;
if (containers.size() == 1) {
cfc = containers.get(0);
} else {
cfc = new CompositeApiTypeContainer(project, containers);
}
pathToOutputContainers.put(jar, cfc);
}
}
}
@Override
protected IApiTypeContainer createApiTypeContainer(String path) throws CoreException {
if (isDisposed() || this.fPathToOutputContainers == null) {
baselineDisposed(getBaseline());
}
IApiTypeContainer container = fPathToOutputContainers.get(path);
if (container == null) {
// could be a binary jar included in the plug-in, just look for it
container = findApiTypeContainer(path);
}
return container;
}
/**
* Finds and returns an existing {@link IApiTypeContainer} at the specified
* location in this project, or <code>null</code> if none.
*
* @param location project relative path to the class file container
* @return {@link IApiTypeContainer} or <code>null</code>
*/
private IApiTypeContainer findApiTypeContainer(String location) {
IResource res = fProject.getProject().findMember(new Path(location));
if (res != null) {
if (res.getType() == IResource.FILE) {
return new ArchiveApiTypeContainer(this, res.getLocation().toOSString());
} else {
return new DirectoryApiTypeContainer(this, res.getLocation().toOSString());
}
}
return null;
}
/**
* Finds and returns an {@link IApiTypeContainer} for the specified source
* folder, or <code>null</code> if it does not exist. If the source folder
* shares an output location with a previous source folder, the output
* location is shared (a new one is not created).
*
* @param location project relative path to the source folder
* @return {@link IApiTypeContainer} or <code>null</code>
*/
private static IApiTypeContainer getApiTypeContainer(String location, ProjectComponent component,
Map<IPath, IApiTypeContainer> outputLocationToContainer) throws CoreException {
IJavaProject project = component.fProject;
IResource res = project.getProject().findMember(new Path(location));
if (res != null) {
IPackageFragmentRoot root = project.getPackageFragmentRoot(res);
if (root.exists()) {
if (root.getKind() == IPackageFragmentRoot.K_BINARY) {
if (res.getType() == IResource.FOLDER) {
// class file folder
IPath location2 = res.getLocation();
IApiTypeContainer cfc = outputLocationToContainer.get(location2);
if (cfc == null) {
cfc = new ProjectTypeContainer(component, (IContainer) res);
outputLocationToContainer.put(location2, cfc);
}
return cfc;
}
} else {
IClasspathEntry entry = root.getRawClasspathEntry();
IPath outputLocation = entry.getOutputLocation();
if (outputLocation == null) {
outputLocation = project.getOutputLocation();
}
IApiTypeContainer cfc = outputLocationToContainer.get(outputLocation);
if (cfc == null) {
IPath projectFullPath = project.getProject().getFullPath();
IContainer container = null;
if (projectFullPath.equals(outputLocation)) {
// The project is its own output location
container = project.getProject();
} else {
container = project.getProject().getWorkspace().getRoot().getFolder(outputLocation);
}
cfc = new ProjectTypeContainer(component, container);
outputLocationToContainer.put(outputLocation, cfc);
}
return cfc;
}
}
}
return null;
}
/**
* Returns the Java project associated with this component.
*
* @return associated Java project
*/
public IJavaProject getJavaProject() {
return fProject;
}
/**
* Returns the cached API type container for the given package fragment
* root, or <code>null</code> if none. The given package fragment has to be
* a SOURCE package fragment - this method is only used by the project API
* description to obtain a class file corresponding to a compilation unit
* when tag scanning (to resolve signatures).
*
* @param root source package fragment root
* @return API type container associated with the package fragment root, or
* <code>null</code> if none
*/
public IApiTypeContainer getTypeContainer(IPackageFragmentRoot root) throws CoreException {
if (root.getKind() == IPackageFragmentRoot.K_SOURCE) {
if (isDisposed()) {
baselineDisposed(getBaseline());
}
getApiTypeContainers(); // ensure initialized
IResource resource = root.getResource();
if (resource != null) {
String location = resource.getProjectRelativePath().toString();
return getApiTypeContainer(location, this, fOutputLocationToContainer);
}
}
return null;
}
}