001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: */
018: package org.apache.tools.ant.taskdefs.optional.extension;
019:
020: import java.util.ArrayList;
021: import java.util.Iterator;
022: import java.util.Map;
023: import java.util.StringTokenizer;
024: import java.util.jar.Attributes;
025: import java.util.jar.Manifest;
026:
027: import org.apache.tools.ant.util.StringUtils;
028:
029: /**
030: * <p>Utility class that represents either an available "Optional Package"
031: * (formerly known as "Standard Extension") as described in the manifest
032: * of a JAR file, or the requirement for such an optional package.</p>
033: *
034: * <p>For more information about optional packages, see the document
035: * <em>Optional Package Versioning</em> in the documentation bundle for your
036: * Java2 Standard Edition package, in file
037: * <code>guide/extensions/versioning.html</code>.</p>
038: *
039: */
040: public final class Extension {
041: /**
042: * Manifest Attribute Name object for EXTENSION_LIST.
043: */
044: public static final Attributes.Name EXTENSION_LIST = new Attributes.Name(
045: "Extension-List");
046:
047: /**
048: * <code>Name</code> object for <code>Optional-Extension-List</code>
049: * manifest attribute used for declaring optional dependencies on
050: * installed extensions. Note that the dependencies declared by this method
051: * are not required for the library to operate but if present will be used.
052: * It is NOT part of the official "Optional Package" specification.
053: *
054: * @see <a href="http://java.sun.com/j2se/1.3/docs/guide/extensions/spec.html#dependnecy">
055: * Installed extension dependency</a>
056: */
057: public static final Attributes.Name OPTIONAL_EXTENSION_LIST = new Attributes.Name(
058: "Optional-Extension-List");
059:
060: /**
061: * Manifest Attribute Name object for EXTENSION_NAME.
062: */
063: public static final Attributes.Name EXTENSION_NAME = new Attributes.Name(
064: "Extension-Name");
065: /**
066: * Manifest Attribute Name object for SPECIFICATION_VERSION.
067: */
068: public static final Attributes.Name SPECIFICATION_VERSION = Attributes.Name.SPECIFICATION_VERSION;
069:
070: /**
071: * Manifest Attribute Name object for SPECIFICATION_VENDOR.
072: */
073: public static final Attributes.Name SPECIFICATION_VENDOR = Attributes.Name.SPECIFICATION_VENDOR;
074:
075: /**
076: * Manifest Attribute Name object for IMPLEMENTATION_VERSION.
077: */
078: public static final Attributes.Name IMPLEMENTATION_VERSION = Attributes.Name.IMPLEMENTATION_VERSION;
079:
080: /**
081: * Manifest Attribute Name object for IMPLEMENTATION_VENDOR.
082: */
083: public static final Attributes.Name IMPLEMENTATION_VENDOR = Attributes.Name.IMPLEMENTATION_VENDOR;
084:
085: /**
086: * Manifest Attribute Name object for IMPLEMENTATION_URL.
087: */
088: public static final Attributes.Name IMPLEMENTATION_URL = new Attributes.Name(
089: "Implementation-URL");
090:
091: /**
092: * Manifest Attribute Name object for IMPLEMENTATION_VENDOR_ID.
093: */
094: public static final Attributes.Name IMPLEMENTATION_VENDOR_ID = new Attributes.Name(
095: "Implementation-Vendor-Id");
096:
097: /**
098: * Enum indicating that extension is compatible with other extension.
099: */
100: public static final Compatibility COMPATIBLE = new Compatibility(
101: "COMPATIBLE");
102:
103: /**
104: * Enum indicating that extension requires an upgrade
105: * of specification to be compatible with other extension.
106: */
107: public static final Compatibility REQUIRE_SPECIFICATION_UPGRADE = new Compatibility(
108: "REQUIRE_SPECIFICATION_UPGRADE");
109:
110: /**
111: * Enum indicating that extension requires a vendor
112: * switch to be compatible with other extension.
113: */
114: public static final Compatibility REQUIRE_VENDOR_SWITCH = new Compatibility(
115: "REQUIRE_VENDOR_SWITCH");
116:
117: /**
118: * Enum indicating that extension requires an upgrade
119: * of implementation to be compatible with other extension.
120: */
121: public static final Compatibility REQUIRE_IMPLEMENTATION_UPGRADE = new Compatibility(
122: "REQUIRE_IMPLEMENTATION_UPGRADE");
123:
124: /**
125: * Enum indicating that extension is incompatible with
126: * other extension in ways other than other enums
127: * indicate). For example the other extension may have
128: * a different ID.
129: */
130: public static final Compatibility INCOMPATIBLE = new Compatibility(
131: "INCOMPATIBLE");
132:
133: /**
134: * The name of the optional package being made available, or required.
135: */
136: private String extensionName;
137:
138: /**
139: * The version number (dotted decimal notation) of the specification
140: * to which this optional package conforms.
141: */
142: private DeweyDecimal specificationVersion;
143:
144: /**
145: * The name of the company or organization that originated the
146: * specification to which this optional package conforms.
147: */
148: private String specificationVendor;
149:
150: /**
151: * The unique identifier of the company that produced the optional
152: * package contained in this JAR file.
153: */
154: private String implementationVendorID;
155:
156: /**
157: * The name of the company or organization that produced this
158: * implementation of this optional package.
159: */
160: private String implementationVendor;
161:
162: /**
163: * The version number (dotted decimal notation) for this implementation
164: * of the optional package.
165: */
166: private DeweyDecimal implementationVersion;
167:
168: /**
169: * The URL from which the most recent version of this optional package
170: * can be obtained if it is not already installed.
171: */
172: private String implementationURL;
173:
174: /**
175: * Return an array of <code>Extension</code> objects representing optional
176: * packages that are available in the JAR file associated with the
177: * specified <code>Manifest</code>. If there are no such optional
178: * packages, a zero-length array is returned.
179: *
180: * @param manifest Manifest to be parsed
181: * @return the "available" extensions in specified manifest
182: */
183: public static Extension[] getAvailable(final Manifest manifest) {
184: if (null == manifest) {
185: return new Extension[0];
186: }
187:
188: final ArrayList results = new ArrayList();
189:
190: final Attributes mainAttributes = manifest.getMainAttributes();
191: if (null != mainAttributes) {
192: final Extension extension = getExtension("", mainAttributes);
193: if (null != extension) {
194: results.add(extension);
195: }
196: }
197:
198: final Map entries = manifest.getEntries();
199: final Iterator keys = entries.keySet().iterator();
200: while (keys.hasNext()) {
201: final String key = (String) keys.next();
202: final Attributes attributes = (Attributes) entries.get(key);
203: final Extension extension = getExtension("", attributes);
204: if (null != extension) {
205: results.add(extension);
206: }
207: }
208:
209: return (Extension[]) results.toArray(new Extension[results
210: .size()]);
211: }
212:
213: /**
214: * Return the set of <code>Extension</code> objects representing optional
215: * packages that are required by the application contained in the JAR
216: * file associated with the specified <code>Manifest</code>. If there
217: * are no such optional packages, a zero-length list is returned.
218: *
219: * @param manifest Manifest to be parsed
220: * @return the dependencies that are specified in manifes
221: */
222: public static Extension[] getRequired(final Manifest manifest) {
223: return getListed(manifest, Attributes.Name.EXTENSION_LIST);
224: }
225:
226: /**
227: * Return the set of <code>Extension</code> objects representing "Optional
228: * Packages" that the application declares they will use if present. If
229: * there are no such optional packages, a zero-length list is returned.
230: *
231: * @param manifest Manifest to be parsed
232: * @return the optional dependencies that are specified in manifest
233: */
234: public static Extension[] getOptions(final Manifest manifest) {
235: return getListed(manifest, OPTIONAL_EXTENSION_LIST);
236: }
237:
238: /**
239: * Add Extension to the specified manifest Attributes.
240: *
241: * @param attributes the attributes of manifest to add to
242: * @param extension the extension
243: */
244: public static void addExtension(final Extension extension,
245: final Attributes attributes) {
246: addExtension(extension, "", attributes);
247: }
248:
249: /**
250: * Add Extension to the specified manifest Attributes.
251: * Use the specified prefix so that dependencies can added
252: * with a prefix such as "java3d-" etc.
253: *
254: * @param attributes the attributes of manifest to add to
255: * @param extension the extension
256: * @param prefix the name to prefix to extension
257: */
258: public static void addExtension(final Extension extension,
259: final String prefix, final Attributes attributes) {
260: attributes.putValue(prefix + EXTENSION_NAME, extension
261: .getExtensionName());
262:
263: final String specificationVendor = extension
264: .getSpecificationVendor();
265: if (null != specificationVendor) {
266: attributes.putValue(prefix + SPECIFICATION_VENDOR,
267: specificationVendor);
268: }
269:
270: final DeweyDecimal specificationVersion = extension
271: .getSpecificationVersion();
272: if (null != specificationVersion) {
273: attributes.putValue(prefix + SPECIFICATION_VERSION,
274: specificationVersion.toString());
275: }
276:
277: final String implementationVendorID = extension
278: .getImplementationVendorID();
279: if (null != implementationVendorID) {
280: attributes.putValue(prefix + IMPLEMENTATION_VENDOR_ID,
281: implementationVendorID);
282: }
283:
284: final String implementationVendor = extension
285: .getImplementationVendor();
286: if (null != implementationVendor) {
287: attributes.putValue(prefix + IMPLEMENTATION_VENDOR,
288: implementationVendor);
289: }
290:
291: final DeweyDecimal implementationVersion = extension
292: .getImplementationVersion();
293: if (null != implementationVersion) {
294: attributes.putValue(prefix + IMPLEMENTATION_VERSION,
295: implementationVersion.toString());
296: }
297:
298: final String implementationURL = extension
299: .getImplementationURL();
300: if (null != implementationURL) {
301: attributes.putValue(prefix + IMPLEMENTATION_URL,
302: implementationURL);
303: }
304: }
305:
306: /**
307: * The constructor to create Extension object.
308: * Note that every component is allowed to be specified
309: * but only the extensionName is mandatory.
310: *
311: * @param extensionName the name of extension.
312: * @param specificationVersion the specification Version of extension.
313: * @param specificationVendor the specification Vendor of extension.
314: * @param implementationVersion the implementation Version of extension.
315: * @param implementationVendor the implementation Vendor of extension.
316: * @param implementationVendorId the implementation VendorId of extension.
317: * @param implementationURL the implementation URL of extension.
318: */
319: public Extension(final String extensionName,
320: final String specificationVersion,
321: final String specificationVendor,
322: final String implementationVersion,
323: final String implementationVendor,
324: final String implementationVendorId,
325: final String implementationURL) {
326: this .extensionName = extensionName;
327: this .specificationVendor = specificationVendor;
328:
329: if (null != specificationVersion) {
330: try {
331: this .specificationVersion = new DeweyDecimal(
332: specificationVersion);
333: } catch (final NumberFormatException nfe) {
334: final String error = "Bad specification version format '"
335: + specificationVersion
336: + "' in '"
337: + extensionName + "'. (Reason: " + nfe + ")";
338: throw new IllegalArgumentException(error);
339: }
340: }
341:
342: this .implementationURL = implementationURL;
343: this .implementationVendor = implementationVendor;
344: this .implementationVendorID = implementationVendorId;
345:
346: if (null != implementationVersion) {
347: try {
348: this .implementationVersion = new DeweyDecimal(
349: implementationVersion);
350: } catch (final NumberFormatException nfe) {
351: final String error = "Bad implementation version format '"
352: + implementationVersion
353: + "' in '"
354: + extensionName + "'. (Reason: " + nfe + ")";
355: throw new IllegalArgumentException(error);
356: }
357: }
358:
359: if (null == this .extensionName) {
360: throw new NullPointerException(
361: "extensionName property is null");
362: }
363: }
364:
365: /**
366: * Get the name of the extension.
367: *
368: * @return the name of the extension
369: */
370: public String getExtensionName() {
371: return extensionName;
372: }
373:
374: /**
375: * Get the vendor of the extensions specification.
376: *
377: * @return the vendor of the extensions specification.
378: */
379: public String getSpecificationVendor() {
380: return specificationVendor;
381: }
382:
383: /**
384: * Get the version of the extensions specification.
385: *
386: * @return the version of the extensions specification.
387: */
388: public DeweyDecimal getSpecificationVersion() {
389: return specificationVersion;
390: }
391:
392: /**
393: * Get the url of the extensions implementation.
394: *
395: * @return the url of the extensions implementation.
396: */
397: public String getImplementationURL() {
398: return implementationURL;
399: }
400:
401: /**
402: * Get the vendor of the extensions implementation.
403: *
404: * @return the vendor of the extensions implementation.
405: */
406: public String getImplementationVendor() {
407: return implementationVendor;
408: }
409:
410: /**
411: * Get the vendorID of the extensions implementation.
412: *
413: * @return the vendorID of the extensions implementation.
414: */
415: public String getImplementationVendorID() {
416: return implementationVendorID;
417: }
418:
419: /**
420: * Get the version of the extensions implementation.
421: *
422: * @return the version of the extensions implementation.
423: */
424: public DeweyDecimal getImplementationVersion() {
425: return implementationVersion;
426: }
427:
428: /**
429: * Return a Compatibility enum indicating the relationship of this
430: * <code>Extension</code> with the specified <code>Extension</code>.
431: *
432: * @param required Description of the required optional package
433: * @return the enum indicating the compatibility (or lack thereof)
434: * of specifed extension
435: */
436: public Compatibility getCompatibilityWith(final Extension required) {
437: // Extension Name must match
438: if (!extensionName.equals(required.getExtensionName())) {
439: return INCOMPATIBLE;
440: }
441:
442: // Available specification version must be >= required
443: final DeweyDecimal requiredSpecificationVersion = required
444: .getSpecificationVersion();
445: if (null != requiredSpecificationVersion) {
446: if (null == specificationVersion
447: || !isCompatible(specificationVersion,
448: requiredSpecificationVersion)) {
449: return REQUIRE_SPECIFICATION_UPGRADE;
450: }
451: }
452:
453: // Implementation Vendor ID must match
454: final String requiredImplementationVendorID = required
455: .getImplementationVendorID();
456: if (null != requiredImplementationVendorID) {
457: if (null == implementationVendorID
458: || !implementationVendorID
459: .equals(requiredImplementationVendorID)) {
460: return REQUIRE_VENDOR_SWITCH;
461: }
462: }
463:
464: // Implementation version must be >= required
465: final DeweyDecimal requiredImplementationVersion = required
466: .getImplementationVersion();
467: if (null != requiredImplementationVersion) {
468: if (null == implementationVersion
469: || !isCompatible(implementationVersion,
470: requiredImplementationVersion)) {
471: return REQUIRE_IMPLEMENTATION_UPGRADE;
472: }
473: }
474:
475: // This available optional package satisfies the requirements
476: return COMPATIBLE;
477: }
478:
479: /**
480: * Return <code>true</code> if the specified <code>Extension</code>
481: * (which represents an optional package required by an application)
482: * is satisfied by this <code>Extension</code> (which represents an
483: * optional package that is already installed. Otherwise, return
484: * <code>false</code>.
485: *
486: * @param required Description of the required optional package
487: * @return true if the specified extension is compatible with this extension
488: */
489: public boolean isCompatibleWith(final Extension required) {
490: return (COMPATIBLE == getCompatibilityWith(required));
491: }
492:
493: /**
494: * Return a String representation of this object.
495: *
496: * @return string representation of object.
497: */
498: public String toString() {
499: final String brace = ": ";
500:
501: final StringBuffer sb = new StringBuffer(EXTENSION_NAME
502: .toString());
503: sb.append(brace);
504: sb.append(extensionName);
505: sb.append(StringUtils.LINE_SEP);
506:
507: if (null != specificationVersion) {
508: sb.append(SPECIFICATION_VERSION);
509: sb.append(brace);
510: sb.append(specificationVersion);
511: sb.append(StringUtils.LINE_SEP);
512: }
513:
514: if (null != specificationVendor) {
515: sb.append(SPECIFICATION_VENDOR);
516: sb.append(brace);
517: sb.append(specificationVendor);
518: sb.append(StringUtils.LINE_SEP);
519: }
520:
521: if (null != implementationVersion) {
522: sb.append(IMPLEMENTATION_VERSION);
523: sb.append(brace);
524: sb.append(implementationVersion);
525: sb.append(StringUtils.LINE_SEP);
526: }
527:
528: if (null != implementationVendorID) {
529: sb.append(IMPLEMENTATION_VENDOR_ID);
530: sb.append(brace);
531: sb.append(implementationVendorID);
532: sb.append(StringUtils.LINE_SEP);
533: }
534:
535: if (null != implementationVendor) {
536: sb.append(IMPLEMENTATION_VENDOR);
537: sb.append(brace);
538: sb.append(implementationVendor);
539: sb.append(StringUtils.LINE_SEP);
540: }
541:
542: if (null != implementationURL) {
543: sb.append(IMPLEMENTATION_URL);
544: sb.append(brace);
545: sb.append(implementationURL);
546: sb.append(StringUtils.LINE_SEP);
547: }
548:
549: return sb.toString();
550: }
551:
552: /**
553: * Return <code>true</code> if the first version number is greater than
554: * or equal to the second; otherwise return <code>false</code>.
555: *
556: * @param first First version number (dotted decimal)
557: * @param second Second version number (dotted decimal)
558: */
559: private boolean isCompatible(final DeweyDecimal first,
560: final DeweyDecimal second) {
561: return first.isGreaterThanOrEqual(second);
562: }
563:
564: /**
565: * Retrieve all the extensions listed under a particular key
566: * (Usually EXTENSION_LIST or OPTIONAL_EXTENSION_LIST).
567: *
568: * @param manifest the manifest to extract extensions from
569: * @param listKey the key used to get list (Usually
570: * EXTENSION_LIST or OPTIONAL_EXTENSION_LIST)
571: * @return the list of listed extensions
572: */
573: private static Extension[] getListed(final Manifest manifest,
574: final Attributes.Name listKey) {
575: final ArrayList results = new ArrayList();
576: final Attributes mainAttributes = manifest.getMainAttributes();
577:
578: if (null != mainAttributes) {
579: getExtension(mainAttributes, results, listKey);
580: }
581:
582: final Map entries = manifest.getEntries();
583: final Iterator keys = entries.keySet().iterator();
584: while (keys.hasNext()) {
585: final String key = (String) keys.next();
586: final Attributes attributes = (Attributes) entries.get(key);
587: getExtension(attributes, results, listKey);
588: }
589:
590: return (Extension[]) results.toArray(new Extension[results
591: .size()]);
592: }
593:
594: /**
595: * Add required optional packages defined in the specified
596: * attributes entry, if any.
597: *
598: * @param attributes Attributes to be parsed
599: * @param required list to add required optional packages to
600: * @param listKey the key to use to lookup list, usually EXTENSION_LIST
601: * or OPTIONAL_EXTENSION_LIST
602: */
603: private static void getExtension(final Attributes attributes,
604: final ArrayList required, final Attributes.Name listKey) {
605: final String names = attributes.getValue(listKey);
606: if (null == names) {
607: return;
608: }
609:
610: final String[] extentions = split(names, " ");
611: for (int i = 0; i < extentions.length; i++) {
612: final String prefix = extentions[i] + "-";
613: final Extension extension = getExtension(prefix, attributes);
614:
615: if (null != extension) {
616: required.add(extension);
617: }
618: }
619: }
620:
621: /**
622: * Splits the string on every token into an array of strings.
623: *
624: * @param string the string
625: * @param onToken the token
626: * @return the resultant array
627: */
628: private static String[] split(final String string,
629: final String onToken) {
630: final StringTokenizer tokenizer = new StringTokenizer(string,
631: onToken);
632: final String[] result = new String[tokenizer.countTokens()];
633:
634: for (int i = 0; i < result.length; i++) {
635: result[i] = tokenizer.nextToken();
636: }
637:
638: return result;
639: }
640:
641: /**
642: * Extract an Extension from Attributes.
643: * Prefix indicates the prefix checked for each string.
644: * Usually the prefix is <em>"<extension>-"</em> if looking for a
645: * <b>Required</b> extension. If you are looking for an
646: * <b>Available</b> extension
647: * then the prefix is <em>""</em>.
648: *
649: * @param prefix the prefix for each attribute name
650: * @param attributes Attributes to searched
651: * @return the new Extension object, or null
652: */
653: private static Extension getExtension(final String prefix,
654: final Attributes attributes) {
655: //WARNING: We trim the values of all the attributes because
656: //Some extension declarations are badly defined (ie have spaces
657: //after version or vendorID)
658: final String nameKey = prefix + EXTENSION_NAME;
659: final String name = getTrimmedString(attributes
660: .getValue(nameKey));
661: if (null == name) {
662: return null;
663: }
664:
665: final String specVendorKey = prefix + SPECIFICATION_VENDOR;
666: final String specVendor = getTrimmedString(attributes
667: .getValue(specVendorKey));
668: final String specVersionKey = prefix + SPECIFICATION_VERSION;
669: final String specVersion = getTrimmedString(attributes
670: .getValue(specVersionKey));
671:
672: final String impVersionKey = prefix + IMPLEMENTATION_VERSION;
673: final String impVersion = getTrimmedString(attributes
674: .getValue(impVersionKey));
675: final String impVendorKey = prefix + IMPLEMENTATION_VENDOR;
676: final String impVendor = getTrimmedString(attributes
677: .getValue(impVendorKey));
678: final String impVendorIDKey = prefix + IMPLEMENTATION_VENDOR_ID;
679: final String impVendorId = getTrimmedString(attributes
680: .getValue(impVendorIDKey));
681: final String impURLKey = prefix + IMPLEMENTATION_URL;
682: final String impURL = getTrimmedString(attributes
683: .getValue(impURLKey));
684:
685: return new Extension(name, specVersion, specVendor, impVersion,
686: impVendor, impVendorId, impURL);
687: }
688:
689: /**
690: * Trim the supplied string if the string is non-null
691: *
692: * @param value the string to trim or null
693: * @return the trimmed string or null
694: */
695: private static String getTrimmedString(final String value) {
696: return null == value ? null : value.trim();
697: }
698: }
|