| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * https://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| * |
| */ |
| package org.apache.ivy.osgi.core; |
| |
| import java.io.ByteArrayInputStream; |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.nio.charset.StandardCharsets; |
| import java.text.ParseException; |
| import java.util.List; |
| import java.util.jar.Attributes; |
| import java.util.jar.JarInputStream; |
| import java.util.jar.Manifest; |
| |
| import org.apache.ivy.osgi.util.Version; |
| import org.apache.ivy.osgi.util.VersionRange; |
| |
| import static org.apache.ivy.util.StringUtils.splitToArray; |
| |
| /** |
| * Provides an OSGi manifest parser. |
| * |
| */ |
| public class ManifestParser { |
| |
| private static final String EXPORT_PACKAGE = "Export-Package"; |
| |
| private static final String IMPORT_PACKAGE = "Import-Package"; |
| |
| private static final String EXPORT_SERVICE = "Export-Service"; |
| |
| private static final String IMPORT_SERVICE = "Import-Service"; |
| |
| private static final String REQUIRE_BUNDLE = "Require-Bundle"; |
| |
| private static final String BUNDLE_VERSION = "Bundle-Version"; |
| |
| @SuppressWarnings("unused") |
| private static final String BUNDLE_NAME = "Bundle-Name"; |
| |
| private static final String BUNDLE_DESCRIPTION = "Bundle-Description"; |
| |
| private static final String BUNDLE_SYMBOLIC_NAME = "Bundle-SymbolicName"; |
| |
| @SuppressWarnings("unused") |
| private static final String BUNDLE_MANIFEST_VERSION = "Bundle-ManifestVersion"; |
| |
| private static final String BUNDLE_REQUIRED_EXECUTION_ENVIRONMENT = "Bundle-RequiredExecutionEnvironment"; |
| |
| private static final String BUNDLE_CLASSPATH = "Bundle-ClassPath"; |
| |
| private static final String ECLIPSE_SOURCE_BUNDLE = "Eclipse-SourceBundle"; |
| |
| private static final String ATTR_RESOLUTION = "resolution"; |
| |
| private static final String ATTR_VERSION = "version"; |
| |
| private static final String ATTR_BUNDLE_VERSION = "bundle-version"; |
| |
| private static final String ATTR_USE = "use"; |
| |
| public static BundleInfo parseJarManifest(InputStream jarStream) throws IOException, |
| ParseException { |
| JarInputStream jis = new JarInputStream(jarStream); |
| Manifest manifest = jis.getManifest(); |
| jis.close(); |
| if (manifest == null) { |
| return null; |
| } |
| return parseManifest(manifest); |
| } |
| |
| public static BundleInfo parseManifest(File manifestFile) throws IOException, ParseException { |
| try (FileInputStream fis = new FileInputStream(manifestFile)) { |
| return parseManifest(fis); |
| } |
| } |
| |
| public static BundleInfo parseManifest(String manifest) throws IOException, ParseException { |
| ByteArrayInputStream bais = new ByteArrayInputStream(manifest.getBytes(StandardCharsets.UTF_8)); |
| BundleInfo parseManifest = parseManifest(bais); |
| bais.close(); |
| return parseManifest; |
| } |
| |
| public static BundleInfo parseManifest(InputStream manifestStream) throws IOException, |
| ParseException { |
| return parseManifest(new Manifest(manifestStream)); |
| } |
| |
| public static BundleInfo parseManifest(Manifest manifest) throws ParseException { |
| Attributes mainAttributes = manifest.getMainAttributes(); |
| |
| // Eclipse source bundle doesn't have it. Disable it until proven actually useful |
| // String manifestVersion = mainAttributes.getValue(BUNDLE_MANIFEST_VERSION); |
| // if (manifestVersion == null) { |
| // // non OSGi manifest |
| // throw new ParseException("No " + BUNDLE_MANIFEST_VERSION + " in the manifest", 0); |
| // } |
| |
| String symbolicName = new ManifestHeaderValue(mainAttributes.getValue(BUNDLE_SYMBOLIC_NAME)) |
| .getSingleValue(); |
| if (symbolicName == null) { |
| throw new ParseException("No " + BUNDLE_SYMBOLIC_NAME + " in the manifest", 0); |
| } |
| |
| String description = new ManifestHeaderValue(mainAttributes.getValue(BUNDLE_DESCRIPTION)) |
| .getSingleValue(); |
| if (description == null) { |
| description = new ManifestHeaderValue(mainAttributes.getValue(BUNDLE_DESCRIPTION)) |
| .getSingleValue(); |
| } |
| |
| String vBundle = new ManifestHeaderValue(mainAttributes.getValue(BUNDLE_VERSION)) |
| .getSingleValue(); |
| Version version; |
| try { |
| version = versionOf(vBundle); |
| } catch (NumberFormatException e) { |
| throw new ParseException("The " + BUNDLE_VERSION + " has an incorrect version: " |
| + vBundle + " (" + e.getMessage() + ")", 0); |
| } |
| |
| BundleInfo bundleInfo = new BundleInfo(symbolicName, version); |
| |
| bundleInfo.setDescription(description); |
| |
| List<String> environments = new ManifestHeaderValue( |
| mainAttributes.getValue(BUNDLE_REQUIRED_EXECUTION_ENVIRONMENT)).getValues(); |
| bundleInfo.setExecutionEnvironments(environments); |
| |
| parseRequirement(bundleInfo, mainAttributes, REQUIRE_BUNDLE, BundleInfo.BUNDLE_TYPE, |
| ATTR_BUNDLE_VERSION); |
| parseRequirement(bundleInfo, mainAttributes, IMPORT_PACKAGE, BundleInfo.PACKAGE_TYPE, |
| ATTR_VERSION); |
| parseRequirement(bundleInfo, mainAttributes, IMPORT_SERVICE, BundleInfo.SERVICE_TYPE, |
| ATTR_VERSION); |
| |
| ManifestHeaderValue exportElements = new ManifestHeaderValue( |
| mainAttributes.getValue(EXPORT_PACKAGE)); |
| for (ManifestHeaderElement exportElement : exportElements.getElements()) { |
| String vExport = exportElement.getAttributes().get(ATTR_VERSION); |
| Version v = null; |
| try { |
| v = versionOf(vExport); |
| } catch (NumberFormatException e) { |
| throw new ParseException("The " + EXPORT_PACKAGE + " has an incorrect version: " |
| + vExport + " (" + e.getMessage() + ")", 0); |
| } |
| |
| for (String name : exportElement.getValues()) { |
| ExportPackage export = new ExportPackage(name, v); |
| String uses = exportElement.getDirectives().get(ATTR_USE); |
| if (uses != null) { |
| for (String use : splitToArray(uses)) { |
| export.addUse(use); |
| } |
| } |
| bundleInfo.addCapability(export); |
| } |
| } |
| |
| parseCapability(bundleInfo, mainAttributes, EXPORT_SERVICE, BundleInfo.SERVICE_TYPE); |
| |
| // handle Eclipse specific source attachment |
| String eclipseSourceBundle = mainAttributes.getValue(ECLIPSE_SOURCE_BUNDLE); |
| if (eclipseSourceBundle != null) { |
| bundleInfo.setSource(true); |
| ManifestHeaderValue eclipseSourceBundleValue = new ManifestHeaderValue( |
| eclipseSourceBundle); |
| ManifestHeaderElement element = eclipseSourceBundleValue.getElements().iterator() |
| .next(); |
| String symbolicNameTarget = element.getValues().iterator().next(); |
| bundleInfo.setSymbolicNameTarget(symbolicNameTarget); |
| String v = element.getAttributes().get(ATTR_VERSION); |
| if (v != null) { |
| bundleInfo.setVersionTarget(new Version(v)); |
| } |
| } |
| |
| String bundleClasspath = mainAttributes.getValue(BUNDLE_CLASSPATH); |
| if (bundleClasspath != null) { |
| ManifestHeaderValue bundleClasspathValue = new ManifestHeaderValue(bundleClasspath); |
| bundleInfo.setClasspath(bundleClasspathValue.getValues()); |
| bundleInfo.setHasInnerClasspath(true); |
| } |
| |
| return bundleInfo; |
| } |
| |
| private static void parseRequirement(BundleInfo bundleInfo, Attributes mainAttributes, |
| String headerName, String type, String versionAttr) throws ParseException { |
| ManifestHeaderValue elements = new ManifestHeaderValue(mainAttributes.getValue(headerName)); |
| for (ManifestHeaderElement element : elements.getElements()) { |
| String resolution = element.getDirectives().get(ATTR_RESOLUTION); |
| String attVersion = element.getAttributes().get(versionAttr); |
| VersionRange version = null; |
| try { |
| version = versionRangeOf(attVersion); |
| } catch (ParseException e) { |
| throw new ParseException("The " + headerName + " has an incorrect version: " |
| + attVersion + " (" + e.getMessage() + ")", 0); |
| } |
| |
| for (String name : element.getValues()) { |
| bundleInfo.addRequirement(new BundleRequirement(type, name, version, resolution)); |
| } |
| } |
| } |
| |
| private static void parseCapability(BundleInfo bundleInfo, Attributes mainAttributes, |
| String headerName, String type) throws ParseException { |
| ManifestHeaderValue elements = new ManifestHeaderValue(mainAttributes.getValue(headerName)); |
| for (ManifestHeaderElement element : elements.getElements()) { |
| String attVersion = element.getAttributes().get(ATTR_VERSION); |
| Version version = null; |
| try { |
| version = versionOf(attVersion); |
| } catch (NumberFormatException e) { |
| throw new ParseException("The " + headerName + " has an incorrect version: " |
| + attVersion + " (" + e.getMessage() + ")", 0); |
| } |
| |
| for (String name : element.getValues()) { |
| BundleCapability export = new BundleCapability(type, name, version); |
| bundleInfo.addCapability(export); |
| } |
| } |
| |
| } |
| |
| private static VersionRange versionRangeOf(String v) throws ParseException { |
| if (v == null) { |
| return null; |
| } |
| return new VersionRange(v); |
| } |
| |
| private static Version versionOf(String v) { |
| if (v == null) { |
| return null; |
| } |
| return new Version(v); |
| } |
| |
| /** |
| * Ensure that the lines are not longer than 72 characters, so it can be parsed by the |
| * {@link Manifest} class |
| * |
| * @param manifest ditto |
| * @return String |
| */ |
| public static String formatLines(String manifest) { |
| StringBuilder buffer = new StringBuilder(manifest.length()); |
| for (String line : manifest.split("\n")) { |
| if (line.length() <= 72) { |
| buffer.append(line); |
| buffer.append('\n'); |
| } else { |
| buffer.append(line, 0, 72); |
| buffer.append("\n "); |
| int n = 72; |
| while (n <= line.length() - 1) { |
| int end = n + 71; |
| if (end > line.length()) { |
| end = line.length(); |
| } |
| buffer.append(line, n, end); |
| buffer.append('\n'); |
| if (end != line.length()) { |
| buffer.append(' '); |
| } |
| n = end; |
| } |
| } |
| } |
| return buffer.toString(); |
| } |
| } |