blob: 02327408b46520275b25e67d54c685ecbf7bae48 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2009, 2017 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.core.target;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.util.Map;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExtension;
import org.eclipse.core.runtime.IExtensionRegistry;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.RegistryFactory;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.spi.RegistryContributor;
import org.eclipse.equinox.frameworkadmin.BundleInfo;
import org.eclipse.osgi.util.ManifestElement;
import org.eclipse.osgi.util.NLS;
import org.eclipse.pde.internal.core.ICoreConstants;
import org.eclipse.pde.internal.core.PDECore;
import org.eclipse.pde.internal.core.target.Messages;
import org.eclipse.pde.internal.core.util.ManifestUtils;
import org.osgi.framework.BundleException;
import org.osgi.framework.Constants;
/**
* Describes a single bundle in a target definition. Also used to represent
* content in the target that is missing or invalid.
*
* @since 3.8
*/
public class TargetBundle {
/**
* Status code indicating that this target bundle represents a required plug-in that is missing from a target definition
*/
public static final int STATUS_PLUGIN_DOES_NOT_EXIST = 100;
/**
* Status code indicating that this target bundle represents a required feature that is missing from a target definition
*/
public static final int STATUS_FEATURE_DOES_NOT_EXIST = 110;
/**
* Status code indicating that a required bundle version does not exist (a bundle
* with the correct symbolic name is present, but the specified version was not
* found).
*/
public static final int STATUS_VERSION_DOES_NOT_EXIST = 101;
/**
* Status code indicating that a bundle's manifest could not be read, or did not exist.
*/
public static final int STATUS_INVALID_MANIFEST = 102;
protected BundleInfo fInfo;
protected boolean fIsFragment = false;
protected BundleInfo fSourceTarget;
protected String fSourcePath = null;
/**
* Constructs a target bundle for a local bundle. The bundle may be a directory or
* an archive file. The manifest of the bundle will be read to collect the additional
* information.
*
* @param bundleLocation the location of the bundle (directory or archive) to open
* @throws CoreException if there is a problem opening the bundle or its manifest
*/
public TargetBundle(File bundleLocation) throws CoreException {
initialize(bundleLocation);
}
/**
* Constructs an empty target bundle with no information.
*/
protected TargetBundle() {
fInfo = new BundleInfo();
}
/**
* Returns a {@link BundleInfo} object containing additional information about the bundle
* this target bundle represents. It is not guaranteed that the bundle info will have any
* fields set. The base implementation of {@link TargetBundle} will fill in the location,
* symbolic name and version if that information was available in the bundle's manifest.
*
* @return a bundle info object with information on the bundle this target bundle represents
*/
public BundleInfo getBundleInfo() {
return fInfo;
}
/**
* Returns a status object describing any problems with this target bundle. The base
* implementation of {@link TargetBundle} will always return an OK status.
*
* @return status of this bundle
*/
public IStatus getStatus() {
// The status will always be ok as the constructor would throw an exception for any issues.
return Status.OK_STATUS;
}
/**
* Returns <code>true</code> if this bundle is a source bundle and
* <code>false</code> if this bundle is an executable bundle.
*
* @return whether the resolved bundle is a source bundle
*/
public boolean isSourceBundle() {
return fSourceTarget != null;
}
/**
* If this bundle is a source bundle this method returns a bundle info
* representing the executable bundle that this bundle provides source for.
* The returned bundle info may not have a symbolic name and version set if
* this source bundle is an old style source plug-in.
*
* @return bundle info representing bundle this bundle provides source for or <code>null</code>
*/
public BundleInfo getSourceTarget() {
return fSourceTarget;
}
/**
* Returns whether this bundle is a fragment.
*
* @return whether this bundle is a fragment
*/
public boolean isFragment() {
return fIsFragment;
}
/**
* Returns bundle relative path to old-style source folders, or <code>null</code>
* if not applicable.
*
* @return bundle relative path to old-style source folders, or <code>null</code>
*/
public String getSourcePath() {
return fSourcePath;
}
/**
* Initializes the contents of this target bundle from the provided local bundle
*
* @param file the bundle to initialize from
*/
private void initialize(File file) throws CoreException {
if (file == null || !file.exists()) {
throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, NLS.bind(Messages.TargetFeature_FileDoesNotExist, file)));
}
Map<String, String> manifest = ManifestUtils.loadManifest(file);
try {
fInfo = new BundleInfo(file.toURI());
// Attempt to retrieve additional bundle information from the manifest
String header = manifest.get(Constants.BUNDLE_SYMBOLICNAME);
if (header != null) {
ManifestElement[] elements = ManifestElement.parseHeader(Constants.BUNDLE_SYMBOLICNAME, header);
if (elements != null) {
String name = elements[0].getValue();
if (name != null) {
fInfo.setSymbolicName(name);
header = manifest.get(Constants.BUNDLE_VERSION);
if (header != null) {
elements = ManifestElement.parseHeader(Constants.BUNDLE_VERSION, header);
if (elements != null) {
fInfo.setVersion(elements[0].getValue());
}
}
}
fSourceTarget = getProvidedSource(file, name, manifest);
}
}
fIsFragment = manifest.containsKey(Constants.FRAGMENT_HOST);
} catch (BundleException e) {
throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, STATUS_INVALID_MANIFEST, NLS.bind(Messages.TargetBundle_ErrorReadingManifest, file.getAbsolutePath()), e));
}
}
/**
* If the given bundle is a source bundle, the bundle that this bundle provides source for will be returned.
* If the given bundle is not a source bundle or there was a problem getting the source target, <code>null</code>
* will be returned.
*
* @param bundle location of the bundle in the file system, can be <code>null</code> to skip searching plugin.xml
* @param symbolicName symbolic name of the bundle, can be <code>null</code> to skip searching of plugin.xml
* @param manifest the bundle's manifest, can be <code>null</code> to skip searching of manifest entries
* @return bundle for provided source or <code>null</code> if not a source bundle
*/
private BundleInfo getProvidedSource(File bundle, String symbolicName, Map<String, String> manifest) {
fSourcePath = null;
if (manifest != null) {
if (manifest.containsKey(ICoreConstants.ECLIPSE_SOURCE_BUNDLE)) {
try {
ManifestElement[] manifestElements = ManifestElement.parseHeader(ICoreConstants.ECLIPSE_SOURCE_BUNDLE, manifest.get(ICoreConstants.ECLIPSE_SOURCE_BUNDLE));
if (manifestElements != null) {
for (ManifestElement element : manifestElements) {
String binaryPluginName = element.getValue();
String versionEntry = element.getAttribute(Constants.VERSION_ATTRIBUTE);
// Currently the version attribute is required
if (binaryPluginName != null && binaryPluginName.length() > 0 && versionEntry != null && versionEntry.length() > 0) {
return new BundleInfo(binaryPluginName, versionEntry, null, BundleInfo.NO_LEVEL, false);
}
}
}
} catch (BundleException e) {
PDECore.log(e);
return null;
}
}
// source bundles never have a class path
if (manifest.containsKey(Constants.BUNDLE_CLASSPATH)) {
return null;
}
}
if (bundle != null && symbolicName != null) {
// old source bundles were never jar'd
if (bundle.isFile()) {
return null;
}
// check for an "org.eclipse.pde.core.source" extension
File pxml = new File(bundle, ICoreConstants.PLUGIN_FILENAME_DESCRIPTOR);
if (!pxml.exists()) {
pxml = new File(bundle, ICoreConstants.FRAGMENT_FILENAME_DESCRIPTOR);
}
if (pxml.exists()) {
IExtensionRegistry registry = RegistryFactory.createRegistry(null, this, this);
// Contribute PDE source extension point
String bogusDef = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<?eclipse version=\"3.2\"?>\n<plugin><extension-point id=\"source\" name=\"source\"/>\n</plugin>"; //$NON-NLS-1$
RegistryContributor pointContributor = new RegistryContributor(PDECore.PLUGIN_ID, PDECore.PLUGIN_ID, null, null);
registry.addContribution(new ByteArrayInputStream(bogusDef.getBytes()), pointContributor, false, null, null, this);
// Search for extensions to the extension point
RegistryContributor contributor = new RegistryContributor(symbolicName, symbolicName, null, null);
try {
registry.addContribution(new BufferedInputStream(new FileInputStream(pxml)), contributor, false, null, null, this);
IExtension[] extensions = registry.getExtensions(contributor);
for (IExtension extension : extensions) {
if (ICoreConstants.EXTENSION_POINT_SOURCE.equals(extension.getExtensionPointUniqueIdentifier())) {
IConfigurationElement[] elements = extension.getConfigurationElements();
if (elements.length == 1) {
fSourcePath = elements[0].getAttribute("path"); //$NON-NLS-1$
}
return new BundleInfo(null, null, bundle.toURI(), BundleInfo.NO_LEVEL, false);
}
}
} catch (FileNotFoundException e) {
}
}
}
return null;
}
@Override
public String toString() {
StringBuilder result = new StringBuilder(getBundleInfo().toString());
IStatus status = getStatus();
if (status != null && !status.isOK()) {
result.append(' ').append(status.toString());
}
return result.toString();
}
}