/* * Copyright 2002-2004 Apache Software Foundation * * Licensed 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 * * http://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.tools.ant.taskdefs.optional.extension; import java.util.ArrayList; import java.util.Iterator; import java.util.Map; import java.util.StringTokenizer; import java.util.jar.Attributes; import java.util.jar.Manifest; /** *

Utility class that represents either an available "Optional Package" * (formerly known as "Standard Extension") as described in the manifest * of a JAR file, or the requirement for such an optional package.

* *

For more information about optional packages, see the document * Optional Package Versioning in the documentation bundle for your * Java2 Standard Edition package, in file * guide/extensions/versioning.html.

* * @author Craig R. McClanahan * @author Peter Donald * @version $Revision$ $Date$ */ public final class Extension { /** * Manifest Attribute Name object for EXTENSION_LIST. * @see Attributes.Name#EXTENSION_LIST */ public static final Attributes.Name EXTENSION_LIST = new Attributes.Name("Extension-List"); /** * Name object for Optional-Extension-List * manifest attribute used for declaring optional dependencies on * installed extensions. Note that the dependencies declared by this method * are not required for the library to operate but if present will be used. * It is NOT part of the official "Optional Package" specification. * * @see * Installed extension dependency */ public static final Attributes.Name OPTIONAL_EXTENSION_LIST = new Attributes.Name("Optional-Extension-List"); /** * Manifest Attribute Name object for EXTENSION_NAME. * @see Attributes.Name#EXTENSION_NAME */ public static final Attributes.Name EXTENSION_NAME = new Attributes.Name("Extension-Name"); /** * Manifest Attribute Name object for SPECIFICATION_VERSION. * @see Attributes.Name#SPECIFICATION_VERSION */ public static final Attributes.Name SPECIFICATION_VERSION = Attributes.Name.SPECIFICATION_VERSION; /** * Manifest Attribute Name object for SPECIFICATION_VENDOR. * @see Attributes.Name#SPECIFICATION_VENDOR */ public static final Attributes.Name SPECIFICATION_VENDOR = Attributes.Name.SPECIFICATION_VENDOR; /** * Manifest Attribute Name object for IMPLEMENTATION_VERSION. * @see Attributes.Name#IMPLEMENTATION_VERSION */ public static final Attributes.Name IMPLEMENTATION_VERSION = Attributes.Name.IMPLEMENTATION_VERSION; /** * Manifest Attribute Name object for IMPLEMENTATION_VENDOR. * @see Attributes.Name#IMPLEMENTATION_VENDOR */ public static final Attributes.Name IMPLEMENTATION_VENDOR = Attributes.Name.IMPLEMENTATION_VENDOR; /** * Manifest Attribute Name object for IMPLEMENTATION_URL. * @see Attributes.Name#IMPLEMENTATION_URL */ public static final Attributes.Name IMPLEMENTATION_URL = new Attributes.Name("Implementation-URL"); /** * Manifest Attribute Name object for IMPLEMENTATION_VENDOR_ID. * @see Attributes.Name#IMPLEMENTATION_VENDOR_ID */ public static final Attributes.Name IMPLEMENTATION_VENDOR_ID = new Attributes.Name("Implementation-Vendor-Id"); /** * Enum indicating that extension is compatible with other extension. */ public static final Compatibility COMPATIBLE = new Compatibility("COMPATIBLE"); /** * Enum indicating that extension requires an upgrade * of specification to be compatible with other extension. */ public static final Compatibility REQUIRE_SPECIFICATION_UPGRADE = new Compatibility("REQUIRE_SPECIFICATION_UPGRADE"); /** * Enum indicating that extension requires a vendor * switch to be compatible with other extension. */ public static final Compatibility REQUIRE_VENDOR_SWITCH = new Compatibility("REQUIRE_VENDOR_SWITCH"); /** * Enum indicating that extension requires an upgrade * of implementation to be compatible with other extension. */ public static final Compatibility REQUIRE_IMPLEMENTATION_UPGRADE = new Compatibility("REQUIRE_IMPLEMENTATION_UPGRADE"); /** * Enum indicating that extension is incompatible with * other extension in ways other than other enums * indicate). For example the other extension may have * a different ID. */ public static final Compatibility INCOMPATIBLE = new Compatibility("INCOMPATIBLE"); /** * The name of the optional package being made available, or required. */ private String extensionName; /** * The version number (dotted decimal notation) of the specification * to which this optional package conforms. */ private DeweyDecimal specificationVersion; /** * The name of the company or organization that originated the * specification to which this optional package conforms. */ private String specificationVendor; /** * The unique identifier of the company that produced the optional * package contained in this JAR file. */ private String implementationVendorID; /** * The name of the company or organization that produced this * implementation of this optional package. */ private String implementationVendor; /** * The version number (dotted decimal notation) for this implementation * of the optional package. */ private DeweyDecimal implementationVersion; /** * The URL from which the most recent version of this optional package * can be obtained if it is not already installed. */ private String implementationURL; /** * Return an array of Extension objects representing optional * packages that are available in the JAR file associated with the * specified Manifest. If there are no such optional * packages, a zero-length array is returned. * * @param manifest Manifest to be parsed * @return the "available" extensions in specified manifest */ public static Extension[] getAvailable(final Manifest manifest) { if (null == manifest) { return new Extension[ 0 ]; } final ArrayList results = new ArrayList(); final Attributes mainAttributes = manifest.getMainAttributes(); if (null != mainAttributes) { final Extension extension = getExtension("", mainAttributes); if (null != extension) { results.add(extension); } } final Map entries = manifest.getEntries(); final Iterator keys = entries.keySet().iterator(); while (keys.hasNext()) { final String key = (String) keys.next(); final Attributes attributes = (Attributes) entries.get(key); final Extension extension = getExtension("", attributes); if (null != extension) { results.add(extension); } } return (Extension[]) results.toArray(new Extension[0]); } /** * Return the set of Extension objects representing optional * packages that are required by the application contained in the JAR * file associated with the specified Manifest. If there * are no such optional packages, a zero-length list is returned. * * @param manifest Manifest to be parsed * @return the dependencies that are specified in manifes */ public static Extension[] getRequired(final Manifest manifest) { return getListed(manifest, Attributes.Name.EXTENSION_LIST); } /** * Return the set of Extension objects representing "Optional * Packages" that the application declares they will use if present. If * there are no such optional packages, a zero-length list is returned. * * @param manifest Manifest to be parsed * @return the optional dependencies that are specified in manifest */ public static Extension[] getOptions(final Manifest manifest) { return getListed(manifest, OPTIONAL_EXTENSION_LIST); } /** * Add Extension to the specified manifest Attributes. * * @param attributes the attributes of manifest to add to * @param extension the extension */ public static void addExtension(final Extension extension, final Attributes attributes) { addExtension(extension, "", attributes); } /** * Add Extension to the specified manifest Attributes. * Use the specified prefix so that dependencies can added * with a prefix such as "java3d-" etc. * * @param attributes the attributes of manifest to add to * @param extension the extension * @param prefix the name to prefix to extension */ public static void addExtension(final Extension extension, final String prefix, final Attributes attributes) { attributes.putValue(prefix + EXTENSION_NAME, extension.getExtensionName()); final String specificationVendor = extension.getSpecificationVendor(); if (null != specificationVendor) { attributes.putValue(prefix + SPECIFICATION_VENDOR, specificationVendor); } final DeweyDecimal specificationVersion = extension.getSpecificationVersion(); if (null != specificationVersion) { attributes.putValue(prefix + SPECIFICATION_VERSION, specificationVersion.toString()); } final String implementationVendorID = extension.getImplementationVendorID(); if (null != implementationVendorID) { attributes.putValue(prefix + IMPLEMENTATION_VENDOR_ID, implementationVendorID); } final String implementationVendor = extension.getImplementationVendor(); if (null != implementationVendor) { attributes.putValue(prefix + IMPLEMENTATION_VENDOR, implementationVendor); } final DeweyDecimal implementationVersion = extension.getImplementationVersion(); if (null != implementationVersion) { attributes.putValue(prefix + IMPLEMENTATION_VERSION, implementationVersion.toString()); } final String implementationURL = extension.getImplementationURL(); if (null != implementationURL) { attributes.putValue(prefix + IMPLEMENTATION_URL, implementationURL); } } /** * The constructor to create Extension object. * Note that every component is allowed to be specified * but only the extensionName is mandatory. * * @param extensionName the name of extension. * @param specificationVersion the specification Version of extension. * @param specificationVendor the specification Vendor of extension. * @param implementationVersion the implementation Version of extension. * @param implementationVendor the implementation Vendor of extension. * @param implementationVendorId the implementation VendorId of extension. * @param implementationURL the implementation URL of extension. */ public Extension(final String extensionName, final String specificationVersion, final String specificationVendor, final String implementationVersion, final String implementationVendor, final String implementationVendorId, final String implementationURL) { this.extensionName = extensionName; this.specificationVendor = specificationVendor; if (null != specificationVersion) { try { this.specificationVersion = new DeweyDecimal(specificationVersion); } catch (final NumberFormatException nfe) { final String error = "Bad specification version format '" + specificationVersion + "' in '" + extensionName + "'. (Reason: " + nfe + ")"; throw new IllegalArgumentException(error); } } this.implementationURL = implementationURL; this.implementationVendor = implementationVendor; this.implementationVendorID = implementationVendorId; if (null != implementationVersion) { try { this.implementationVersion = new DeweyDecimal(implementationVersion); } catch (final NumberFormatException nfe) { final String error = "Bad implementation version format '" + implementationVersion + "' in '" + extensionName + "'. (Reason: " + nfe + ")"; throw new IllegalArgumentException(error); } } if (null == this.extensionName) { throw new NullPointerException("extensionName property is null"); } } /** * Get the name of the extension. * * @return the name of the extension */ public String getExtensionName() { return extensionName; } /** * Get the vendor of the extensions specification. * * @return the vendor of the extensions specification. */ public String getSpecificationVendor() { return specificationVendor; } /** * Get the version of the extensions specification. * * @return the version of the extensions specification. */ public DeweyDecimal getSpecificationVersion() { return specificationVersion; } /** * Get the url of the extensions implementation. * * @return the url of the extensions implementation. */ public String getImplementationURL() { return implementationURL; } /** * Get the vendor of the extensions implementation. * * @return the vendor of the extensions implementation. */ public String getImplementationVendor() { return implementationVendor; } /** * Get the vendorID of the extensions implementation. * * @return the vendorID of the extensions implementation. */ public String getImplementationVendorID() { return implementationVendorID; } /** * Get the version of the extensions implementation. * * @return the version of the extensions implementation. */ public DeweyDecimal getImplementationVersion() { return implementationVersion; } /** * Return a Compatibility enum indicating the relationship of this * Extension with the specified Extension. * * @param required Description of the required optional package * @return the enum indicating the compatibility (or lack thereof) * of specifed extension */ public Compatibility getCompatibilityWith(final Extension required) { // Extension Name must match if (!extensionName.equals(required.getExtensionName())) { return INCOMPATIBLE; } // Available specification version must be >= required final DeweyDecimal specificationVersion = required.getSpecificationVersion(); if (null != specificationVersion) { if (null == specificationVersion || !isCompatible(specificationVersion, specificationVersion)) { return REQUIRE_SPECIFICATION_UPGRADE; } } // Implementation Vendor ID must match final String implementationVendorId = required.getImplementationVendorID(); if (null != implementationVendorId) { if (null == implementationVendorID || !implementationVendorID.equals(implementationVendorId)) { return REQUIRE_VENDOR_SWITCH; } } // Implementation version must be >= required final DeweyDecimal implementationVersion = required.getImplementationVersion(); if (null != implementationVersion) { if (null == implementationVersion || !isCompatible(implementationVersion, implementationVersion)) { return REQUIRE_IMPLEMENTATION_UPGRADE; } } // This available optional package satisfies the requirements return COMPATIBLE; } /** * Return true if the specified Extension * (which represents an optional package required by an application) * is satisfied by this Extension (which represents an * optional package that is already installed. Otherwise, return * false. * * @param required Description of the required optional package * @return true if the specified extension is compatible with this extension */ public boolean isCompatibleWith(final Extension required) { return (COMPATIBLE == getCompatibilityWith(required)); } /** * Return a String representation of this object. * * @return string representation of object. */ public String toString() { final String lineSeparator = System.getProperty("line.separator"); final String brace = ": "; final StringBuffer sb = new StringBuffer(EXTENSION_NAME.toString()); sb.append(brace); sb.append(extensionName); sb.append(lineSeparator); if (null != specificationVersion) { sb.append(SPECIFICATION_VERSION); sb.append(brace); sb.append(specificationVersion); sb.append(lineSeparator); } if (null != specificationVendor) { sb.append(SPECIFICATION_VENDOR); sb.append(brace); sb.append(specificationVendor); sb.append(lineSeparator); } if (null != implementationVersion) { sb.append(IMPLEMENTATION_VERSION); sb.append(brace); sb.append(implementationVersion); sb.append(lineSeparator); } if (null != implementationVendorID) { sb.append(IMPLEMENTATION_VENDOR_ID); sb.append(brace); sb.append(implementationVendorID); sb.append(lineSeparator); } if (null != implementationVendor) { sb.append(IMPLEMENTATION_VENDOR); sb.append(brace); sb.append(implementationVendor); sb.append(lineSeparator); } if (null != implementationURL) { sb.append(IMPLEMENTATION_URL); sb.append(brace); sb.append(implementationURL); sb.append(lineSeparator); } return sb.toString(); } /** * Return true if the first version number is greater than * or equal to the second; otherwise return false. * * @param first First version number (dotted decimal) * @param second Second version number (dotted decimal) */ private boolean isCompatible(final DeweyDecimal first, final DeweyDecimal second) { return first.isGreaterThanOrEqual(second); } /** * Retrieve all the extensions listed under a particular key * (Usually EXTENSION_LIST or OPTIONAL_EXTENSION_LIST). * * @param manifest the manifest to extract extensions from * @param listKey the key used to get list (Usually * EXTENSION_LIST or OPTIONAL_EXTENSION_LIST) * @return the list of listed extensions */ private static Extension[] getListed(final Manifest manifest, final Attributes.Name listKey) { final ArrayList results = new ArrayList(); final Attributes mainAttributes = manifest.getMainAttributes(); if (null != mainAttributes) { getExtension(mainAttributes, results, listKey); } final Map entries = manifest.getEntries(); final Iterator keys = entries.keySet().iterator(); while (keys.hasNext()) { final String key = (String) keys.next(); final Attributes attributes = (Attributes) entries.get(key); getExtension(attributes, results, listKey); } return (Extension[]) results.toArray(new Extension[ 0 ]); } /** * Add required optional packages defined in the specified * attributes entry, if any. * * @param attributes Attributes to be parsed * @param required list to add required optional packages to * @param listKey the key to use to lookup list, usually EXTENSION_LIST * or OPTIONAL_EXTENSION_LIST */ private static void getExtension(final Attributes attributes, final ArrayList required, final Attributes.Name listKey) { final String names = attributes.getValue(listKey); if (null == names) { return; } final String[] extentions = split(names, " "); for (int i = 0; i < extentions.length; i++) { final String prefix = extentions[ i ] + "-"; final Extension extension = getExtension(prefix, attributes); if (null != extension) { required.add(extension); } } } /** * Splits the string on every token into an array of strings. * * @param string the string * @param onToken the token * @return the resultant array */ private static final String[] split(final String string, final String onToken) { final StringTokenizer tokenizer = new StringTokenizer(string, onToken); final String[] result = new String[ tokenizer.countTokens() ]; for (int i = 0; i < result.length; i++) { result[ i ] = tokenizer.nextToken(); } return result; } /** * Extract an Extension from Attributes. * Prefix indicates the prefix checked for each string. * Usually the prefix is "<extension>-" if looking for a * Required extension. If you are looking for an * Available extension * then the prefix is "". * * @param prefix the prefix for each attribute name * @param attributes Attributes to searched * @return the new Extension object, or null */ private static Extension getExtension(final String prefix, final Attributes attributes) { //WARNING: We trim the values of all the attributes because //Some extension declarations are badly defined (ie have spaces //after version or vendorID) final String nameKey = prefix + EXTENSION_NAME; final String name = getTrimmedString(attributes.getValue(nameKey)); if (null == name) { return null; } final String specVendorKey = prefix + SPECIFICATION_VENDOR; final String specVendor = getTrimmedString(attributes.getValue(specVendorKey)); final String specVersionKey = prefix + SPECIFICATION_VERSION; final String specVersion = getTrimmedString(attributes.getValue(specVersionKey)); final String impVersionKey = prefix + IMPLEMENTATION_VERSION; final String impVersion = getTrimmedString(attributes.getValue(impVersionKey)); final String impVendorKey = prefix + IMPLEMENTATION_VENDOR; final String impVendor = getTrimmedString(attributes.getValue(impVendorKey)); final String impVendorIDKey = prefix + IMPLEMENTATION_VENDOR_ID; final String impVendorId = getTrimmedString(attributes.getValue(impVendorIDKey)); final String impURLKey = prefix + IMPLEMENTATION_URL; final String impURL = getTrimmedString(attributes.getValue(impURLKey)); return new Extension(name, specVersion, specVendor, impVersion, impVendor, impVendorId, impURL); } /** * Trim the supplied string if the string is non-null * * @param value the string to trim or null * @return the trimmed string or null */ private static String getTrimmedString(final String value) { if (null == value) { return null; } else { return value.trim(); } } }