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.text.ParseException;
021: import java.util.ArrayList;
022: import java.util.Arrays;
023: import java.util.Iterator;
024: import java.util.Map;
025: import java.util.jar.Attributes;
026: import java.util.jar.Manifest;
027:
028: import org.apache.tools.ant.util.StringUtils;
029:
030: /**
031: * <p>Utility class that represents either an available "Optional Package"
032: * (formerly known as "Standard Extension") as described in the manifest
033: * of a JAR file, or the requirement for such an optional package.</p>
034: *
035: * <p>For more information about optional packages, see the document
036: * <em>Optional Package Versioning</em> in the documentation bundle for your
037: * Java2 Standard Edition package, in file
038: * <code>guide/extensions/versioning.html</code>.</p>
039: *
040: */
041: public final class Specification {
042:
043: private static final String MISSING = "Missing ";
044:
045: /**
046: * Manifest Attribute Name object for SPECIFICATION_TITLE.
047: */
048: public static final Attributes.Name SPECIFICATION_TITLE = Attributes.Name.SPECIFICATION_TITLE;
049:
050: /**
051: * Manifest Attribute Name object for SPECIFICATION_VERSION.
052: */
053: public static final Attributes.Name SPECIFICATION_VERSION = Attributes.Name.SPECIFICATION_VERSION;
054:
055: /**
056: * Manifest Attribute Name object for SPECIFICATION_VENDOR.
057: */
058: public static final Attributes.Name SPECIFICATION_VENDOR = Attributes.Name.SPECIFICATION_VENDOR;
059:
060: /**
061: * Manifest Attribute Name object for IMPLEMENTATION_TITLE.
062: */
063: public static final Attributes.Name IMPLEMENTATION_TITLE = Attributes.Name.IMPLEMENTATION_TITLE;
064:
065: /**
066: * Manifest Attribute Name object for IMPLEMENTATION_VERSION.
067: */
068: public static final Attributes.Name IMPLEMENTATION_VERSION = Attributes.Name.IMPLEMENTATION_VERSION;
069:
070: /**
071: * Manifest Attribute Name object for IMPLEMENTATION_VENDOR.
072: */
073: public static final Attributes.Name IMPLEMENTATION_VENDOR = Attributes.Name.IMPLEMENTATION_VENDOR;
074:
075: /**
076: * Enum indicating that extension is compatible with other Package
077: * Specification.
078: */
079: public static final Compatibility COMPATIBLE = new Compatibility(
080: "COMPATIBLE");
081:
082: /**
083: * Enum indicating that extension requires an upgrade
084: * of specification to be compatible with other Package Specification.
085: */
086: public static final Compatibility REQUIRE_SPECIFICATION_UPGRADE = new Compatibility(
087: "REQUIRE_SPECIFICATION_UPGRADE");
088:
089: /**
090: * Enum indicating that extension requires a vendor
091: * switch to be compatible with other Package Specification.
092: */
093: public static final Compatibility REQUIRE_VENDOR_SWITCH = new Compatibility(
094: "REQUIRE_VENDOR_SWITCH");
095:
096: /**
097: * Enum indicating that extension requires an upgrade
098: * of implementation to be compatible with other Package Specification.
099: */
100: public static final Compatibility REQUIRE_IMPLEMENTATION_CHANGE = new Compatibility(
101: "REQUIRE_IMPLEMENTATION_CHANGE");
102:
103: /**
104: * This enum indicates that an extension is incompatible with
105: * other Package Specification in ways other than other enums
106: * indicate. For example, the other Package Specification
107: * may have a different ID.
108: */
109: public static final Compatibility INCOMPATIBLE = new Compatibility(
110: "INCOMPATIBLE");
111:
112: /**
113: * The name of the Package Specification.
114: */
115: private String specificationTitle;
116:
117: /**
118: * The version number (dotted decimal notation) of the specification
119: * to which this optional package conforms.
120: */
121: private DeweyDecimal specificationVersion;
122:
123: /**
124: * The name of the company or organization that originated the
125: * specification to which this specification conforms.
126: */
127: private String specificationVendor;
128:
129: /**
130: * The title of implementation.
131: */
132: private String implementationTitle;
133:
134: /**
135: * The name of the company or organization that produced this
136: * implementation of this specification.
137: */
138: private String implementationVendor;
139:
140: /**
141: * The version string for implementation. The version string is
142: * opaque.
143: */
144: private String implementationVersion;
145:
146: /**
147: * The sections of jar that the specification applies to.
148: */
149: private String[] sections;
150:
151: /**
152: * Return an array of <code>Package Specification</code> objects.
153: * If there are no such optional packages, a zero-length array is returned.
154: *
155: * @param manifest Manifest to be parsed
156: * @return the Package Specifications extensions in specified manifest
157: * @throws ParseException if the attributes of the specifications cannot
158: * be parsed according to their expected formats.
159: */
160: public static Specification[] getSpecifications(
161: final Manifest manifest) throws ParseException {
162: if (null == manifest) {
163: return new Specification[0];
164: }
165:
166: final ArrayList results = new ArrayList();
167:
168: final Map entries = manifest.getEntries();
169: final Iterator keys = entries.keySet().iterator();
170: while (keys.hasNext()) {
171: final String key = (String) keys.next();
172: final Attributes attributes = (Attributes) entries.get(key);
173: final Specification specification = getSpecification(key,
174: attributes);
175: if (null != specification) {
176: results.add(specification);
177: }
178: }
179:
180: final ArrayList trimmedResults = removeDuplicates(results);
181: return (Specification[]) trimmedResults
182: .toArray(new Specification[trimmedResults.size()]);
183: }
184:
185: /**
186: * The constructor to create Package Specification object.
187: * Note that every component is allowed to be specified
188: * but only the specificationTitle is mandatory.
189: *
190: * @param specificationTitle the name of specification.
191: * @param specificationVersion the specification Version.
192: * @param specificationVendor the specification Vendor.
193: * @param implementationTitle the title of implementation.
194: * @param implementationVersion the implementation Version.
195: * @param implementationVendor the implementation Vendor.
196: */
197: public Specification(final String specificationTitle,
198: final String specificationVersion,
199: final String specificationVendor,
200: final String implementationTitle,
201: final String implementationVersion,
202: final String implementationVendor) {
203: this (specificationTitle, specificationVersion,
204: specificationVendor, implementationTitle,
205: implementationVersion, implementationVendor, null);
206: }
207:
208: /**
209: * The constructor to create Package Specification object.
210: * Note that every component is allowed to be specified
211: * but only the specificationTitle is mandatory.
212: *
213: * @param specificationTitle the name of specification.
214: * @param specificationVersion the specification Version.
215: * @param specificationVendor the specification Vendor.
216: * @param implementationTitle the title of implementation.
217: * @param implementationVersion the implementation Version.
218: * @param implementationVendor the implementation Vendor.
219: * @param sections the sections/packages that Specification applies to.
220: */
221: public Specification(final String specificationTitle,
222: final String specificationVersion,
223: final String specificationVendor,
224: final String implementationTitle,
225: final String implementationVersion,
226: final String implementationVendor, final String[] sections) {
227: this .specificationTitle = specificationTitle;
228: this .specificationVendor = specificationVendor;
229:
230: if (null != specificationVersion) {
231: try {
232: this .specificationVersion = new DeweyDecimal(
233: specificationVersion);
234: } catch (final NumberFormatException nfe) {
235: final String error = "Bad specification version format '"
236: + specificationVersion
237: + "' in '"
238: + specificationTitle
239: + "'. (Reason: "
240: + nfe
241: + ")";
242: throw new IllegalArgumentException(error);
243: }
244: }
245:
246: this .implementationTitle = implementationTitle;
247: this .implementationVendor = implementationVendor;
248: this .implementationVersion = implementationVersion;
249:
250: if (null == this .specificationTitle) {
251: throw new NullPointerException("specificationTitle");
252: }
253:
254: String[] copy = null;
255: if (null != sections) {
256: copy = new String[sections.length];
257: System.arraycopy(sections, 0, copy, 0, sections.length);
258: }
259: this .sections = copy;
260: }
261:
262: /**
263: * Get the title of the specification.
264: *
265: * @return the title of speciication
266: */
267: public String getSpecificationTitle() {
268: return specificationTitle;
269: }
270:
271: /**
272: * Get the vendor of the specification.
273: *
274: * @return the vendor of the specification.
275: */
276: public String getSpecificationVendor() {
277: return specificationVendor;
278: }
279:
280: /**
281: * Get the title of the specification.
282: *
283: * @return the title of the specification.
284: */
285: public String getImplementationTitle() {
286: return implementationTitle;
287: }
288:
289: /**
290: * Get the version of the specification.
291: *
292: * @return the version of the specification.
293: */
294: public DeweyDecimal getSpecificationVersion() {
295: return specificationVersion;
296: }
297:
298: /**
299: * Get the vendor of the extensions implementation.
300: *
301: * @return the vendor of the extensions implementation.
302: */
303: public String getImplementationVendor() {
304: return implementationVendor;
305: }
306:
307: /**
308: * Get the version of the implementation.
309: *
310: * @return the version of the implementation.
311: */
312: public String getImplementationVersion() {
313: return implementationVersion;
314: }
315:
316: /**
317: * Return an array containing sections to which specification applies
318: * or null if relevent to no sections.
319: *
320: * @return an array containing sections to which specification applies
321: * or null if relevent to no sections.
322: */
323: public String[] getSections() {
324: if (null == sections) {
325: return null;
326: }
327: final String[] newSections = new String[sections.length];
328: System.arraycopy(sections, 0, newSections, 0, sections.length);
329: return newSections;
330: }
331:
332: /**
333: * Return a Compatibility enum indicating the relationship of this
334: * <code>Package Specification</code> with the specified
335: * <code>Extension</code>.
336: *
337: * @param other the other specification
338: * @return the enum indicating the compatibility (or lack thereof)
339: * of specifed Package Specification
340: */
341: public Compatibility getCompatibilityWith(final Specification other) {
342: // Specification Name must match
343: if (!specificationTitle.equals(other.getSpecificationTitle())) {
344: return INCOMPATIBLE;
345: }
346:
347: // Available specification version must be >= required
348: final DeweyDecimal otherSpecificationVersion = other
349: .getSpecificationVersion();
350: if (null != specificationVersion) {
351: if (null == otherSpecificationVersion
352: || !isCompatible(specificationVersion,
353: otherSpecificationVersion)) {
354: return REQUIRE_SPECIFICATION_UPGRADE;
355: }
356: }
357:
358: // Implementation Vendor ID must match
359: final String otherImplementationVendor = other
360: .getImplementationVendor();
361: if (null != implementationVendor) {
362: if (null == otherImplementationVendor
363: || !implementationVendor
364: .equals(otherImplementationVendor)) {
365: return REQUIRE_VENDOR_SWITCH;
366: }
367: }
368:
369: // Implementation version must be >= required
370: final String otherImplementationVersion = other
371: .getImplementationVersion();
372: if (null != implementationVersion) {
373: if (null == otherImplementationVersion
374: || !implementationVersion
375: .equals(otherImplementationVersion)) {
376: return REQUIRE_IMPLEMENTATION_CHANGE;
377: }
378: }
379:
380: // This available optional package satisfies the requirements
381: return COMPATIBLE;
382: }
383:
384: /**
385: * Return <code>true</code> if the specified <code>package</code>
386: * is satisfied by this <code>Specification</code>. Otherwise, return
387: * <code>false</code>.
388: *
389: * @param other the specification
390: * @return true if the specification is compatible with this specification
391: */
392: public boolean isCompatibleWith(final Specification other) {
393: return (COMPATIBLE == getCompatibilityWith(other));
394: }
395:
396: /**
397: * Return a String representation of this object.
398: *
399: * @return string representation of object.
400: */
401: public String toString() {
402: final String brace = ": ";
403:
404: final StringBuffer sb = new StringBuffer(SPECIFICATION_TITLE
405: .toString());
406: sb.append(brace);
407: sb.append(specificationTitle);
408: sb.append(StringUtils.LINE_SEP);
409:
410: if (null != specificationVersion) {
411: sb.append(SPECIFICATION_VERSION);
412: sb.append(brace);
413: sb.append(specificationVersion);
414: sb.append(StringUtils.LINE_SEP);
415: }
416:
417: if (null != specificationVendor) {
418: sb.append(SPECIFICATION_VENDOR);
419: sb.append(brace);
420: sb.append(specificationVendor);
421: sb.append(StringUtils.LINE_SEP);
422: }
423:
424: if (null != implementationTitle) {
425: sb.append(IMPLEMENTATION_TITLE);
426: sb.append(brace);
427: sb.append(implementationTitle);
428: sb.append(StringUtils.LINE_SEP);
429: }
430:
431: if (null != implementationVersion) {
432: sb.append(IMPLEMENTATION_VERSION);
433: sb.append(brace);
434: sb.append(implementationVersion);
435: sb.append(StringUtils.LINE_SEP);
436: }
437:
438: if (null != implementationVendor) {
439: sb.append(IMPLEMENTATION_VENDOR);
440: sb.append(brace);
441: sb.append(implementationVendor);
442: sb.append(StringUtils.LINE_SEP);
443: }
444:
445: return sb.toString();
446: }
447:
448: /**
449: * Return <code>true</code> if the first version number is greater than
450: * or equal to the second; otherwise return <code>false</code>.
451: *
452: * @param first First version number (dotted decimal)
453: * @param second Second version number (dotted decimal)
454: */
455: private boolean isCompatible(final DeweyDecimal first,
456: final DeweyDecimal second) {
457: return first.isGreaterThanOrEqual(second);
458: }
459:
460: /**
461: * Combine all specifications objects that are identical except
462: * for the sections.
463: *
464: * <p>Note this is very inefficent and should probably be fixed
465: * in the future.</p>
466: *
467: * @param list the array of results to trim
468: * @return an array list with all duplicates removed
469: */
470: private static ArrayList removeDuplicates(final ArrayList list) {
471: final ArrayList results = new ArrayList();
472: final ArrayList sections = new ArrayList();
473: while (list.size() > 0) {
474: final Specification specification = (Specification) list
475: .remove(0);
476: final Iterator iterator = list.iterator();
477: while (iterator.hasNext()) {
478: final Specification other = (Specification) iterator
479: .next();
480: if (isEqual(specification, other)) {
481: final String[] otherSections = other.getSections();
482: if (null != sections) {
483: sections.addAll(Arrays.asList(otherSections));
484: }
485: iterator.remove();
486: }
487: }
488:
489: final Specification merged = mergeInSections(specification,
490: sections);
491: results.add(merged);
492: //Reset list of sections
493: sections.clear();
494: }
495:
496: return results;
497: }
498:
499: /**
500: * Test if two specifications are equal except for their sections.
501: *
502: * @param specification one specificaiton
503: * @param other the ohter specification
504: * @return true if two specifications are equal except for their
505: * sections, else false
506: */
507: private static boolean isEqual(final Specification specification,
508: final Specification other) {
509: return specification.getSpecificationTitle().equals(
510: other.getSpecificationTitle())
511: && specification.getSpecificationVersion().isEqual(
512: other.getSpecificationVersion())
513: && specification.getSpecificationVendor().equals(
514: other.getSpecificationVendor())
515: && specification.getImplementationTitle().equals(
516: other.getImplementationTitle())
517: && specification.getImplementationVersion().equals(
518: other.getImplementationVersion())
519: && specification.getImplementationVendor().equals(
520: other.getImplementationVendor());
521: }
522:
523: /**
524: * Merge the specified sections into specified section and return result.
525: * If no sections to be added then just return original specification.
526: *
527: * @param specification the specification
528: * @param sectionsToAdd the list of sections to merge
529: * @return the merged specification
530: */
531: private static Specification mergeInSections(
532: final Specification specification,
533: final ArrayList sectionsToAdd) {
534: if (0 == sectionsToAdd.size()) {
535: return specification;
536: }
537: sectionsToAdd
538: .addAll(Arrays.asList(specification.getSections()));
539:
540: final String[] sections = (String[]) sectionsToAdd
541: .toArray(new String[sectionsToAdd.size()]);
542:
543: return new Specification(specification.getSpecificationTitle(),
544: specification.getSpecificationVersion().toString(),
545: specification.getSpecificationVendor(), specification
546: .getImplementationTitle(), specification
547: .getImplementationVersion(), specification
548: .getImplementationVendor(), sections);
549: }
550:
551: /**
552: * Trim the supplied string if the string is non-null
553: *
554: * @param value the string to trim or null
555: * @return the trimmed string or null
556: */
557: private static String getTrimmedString(final String value) {
558: return value == null ? null : value.trim();
559: }
560:
561: /**
562: * Extract an Package Specification from Attributes.
563: *
564: * @param attributes Attributes to searched
565: * @return the new Specification object, or null
566: */
567: private static Specification getSpecification(final String section,
568: final Attributes attributes) throws ParseException {
569: //WARNING: We trim the values of all the attributes because
570: //Some extension declarations are badly defined (ie have spaces
571: //after version or vendor)
572: final String name = getTrimmedString(attributes
573: .getValue(SPECIFICATION_TITLE));
574: if (null == name) {
575: return null;
576: }
577:
578: final String specVendor = getTrimmedString(attributes
579: .getValue(SPECIFICATION_VENDOR));
580: if (null == specVendor) {
581: throw new ParseException(MISSING + SPECIFICATION_VENDOR, 0);
582: }
583:
584: final String specVersion = getTrimmedString(attributes
585: .getValue(SPECIFICATION_VERSION));
586: if (null == specVersion) {
587: throw new ParseException(MISSING + SPECIFICATION_VERSION, 0);
588: }
589:
590: final String impTitle = getTrimmedString(attributes
591: .getValue(IMPLEMENTATION_TITLE));
592: if (null == impTitle) {
593: throw new ParseException(MISSING + IMPLEMENTATION_TITLE, 0);
594: }
595:
596: final String impVersion = getTrimmedString(attributes
597: .getValue(IMPLEMENTATION_VERSION));
598: if (null == impVersion) {
599: throw new ParseException(MISSING + IMPLEMENTATION_VERSION,
600: 0);
601: }
602:
603: final String impVendor = getTrimmedString(attributes
604: .getValue(IMPLEMENTATION_VENDOR));
605: if (null == impVendor) {
606: throw new ParseException(MISSING + IMPLEMENTATION_VENDOR, 0);
607: }
608:
609: return new Specification(name, specVersion, specVendor,
610: impTitle, impVersion, impVendor,
611: new String[] { section });
612: }
613: }
|