blob: 41996da2e4f7b2f4e468baea52ec0720febb6386 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2006, 2012 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.internal.core;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.FactoryConfigurationError;
import javax.xml.parsers.ParserConfigurationException;
import org.eclipse.osgi.service.resolver.BundleDescription;
import org.eclipse.osgi.util.ManifestElement;
import org.eclipse.pde.core.plugin.IPlugin;
import org.eclipse.pde.core.plugin.IPluginBase;
import org.eclipse.pde.core.plugin.IPluginLibrary;
import org.eclipse.pde.core.plugin.IPluginModelBase;
import org.eclipse.pde.internal.core.ibundle.IBundleModel;
import org.eclipse.pde.internal.core.ibundle.IBundlePluginModelBase;
import org.osgi.framework.BundleException;
import org.osgi.framework.Constants;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.helpers.DefaultHandler;
/**
* Stores additional information from the manifest files of plugins and stores
* this information in separate xml file. Accessed through PDEState.
*/
public class PDEAuxiliaryState {
private static String CACHE_EXTENSION = ".pluginInfo"; //$NON-NLS-1$
private static String ATTR_BUNDLE_ID = "bundleID"; //$NON-NLS-1$
private static String ATTR_BUNDLE_STRUCTURE = "isBundle"; //$NON-NLS-1$
private static String ATTR_CLASS = "class"; //$NON-NLS-1$
private static String ATTR_EXPORTED = "exported"; //$NON-NLS-1$
private static String ATTR_EXTENSIBLE_API = "hasExtensibleAPI"; //$NON-NLS-1$
private static String ATTR_LOCALIZATION = "localization"; //$NON-NLS-1$
private static String ATTR_NAME = "name"; //$NON-NLS-1$
private static String ATTR_PATCH = "patch"; //$NON-NLS-1$
private static String ATTR_PROJECT = "project"; //$NON-NLS-1$
private static String ATTR_PROVIDER = "provider"; //$NON-NLS-1$
private static String ATTR_BUNDLE_SOURCE = "bundleSource"; //$NON-NLS-1$
private static String ELEMENT_BUNDLE = "bundle"; //$NON-NLS-1$
private static String ELEMENT_LIB = "library"; //$NON-NLS-1$
private static String ELEMENT_ROOT = "map"; //$NON-NLS-1$
protected Map<String, PluginInfo> fPluginInfos;
/**
* Constructor
*/
protected PDEAuxiliaryState() {
fPluginInfos = new HashMap<>();
}
/**
* Constructor, gets the plugin info objects stored in the passed state
* and adds them to this state.
* @param state state containing plugin infos to initialize this state with
*/
protected PDEAuxiliaryState(PDEAuxiliaryState state) {
fPluginInfos = new HashMap<>(state.fPluginInfos);
}
/**
* Provides a simple way of storing auxiliary data for a plugin
*/
static class PluginInfo {
String name;
String providerName;
String className;
boolean hasExtensibleAPI;
boolean isPatchFragment;
boolean hasBundleStructure;
String[] libraries;
String project;
String localization;
String bundleSourceEntry;
boolean exportsExternalAnnotations;
}
/**
* Helper method to create a plugin info object for the given
* element. The plugin info object is added to the map.
* @param element
*/
private void createPluginInfo(Element element) {
PluginInfo info = new PluginInfo();
if (element.hasAttribute(ATTR_NAME)) {
info.name = element.getAttribute(ATTR_NAME);
}
if (element.hasAttribute(ATTR_PROVIDER)) {
info.providerName = element.getAttribute(ATTR_PROVIDER);
}
if (element.hasAttribute(ATTR_CLASS)) {
info.className = element.getAttribute(ATTR_CLASS);
}
info.hasExtensibleAPI = "true".equals(element.getAttribute(ATTR_EXTENSIBLE_API)); //$NON-NLS-1$
info.isPatchFragment = "true".equals(element.getAttribute(ATTR_PATCH)); //$NON-NLS-1$
info.hasBundleStructure = !"false".equals(element.getAttribute(ATTR_BUNDLE_STRUCTURE)); //$NON-NLS-1$
if (element.hasAttribute(ATTR_PROJECT)) {
info.project = element.getAttribute(ATTR_PROJECT);
}
if (element.hasAttribute(ATTR_LOCALIZATION)) {
info.localization = element.getAttribute(ATTR_LOCALIZATION);
}
if (element.hasAttribute(ATTR_BUNDLE_SOURCE)) {
info.bundleSourceEntry = element.getAttribute(ATTR_BUNDLE_SOURCE);
}
NodeList libs = element.getChildNodes();
ArrayList<String> list = new ArrayList<>(libs.getLength());
for (int i = 0; i < libs.getLength(); i++) {
if (libs.item(i).getNodeType() == Node.ELEMENT_NODE) {
Element lib = (Element) libs.item(i);
list.add(lib.getAttribute(ATTR_NAME));
}
}
info.libraries = list.toArray(new String[list.size()]);
fPluginInfos.put(element.getAttribute(ATTR_BUNDLE_ID), info);
}
public String getClassName(long bundleID) {
PluginInfo info = fPluginInfos.get(Long.toString(bundleID));
return info == null ? null : info.className;
}
public boolean hasExtensibleAPI(long bundleID) {
PluginInfo info = fPluginInfos.get(Long.toString(bundleID));
return info == null ? false : info.hasExtensibleAPI;
}
public boolean isPatchFragment(long bundleID) {
PluginInfo info = fPluginInfos.get(Long.toString(bundleID));
return info == null ? false : info.isPatchFragment;
}
public boolean hasBundleStructure(long bundleID) {
PluginInfo info = fPluginInfos.get(Long.toString(bundleID));
return info == null ? false : info.hasBundleStructure;
}
public String getPluginName(long bundleID) {
PluginInfo info = fPluginInfos.get(Long.toString(bundleID));
return info == null ? null : info.name;
}
public String getProviderName(long bundleID) {
PluginInfo info = fPluginInfos.get(Long.toString(bundleID));
return info == null ? null : info.providerName;
}
public String[] getLibraryNames(long bundleID) {
PluginInfo info = fPluginInfos.get(Long.toString(bundleID));
return info == null ? new String[0] : info.libraries;
}
public String getBundleLocalization(long bundleID) {
PluginInfo info = fPluginInfos.get(Long.toString(bundleID));
return info == null ? null : info.localization;
}
public String getProject(long bundleID) {
PluginInfo info = fPluginInfos.get(Long.toString(bundleID));
return info == null ? null : info.project;
}
public String getBundleSourceEntry(long bundleID) {
PluginInfo info = fPluginInfos.get(Long.toString(bundleID));
return info == null ? null : info.bundleSourceEntry;
}
public boolean exportsExternalAnnotations(long bundleID) {
PluginInfo info = fPluginInfos.get(Long.toString(bundleID));
return info == null ? false : info.exportsExternalAnnotations;
}
/**
* Builds an xml document storing the auxiliary plugin info.
* @param dir directory location to create the file
*/
protected void savePluginInfo(File dir) {
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
Document doc = factory.newDocumentBuilder().newDocument();
Element root = doc.createElement(ELEMENT_ROOT);
Iterator<String> iter = fPluginInfos.keySet().iterator();
while (iter.hasNext()) {
String key = iter.next();
Element element = doc.createElement(ELEMENT_BUNDLE);
element.setAttribute(ATTR_BUNDLE_ID, key);
PluginInfo info = fPluginInfos.get(key);
if (info.className != null) {
element.setAttribute(ATTR_CLASS, info.className);
}
if (info.providerName != null) {
element.setAttribute(ATTR_PROVIDER, info.providerName);
}
if (info.name != null) {
element.setAttribute(ATTR_NAME, info.name);
}
if (info.hasExtensibleAPI) {
element.setAttribute(ATTR_EXTENSIBLE_API, "true"); //$NON-NLS-1$
}
if (info.isPatchFragment) {
element.setAttribute(ATTR_PATCH, "true"); //$NON-NLS-1$
}
if (!info.hasBundleStructure) {
element.setAttribute(ATTR_BUNDLE_STRUCTURE, "false"); //$NON-NLS-1$
}
if (info.localization != null) {
element.setAttribute(ATTR_LOCALIZATION, info.localization);
}
if (info.bundleSourceEntry != null) {
element.setAttribute(ATTR_BUNDLE_SOURCE, info.bundleSourceEntry);
}
if (info.libraries != null) {
for (String library : info.libraries) {
Element lib = doc.createElement(ELEMENT_LIB);
lib.setAttribute(ATTR_NAME, library);
element.appendChild(lib);
}
}
root.appendChild(element);
}
doc.appendChild(root);
XMLPrintHandler.writeFile(doc, new File(dir, CACHE_EXTENSION));
} catch (Exception e) {
PDECore.log(e);
}
}
/**
* Loads plugin info objects from the pluginInfo xml file stored in the
* given directory.
* @param dir location to look for the pluginInfo file
* @return true if the file was read successfully, false otherwise
*/
protected boolean readPluginInfoCache(File dir) {
File file = new File(dir, CACHE_EXTENSION);
if (file.exists() && file.isFile()) {
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder documentBuilder = factory.newDocumentBuilder();
documentBuilder.setErrorHandler(new DefaultHandler());
Document doc = documentBuilder.parse(file);
Element root = doc.getDocumentElement();
if (root != null) {
NodeList list = root.getChildNodes();
for (int i = 0; i < list.getLength(); i++) {
if (list.item(i).getNodeType() == Node.ELEMENT_NODE) {
createPluginInfo((Element) list.item(i));
}
}
}
return true;
} catch (org.xml.sax.SAXException | IOException | ParserConfigurationException e) {
PDECore.log(e);
}
}
return false;
}
/**
* Returns whether the auxiliary state exists in the given directory.
*
* @param dir parent directory
* @return whether the state file exist
*/
protected boolean exists(File dir) {
File file = new File(dir, CACHE_EXTENSION);
return file.exists() && file.isFile();
}
/**
* Writes out auxiliary information from the given models to an xml file
* in the given destination directory.
* @param models models to collect information from
* @param destination directory to create the xml file in
*/
public static void writePluginInfo(IPluginModelBase[] models, File destination) {
try {
DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
Document doc = builder.newDocument();
Element root = doc.createElement(ELEMENT_ROOT);
doc.appendChild(root);
for (int i = 0; i < models.length; i++) {
IPluginBase plugin = models[i].getPluginBase();
BundleDescription desc = models[i].getBundleDescription();
if (desc == null) {
continue;
}
Element element = doc.createElement(ELEMENT_BUNDLE);
element.setAttribute(ATTR_BUNDLE_ID, Long.toString(desc.getBundleId()));
element.setAttribute(ATTR_PROJECT, models[i].getUnderlyingResource().getProject().getName());
if (plugin instanceof IPlugin && ((IPlugin) plugin).getClassName() != null) {
element.setAttribute(ATTR_CLASS, ((IPlugin) plugin).getClassName());
}
if (plugin.getProviderName() != null) {
element.setAttribute(ATTR_PROVIDER, plugin.getProviderName());
}
if (plugin.getName() != null) {
element.setAttribute(ATTR_NAME, plugin.getName());
}
if (ClasspathUtilCore.hasExtensibleAPI(models[i])) {
element.setAttribute(ATTR_EXTENSIBLE_API, "true"); //$NON-NLS-1$
} else if (ClasspathUtilCore.isPatchFragment(models[i])) {
element.setAttribute(ATTR_PATCH, "true"); //$NON-NLS-1$
}
if (!(models[i] instanceof IBundlePluginModelBase)) {
element.setAttribute(ATTR_BUNDLE_STRUCTURE, "false"); //$NON-NLS-1$
}
if (models[i] instanceof IBundlePluginModelBase) {
String localization = ((IBundlePluginModelBase) models[i]).getBundleLocalization();
if (localization != null) {
element.setAttribute(ATTR_LOCALIZATION, localization);
}
}
if (models[i] instanceof IBundlePluginModelBase) {
IBundleModel bundleModel = ((IBundlePluginModelBase) models[i]).getBundleModel();
if (bundleModel != null) {
String bundleSourceEntry = bundleModel.getBundle().getHeader(ICoreConstants.ECLIPSE_SOURCE_BUNDLE);
if (bundleSourceEntry != null) {
element.setAttribute(ATTR_BUNDLE_SOURCE, bundleSourceEntry);
}
}
}
IPluginLibrary[] libraries = plugin.getLibraries();
for (int j = 0; j < libraries.length; j++) {
Element lib = doc.createElement(ELEMENT_LIB);
lib.setAttribute(ATTR_NAME, libraries[j].getName());
if (!libraries[j].isExported()) {
lib.setAttribute(ATTR_EXPORTED, "false"); //$NON-NLS-1$
}
element.appendChild(lib);
}
root.appendChild(element);
}
XMLPrintHandler.writeFile(doc, new File(destination, CACHE_EXTENSION));
} catch (ParserConfigurationException | FactoryConfigurationError | IOException e) {
}
}
/**
* Collects auxiliary information from the manifest and stores it in this state.
* @param desc bundle description for the given manifest
* @param manifest dictionary of headers in the bundle's manifest file
* @param hasBundleStructure whether the plugin has bundle structure
*/
protected void addAuxiliaryData(BundleDescription desc, Map<String, String> manifest, boolean hasBundleStructure) {
PluginInfo info = new PluginInfo();
info.name = manifest.get(Constants.BUNDLE_NAME);
info.providerName = manifest.get(Constants.BUNDLE_VENDOR);
String className = manifest.get(ICoreConstants.PLUGIN_CLASS);
info.className = className != null ? className : (String) manifest.get(Constants.BUNDLE_ACTIVATOR);
info.libraries = getClasspath(manifest);
info.hasExtensibleAPI = "true".equals(manifest.get(ICoreConstants.EXTENSIBLE_API)); //$NON-NLS-1$
info.isPatchFragment = "true".equals(manifest.get(ICoreConstants.PATCH_FRAGMENT)); //$NON-NLS-1$
info.localization = manifest.get(Constants.BUNDLE_LOCALIZATION);
info.hasBundleStructure = hasBundleStructure;
info.bundleSourceEntry = manifest.get(ICoreConstants.ECLIPSE_SOURCE_BUNDLE);
info.exportsExternalAnnotations = "true" //$NON-NLS-1$
.equals(manifest.get(ICoreConstants.ECLIPSE_EXPORT_EXTERNAL_ANNOTATIONS));
fPluginInfos.put(Long.toString(desc.getBundleId()), info);
}
/**
* Retrieves the classpath entries from the manifest dictionary
* @param manifest dictionary containing manifest headers
* @return string array of classpath entries
*/
protected String[] getClasspath(Map<String, String> manifest) {
String fullClasspath = manifest.get(Constants.BUNDLE_CLASSPATH);
String[] result = new String[0];
try {
if (fullClasspath != null) {
ManifestElement[] classpathEntries = ManifestElement.parseHeader(Constants.BUNDLE_CLASSPATH, fullClasspath);
result = new String[classpathEntries.length];
for (int i = 0; i < classpathEntries.length; i++) {
result[i] = classpathEntries[i].getValue();
}
}
} catch (BundleException e) {
}
return result;
}
/**
* Clears the plugin info object map.
*/
protected void clear() {
fPluginInfos.clear();
}
}