diff options
author | Thomas Watson | 2013-06-19 13:12:02 +0000 |
---|---|---|
committer | Thomas Watson | 2013-07-12 18:59:59 +0000 |
commit | 9eb78bc46db677c14ed8cbb5581f05008fbcb2e6 (patch) | |
tree | 74a01abb39343c18a985aa0eab71c614f0f97745 /bundles/org.eclipse.osgi.compatibility.plugins | |
parent | f06f147312372e02aac14920a567604c548a946b (diff) | |
download | rt.equinox.framework-9eb78bc46db677c14ed8cbb5581f05008fbcb2e6.tar.gz rt.equinox.framework-9eb78bc46db677c14ed8cbb5581f05008fbcb2e6.tar.xz rt.equinox.framework-9eb78bc46db677c14ed8cbb5581f05008fbcb2e6.zip |
Add compatibility fragment to support 2.0 style plugins
Diffstat (limited to 'bundles/org.eclipse.osgi.compatibility.plugins')
18 files changed, 1953 insertions, 0 deletions
diff --git a/bundles/org.eclipse.osgi.compatibility.plugins/.classpath b/bundles/org.eclipse.osgi.compatibility.plugins/.classpath new file mode 100644 index 000000000..ad32c83a7 --- /dev/null +++ b/bundles/org.eclipse.osgi.compatibility.plugins/.classpath @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="UTF-8"?> +<classpath> + <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/> + <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/> + <classpathentry kind="src" path="src"/> + <classpathentry kind="output" path="bin"/> +</classpath> diff --git a/bundles/org.eclipse.osgi.compatibility.plugins/.gitignore b/bundles/org.eclipse.osgi.compatibility.plugins/.gitignore new file mode 100644 index 000000000..5e56e040e --- /dev/null +++ b/bundles/org.eclipse.osgi.compatibility.plugins/.gitignore @@ -0,0 +1 @@ +/bin diff --git a/bundles/org.eclipse.osgi.compatibility.plugins/.project b/bundles/org.eclipse.osgi.compatibility.plugins/.project new file mode 100644 index 000000000..40eea3d76 --- /dev/null +++ b/bundles/org.eclipse.osgi.compatibility.plugins/.project @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="UTF-8"?> +<projectDescription> + <name>org.eclipse.osgi.compatibility.plugins</name> + <comment></comment> + <projects> + </projects> + <buildSpec> + <buildCommand> + <name>org.eclipse.jdt.core.javabuilder</name> + <arguments> + </arguments> + </buildCommand> + <buildCommand> + <name>org.eclipse.pde.ManifestBuilder</name> + <arguments> + </arguments> + </buildCommand> + <buildCommand> + <name>org.eclipse.pde.SchemaBuilder</name> + <arguments> + </arguments> + </buildCommand> + </buildSpec> + <natures> + <nature>org.eclipse.pde.PluginNature</nature> + <nature>org.eclipse.jdt.core.javanature</nature> + </natures> +</projectDescription> diff --git a/bundles/org.eclipse.osgi.compatibility.plugins/.settings/org.eclipse.jdt.core.prefs b/bundles/org.eclipse.osgi.compatibility.plugins/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 000000000..c537b6306 --- /dev/null +++ b/bundles/org.eclipse.osgi.compatibility.plugins/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,7 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 +org.eclipse.jdt.core.compiler.compliance=1.6 +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.6 diff --git a/bundles/org.eclipse.osgi.compatibility.plugins/.settings/org.eclipse.pde.core.prefs b/bundles/org.eclipse.osgi.compatibility.plugins/.settings/org.eclipse.pde.core.prefs new file mode 100644 index 000000000..f29e940a0 --- /dev/null +++ b/bundles/org.eclipse.osgi.compatibility.plugins/.settings/org.eclipse.pde.core.prefs @@ -0,0 +1,3 @@ +eclipse.preferences.version=1 +pluginProject.extensions=false +resolve.requirebundle=false diff --git a/bundles/org.eclipse.osgi.compatibility.plugins/META-INF/MANIFEST.MF b/bundles/org.eclipse.osgi.compatibility.plugins/META-INF/MANIFEST.MF new file mode 100644 index 000000000..9ccfc997e --- /dev/null +++ b/bundles/org.eclipse.osgi.compatibility.plugins/META-INF/MANIFEST.MF @@ -0,0 +1,8 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: %Bundle-Name +Bundle-SymbolicName: org.eclipse.osgi.compatibility.plugins +Bundle-Version: 1.0.0.qualifier +Fragment-Host: org.eclipse.osgi;bundle-version="3.10.0" +Bundle-RequiredExecutionEnvironment: JavaSE-1.6 +Bundle-Vendor: %Bundle-Vendor diff --git a/bundles/org.eclipse.osgi.compatibility.plugins/OSGI-INF/l10n/bundle.properties b/bundles/org.eclipse.osgi.compatibility.plugins/OSGI-INF/l10n/bundle.properties new file mode 100644 index 000000000..82b8f0726 --- /dev/null +++ b/bundles/org.eclipse.osgi.compatibility.plugins/OSGI-INF/l10n/bundle.properties @@ -0,0 +1,3 @@ +#Properties file for org.eclipse.osgi.compatibility.plugins +Bundle-Vendor = Eclipse.org - Equinox +Bundle-Name = Equinox Compatibility Fragment for Eclipse 2.0 Plugins
\ No newline at end of file diff --git a/bundles/org.eclipse.osgi.compatibility.plugins/about.html b/bundles/org.eclipse.osgi.compatibility.plugins/about.html new file mode 100644 index 000000000..460233046 --- /dev/null +++ b/bundles/org.eclipse.osgi.compatibility.plugins/about.html @@ -0,0 +1,28 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> +<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"/> +<title>About</title> +</head> +<body lang="EN-US"> +<h2>About This Content</h2> + +<p>June 2, 2006</p> +<h3>License</h3> + +<p>The Eclipse Foundation makes available all content in this plug-in ("Content"). Unless otherwise +indicated below, the Content is provided to you under the terms and conditions of the +Eclipse Public License Version 1.0 ("EPL"). A copy of the EPL is available +at <a href="http://www.eclipse.org/legal/epl-v10.html">http://www.eclipse.org/legal/epl-v10.html</a>. +For purposes of the EPL, "Program" will mean the Content.</p> + +<p>If you did not receive this Content directly from the Eclipse Foundation, the Content is +being redistributed by another party ("Redistributor") and different terms and conditions may +apply to your use of any object code in the Content. Check the Redistributor's license that was +provided with the Content. If no such license exists, contact the Redistributor. Unless otherwise +indicated below, the terms and conditions of the EPL still apply to any source code in the Content +and such source code may be obtained at <a href="http://www.eclipse.org">http://www.eclipse.org</a>.</p> + +</body> +</html>
\ No newline at end of file diff --git a/bundles/org.eclipse.osgi.compatibility.plugins/build.properties b/bundles/org.eclipse.osgi.compatibility.plugins/build.properties new file mode 100644 index 000000000..bf4ae9871 --- /dev/null +++ b/bundles/org.eclipse.osgi.compatibility.plugins/build.properties @@ -0,0 +1,8 @@ +source.. = src/ +output.. = bin/ +bin.includes = META-INF/,\ + .,\ + hookconfigurators.properties,\ + OSGI-INF/l10n/bundle.properties,\ + about.html +src.includes = about.html diff --git a/bundles/org.eclipse.osgi.compatibility.plugins/hookconfigurators.properties b/bundles/org.eclipse.osgi.compatibility.plugins/hookconfigurators.properties new file mode 100644 index 000000000..5e02cbe82 --- /dev/null +++ b/bundles/org.eclipse.osgi.compatibility.plugins/hookconfigurators.properties @@ -0,0 +1,12 @@ +############################################################################### +# Copyright (c) 2013 IBM Corporation and others. +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Eclipse Public License v1.0 +# which accompanies this distribution, and is available at +# http://www.eclipse.org/legal/epl-v10.html +# +# Contributors: +# IBM Corporation - initial API and implementation +############################################################################### +hook.configurators= \ + org.eclipse.osgi.compatibility.plugins.PluginConverterHook diff --git a/bundles/org.eclipse.osgi.compatibility.plugins/pom.xml b/bundles/org.eclipse.osgi.compatibility.plugins/pom.xml new file mode 100644 index 000000000..883e4e127 --- /dev/null +++ b/bundles/org.eclipse.osgi.compatibility.plugins/pom.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Copyright (c) 2013 Eclipse Foundation. + All rights reserved. This program and the accompanying materials + are made available under the terms of the Eclipse Distribution License v1.0 + which accompanies this distribution, and is available at + http://www.eclipse.org/org/documents/edl-v10.php + + Contributors: + Igor Fedorenko - initial implementation +--> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <artifactId>rt.equinox.framework</artifactId> + <groupId>org.eclipse.equinox.framework</groupId> + <version>4.3.0-SNAPSHOT</version> + <relativePath>../../</relativePath> + </parent> + <groupId>org.eclipse.osgi</groupId> + <artifactId>org.eclipse.osgi.compatibility.plugins</artifactId> + <version>1.0.0-SNAPSHOT</version> + <packaging>eclipse-plugin</packaging> +</project> diff --git a/bundles/org.eclipse.osgi.compatibility.plugins/src/org/eclipse/osgi/compatibility/plugins/IModel.java b/bundles/org.eclipse.osgi.compatibility.plugins/src/org/eclipse/osgi/compatibility/plugins/IModel.java new file mode 100644 index 000000000..d53ed6882 --- /dev/null +++ b/bundles/org.eclipse.osgi.compatibility.plugins/src/org/eclipse/osgi/compatibility/plugins/IModel.java @@ -0,0 +1,92 @@ +/******************************************************************************* + * Copyright (c) 2000, 2013 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ + +package org.eclipse.osgi.compatibility.plugins; + +/** + * Internal class. + */ +public interface IModel { + public static final int INDENT = 2; + public static final int RADIX = 36; + + public static final String TRUE = "true"; //$NON-NLS-1$ + public static final String FALSE = "false"; //$NON-NLS-1$ + + public static final String REGISTRY = "plugin-registry"; //$NON-NLS-1$ + public static final String REGISTRY_PATH = "path"; //$NON-NLS-1$ + + public static final String FRAGMENT = "fragment"; //$NON-NLS-1$ + public static final String FRAGMENT_ID = "id"; //$NON-NLS-1$ + public static final String FRAGMENT_NAME = "name"; //$NON-NLS-1$ + public static final String FRAGMENT_PROVIDER = "provider-name"; //$NON-NLS-1$ + public static final String FRAGMENT_VERSION = "version"; //$NON-NLS-1$ + public static final String FRAGMENT_PLUGIN_ID = "plugin-id"; //$NON-NLS-1$ + public static final String FRAGMENT_PLUGIN_VERSION = "plugin-version"; //$NON-NLS-1$ + public static final String FRAGMENT_PLUGIN_MATCH = "match"; //$NON-NLS-1$ + public static final String FRAGMENT_PLUGIN_MATCH_PERFECT = "perfect"; //$NON-NLS-1$ + public static final String FRAGMENT_PLUGIN_MATCH_EQUIVALENT = "equivalent"; //$NON-NLS-1$ + public static final String FRAGMENT_PLUGIN_MATCH_COMPATIBLE = "compatible"; //$NON-NLS-1$ + public static final String FRAGMENT_PLUGIN_MATCH_GREATER_OR_EQUAL = "greaterOrEqual"; //$NON-NLS-1$ + + public static final String PLUGIN = "plugin"; //$NON-NLS-1$ + public static final String PLUGIN_ID = "id"; //$NON-NLS-1$ + public static final String PLUGIN_NAME = "name"; //$NON-NLS-1$ + public static final String PLUGIN_VENDOR = "vendor-name"; //$NON-NLS-1$ + public static final String PLUGIN_PROVIDER = "provider-name"; //$NON-NLS-1$ + public static final String PLUGIN_VERSION = "version"; //$NON-NLS-1$ + public static final String PLUGIN_CLASS = "class"; //$NON-NLS-1$ + + public static final String PLUGIN_REQUIRES = "requires"; //$NON-NLS-1$ + public static final String PLUGIN_REQUIRES_PLATFORM = "platform-version"; //$NON-NLS-1$ + public static final String PLUGIN_REQUIRES_PLUGIN = "plugin"; //$NON-NLS-1$ + public static final String PLUGIN_REQUIRES_PLUGIN_VERSION = "version"; //$NON-NLS-1$ + public static final String PLUGIN_REQUIRES_OPTIONAL = "optional"; //$NON-NLS-1$ + public static final String PLUGIN_REQUIRES_IMPORT = "import"; //$NON-NLS-1$ + public static final String PLUGIN_REQUIRES_EXPORT = "export"; //$NON-NLS-1$ + public static final String PLUGIN_REQUIRES_MATCH = "match"; //$NON-NLS-1$ + public static final String PLUGIN_REQUIRES_MATCH_EXACT = "exact"; //$NON-NLS-1$ + public static final String PLUGIN_REQUIRES_MATCH_PERFECT = "perfect"; //$NON-NLS-1$ + public static final String PLUGIN_REQUIRES_MATCH_EQUIVALENT = "equivalent"; //$NON-NLS-1$ + public static final String PLUGIN_REQUIRES_MATCH_COMPATIBLE = "compatible"; //$NON-NLS-1$ + public static final String PLUGIN_REQUIRES_MATCH_GREATER_OR_EQUAL = "greaterOrEqual"; //$NON-NLS-1$ + + public static final String PLUGIN_KEY_VERSION_SEPARATOR = "_"; //$NON-NLS-1$ + + public static final String RUNTIME = "runtime"; //$NON-NLS-1$ + + public static final String LIBRARY = "library"; //$NON-NLS-1$ + public static final String LIBRARY_NAME = "name"; //$NON-NLS-1$ + public static final String LIBRARY_SOURCE = "source"; //$NON-NLS-1$ + public static final String LIBRARY_TYPE = "type"; //$NON-NLS-1$ + public static final String LIBRARY_EXPORT = "export"; //$NON-NLS-1$ + public static final String LIBRARY_EXPORT_MASK = "name"; //$NON-NLS-1$ + public static final String LIBRARY_PACKAGES = "packages"; //$NON-NLS-1$ + public static final String LIBRARY_PACKAGES_PREFIXES = "prefixes"; //$NON-NLS-1$ + + public static final String EXTENSION_POINT = "extension-point"; //$NON-NLS-1$ + public static final String EXTENSION_POINT_NAME = "name"; //$NON-NLS-1$ + public static final String EXTENSION_POINT_ID = "id"; //$NON-NLS-1$ + public static final String EXTENSION_POINT_SCHEMA = "schema"; //$NON-NLS-1$ + + public static final String EXTENSION = "extension"; //$NON-NLS-1$ + public static final String EXTENSION_NAME = "name"; //$NON-NLS-1$ + public static final String EXTENSION_ID = "id"; //$NON-NLS-1$ + public static final String EXTENSION_TARGET = "point"; //$NON-NLS-1$ + + public static final String ELEMENT = "element"; //$NON-NLS-1$ + public static final String ELEMENT_NAME = "name"; //$NON-NLS-1$ + public static final String ELEMENT_VALUE = "value"; //$NON-NLS-1$ + + public static final String PROPERTY = "property"; //$NON-NLS-1$ + public static final String PROPERTY_NAME = "name"; //$NON-NLS-1$ + public static final String PROPERTY_VALUE = "value"; //$NON-NLS-1$ +} diff --git a/bundles/org.eclipse.osgi.compatibility.plugins/src/org/eclipse/osgi/compatibility/plugins/IPluginInfo.java b/bundles/org.eclipse.osgi.compatibility.plugins/src/org/eclipse/osgi/compatibility/plugins/IPluginInfo.java new file mode 100644 index 000000000..214c95d2d --- /dev/null +++ b/bundles/org.eclipse.osgi.compatibility.plugins/src/org/eclipse/osgi/compatibility/plugins/IPluginInfo.java @@ -0,0 +1,52 @@ +/******************************************************************************* + * Copyright (c) 2003, 2013 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.osgi.compatibility.plugins; + +import java.util.*; + +/** + * Interface used as an entry to the IPluginConverter + * + * <p>Internal class.</p> + */ +public interface IPluginInfo { + public Map<String, List<String>> getLibraries(); + + public String[] getLibrariesName(); + + public ArrayList<PluginParser.Prerequisite> getRequires(); + + public String getMasterId(); + + public String getMasterVersion(); + + public String getMasterMatch(); + + public String getPluginClass(); + + public String getUniqueId(); + + public String getVersion(); + + public boolean isFragment(); + + public Set<String> getPackageFilters(); + + public String getPluginName(); + + public String getProviderName(); + + public boolean isSingleton(); + + public boolean hasExtensionExtensionPoints(); + + String validateForm(); +} diff --git a/bundles/org.eclipse.osgi.compatibility.plugins/src/org/eclipse/osgi/compatibility/plugins/PluginConverterHook.java b/bundles/org.eclipse.osgi.compatibility.plugins/src/org/eclipse/osgi/compatibility/plugins/PluginConverterHook.java new file mode 100644 index 000000000..392155ee3 --- /dev/null +++ b/bundles/org.eclipse.osgi.compatibility.plugins/src/org/eclipse/osgi/compatibility/plugins/PluginConverterHook.java @@ -0,0 +1,122 @@ +/******************************************************************************* + * Copyright (c) 2013 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.osgi.compatibility.plugins; + +import java.io.File; +import java.io.IOException; +import java.util.Enumeration; +import org.eclipse.osgi.framework.util.Headers; +import org.eclipse.osgi.internal.hookregistry.*; +import org.eclipse.osgi.service.pluginconversion.PluginConversionException; +import org.eclipse.osgi.service.pluginconversion.PluginConverter; +import org.eclipse.osgi.storage.BundleInfo.Generation; +import org.eclipse.osgi.storage.bundlefile.*; +import org.osgi.framework.*; + +public class PluginConverterHook implements HookConfigurator { + @Override + public void addHooks(final HookRegistry hookRegistry) { + PluginConverterImpl tempConverter; + try { + tempConverter = new PluginConverterImpl(hookRegistry); + } catch (IOException e) { + throw new RuntimeException(e); + } + final PluginConverterImpl converter = tempConverter; + hookRegistry.addBundleFileWrapperFactoryHook(new BundleFileWrapperFactoryHook() { + + @Override + public BundleFile wrapBundleFile(final BundleFile bundleFile, Generation generation, boolean base) { + if (!base) { + return null; + } + return new BundleFile(bundleFile.getBaseFile()) { + + @Override + public void open() throws IOException { + bundleFile.open(); + } + + @Override + public File getFile(String path, boolean nativeCode) { + return bundleFile.getFile(path, nativeCode); + } + + @Override + public Enumeration<String> getEntryPaths(String path) { + return bundleFile.getEntryPaths(path); + } + + @Override + public BundleEntry getEntry(String path) { + BundleEntry entry = bundleFile.getEntry(path); + if (!PluginConverterImpl.OSGI_BUNDLE_MANIFEST.equals(path)) { + return entry; + } + Headers<String, String> headers = null; + if (entry != null) { + try { + headers = Headers.parseManifest(entry.getInputStream()); + } catch (Exception e) { + throw new RuntimeException(e); + } + if (headers.containsKey(Constants.BUNDLE_MANIFESTVERSION)) { + return entry; + } + if (headers.containsKey(Constants.BUNDLE_SYMBOLICNAME)) { + return entry; + } + } + try { + File manifest = converter.convertManifest(bundleFile.getBaseFile(), null, true, null, true, null, false); + if (manifest == null) { + return entry; + } + return new FileBundleEntry(manifest, PluginConverterImpl.OSGI_BUNDLE_MANIFEST); + } catch (PluginConversionException e) { + throw new RuntimeException(e); + } + } + + @Override + public boolean containsDir(String dir) { + return bundleFile.containsDir(dir); + } + + @Override + public void close() throws IOException { + bundleFile.close(); + } + }; + } + }); + + hookRegistry.addActivatorHookFactory(new ActivatorHookFactory() { + + @Override + public BundleActivator createActivator() { + return new BundleActivator() { + ServiceRegistration<PluginConverter> reg; + + @Override + public void start(BundleContext context) throws Exception { + reg = context.registerService(PluginConverter.class, converter, null); + } + + @Override + public void stop(BundleContext context) throws Exception { + reg.unregister(); + } + }; + } + }); + } +} diff --git a/bundles/org.eclipse.osgi.compatibility.plugins/src/org/eclipse/osgi/compatibility/plugins/PluginConverterImpl.java b/bundles/org.eclipse.osgi.compatibility.plugins/src/org/eclipse/osgi/compatibility/plugins/PluginConverterImpl.java new file mode 100644 index 000000000..9ec8f8d01 --- /dev/null +++ b/bundles/org.eclipse.osgi.compatibility.plugins/src/org/eclipse/osgi/compatibility/plugins/PluginConverterImpl.java @@ -0,0 +1,788 @@ +/******************************************************************************* + * Copyright (c) 2003, 2013 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.osgi.compatibility.plugins; + +import java.io.*; +import java.net.URL; +import java.util.*; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +import org.eclipse.osgi.framework.log.FrameworkLogEntry; +import org.eclipse.osgi.internal.framework.EquinoxConfiguration; +import org.eclipse.osgi.internal.framework.EquinoxContainer; +import org.eclipse.osgi.internal.hookregistry.HookRegistry; +import org.eclipse.osgi.service.datalocation.Location; +import org.eclipse.osgi.service.pluginconversion.PluginConversionException; +import org.eclipse.osgi.service.pluginconversion.PluginConverter; +import org.eclipse.osgi.service.resolver.VersionRange; +import org.eclipse.osgi.util.ManifestElement; +import org.eclipse.osgi.util.NLS; +import org.osgi.framework.*; + +/** + * Internal class. + */ +public class PluginConverterImpl implements PluginConverter { + public static boolean DEBUG = false; + /** bundle manifest type unknown */ + static public final byte MANIFEST_TYPE_UNKNOWN = 0x00; + /** bundle manifest type bundle (META-INF/MANIFEST.MF) */ + static public final byte MANIFEST_TYPE_BUNDLE = 0x01; + /** bundle manifest type plugin (plugin.xml) */ + static public final byte MANIFEST_TYPE_PLUGIN = 0x02; + /** bundle manifest type fragment (fragment.xml) */ + static public final byte MANIFEST_TYPE_FRAGMENT = 0x04; + /** bundle manifest type jared bundle */ + static public final byte MANIFEST_TYPE_JAR = 0x08; + private static final String SEMICOLON = "; "; //$NON-NLS-1$ + private static final String UTF_8 = "UTF-8"; //$NON-NLS-1$ + private static final String LIST_SEPARATOR = ",\n "; //$NON-NLS-1$ + private static final String LINE_SEPARATOR = "\n "; //$NON-NLS-1$ + private static final String DOT = "."; //$NON-NLS-1$ + private static int MAXLINE = 511; + private final EquinoxConfiguration configuration; + private final File cacheLocation; + private BufferedWriter out; + private IPluginInfo pluginInfo; + private File pluginManifestLocation; + private ZipFile pluginZip; + private Dictionary<String, String> generatedManifest; + private byte manifestType; + private Version target; + private Dictionary<String, String> devProperties; + static final Version TARGET31 = new Version(3, 1, 0); + static final Version TARGET32 = new Version(3, 2, 0); + private static final String MANIFEST_VERSION = "Manifest-Version"; //$NON-NLS-1$ + private static final String PLUGIN_PROPERTIES_FILENAME = "plugin"; //$NON-NLS-1$ + @SuppressWarnings("deprecation") + private static final String[] ARCH_LIST = {org.eclipse.osgi.service.environment.Constants.ARCH_PA_RISC, org.eclipse.osgi.service.environment.Constants.ARCH_PPC, org.eclipse.osgi.service.environment.Constants.ARCH_SPARC, org.eclipse.osgi.service.environment.Constants.ARCH_X86, org.eclipse.osgi.service.environment.Constants.ARCH_AMD64, org.eclipse.osgi.service.environment.Constants.ARCH_IA64}; + static public final String FRAGMENT_MANIFEST = "fragment.xml"; //$NON-NLS-1$ + static public final String GENERATED_FROM = "Generated-from"; //$NON-NLS-1$ + static public final String MANIFEST_TYPE_ATTRIBUTE = "type"; //$NON-NLS-1$ + private static final String[] OS_LIST = {org.eclipse.osgi.service.environment.Constants.OS_AIX, org.eclipse.osgi.service.environment.Constants.OS_HPUX, org.eclipse.osgi.service.environment.Constants.OS_LINUX, org.eclipse.osgi.service.environment.Constants.OS_MACOSX, org.eclipse.osgi.service.environment.Constants.OS_QNX, org.eclipse.osgi.service.environment.Constants.OS_SOLARIS, org.eclipse.osgi.service.environment.Constants.OS_WIN32}; + protected static final String PI_RUNTIME = "org.eclipse.core.runtime"; //$NON-NLS-1$ + protected static final String PI_BOOT = "org.eclipse.core.boot"; //$NON-NLS-1$ + protected static final String PI_RUNTIME_COMPATIBILITY = "org.eclipse.core.runtime.compatibility"; //$NON-NLS-1$ + static public final String PLUGIN_MANIFEST = "plugin.xml"; //$NON-NLS-1$ + private static final String COMPATIBILITY_ACTIVATOR = "org.eclipse.core.internal.compatibility.PluginActivator"; //$NON-NLS-1$ + private static final String[] WS_LIST = {org.eclipse.osgi.service.environment.Constants.WS_CARBON, org.eclipse.osgi.service.environment.Constants.WS_GTK, org.eclipse.osgi.service.environment.Constants.WS_MOTIF, org.eclipse.osgi.service.environment.Constants.WS_PHOTON, org.eclipse.osgi.service.environment.Constants.WS_WIN32}; + private static final String IGNORE_DOT = "@ignoredot@"; //$NON-NLS-1$ + + public static final String PLUGIN_CLASS = "Plugin-Class"; //$NON-NLS-1$ + public static final String PROVIDE_PACKAGE = "Provide-Package"; //$NON-NLS-1$ + public final static String REPROVIDE_ATTRIBUTE = "reprovide"; //$NON-NLS-1$ + public final static String OPTIONAL_ATTRIBUTE = "optional"; //$NON-NLS-1$ + public static final String ECLIPSE_LAZYSTART = "Eclipse-LazyStart"; //$NON-NLS-1$ + public static final String ECLIPSE_LAZYSTART_EXCEPTIONS = "exceptions"; //$NON-NLS-1$ + + /** + * Manifest header used to specify the auto start properties of a bundle + * @deprecated use {@link #ECLIPSE_LAZYSTART} + */ + public static final String ECLIPSE_AUTOSTART = "Eclipse-AutoStart"; //$NON-NLS-1$ + public static final String OSGI_BUNDLE_MANIFEST = "META-INF/MANIFEST.MF"; //$NON-NLS-1$ + + public PluginConverterImpl(HookRegistry hookRegistry) throws IOException { + this.configuration = hookRegistry.getConfiguration(); + Location configLocation = hookRegistry.getContainer().getLocations().getConfigurationLocation(); + URL cacheURL = configLocation.getDataArea("org.eclipse.osgi.compatibility.plugin"); //$NON-NLS-1$ + this.cacheLocation = new File(cacheURL.getPath()); + } + + private void init() { + // need to make sure these fields are cleared out for each conversion. + out = null; + pluginInfo = null; + pluginManifestLocation = null; + pluginZip = null; + generatedManifest = new Hashtable<String, String>(10); + manifestType = MANIFEST_TYPE_UNKNOWN; + target = null; + devProperties = null; + } + + private boolean fillPluginInfo(File pluginBaseLocation) throws PluginConversionException { + pluginManifestLocation = pluginBaseLocation; + if (pluginManifestLocation == null) + throw new IllegalArgumentException(); + InputStream pluginFile = null; + try { + try { + pluginFile = findPluginManifest(pluginBaseLocation); + } catch (IOException e) { + throw new PluginConversionException(NLS.bind(PluginConverterMsg.ECLIPSE_CONVERTER_FILENOTFOUND, pluginBaseLocation.getAbsolutePath()), e); + } + if (pluginFile == null) + return false; + pluginInfo = parsePluginInfo(pluginFile); + } finally { + if (pluginZip != null) + try { + pluginZip.close(); + pluginZip = null; + } catch (IOException e) { + // ignore + } + } + String validation = pluginInfo.validateForm(); + if (validation != null) + throw new PluginConversionException(validation); + return true; + } + + private Set<String> filterExport(Set<String> exportToFilter, Collection<String> filter) { + if (filter == null || filter.contains("*")) //$NON-NLS-1$ + return exportToFilter; + Set<String> filteredExport = new HashSet<String>(exportToFilter.size()); + for (String anExport : exportToFilter) { + for (String aFilter : filter) { + int dotStar = aFilter.indexOf(".*"); //$NON-NLS-1$ + if (dotStar != -1) + aFilter = aFilter.substring(0, dotStar); + if (anExport.equals(aFilter)) { + filteredExport.add(anExport); + break; + } + } + } + return filteredExport; + } + + private List<String> findOSJars(File pluginRoot, String path, boolean filter) { + path = path.substring(4); + List<String> found = new ArrayList<String>(0); + for (int i = 0; i < OS_LIST.length; i++) { + //look for os/osname/path + String searchedPath = "os/" + OS_LIST[i] + "/" + path; //$NON-NLS-1$ //$NON-NLS-2$ + if (new File(pluginRoot, searchedPath).exists()) + found.add(searchedPath + (filter ? ";(os=" + WS_LIST[i] + ")" : "")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + //look for os/osname/archname/path + for (int j = 0; j < ARCH_LIST.length; j++) { + searchedPath = "os/" + OS_LIST[i] + "/" + ARCH_LIST[j] + "/" + path; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + if (new File(pluginRoot, searchedPath).exists()) { + found.add(searchedPath + (filter ? ";(& (os=" + WS_LIST[i] + ") (arch=" + ARCH_LIST[j] + ")" : "")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ + } + } + } + return found; + } + + private InputStream findPluginManifest(File baseLocation) throws IOException { + //Here, we can not use the bundlefile because it may explode the jar and returns a location from which we will not be able to derive the jars location + if (pluginZip != null) + try { + pluginZip.close(); + } catch (IOException e) { + // ignore + } + pluginZip = null; + if (!baseLocation.isDirectory()) { + manifestType |= MANIFEST_TYPE_JAR; + pluginZip = new ZipFile(baseLocation); + } + + if (pluginZip != null) { + ZipEntry manifestEntry = pluginZip.getEntry(PLUGIN_MANIFEST); + if (manifestEntry != null) { + manifestType |= MANIFEST_TYPE_PLUGIN; + return pluginZip.getInputStream(manifestEntry); + } + } else { + File manifestFile = new File(baseLocation, PLUGIN_MANIFEST); + if (manifestFile.exists()) { + manifestType |= MANIFEST_TYPE_PLUGIN; + return new FileInputStream(manifestFile); + } + } + + if (pluginZip != null) { + ZipEntry manifestEntry = pluginZip.getEntry(FRAGMENT_MANIFEST); + if (manifestEntry != null) { + manifestType |= MANIFEST_TYPE_PLUGIN; + return pluginZip.getInputStream(manifestEntry); + } + } else { + File manifestFile = new File(baseLocation, FRAGMENT_MANIFEST); + if (manifestFile.exists()) { + manifestType |= MANIFEST_TYPE_FRAGMENT; + return new FileInputStream(manifestFile); + } + } + + return null; + } + + private List<String> findWSJars(File pluginRoot, String path, boolean filter) { + path = path.substring(4); + List<String> found = new ArrayList<String>(0); + for (int i = 0; i < WS_LIST.length; i++) { + String searchedPath = "ws/" + WS_LIST[i] + path; //$NON-NLS-1$ + if (new File(pluginRoot, searchedPath).exists()) { + found.add(searchedPath + (filter ? ";(ws=" + WS_LIST[i] + ")" : "")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + } + return found; + } + + protected void fillManifest(boolean compatibilityManifest, boolean analyseJars) { + generateManifestVersion(); + generateHeaders(); + generateClasspath(); + generateActivator(); + generatePluginClass(); + if (analyseJars) + generateProvidePackage(); + generateRequireBundle(); + generateLocalizationEntry(); + generateEclipseHeaders(); + if (compatibilityManifest) { + generateTimestamp(); + } + } + + @SuppressWarnings("deprecation") + public void writeManifest(File generationLocation, Dictionary<String, String> manifestToWrite, boolean compatibilityManifest) throws PluginConversionException { + long start = System.currentTimeMillis(); + try { + File parentFile = new File(generationLocation.getParent()); + parentFile.mkdirs(); + generationLocation.createNewFile(); + if (!generationLocation.isFile()) { + String message = NLS.bind(PluginConverterMsg.ECLIPSE_CONVERTER_ERROR_CREATING_BUNDLE_MANIFEST, this.pluginInfo.getUniqueId(), generationLocation); + throw new PluginConversionException(message); + } + // replaces any eventual existing file + manifestToWrite = new Hashtable<String, String>((Hashtable) manifestToWrite); + // MANIFEST.MF files must be written using UTF-8 + out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(generationLocation), UTF_8)); + writeEntry(MANIFEST_VERSION, manifestToWrite.remove(MANIFEST_VERSION)); + writeEntry(GENERATED_FROM, manifestToWrite.remove(GENERATED_FROM)); //Need to do this first uptoDate check expect the generated-from tag to be in the first line + // always attempt to write the Bundle-ManifestVersion header if it exists (bug 109863) + writeEntry(Constants.BUNDLE_MANIFESTVERSION, manifestToWrite.remove(Constants.BUNDLE_MANIFESTVERSION)); + writeEntry(Constants.BUNDLE_NAME, manifestToWrite.remove(Constants.BUNDLE_NAME)); + writeEntry(Constants.BUNDLE_SYMBOLICNAME, manifestToWrite.remove(Constants.BUNDLE_SYMBOLICNAME)); + writeEntry(Constants.BUNDLE_VERSION, manifestToWrite.remove(Constants.BUNDLE_VERSION)); + writeEntry(Constants.BUNDLE_CLASSPATH, manifestToWrite.remove(Constants.BUNDLE_CLASSPATH)); + writeEntry(Constants.BUNDLE_ACTIVATOR, manifestToWrite.remove(Constants.BUNDLE_ACTIVATOR)); + writeEntry(Constants.BUNDLE_VENDOR, manifestToWrite.remove(Constants.BUNDLE_VENDOR)); + writeEntry(Constants.FRAGMENT_HOST, manifestToWrite.remove(Constants.FRAGMENT_HOST)); + writeEntry(Constants.BUNDLE_LOCALIZATION, manifestToWrite.remove(Constants.BUNDLE_LOCALIZATION)); + // always attempt to write the Export-Package header if it exists (bug 109863) + writeEntry(Constants.EXPORT_PACKAGE, manifestToWrite.remove(Constants.EXPORT_PACKAGE)); + // always attempt to write the Provide-Package header if it exists (bug 109863) + writeEntry(PluginConverterImpl.PROVIDE_PACKAGE, manifestToWrite.remove(PluginConverterImpl.PROVIDE_PACKAGE)); + writeEntry(Constants.REQUIRE_BUNDLE, manifestToWrite.remove(Constants.REQUIRE_BUNDLE)); + Enumeration<String> keys = manifestToWrite.keys(); + while (keys.hasMoreElements()) { + String key = keys.nextElement(); + writeEntry(key, manifestToWrite.get(key)); + } + out.flush(); + } catch (IOException e) { + String message = NLS.bind(PluginConverterMsg.ECLIPSE_CONVERTER_ERROR_CREATING_BUNDLE_MANIFEST, this.pluginInfo.getUniqueId(), generationLocation); + throw new PluginConversionException(message, e); + } finally { + if (out != null) + try { + out.close(); + } catch (IOException e) { + // only report problems writing to/flushing the file + } + } + if (DEBUG) + System.out.println("Time to write out converted manifest to: " + generationLocation + ": " + (System.currentTimeMillis() - start) + "ms."); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + + private void generateLocalizationEntry() { + generatedManifest.put(Constants.BUNDLE_LOCALIZATION, PLUGIN_PROPERTIES_FILENAME); + } + + private void generateManifestVersion() { + generatedManifest.put(MANIFEST_VERSION, "1.0"); //$NON-NLS-1$ + } + + private boolean requireRuntimeCompatibility() { + ArrayList<PluginParser.Prerequisite> requireList = pluginInfo.getRequires(); + for (Iterator<PluginParser.Prerequisite> iter = requireList.iterator(); iter.hasNext();) { + if (iter.next().getName().equalsIgnoreCase(PI_RUNTIME_COMPATIBILITY)) + return true; + } + return false; + } + + private void generateActivator() { + if (!pluginInfo.isFragment()) + if (!requireRuntimeCompatibility()) { + String pluginClass = pluginInfo.getPluginClass(); + if (pluginClass != null && !pluginClass.trim().equals("")) //$NON-NLS-1$ + generatedManifest.put(Constants.BUNDLE_ACTIVATOR, pluginClass); + } else { + generatedManifest.put(Constants.BUNDLE_ACTIVATOR, COMPATIBILITY_ACTIVATOR); + } + } + + private void generateClasspath() { + String[] classpath = pluginInfo.getLibrariesName(); + if (classpath.length != 0) + generatedManifest.put(Constants.BUNDLE_CLASSPATH, getStringFromArray(classpath, LIST_SEPARATOR)); + } + + private void generateHeaders() { + if (TARGET31.compareTo(target) <= 0) + generatedManifest.put(Constants.BUNDLE_MANIFESTVERSION, "2"); //$NON-NLS-1$ + generatedManifest.put(Constants.BUNDLE_NAME, pluginInfo.getPluginName()); + generatedManifest.put(Constants.BUNDLE_VERSION, pluginInfo.getVersion()); + generatedManifest.put(Constants.BUNDLE_SYMBOLICNAME, getSymbolicNameEntry()); + String provider = pluginInfo.getProviderName(); + if (provider != null) + generatedManifest.put(Constants.BUNDLE_VENDOR, provider); + if (pluginInfo.isFragment()) { + StringBuffer hostBundle = new StringBuffer(); + hostBundle.append(pluginInfo.getMasterId()); + String versionRange = getVersionRange(pluginInfo.getMasterVersion(), pluginInfo.getMasterMatch()); // TODO need to get match rule here! + if (versionRange != null) + hostBundle.append(versionRange); + generatedManifest.put(Constants.FRAGMENT_HOST, hostBundle.toString()); + } + } + + /* + * Generates an entry in the form: + * <symbolic-name>[; singleton=true] + */ + private String getSymbolicNameEntry() { + // false is the default, so don't bother adding anything + if (!pluginInfo.isSingleton()) + return pluginInfo.getUniqueId(); + StringBuffer result = new StringBuffer(pluginInfo.getUniqueId()); + result.append(SEMICOLON); + result.append(Constants.SINGLETON_DIRECTIVE); + String assignment = TARGET31.compareTo(target) <= 0 ? ":=" : "="; //$NON-NLS-1$ //$NON-NLS-2$ + result.append(assignment).append("true"); //$NON-NLS-1$ + return result.toString(); + } + + private void generatePluginClass() { + if (requireRuntimeCompatibility()) { + String pluginClass = pluginInfo.getPluginClass(); + if (pluginClass != null) + generatedManifest.put(PluginConverterImpl.PLUGIN_CLASS, pluginClass); + } + } + + @SuppressWarnings("deprecation") + private void generateProvidePackage() { + Set<String> exports = getExports(); + if (exports != null && exports.size() != 0) { + generatedManifest.put(TARGET31.compareTo(target) <= 0 ? Constants.EXPORT_PACKAGE : PluginConverterImpl.PROVIDE_PACKAGE, getStringFromCollection(exports, LIST_SEPARATOR)); + } + } + + @SuppressWarnings("deprecation") + private void generateRequireBundle() { + ArrayList<PluginParser.Prerequisite> requiredBundles = pluginInfo.getRequires(); + if (requiredBundles.size() == 0) + return; + StringBuffer bundleRequire = new StringBuffer(); + for (Iterator<PluginParser.Prerequisite> iter = requiredBundles.iterator(); iter.hasNext();) { + PluginParser.Prerequisite element = iter.next(); + StringBuffer modImport = new StringBuffer(element.getName()); + String versionRange = getVersionRange(element.getVersion(), element.getMatch()); + if (versionRange != null) + modImport.append(versionRange); + if (element.isExported()) { + if (TARGET31.compareTo(target) <= 0) + modImport.append(';').append(Constants.VISIBILITY_DIRECTIVE).append(":=").append(Constants.VISIBILITY_REEXPORT);//$NON-NLS-1$ + else + modImport.append(';').append(PluginConverterImpl.REPROVIDE_ATTRIBUTE).append("=true");//$NON-NLS-1$ + } + if (element.isOptional()) { + if (TARGET31.compareTo(target) <= 0) + modImport.append(';').append(Constants.RESOLUTION_DIRECTIVE).append(":=").append(Constants.RESOLUTION_OPTIONAL);//$NON-NLS-1$ + else + modImport.append(';').append(PluginConverterImpl.OPTIONAL_ATTRIBUTE).append("=true");//$NON-NLS-1$ + } + bundleRequire.append(modImport.toString()); + if (iter.hasNext()) + bundleRequire.append(LIST_SEPARATOR); + } + generatedManifest.put(Constants.REQUIRE_BUNDLE, bundleRequire.toString()); + } + + private void generateTimestamp() { + // so it is easy to tell which ones are generated + generatedManifest.put(GENERATED_FROM, Long.toString(getTimeStamp(pluginManifestLocation, manifestType)) + ";" + MANIFEST_TYPE_ATTRIBUTE + "=" + manifestType); //$NON-NLS-1$ //$NON-NLS-2$ + } + + @SuppressWarnings("deprecation") + private void generateEclipseHeaders() { + if (pluginInfo.isFragment()) + return; + + String pluginClass = pluginInfo.getPluginClass(); + if (pluginInfo.hasExtensionExtensionPoints() || (pluginClass != null && !pluginClass.trim().equals(""))) //$NON-NLS-1$ + generatedManifest.put(TARGET32.compareTo(target) <= 0 ? PluginConverterImpl.ECLIPSE_LAZYSTART : PluginConverterImpl.ECLIPSE_AUTOSTART, "true"); //$NON-NLS-1$ + } + + private Set<String> getExports() { + Map<String, List<String>> libs = pluginInfo.getLibraries(); + if (libs == null) + return null; + + //If we are in dev mode, then add the binary folders on the list libs with the export clause set to be the cumulation of the export clause of the real libs + if (devProperties != null || configuration.inDevelopmentMode()) { + String[] devClassPath = configuration.getDevClassPath(pluginInfo.getUniqueId(), devProperties); + // collect export clauses + List<String> allExportClauses = new ArrayList<String>(libs.size()); + Set<Map.Entry<String, List<String>>> libEntries = libs.entrySet(); + for (Iterator<Map.Entry<String, List<String>>> iter = libEntries.iterator(); iter.hasNext();) { + Map.Entry<String, List<String>> element = iter.next(); + allExportClauses.addAll(element.getValue()); + } + if (devClassPath != null) { + // bug 88498 + // if there is a devClassPath defined for this plugin and the @ignoredot@ flag is true + // then we will ignore the '.' library specified in the plugin.xml + String[] ignoreDotProp = configuration.getDevClassPath(IGNORE_DOT, devProperties); + if (devClassPath.length > 0 && ignoreDotProp != null && ignoreDotProp.length > 0 && "true".equals(ignoreDotProp[0])) //$NON-NLS-1$ + libs.remove(DOT); + for (int i = 0; i < devClassPath.length; i++) + libs.put(devClassPath[i], allExportClauses); + } + } + + Set<String> result = new TreeSet<String>(); + Set<Map.Entry<String, List<String>>> libEntries = libs.entrySet(); + for (Iterator<Map.Entry<String, List<String>>> iter = libEntries.iterator(); iter.hasNext();) { + Map.Entry<String, List<String>> element = iter.next(); + List<String> filter = element.getValue(); + if (filter.size() == 0) //If the library is not exported, then ignore it + continue; + String libEntryText = element.getKey().trim(); + File libraryLocation; + if (libEntryText.equals(DOT)) + libraryLocation = pluginManifestLocation; + else { + // in development time, libEntries may contain absolute locations (linked folders) + File libEntryAsPath = new File(libEntryText); + libraryLocation = libEntryAsPath.isAbsolute() ? libEntryAsPath : new File(pluginManifestLocation, libEntryText); + } + Set<String> exports = null; + if (libraryLocation.exists()) { + if (libraryLocation.isFile()) + exports = filterExport(getExportsFromJAR(libraryLocation), filter); //TODO Need to handle $xx$ variables + else if (libraryLocation.isDirectory()) + exports = filterExport(getExportsFromDir(libraryLocation), filter); + } else { + List<String> expandedLibs = getLibrariesExpandingVariables(element.getKey(), false); + exports = new HashSet<String>(); + for (Iterator<String> iterator = expandedLibs.iterator(); iterator.hasNext();) { + String libName = iterator.next(); + File libFile = new File(pluginManifestLocation, libName); + if (libFile.isFile()) { + exports.addAll(filterExport(getExportsFromJAR(libFile), filter)); + } + } + } + if (exports != null) + result.addAll(exports); + } + return result; + } + + private Set<String> getExportsFromDir(File location) { + return getExportsFromDir(location, ""); //$NON-NLS-1$ + } + + private Set<String> getExportsFromDir(File location, String packageName) { + String prefix = (packageName.length() > 0) ? (packageName + '.') : ""; //$NON-NLS-1$ + String[] files = location.list(); + Set<String> exportedPaths = new HashSet<String>(); + boolean containsFile = false; + if (files != null) + for (int i = 0; i < files.length; i++) { + if (!isValidPackageName(files[i])) + continue; + File pkgFile = new File(location, files[i]); + if (pkgFile.isDirectory()) + exportedPaths.addAll(getExportsFromDir(pkgFile, prefix + files[i])); + else + containsFile = true; + } + if (containsFile) + // Allow the default package to be provided. If the default package + // contains a File then use "." as the package name to provide for default. + if (packageName.length() > 0) + exportedPaths.add(packageName); + else + exportedPaths.add(DOT); + return exportedPaths; + } + + private Set<String> getExportsFromJAR(File jarFile) { + Set<String> names = new HashSet<String>(); + ZipFile file = null; + try { + file = new ZipFile(jarFile); + } catch (IOException e) { + String message = NLS.bind(PluginConverterMsg.ECLIPSE_CONVERTER_PLUGIN_LIBRARY_IGNORED, jarFile, pluginInfo.getUniqueId()); + configuration.getHookRegistry().getContainer().getLogServices().log(EquinoxContainer.NAME, FrameworkLogEntry.ERROR, message, null); + return names; + } + //Run through the entries + for (Enumeration<? extends ZipEntry> entriesEnum = file.entries(); entriesEnum.hasMoreElements();) { + ZipEntry entry = entriesEnum.nextElement(); + String name = entry.getName(); + if (!isValidPackageName(name)) + continue; + int lastSlash = name.lastIndexOf("/"); //$NON-NLS-1$ + //Ignore folders that do not contain files + if (lastSlash != -1) { + if (lastSlash != name.length() - 1 && name.lastIndexOf(' ') == -1) + names.add(name.substring(0, lastSlash).replace('/', '.')); + } else { + // Allow the default package to be provided. If the default package + // contains a File then use "." as the package name to provide for default. + names.add(DOT); + } + } + try { + file.close(); + } catch (IOException e) { + // Nothing to do + } + return names; + } + + private List<String> getLibrariesExpandingVariables(String libraryPath, boolean filter) { + String var = hasPrefix(libraryPath); + if (var == null) { + List<String> returnValue = new ArrayList<String>(1); + returnValue.add(libraryPath); + return returnValue; + } + if (var.equals("ws")) { //$NON-NLS-1$ + return findWSJars(pluginManifestLocation, libraryPath, filter); + } + if (var.equals("os")) { //$NON-NLS-1$ + return findOSJars(pluginManifestLocation, libraryPath, filter); + } + return new ArrayList<String>(0); + } + + //return a String representing the string found between the $s + private String hasPrefix(String libPath) { + if (libPath.startsWith("$ws$")) //$NON-NLS-1$ + return "ws"; //$NON-NLS-1$ + if (libPath.startsWith("$os$")) //$NON-NLS-1$ + return "os"; //$NON-NLS-1$ + if (libPath.startsWith("$nl$")) //$NON-NLS-1$ + return "nl"; //$NON-NLS-1$ + return null; + } + + private boolean isValidPackageName(String name) { + if (name.indexOf(' ') > 0 || name.equalsIgnoreCase("META-INF") || name.startsWith("META-INF/")) //$NON-NLS-1$ //$NON-NLS-2$ + return false; + return true; + } + + /** + * Parses the plugin manifest to find out: - the plug-in unique identifier - + * the plug-in version - runtime/libraries entries - the plug-in class - + * the master plugin (for a fragment) + */ + private IPluginInfo parsePluginInfo(InputStream pluginLocation) throws PluginConversionException { + InputStream input = null; + try { + input = new BufferedInputStream(pluginLocation); + return new PluginParser(configuration, target).parsePlugin(input); + } catch (Exception e) { + String message = NLS.bind(PluginConverterMsg.ECLIPSE_CONVERTER_ERROR_PARSING_PLUGIN_MANIFEST, pluginManifestLocation); + throw new PluginConversionException(message, e); + } finally { + if (input != null) + try { + input.close(); + } catch (IOException e) { + //ignore exception + } + } + } + + public static boolean upToDate(File generationLocation, File pluginLocation, byte manifestType) { + if (!generationLocation.isFile()) + return false; + String secondLine = null; + BufferedReader reader = null; + try { + reader = new BufferedReader(new InputStreamReader(new FileInputStream(generationLocation))); + reader.readLine(); + secondLine = reader.readLine(); + } catch (IOException e) { + // not a big deal - we could not read an existing manifest + return false; + } finally { + if (reader != null) + try { + reader.close(); + } catch (IOException e) { + // ignore + } + } + String tag = GENERATED_FROM + ": "; //$NON-NLS-1$ + if (secondLine == null || !secondLine.startsWith(tag)) + return false; + + secondLine = secondLine.substring(tag.length()); + ManifestElement generatedFrom; + try { + generatedFrom = ManifestElement.parseHeader(PluginConverterImpl.GENERATED_FROM, secondLine)[0]; + } catch (BundleException be) { + return false; + } + String timestampStr = generatedFrom.getValue(); + try { + return Long.parseLong(timestampStr.trim()) == getTimeStamp(pluginLocation, manifestType); + } catch (NumberFormatException nfe) { + // not a big deal - just a bogus existing manifest that will be ignored + } + return false; + } + + public static long getTimeStamp(File pluginLocation, byte manifestType) { + if ((manifestType & MANIFEST_TYPE_JAR) != 0) + return pluginLocation.lastModified(); + else if ((manifestType & MANIFEST_TYPE_PLUGIN) != 0) + return new File(pluginLocation, PLUGIN_MANIFEST).lastModified(); + else if ((manifestType & MANIFEST_TYPE_FRAGMENT) != 0) + return new File(pluginLocation, FRAGMENT_MANIFEST).lastModified(); + else if ((manifestType & MANIFEST_TYPE_BUNDLE) != 0) + return new File(pluginLocation, PluginConverterImpl.OSGI_BUNDLE_MANIFEST).lastModified(); + return -1; + } + + private void writeEntry(String key, String value) throws IOException { + if (value != null && value.length() > 0) { + out.write(splitOnComma(key + ": " + value)); //$NON-NLS-1$ + out.write('\n'); + } + } + + private String splitOnComma(String value) { + if (value.length() < MAXLINE || value.indexOf(LINE_SEPARATOR) >= 0) + return value; // assume the line is already split + String[] values = ManifestElement.getArrayFromList(value); + if (values == null || values.length == 0) + return value; + StringBuffer sb = new StringBuffer(value.length() + ((values.length - 1) * LIST_SEPARATOR.length())); + for (int i = 0; i < values.length - 1; i++) + sb.append(values[i]).append(LIST_SEPARATOR); + sb.append(values[values.length - 1]); + return sb.toString(); + } + + private String getStringFromArray(String[] values, String separator) { + if (values == null) + return ""; //$NON-NLS-1$ + StringBuffer result = new StringBuffer(); + for (int i = 0; i < values.length; i++) { + if (i > 0) + result.append(separator); + result.append(values[i]); + } + return result.toString(); + } + + private String getStringFromCollection(Collection<String> collection, String separator) { + StringBuffer result = new StringBuffer(); + boolean first = true; + for (Iterator<String> i = collection.iterator(); i.hasNext();) { + if (first) + first = false; + else + result.append(separator); + result.append(i.next()); + } + return result.toString(); + } + + private synchronized Dictionary<String, String> convertManifest(File pluginBaseLocation, boolean compatibility, String targetVersion, boolean analyseJars, Dictionary<String, String> devProps, boolean failOnNoManifest) throws PluginConversionException { + long start = System.currentTimeMillis(); + if (DEBUG) + System.out.println("Convert " + pluginBaseLocation); //$NON-NLS-1$ + init(); + this.target = targetVersion == null ? TARGET32 : new Version(targetVersion); + this.devProperties = devProps; + if (!fillPluginInfo(pluginBaseLocation)) { + if (failOnNoManifest) { + throw new PluginConversionException(NLS.bind(PluginConverterMsg.ECLIPSE_CONVERTER_FILENOTFOUND, pluginBaseLocation.getAbsolutePath())); + } + return null; + } + fillManifest(compatibility, analyseJars); + if (DEBUG) + System.out.println("Time to convert manifest for: " + pluginBaseLocation + ": " + (System.currentTimeMillis() - start) + "ms."); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + return generatedManifest; + } + + public Dictionary<String, String> convertManifest(File pluginBaseLocation, boolean compatibility, String targetVersion, boolean analyseJars, Dictionary<String, String> devProps) throws PluginConversionException { + return convertManifest(pluginBaseLocation, compatibility, targetVersion, analyseJars, devProps, true); + } + + synchronized File convertManifest(File pluginBaseLocation, File bundleManifestLocation, boolean compatibilityManifest, String targetVersion, boolean analyseJars, Dictionary<String, String> devProps, boolean failOnNoManifest) throws PluginConversionException { + Dictionary<String, String> converted = convertManifest(pluginBaseLocation, compatibilityManifest, targetVersion, analyseJars, devProps, failOnNoManifest); + if (converted == null) { + return null; + } + if (bundleManifestLocation == null) { + bundleManifestLocation = new File(cacheLocation, pluginInfo.getUniqueId() + '_' + pluginInfo.getVersion() + ".MF"); //$NON-NLS-1$ + } + if (upToDate(bundleManifestLocation, pluginManifestLocation, manifestType)) + return bundleManifestLocation; + writeManifest(bundleManifestLocation, generatedManifest, compatibilityManifest); + return bundleManifestLocation; + } + + public File convertManifest(File pluginBaseLocation, File bundleManifestLocation, boolean compatibilityManifest, String targetVersion, boolean analyseJars, Dictionary<String, String> devProps) throws PluginConversionException { + return convertManifest(pluginBaseLocation, bundleManifestLocation, compatibilityManifest, targetVersion, analyseJars, devProps, true); + } + + private String getVersionRange(String reqVersion, String matchRule) { + if (reqVersion == null) + return null; + + Version minVersion = Version.parseVersion(reqVersion); + String versionRange; + if (matchRule != null) { + if (matchRule.equalsIgnoreCase(IModel.PLUGIN_REQUIRES_MATCH_PERFECT)) { + versionRange = new VersionRange(minVersion, true, minVersion, true).toString(); + } else if (matchRule.equalsIgnoreCase(IModel.PLUGIN_REQUIRES_MATCH_EQUIVALENT)) { + versionRange = new VersionRange(minVersion, true, new Version(minVersion.getMajor(), minVersion.getMinor() + 1, 0, ""), false).toString(); //$NON-NLS-1$ + } else if (matchRule.equalsIgnoreCase(IModel.PLUGIN_REQUIRES_MATCH_COMPATIBLE)) { + versionRange = new VersionRange(minVersion, true, new Version(minVersion.getMajor() + 1, 0, 0, ""), false).toString(); //$NON-NLS-1$ + } else if (matchRule.equalsIgnoreCase(IModel.PLUGIN_REQUIRES_MATCH_GREATER_OR_EQUAL)) { + // just return the reqVersion here without any version range + versionRange = reqVersion; + } else { + versionRange = new VersionRange(minVersion, true, new Version(minVersion.getMajor() + 1, 0, 0, ""), false).toString(); //$NON-NLS-1$ + } + } else { + versionRange = new VersionRange(minVersion, true, new Version(minVersion.getMajor() + 1, 0, 0, ""), false).toString(); //$NON-NLS-1$ + } + + StringBuffer result = new StringBuffer(); + result.append(';').append(Constants.BUNDLE_VERSION_ATTRIBUTE).append('='); + result.append('\"').append(versionRange).append('\"'); + return result.toString(); + } +} diff --git a/bundles/org.eclipse.osgi.compatibility.plugins/src/org/eclipse/osgi/compatibility/plugins/PluginConverterMsg.java b/bundles/org.eclipse.osgi.compatibility.plugins/src/org/eclipse/osgi/compatibility/plugins/PluginConverterMsg.java new file mode 100644 index 000000000..8bec72eec --- /dev/null +++ b/bundles/org.eclipse.osgi.compatibility.plugins/src/org/eclipse/osgi/compatibility/plugins/PluginConverterMsg.java @@ -0,0 +1,37 @@ +/******************************************************************************* + * Copyright (c) 2004, 2013 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.osgi.compatibility.plugins; + +import org.eclipse.osgi.util.NLS; + +public class PluginConverterMsg extends NLS { + private static final String BUNDLE_NAME = "org.eclipse.osgi.compatibility.plugins.PluginConverterMsg"; //$NON-NLS-1$ + + public static String ECLIPSE_CONVERTER_ERROR_CONVERTING; + + public static String ECLIPSE_CONVERTER_FILENOTFOUND; + public static String ECLIPSE_CONVERTER_ERROR_CREATING_BUNDLE_MANIFEST; + public static String ECLIPSE_CONVERTER_PLUGIN_LIBRARY_IGNORED; + + public static String ECLIPSE_CONVERTER_ERROR_PARSING_PLUGIN_MANIFEST; + public static String ECLIPSE_CONVERTER_MISSING_ATTRIBUTE; + public static String parse_error; + public static String parse_errorNameLineColumn; + + public static String ECLIPSE_CONVERTER_NO_SAX_FACTORY; + public static String ECLIPSE_CONVERTER_PARSE_UNKNOWNTOP_ELEMENT; + + static { + // initialize resource bundles + NLS.initializeMessages(BUNDLE_NAME, PluginConverterMsg.class); + } + +} diff --git a/bundles/org.eclipse.osgi.compatibility.plugins/src/org/eclipse/osgi/compatibility/plugins/PluginConverterMsg.properties b/bundles/org.eclipse.osgi.compatibility.plugins/src/org/eclipse/osgi/compatibility/plugins/PluginConverterMsg.properties new file mode 100644 index 000000000..4ad20856b --- /dev/null +++ b/bundles/org.eclipse.osgi.compatibility.plugins/src/org/eclipse/osgi/compatibility/plugins/PluginConverterMsg.properties @@ -0,0 +1,25 @@ +############################################################################### +# Copyright (c) 2004, 2013 IBM Corporation and others. +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Eclipse Public License v1.0 +# which accompanies this distribution, and is available at +# http://www.eclipse.org/legal/epl-v10.html +# +# Contributors: +# IBM Corporation - initial API and implementation +############################################################################### + +#External Messages for EN locale + +#Conversion messages +ECLIPSE_CONVERTER_ERROR_CONVERTING=Error converting plugin at {0}. +ECLIPSE_CONVERTER_ERROR_CREATING_BUNDLE_MANIFEST=Error creating bundle manifest file for {0} at {1}. +ECLIPSE_CONVERTER_ERROR_PARSING_PLUGIN_MANIFEST=Error parsing plugin manifest file {0} at {1}. +ECLIPSE_CONVERTER_MISSING_ATTRIBUTE=Error parsing {0} manifest. Missing attribute \"{1}\" in element \"{2}\". +ECLIPSE_CONVERTER_PLUGIN_LIBRARY_IGNORED=Plugin library {0} ignored when creating manifest for {1}. +ECLIPSE_CONVERTER_NO_SAX_FACTORY=No SAX factory parser has been found. +ECLIPSE_CONVERTER_PARSE_UNKNOWNTOP_ELEMENT = Unknown element \"{0}\", found at the top level, ignored. +ECLIPSE_CONVERTER_FILENOTFOUND = Could not find a META-INF/MANIFEST.MF, plugin.xml or a fragment.xml in {0}. +parse_error=Error parsing manifest: {0} +parse_errorNameLineColumn=Error parsing manifest at \"{0}\" line \"{1}\" column \"{2}\": {3} + diff --git a/bundles/org.eclipse.osgi.compatibility.plugins/src/org/eclipse/osgi/compatibility/plugins/PluginParser.java b/bundles/org.eclipse.osgi.compatibility.plugins/src/org/eclipse/osgi/compatibility/plugins/PluginParser.java new file mode 100644 index 000000000..e6cb4d3a5 --- /dev/null +++ b/bundles/org.eclipse.osgi.compatibility.plugins/src/org/eclipse/osgi/compatibility/plugins/PluginParser.java @@ -0,0 +1,708 @@ +/******************************************************************************* + * Copyright (c) 2000, 2013 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.osgi.compatibility.plugins; + +import java.io.InputStream; +import java.util.*; +import javax.xml.parsers.SAXParserFactory; +import org.eclipse.osgi.container.Module; +import org.eclipse.osgi.framework.log.FrameworkLogEntry; +import org.eclipse.osgi.internal.framework.EquinoxConfiguration; +import org.eclipse.osgi.internal.framework.EquinoxContainer; +import org.eclipse.osgi.util.NLS; +import org.osgi.framework.*; +import org.osgi.util.tracker.ServiceTracker; +import org.xml.sax.*; +import org.xml.sax.helpers.DefaultHandler; + +/** + * Internal class. + */ +public class PluginParser extends DefaultHandler implements IModel { + private static ServiceTracker<SAXParserFactory, SAXParserFactory> xmlTracker = null; + + private final EquinoxConfiguration configuration; + private PluginInfo manifestInfo = new PluginInfo(); + Version target; // The targeted platform for the given manifest + static final Version TARGET21 = new Version(2, 1, 0); + + public class PluginInfo implements IPluginInfo { + String schemaVersion; + String pluginId; + String version; + String vendor; + + // an ordered list of library path names. + List<String> libraryPaths; + // TODO Should get rid of the libraries map and just have a + // list of library export statements instead. Library paths must + // preserve order. + Map<String, List<String>> libraries; //represent the libraries and their export statement + ArrayList<PluginParser.Prerequisite> requires; + private boolean requiresExpanded = false; //indicates if the requires have been processed. + boolean compatibilityFound = false; //set to true is the requirement list contain compatilibity + String pluginClass; + String masterPluginId; + String masterVersion; + String masterMatch; + private Set<String> filters; + String pluginName; + boolean singleton; + boolean fragment; + private final static String TARGET21_STRING = "2.1"; //$NON-NLS-1$ + boolean hasExtensionExtensionPoints = false; + + public boolean isFragment() { + return fragment; + } + + public String toString() { + return "plugin-id: " + pluginId + " version: " + version + " libraries: " + libraries + " class:" + pluginClass + " master: " + masterPluginId + " master-version: " + masterVersion + " requires: " + requires + " singleton: " + singleton; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$ + } + + public Map<String, List<String>> getLibraries() { + if (libraries == null) + return new HashMap<String, List<String>>(0); + return libraries; + } + + public ArrayList<Prerequisite> getRequires() { + if (!TARGET21.equals(target) && schemaVersion == null && !requiresExpanded) { + requiresExpanded = true; + if (requires == null) { + requires = new ArrayList<Prerequisite>(1); + requires.add(new Prerequisite(PluginConverterImpl.PI_RUNTIME, TARGET21_STRING, false, false, IModel.PLUGIN_REQUIRES_MATCH_GREATER_OR_EQUAL)); + requires.add(new Prerequisite(PluginConverterImpl.PI_RUNTIME_COMPATIBILITY, null, false, false, null)); + } else { + //Add elements on the requirement list of ui and help. + for (int i = 0; i < requires.size(); i++) { + Prerequisite analyzed = requires.get(i); + if ("org.eclipse.ui".equals(analyzed.getName())) { //$NON-NLS-1$ + requires.add(i + 1, new Prerequisite("org.eclipse.ui.workbench.texteditor", null, true, analyzed.isExported(), null)); //$NON-NLS-1$ + requires.add(i + 1, new Prerequisite("org.eclipse.jface.text", null, true, analyzed.isExported(), null)); //$NON-NLS-1$ + requires.add(i + 1, new Prerequisite("org.eclipse.ui.editors", null, true, analyzed.isExported(), null)); //$NON-NLS-1$ + requires.add(i + 1, new Prerequisite("org.eclipse.ui.views", null, true, analyzed.isExported(), null)); //$NON-NLS-1$ + requires.add(i + 1, new Prerequisite("org.eclipse.ui.ide", null, true, analyzed.isExported(), null)); //$NON-NLS-1$ + } else if ("org.eclipse.help".equals(analyzed.getName())) { //$NON-NLS-1$ + requires.add(i + 1, new Prerequisite("org.eclipse.help.base", null, true, analyzed.isExported(), null)); //$NON-NLS-1$ + } else if (PluginConverterImpl.PI_RUNTIME.equals(analyzed.getName()) && !compatibilityFound) { + requires.add(i + 1, new Prerequisite(PluginConverterImpl.PI_RUNTIME_COMPATIBILITY, null, false, analyzed.isExported(), null)); + } + } + if (!requires.contains(new Prerequisite(PluginConverterImpl.PI_RUNTIME_COMPATIBILITY, null, false, false, null))) { + requires.add(new Prerequisite(PluginConverterImpl.PI_RUNTIME_COMPATIBILITY, null, false, false, null)); + } + //Remove any prereq on runtime and add a prereq on runtime 2.1 + //This is used to recognize the version for which the given plugin was initially targeted. + Prerequisite runtimePrereq = new Prerequisite(PluginConverterImpl.PI_RUNTIME, null, false, false, null); + requires.remove(runtimePrereq); + requires.add(new Prerequisite(PluginConverterImpl.PI_RUNTIME, TARGET21_STRING, false, false, IModel.PLUGIN_REQUIRES_MATCH_GREATER_OR_EQUAL)); + } + } + if (requires == null) + return requires = new ArrayList<Prerequisite>(0); + + return requires; + } + + public String getMasterId() { + return masterPluginId; + } + + public String getMasterVersion() { + return masterVersion; + } + + public String getMasterMatch() { + return masterMatch; + } + + public String getPluginClass() { + return pluginClass; + } + + public String getUniqueId() { + return pluginId; + } + + public String getVersion() { + return version; + } + + public Set<String> getPackageFilters() { + return filters; + } + + public String[] getLibrariesName() { + if (libraryPaths == null) + return new String[0]; + return libraryPaths.toArray(new String[libraryPaths.size()]); + } + + public String getPluginName() { + return pluginName; + } + + public String getProviderName() { + return vendor; + } + + public boolean isSingleton() { + return singleton; + } + + public boolean hasExtensionExtensionPoints() { + return hasExtensionExtensionPoints; + } + + public String getRoot() { + return isFragment() ? FRAGMENT : PLUGIN; + } + + /* + * Provides some basic form of validation. Since plugin/fragment is the only mandatory + * attribute, it is the only one we cara about here. + */ + public String validateForm() { + if (this.pluginId == null) + return NLS.bind(PluginConverterMsg.ECLIPSE_CONVERTER_MISSING_ATTRIBUTE, new String[] {getRoot(), PLUGIN_ID, getRoot()}); + if (this.pluginName == null) + return NLS.bind(PluginConverterMsg.ECLIPSE_CONVERTER_MISSING_ATTRIBUTE, new String[] {getRoot(), PLUGIN_NAME, getRoot()}); + if (this.version == null) + return NLS.bind(PluginConverterMsg.ECLIPSE_CONVERTER_MISSING_ATTRIBUTE, new String[] {getRoot(), PLUGIN_VERSION, getRoot()}); + if (isFragment() && this.masterPluginId == null) + return NLS.bind(PluginConverterMsg.ECLIPSE_CONVERTER_MISSING_ATTRIBUTE, new String[] {getRoot(), FRAGMENT_PLUGIN_ID, getRoot()}); + if (isFragment() && this.masterVersion == null) + return NLS.bind(PluginConverterMsg.ECLIPSE_CONVERTER_MISSING_ATTRIBUTE, new String[] {getRoot(), FRAGMENT_PLUGIN_VERSION, getRoot()}); + return null; + } + } + + // Current State Information + Stack<Integer> stateStack = new Stack<Integer>(); + + // Current object stack (used to hold the current object we are populating in this plugin info + Stack<Object> objectStack = new Stack<Object>(); + Locator locator = null; + + // Valid States + private static final int IGNORED_ELEMENT_STATE = 0; + private static final int INITIAL_STATE = 1; + private static final int PLUGIN_STATE = 2; + private static final int PLUGIN_RUNTIME_STATE = 3; + private static final int PLUGIN_REQUIRES_STATE = 4; + private static final int PLUGIN_EXTENSION_POINT_STATE = 5; + private static final int PLUGIN_EXTENSION_STATE = 6; + private static final int RUNTIME_LIBRARY_STATE = 7; + private static final int LIBRARY_EXPORT_STATE = 8; + private static final int PLUGIN_REQUIRES_IMPORT_STATE = 9; + private static final int FRAGMENT_STATE = 11; + + public PluginParser(EquinoxConfiguration configuration, Version target) { + super(); + this.configuration = configuration; + this.target = target; + } + + /** + * Receive a Locator object for document events. + * + * <p> + * By default, do nothing. Application writers may override this method in + * a subclass if they wish to store the locator for use with other document + * events. + * </p> + * + * @param locator A locator for all SAX document events. + * @see org.xml.sax.ContentHandler#setDocumentLocator(org.xml.sax.Locator) + * @see org.xml.sax.Locator + */ + public void setDocumentLocator(Locator locator) { + this.locator = locator; + } + + public void endDocument() { + // nothing + } + + public void endElement(String uri, String elementName, String qName) { + switch (stateStack.peek().intValue()) { + case IGNORED_ELEMENT_STATE : + stateStack.pop(); + break; + case INITIAL_STATE : + // shouldn't get here + // internalError(Policy.bind("parse.internalStack", elementName)); //$NON-NLS-1$ + break; + case PLUGIN_STATE : + case FRAGMENT_STATE : + break; + case PLUGIN_RUNTIME_STATE : + if (elementName.equals(RUNTIME)) { + stateStack.pop(); + } + break; + case PLUGIN_REQUIRES_STATE : + if (elementName.equals(PLUGIN_REQUIRES)) { + stateStack.pop(); + objectStack.pop(); + } + break; + case PLUGIN_EXTENSION_POINT_STATE : + if (elementName.equals(EXTENSION_POINT)) { + stateStack.pop(); + } + break; + case PLUGIN_EXTENSION_STATE : + if (elementName.equals(EXTENSION)) { + stateStack.pop(); + } + break; + case RUNTIME_LIBRARY_STATE : + if (elementName.equals(LIBRARY)) { + String curLibrary = (String) objectStack.pop(); + if (!curLibrary.trim().equals("")) { //$NON-NLS-1$ + @SuppressWarnings("unchecked") + List<String> exports = (List<String>) objectStack.pop(); + if (manifestInfo.libraries == null) { + manifestInfo.libraries = new HashMap<String, List<String>>(3); + manifestInfo.libraryPaths = new ArrayList<String>(3); + } + manifestInfo.libraries.put(curLibrary, exports); + manifestInfo.libraryPaths.add(curLibrary.replace('\\', '/')); + } + stateStack.pop(); + } + break; + case LIBRARY_EXPORT_STATE : + if (elementName.equals(LIBRARY_EXPORT)) { + stateStack.pop(); + } + break; + case PLUGIN_REQUIRES_IMPORT_STATE : + if (elementName.equals(PLUGIN_REQUIRES_IMPORT)) { + stateStack.pop(); + } + break; + } + } + + public void error(SAXParseException ex) { + logStatus(ex); + } + + public void fatalError(SAXParseException ex) throws SAXException { + logStatus(ex); + throw ex; + } + + public void handleExtensionPointState(String elementName, Attributes attributes) { + // nothing to do for extension-points' children + stateStack.push(new Integer(IGNORED_ELEMENT_STATE)); + manifestInfo.hasExtensionExtensionPoints = true; + } + + public void handleExtensionState(String elementName, Attributes attributes) { + // nothing to do for extensions' children + stateStack.push(new Integer(IGNORED_ELEMENT_STATE)); + manifestInfo.hasExtensionExtensionPoints = true; + } + + public void handleInitialState(String elementName, Attributes attributes) { + if (elementName.equals(PLUGIN)) { + stateStack.push(new Integer(PLUGIN_STATE)); + parsePluginAttributes(attributes); + } else if (elementName.equals(FRAGMENT)) { + manifestInfo.fragment = true; + stateStack.push(new Integer(FRAGMENT_STATE)); + parseFragmentAttributes(attributes); + } else { + stateStack.push(new Integer(IGNORED_ELEMENT_STATE)); + internalError(elementName); + } + } + + public void handleLibraryExportState(String elementName, Attributes attributes) { + // All elements ignored. + stateStack.push(new Integer(IGNORED_ELEMENT_STATE)); + } + + public void handleLibraryState(String elementName, Attributes attributes) { + if (elementName.equals(LIBRARY_EXPORT)) { + // Change State + stateStack.push(new Integer(LIBRARY_EXPORT_STATE)); + // The top element on the stack much be a library element + String currentLib = (String) objectStack.peek(); + if (attributes == null) + return; + String maskValue = attributes.getValue("", LIBRARY_EXPORT_MASK); //$NON-NLS-1$ + // pop off the library - already in currentLib + objectStack.pop(); + @SuppressWarnings("unchecked") + List<String> exportMask = (List<String>) objectStack.peek(); + // push library back on + objectStack.push(currentLib); + //Split the export upfront + if (maskValue != null) { + StringTokenizer tok = new StringTokenizer(maskValue, ","); //$NON-NLS-1$ + while (tok.hasMoreTokens()) { + String value = tok.nextToken(); + if (!exportMask.contains(maskValue)) + exportMask.add(value.trim()); + } + } + return; + } + if (elementName.equals(LIBRARY_PACKAGES)) { + stateStack.push(new Integer(IGNORED_ELEMENT_STATE)); + return; + } + stateStack.push(new Integer(IGNORED_ELEMENT_STATE)); + internalError(elementName); + return; + } + + public void handlePluginState(String elementName, Attributes attributes) { + if (elementName.equals(RUNTIME)) { + // We should only have one Runtime element in a plugin or fragment + Object whatIsIt = objectStack.peek(); + if ((whatIsIt instanceof PluginInfo) && ((PluginInfo) objectStack.peek()).libraries != null) { + // This is at least the 2nd Runtime element we have hit. Ignore it. + stateStack.push(new Integer(IGNORED_ELEMENT_STATE)); + return; + } + stateStack.push(new Integer(PLUGIN_RUNTIME_STATE)); + // Push a new vector to hold all the library entries objectStack.push(new Vector()); + return; + } + if (elementName.equals(PLUGIN_REQUIRES)) { + stateStack.push(new Integer(PLUGIN_REQUIRES_STATE)); + // Push a new vector to hold all the prerequisites + objectStack.push(new ArrayList<String>()); + parseRequiresAttributes(attributes); + return; + } + if (elementName.equals(EXTENSION_POINT)) { + // mark the plugin as singleton and ignore all elements under extension (if there are any) + manifestInfo.singleton = true; + stateStack.push(new Integer(PLUGIN_EXTENSION_POINT_STATE)); + return; + } + if (elementName.equals(EXTENSION)) { + // mark the plugin as singleton and ignore all elements under extension (if there are any) + manifestInfo.singleton = true; + stateStack.push(new Integer(PLUGIN_EXTENSION_STATE)); + return; + } + // If we get to this point, the element name is one we don't currently accept. + // Set the state to indicate that this element will be ignored + stateStack.push(new Integer(IGNORED_ELEMENT_STATE)); + internalError(elementName); + } + + public void handleRequiresImportState(String elementName, Attributes attributes) { + // All elements ignored. + stateStack.push(new Integer(IGNORED_ELEMENT_STATE)); + } + + public void handleRequiresState(String elementName, Attributes attributes) { + if (elementName.equals(PLUGIN_REQUIRES_IMPORT)) { + parsePluginRequiresImport(attributes); + return; + } + // If we get to this point, the element name is one we don't currently accept. + // Set the state to indicate that this element will be ignored + stateStack.push(new Integer(IGNORED_ELEMENT_STATE)); + internalError(elementName); + } + + public void handleRuntimeState(String elementName, Attributes attributes) { + if (elementName.equals(LIBRARY)) { + // Change State + stateStack.push(new Integer(RUNTIME_LIBRARY_STATE)); + // Process library attributes + parseLibraryAttributes(attributes); + return; + } + // If we get to this point, the element name is one we don't currently accept. + // Set the state to indicate that this element will be ignored + stateStack.push(new Integer(IGNORED_ELEMENT_STATE)); + internalError(elementName); + } + + private void logStatus(SAXParseException ex) { + String name = ex.getSystemId(); + if (name == null) + name = ""; //$NON-NLS-1$ + else + name = name.substring(1 + name.lastIndexOf("/")); //$NON-NLS-1$ + String msg; + if (name.equals("")) //$NON-NLS-1$ + msg = NLS.bind(PluginConverterMsg.parse_error, ex.getMessage()); + else + msg = NLS.bind(PluginConverterMsg.parse_errorNameLineColumn, new String[] {name, Integer.toString(ex.getLineNumber()), Integer.toString(ex.getColumnNumber()), ex.getMessage()}); + + configuration.getHookRegistry().getContainer().getLogServices().log(EquinoxContainer.NAME, FrameworkLogEntry.ERROR, msg, null); + } + + synchronized public PluginInfo parsePlugin(InputStream in) throws Exception { + Module systemModule = configuration.getHookRegistry().getContainer().getStorage().getModuleContainer().getModule(0); + Bundle systemBundle = systemModule.getBundle(); + BundleContext systemContext = systemBundle.getBundleContext(); + SAXParserFactory factory = acquireXMLParsing(systemContext); + if (factory == null) { + configuration.getHookRegistry().getContainer().getLogServices().log(EquinoxContainer.NAME, FrameworkLogEntry.ERROR, PluginConverterMsg.ECLIPSE_CONVERTER_NO_SAX_FACTORY, null); + return null; + } + + factory.setNamespaceAware(true); + factory.setNamespaceAware(true); + try { + factory.setFeature("http://xml.org/sax/features/string-interning", true); //$NON-NLS-1$ + } catch (SAXException se) { + // ignore; we can still operate without string-interning + } + factory.setValidating(false); + factory.newSAXParser().parse(in, this); + return manifestInfo; + } + + public static SAXParserFactory acquireXMLParsing(BundleContext context) { + if (xmlTracker == null) { + xmlTracker = new ServiceTracker<SAXParserFactory, SAXParserFactory>(context, "javax.xml.parsers.SAXParserFactory", null); //$NON-NLS-1$ + xmlTracker.open(); + } + SAXParserFactory result = xmlTracker.getService(); + if (result != null) + return result; + // backup to using jaxp to create a new instance + return SAXParserFactory.newInstance(); + } + + public static void releaseXMLParsing() { + if (xmlTracker != null) + xmlTracker.close(); + } + + public void parseFragmentAttributes(Attributes attributes) { + // process attributes + objectStack.push(manifestInfo); + int len = attributes.getLength(); + for (int i = 0; i < len; i++) { + String attrName = attributes.getLocalName(i); + String attrValue = attributes.getValue(i).trim(); + if (attrName.equals(FRAGMENT_ID)) + manifestInfo.pluginId = attrValue; + else if (attrName.equals(FRAGMENT_NAME)) + manifestInfo.pluginName = attrValue; + else if (attrName.equals(FRAGMENT_VERSION)) + manifestInfo.version = attrValue; + else if (attrName.equals(FRAGMENT_PROVIDER)) + manifestInfo.vendor = attrValue; + else if (attrName.equals(FRAGMENT_PLUGIN_ID)) + manifestInfo.masterPluginId = attrValue; + else if (attrName.equals(FRAGMENT_PLUGIN_VERSION)) + manifestInfo.masterVersion = attrValue; + else if (attrName.equals(FRAGMENT_PLUGIN_MATCH)) + manifestInfo.masterMatch = attrValue; + } + } + + public void parseLibraryAttributes(Attributes attributes) { + // Push a vector to hold the export mask + objectStack.push(new ArrayList<String>()); + String current = attributes.getValue("", LIBRARY_NAME); //$NON-NLS-1$ + objectStack.push(current); + } + + public void parsePluginAttributes(Attributes attributes) { + // process attributes + objectStack.push(manifestInfo); + int len = attributes.getLength(); + for (int i = 0; i < len; i++) { + String attrName = attributes.getLocalName(i); + String attrValue = attributes.getValue(i).trim(); + if (attrName.equals(PLUGIN_ID)) + manifestInfo.pluginId = attrValue; + else if (attrName.equals(PLUGIN_NAME)) + manifestInfo.pluginName = attrValue; + else if (attrName.equals(PLUGIN_VERSION)) + manifestInfo.version = attrValue; + else if (attrName.equals(PLUGIN_VENDOR) || (attrName.equals(PLUGIN_PROVIDER))) + manifestInfo.vendor = attrValue; + else if (attrName.equals(PLUGIN_CLASS)) + manifestInfo.pluginClass = attrValue; + } + } + + public class Prerequisite { + String name; + String version; + boolean optional; + boolean export; + String match; + + public boolean isExported() { + return export; + } + + public String getMatch() { + return match; + } + + public String getName() { + return name; + } + + public boolean isOptional() { + return optional; + } + + public String getVersion() { + return version; + } + + public Prerequisite(String preqName, String prereqVersion, boolean isOtional, boolean isExported, String prereqMatch) { + name = preqName; + version = prereqVersion; + optional = isOtional; + export = isExported; + match = prereqMatch; + } + + public String toString() { + return name; + } + + public boolean equals(Object prereq) { + if (!(prereq instanceof Prerequisite)) + return false; + return name.equals(((Prerequisite) prereq).name); + } + + public int hashCode() { + return name.hashCode(); + } + } + + public void parsePluginRequiresImport(Attributes attributes) { + if (manifestInfo.requires == null) { + manifestInfo.requires = new ArrayList<Prerequisite>(); + // to avoid cycles + // if (!manifestInfo.pluginId.equals(PluginConverterImpl.PI_RUNTIME)) //$NON-NLS-1$ + // manifestInfo.requires.add(new Prerequisite(PluginConverterImpl.PI_RUNTIME, null, false, false, null)); //$NON-NLS-1$ + } + // process attributes + String plugin = attributes.getValue("", PLUGIN_REQUIRES_PLUGIN); //$NON-NLS-1$ + if (plugin == null) + return; + if (plugin.equals(PluginConverterImpl.PI_BOOT)) + return; + if (plugin.equals(PluginConverterImpl.PI_RUNTIME_COMPATIBILITY)) + manifestInfo.compatibilityFound = true; + String version = attributes.getValue("", PLUGIN_REQUIRES_PLUGIN_VERSION); //$NON-NLS-1$ + String optional = attributes.getValue("", PLUGIN_REQUIRES_OPTIONAL); //$NON-NLS-1$ + String export = attributes.getValue("", PLUGIN_REQUIRES_EXPORT); //$NON-NLS-1$ + String match = attributes.getValue("", PLUGIN_REQUIRES_MATCH); //$NON-NLS-1$ + manifestInfo.requires.add(new Prerequisite(plugin, version, "true".equalsIgnoreCase(optional) ? true : false, "true".equalsIgnoreCase(export) ? true : false, match)); //$NON-NLS-1$ //$NON-NLS-2$ + } + + public void parseRequiresAttributes(Attributes attributes) { + //Nothing to do. + } + + static String replace(String s, String from, String to) { + String str = s; + int fromLen = from.length(); + int toLen = to.length(); + int ix = str.indexOf(from); + while (ix != -1) { + str = str.substring(0, ix) + to + str.substring(ix + fromLen); + ix = str.indexOf(from, ix + toLen); + } + return str; + } + + public void startDocument() { + stateStack.push(new Integer(INITIAL_STATE)); + } + + public void startElement(String uri, String elementName, String qName, Attributes attributes) { + switch (stateStack.peek().intValue()) { + case INITIAL_STATE : + handleInitialState(elementName, attributes); + break; + case FRAGMENT_STATE : + case PLUGIN_STATE : + handlePluginState(elementName, attributes); + break; + case PLUGIN_RUNTIME_STATE : + handleRuntimeState(elementName, attributes); + break; + case PLUGIN_REQUIRES_STATE : + handleRequiresState(elementName, attributes); + break; + case PLUGIN_EXTENSION_POINT_STATE : + handleExtensionPointState(elementName, attributes); + break; + case PLUGIN_EXTENSION_STATE : + handleExtensionState(elementName, attributes); + break; + case RUNTIME_LIBRARY_STATE : + handleLibraryState(elementName, attributes); + break; + case LIBRARY_EXPORT_STATE : + handleLibraryExportState(elementName, attributes); + break; + case PLUGIN_REQUIRES_IMPORT_STATE : + handleRequiresImportState(elementName, attributes); + break; + default : + stateStack.push(new Integer(IGNORED_ELEMENT_STATE)); + } + } + + public void warning(SAXParseException ex) { + logStatus(ex); + } + + private void internalError(String elementName) { + String message = NLS.bind(PluginConverterMsg.ECLIPSE_CONVERTER_PARSE_UNKNOWNTOP_ELEMENT, elementName); + configuration.getHookRegistry().getContainer().getLogServices().log(EquinoxContainer.NAME, FrameworkLogEntry.ERROR, (manifestInfo.pluginId == null ? message : "Plug-in : " + manifestInfo.pluginId + ", " + message), null); //$NON-NLS-1$//$NON-NLS-2$ + } + + /** + * @throws SAXException + */ + public void processingInstruction(String instructionTarget, String data) throws SAXException { + // Since 3.0, a processing instruction of the form <?eclipse version="3.0"?> at + // the start of the manifest file is used to indicate the plug-in manifest + // schema version in effect. Pre-3.0 (i.e., 2.1) plug-in manifest files do not + // have one of these, and this is how we can distinguish the manifest of a + // pre-3.0 plug-in from a post-3.0 one (for compatibility tranformations). + if (instructionTarget.equalsIgnoreCase("eclipse")) { //$NON-NLS-1$ + // just the presence of this processing instruction indicates that this + // plug-in is at least 3.0 + manifestInfo.schemaVersion = "3.0"; //$NON-NLS-1$ + StringTokenizer tokenizer = new StringTokenizer(data, "=\""); //$NON-NLS-1$ + while (tokenizer.hasMoreTokens()) { + String token = tokenizer.nextToken(); + if (token.equalsIgnoreCase("version")) { //$NON-NLS-1$ + if (!tokenizer.hasMoreTokens()) { + break; + } + manifestInfo.schemaVersion = tokenizer.nextToken(); + break; + } + } + } + } +} |