001: /*
002: * Copyright (c) 2007, intarsys consulting GmbH
003: *
004: * Redistribution and use in source and binary forms, with or without
005: * modification, are permitted provided that the following conditions are met:
006: *
007: * - Redistributions of source code must retain the above copyright notice,
008: * this list of conditions and the following disclaimer.
009: *
010: * - Redistributions in binary form must reproduce the above copyright notice,
011: * this list of conditions and the following disclaimer in the documentation
012: * and/or other materials provided with the distribution.
013: *
014: * - Neither the name of intarsys nor the names of its contributors may be used
015: * to endorse or promote products derived from this software without specific
016: * prior written permission.
017: *
018: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
019: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
020: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
021: * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
022: * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
023: * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
024: * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
025: * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
026: * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
027: * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
028: * POSSIBILITY OF SUCH DAMAGE.
029: */
030: package de.intarsys.pdf.cos;
031:
032: import java.lang.reflect.Constructor;
033: import java.lang.reflect.InvocationTargetException;
034: import de.intarsys.pdf.cds.CDSDate;
035: import de.intarsys.tools.attribute.AttributeSupport;
036: import de.intarsys.tools.attribute.IAttributeSupport;
037:
038: /**
039: * The abstract superclass for all objects/data structures that are build on the
040: * basic COSObject types.
041: *
042: * <p>
043: * The base {@link COSObject} will represent the state while this wrapper will
044: * provide the behavior.
045: *
046: * <p>
047: * The {@link COSBasedObject} and its base {@link COSObject} are always closely
048: * related, all changes are immediately reflected in both objects.
049: * <p>
050: * The {@link COSBasedObject} uses a META framework that ensures identity (you
051: * will always get the identical {@link COSBasedObject} for a {@link COSObject}
052: * created via META) and defines the lifecycle of the {@link COSBasedObject}.
053: *
054: * A {@link COSBasedObject} should always be created using
055: * <code>META.createNew</code> or <code>META.createFromCos</code>.
056: * </p>
057: *
058: * <p>
059: * A {@link COSBasedObject} based on a {@link COSDictionary} can use some
060: * convenience methods for generic access to its fields. As a convention, filed
061: * names are always declared with the associated {@link COSBasedObject} as
062: * <code>public static final COSName DK_<name></code>.
063: *
064: * <p>
065: * The {@link COSBasedObject} implements {@link IAttributeSupport}. Client code
066: * can use this feature to transparently associate objects with objects from
067: * client code, for example for caching or client defined relationships.
068: */
069: public abstract class COSBasedObject implements IAttributeSupport,
070: ICOSObjectListener {
071: /**
072: * The meta class implementation
073: */
074: public static class MetaClass extends de.intarsys.pdf.cos.MetaClass {
075: /** The cached constructor method */
076: private Constructor constructor;
077:
078: protected MetaClass(Class instanceClass) {
079: super (instanceClass);
080: }
081:
082: public COSBasedObject createFromCos(COSObject object) {
083: COSBasedObject result = null;
084: if ((object != null) && !object.isNull()) {
085: if (object instanceof COSCompositeObject) {
086: result = (COSBasedObject) ((COSCompositeObject) object)
087: .getAttribute(getRootClass());
088: }
089: if (result == null) {
090: MetaClass metaClass = doDetermineClass(object);
091: if (metaClass != null) {
092: result = metaClass
093: .doCreateCOSBasedObject(object);
094: if (result != null) {
095: result.initializeFromCos();
096: if (object instanceof COSCompositeObject) {
097: ((COSCompositeObject) object)
098: .setAttribute(getRootClass(),
099: result);
100: }
101: }
102: }
103: }
104: }
105: return result;
106: }
107:
108: public COSBasedObject createNew() {
109: COSObject cosObject = doCreateCOSObject();
110: if (isIndirect()) {
111: cosObject.beIndirect();
112: }
113: COSBasedObject result = doCreateCOSBasedObject(cosObject);
114: result.initializeFromScratch();
115: if (cosObject instanceof COSCompositeObject) {
116: ((COSCompositeObject) cosObject).setAttribute(
117: getRootClass(), result);
118: }
119: return result;
120: }
121:
122: protected COSBasedObject doCreateCOSBasedObject(COSObject object) {
123: try {
124: COSBasedObject result;
125: synchronized (this ) {
126: // lazy access must be synchronized
127: if (constructor == null) {
128: constructor = getInstanceClass()
129: .getDeclaredConstructor(
130: new Class[] { COSObject.class });
131: constructor.setAccessible(true);
132: }
133: }
134: result = (COSBasedObject) constructor
135: .newInstance(new Object[] { object });
136: return result;
137: } catch (NoSuchMethodException e) {
138: throw new IllegalStateException("Constructor " //$NON-NLS-1$
139: + getInstanceClass().getName()
140: + "(COSObject) missing"); //$NON-NLS-1$
141: } catch (InstantiationException e) {
142: throw new IllegalStateException(
143: getInstanceClass().getName()
144: + " can not be instantiated (" + e.getMessage() + ")"); //$NON-NLS-1$ //$NON-NLS-2$
145: } catch (IllegalAccessException e) {
146: throw new IllegalStateException(getInstanceClass()
147: .getName()
148: + " illegal access (" + e.getMessage() + ")"); //$NON-NLS-1$ //$NON-NLS-2$
149: } catch (InvocationTargetException e) {
150: throw new IllegalStateException(
151: getInstanceClass().getName()
152: + " invocation target exception(" + e.getMessage() //$NON-NLS-1$
153: + ")", e.getCause()); //$NON-NLS-1$
154: }
155: }
156:
157: protected COSObject doCreateCOSObject() {
158: return COSDictionary.create();
159: }
160:
161: protected MetaClass doDetermineClass(COSObject object) {
162: return this ;
163: }
164:
165: protected boolean isIndirect() {
166: return true;
167: }
168: }
169:
170: /** The meta class instance */
171: public static final MetaClass META = new MetaClass(MetaClass.class
172: .getDeclaringClass());
173:
174: /**
175: * This is the base object representing the state.
176: *
177: * <p>
178: * Most of the times the base object is a dictionary. In some cases the
179: * complex object is build upon an array (color space) or stream (functions
180: * and color spaces). But there is no rule that a complex object is build
181: * upon these containers or a {@link COSObject} at all. In certain cases the
182: * base object is allowed to be null. This is for example the case with the
183: * singleton implementations of the device color spaces.
184: * </p>
185: */
186: private final COSObject object;
187:
188: private final IAttributeSupport attributeSupport;
189:
190: protected COSBasedObject(COSObject object) {
191: super ();
192: this .object = object;
193: if (object != null) {
194: object.addObjectListener(this );
195: }
196: if (object instanceof IAttributeSupport) {
197: attributeSupport = (IAttributeSupport) object;
198: } else {
199: attributeSupport = new AttributeSupport();
200: }
201: }
202:
203: /*
204: * (non-Javadoc)
205: *
206: * @see de.intarsys.pdf.cos.ICOSObjectListener#changedSlot(de.intarsys.pdf.cos.COSObject,
207: * java.lang.Object, de.intarsys.pdf.cos.COSObject,
208: * de.intarsys.pdf.cos.COSObject)
209: */
210: public void changed(COSObject pObject, Object slot,
211: Object oldValue, Object newValue) {
212: if (slot != COSObject.SLOT_CONTAINER) {
213: invalidateCaches();
214: }
215: }
216:
217: /**
218: * Get the base object as a {@link COSArray}.
219: * <p>
220: * This will throw a {@link ClassCastException} if the base type is not
221: * appropriate!
222: *
223: * @return Get the base object as a {@link COSArray}.
224: */
225: public COSArray cosGetArray() {
226: return (COSArray) object;
227: }
228:
229: /**
230: * Get the base object as a {@link COSDictionary}.
231: * <p>
232: * This will throw a {@link ClassCastException} if the base type is not
233: * appropriate!
234: *
235: * @return Get the base object as a {@link COSDictionary}.
236: */
237: public COSDictionary cosGetDict() {
238: return (COSDictionary) object;
239: }
240:
241: /**
242: * The {@link COSDocument} for this.
243: *
244: * @return The {@link COSDocument} for this.
245: */
246: public COSDocument cosGetDoc() {
247: return object.getDoc();
248: }
249:
250: /**
251: * The {@link COSObject} associated with <code>name</code> in the receiver
252: * or {@link COSNull}.
253: * <p>
254: * This method requires the base object to be a {@link COSDictionary}.
255: *
256: * @param name
257: * The {@link COSDictionary} field to read
258: *
259: * @return The {@link COSObject} associated with <code>name</code> in the
260: * receiver or {@link COSNull}.
261: */
262: public COSObject cosGetField(COSName name) {
263: return cosGetDict().get(name);
264: }
265:
266: /**
267: * The base {@link COSObject} for this.
268: *
269: * @return The base {@link COSObject} for this.
270: */
271: public COSObject cosGetObject() {
272: return object;
273: }
274:
275: /**
276: * Get the base object as a {@link COSStream}.
277: * <p>
278: * This will throw a {@link ClassCastException} if the base type is not
279: * appropriate!
280: *
281: * @return Get the base object as a {@link COSStream}.
282: */
283: public COSStream cosGetStream() {
284: return (COSStream) object;
285: }
286:
287: /**
288: * Answer <code>true</code> if this has a field named <code>name</code>.
289: * <p>
290: * This method requires the base object to be a {@link COSDictionary}.
291: *
292: * @param name
293: * the field to check
294: *
295: * @return Answer <code>true</code> if this has a field named
296: * <code>name</code>.
297: */
298: public boolean cosHasField(COSName name) {
299: return !cosGetDict().get(name).isNull();
300: }
301:
302: /**
303: * Remove a field in this. The previously associated object is returned.
304: * <p>
305: * This method requires the base object to be a {@link COSDictionary}.
306: *
307: * @param name
308: * the field to remove from the receiver
309: * @return The previously associated object is returned.
310: */
311: public COSObject cosRemoveField(COSName name) {
312: return cosGetDict().remove(name);
313: }
314:
315: /**
316: * Set a field value in this. The previously associated object is returned.
317: *
318: * <p>
319: * This method requires the base object to be a {@link COSDictionary}.
320: *
321: * @param name
322: * The field to set
323: * @param cosObj
324: * The object to set in the field
325: * @return The previously associated object is returned.
326: */
327: public COSObject cosSetField(COSName name, COSObject cosObj) {
328: if ((cosObj == null) || cosObj.isNull()) {
329: return cosRemoveField(name);
330: } else {
331: return cosGetDict().put(name, cosObj);
332: }
333: }
334:
335: /*
336: * (non-Javadoc)
337: *
338: * @see de.intarsys.tools.component.IAttributeSupport#getAttribute(java.lang.Object)
339: */
340: public Object getAttribute(Object key) {
341: return attributeSupport.getAttribute(key);
342: }
343:
344: /**
345: * The value of a field within this as a <code>boolean</code> or the
346: * <code>defaultValue</code> if not found or not a {@link COSBoolean}.
347: * <p>
348: * This method requires the base object to be a {@link COSDictionary}.
349: *
350: * @param name
351: * The name of the field.
352: * @param defaultValue
353: * The default value to return if field is not found or not of
354: * appropriate type.
355: * @return The value of a field within this as a <code>boolean</code>
356: */
357: public boolean getFieldBoolean(COSName name, boolean defaultValue) {
358: COSBoolean value = cosGetField(name).asBoolean();
359: if (value == null) {
360: return defaultValue;
361: }
362: return value.booleanValue();
363: }
364:
365: /**
366: * The value of a field within this as a {@link CDSDate} or the
367: * <code>defaultValue</code> if not found or not a {@link COSString}.
368: * <p>
369: * This method requires the base object to be a {@link COSDictionary}.
370: *
371: * @param name
372: * The name of the field.
373: * @param defaultValue
374: * The default value to return if field is not found or not of
375: * appropriate type.
376: * @return The value of a field within this as a {@link CDSDate}
377: */
378: public CDSDate getFieldDate(COSName name, CDSDate defaultValue) {
379: COSString value = cosGetField(name).asString();
380: if (value == null) {
381: return defaultValue;
382: }
383: return CDSDate.createFromCOS(value);
384: }
385:
386: /**
387: * The value of a field within this as a <code>float</code> or the
388: * <code>defaultValue</code> if not found or not a {@link COSNumber}.
389: * <p>
390: * This method requires the base object to be a {@link COSDictionary}.
391: *
392: * @param name
393: * The name of the field.
394: * @param defaultValue
395: * The default value to return if field is not found or not of
396: * appropriate type.
397: * @return The value of a field within this as a <code>float</code>
398: */
399: public float getFieldFixed(COSName name, float defaultValue) {
400: COSNumber value = cosGetField(name).asNumber();
401: if (value == null) {
402: return defaultValue;
403: }
404: return value.floatValue();
405: }
406:
407: /**
408: * The value of a field within this as a <code>float[]</code> or the
409: * <code>defaultValue</code> if not found or not a {@link COSArray}.
410: * <p>
411: * This method requires the base object to be a {@link COSDictionary}.
412: *
413: * @param name
414: * The name of the field.
415: * @param defaultValue
416: * The default value to return if field is not found or not of
417: * appropriate type.
418: * @return The value of a field within this as a <code>float[]</code>
419: */
420: public float[] getFieldFixedArray(COSName name, float[] defaultValue) {
421: COSArray array = cosGetField(name).asArray();
422: if (array != null) {
423: float[] result = new float[array.size()];
424: for (int i = 0; i < array.size(); i++) {
425: COSNumber fixed = array.get(i).asNumber();
426: if (fixed != null) {
427: result[i] = fixed.floatValue();
428: } else {
429: // TODO 3 wrong default, maybe restrict
430: result[i] = 0;
431: }
432: }
433: return result;
434: }
435: return defaultValue;
436: }
437:
438: /**
439: * The value of a field within this as a <code>int</code> or the
440: * <code>defaultValue</code> if not found or not a {@link COSNumber}.
441: * <p>
442: * This method requires the base object to be a {@link COSDictionary}.
443: *
444: * @param name
445: * The name of the field.
446: * @param defaultValue
447: * The default value to return if field is not found or not of
448: * appropriate type.
449: * @return The value of a field within this as a <code>int</code>
450: */
451: public int getFieldInt(COSName name, int defaultValue) {
452: COSNumber value = cosGetField(name).asNumber();
453: if (value == null) {
454: return defaultValue;
455: }
456: return value.intValue();
457: }
458:
459: /**
460: * The value of a field within this as a <code>String</code> or the
461: * <code>defaultValue</code> if not found or not a {@link COSString}. The
462: * String is "expanded" to containn the correct new line characters.
463: * <p>
464: * This method requires the base object to be a {@link COSDictionary}.
465: *
466: * @param name
467: * The name of the field.
468: * @param defaultValue
469: * The default value to return if field is not found or not of
470: * appropriate type.
471: * @return The value of a field within this as a <code>String</code>
472: */
473: public String getFieldMLString(COSName name, String defaultValue) {
474: COSObject value = cosGetField(name);
475:
476: // be lazy about COSString and COSName
477: if (value.isNull()) {
478: return defaultValue;
479: }
480: COSString string = value.asString();
481: if (string != null) {
482: return string.multiLineStringValue();
483: }
484: return value.stringValue();
485: }
486:
487: /**
488: * The value of a field within this as a <code>String</code> or the
489: * <code>defaultValue</code> if not found or not a {@link COSString}.
490: * <p>
491: * This method requires the base object to be a {@link COSDictionary}.
492: *
493: * @param name
494: * The name of the field.
495: * @param defaultValue
496: * The default value to return if field is not found or not of
497: * appropriate type.
498: * @return The value of a field within this as a <code>String</code>
499: */
500: public String getFieldString(COSName name, String defaultValue) {
501: COSObject value = cosGetField(name);
502:
503: // be lazy about COSString and COSName
504: if (value.isNull()) {
505: return defaultValue;
506: }
507: return value.stringValue();
508: }
509:
510: /*
511: * provide some hook for initialization after creation based on a cos object
512: */
513: protected void initializeFromCos() {
514: // do nothing by default
515: }
516:
517: /*
518: * provide some hook for initialization after creation from scratch
519: */
520: protected void initializeFromScratch() {
521: // do nothing by default
522: }
523:
524: /**
525: * Invalidate all local caches as the base object may have changed.
526: *
527: */
528: public void invalidateCaches() {
529: // nothing cached here
530: }
531:
532: /*
533: * (non-Javadoc)
534: *
535: * @see de.intarsys.tools.component.IAttributeSupport#removeAttribute(java.lang.Object)
536: */
537: public Object removeAttribute(Object key) {
538: return attributeSupport.removeAttribute(key);
539: }
540:
541: /*
542: * (non-Javadoc)
543: *
544: * @see de.intarsys.tools.component.IAttributeSupport#setAttribute(java.lang.Object,
545: * java.lang.Object)
546: */
547: public Object setAttribute(Object key, Object value) {
548: return attributeSupport.setAttribute(key, value);
549: }
550:
551: /**
552: * Set the value of field <code>name</code>within this.
553: * <p>
554: * This method requires the base object to be a {@link COSDictionary}.
555: *
556: * @param name
557: * The name of the field.
558: * @param value
559: * The new value of the field.
560: */
561: public void setFieldBoolean(COSName name, boolean value) {
562: COSBoolean cosValue = COSBoolean.create(value);
563: cosSetField(name, cosValue);
564: }
565:
566: /**
567: * Set the value of field <code>name</code>within this.
568: * <p>
569: * This method requires the base object to be a {@link COSDictionary}.
570: *
571: * @param name
572: * The name of the field.
573: * @param value
574: * The new value of the field.
575: */
576: public void setFieldFixed(COSName name, float value) {
577: COSNumber cosValue = COSFixed.create(value);
578: cosSetField(name, cosValue);
579: }
580:
581: /**
582: * Set the value of field <code>name</code>within this.
583: * <p>
584: * This method requires the base object to be a {@link COSDictionary}.
585: *
586: * @param name
587: * The name of the field.
588: * @param value
589: * The new value of the field.
590: */
591: protected void setFieldFixedArray(COSName key, float[] array) {
592: if ((array == null) || (array.length == 0)) {
593: cosRemoveField(key);
594: return;
595: }
596:
597: // todo 3 reuse existing array?
598: COSArray cosArray = COSArray.create();
599: for (int i = 0; i < array.length; i++) {
600: cosArray.add(COSFixed.create(array[i]));
601: }
602: cosSetField(key, cosArray);
603: }
604:
605: /**
606: * Set the value of field <code>name</code>within this.
607: * <p>
608: * This method requires the base object to be a {@link COSDictionary}.
609: *
610: * @param name
611: * The name of the field.
612: * @param value
613: * The new value of the field.
614: */
615: public void setFieldInt(COSName name, int value) {
616: COSNumber cosValue = COSInteger.create(value);
617: cosSetField(name, cosValue);
618: }
619:
620: /**
621: * Set the value of field <code>name</code>within this.
622: * <p>
623: * This method requires the base object to be a {@link COSDictionary}.
624: *
625: * @param name
626: * The name of the field.
627: * @param value
628: * The new value of the field.
629: */
630: public void setFieldMLString(COSName name, String value) {
631: if (value == null) {
632: cosRemoveField(name);
633: } else {
634: COSString cosValue = COSString.createMultiLine(value);
635: cosSetField(name, cosValue);
636: }
637: }
638:
639: /**
640: * Set the value of field <code>name</code>within this.
641: * <p>
642: * This method requires the base object to be a {@link COSDictionary}.
643: *
644: * @param name
645: * The name of the field.
646: * @param value
647: * The new value of the field.
648: */
649: public void setFieldName(COSName name, String value) {
650: if (value == null) {
651: cosRemoveField(name);
652: } else {
653: COSName cosValue = COSName.create(value.getBytes());
654: cosSetField(name, cosValue);
655: }
656: }
657:
658: /**
659: * Set the value of field <code>name</code>within this.
660: * <p>
661: * This method requires the base object to be a {@link COSDictionary}.
662: *
663: * @param name
664: * The name of the field.
665: * @param value
666: * The new value of the field.
667: */
668: public void setFieldString(COSName name, String value) {
669: if (value == null) {
670: cosRemoveField(name);
671: } else {
672: COSString cosValue = COSString.create(value);
673: cosSetField(name, cosValue);
674: }
675: }
676:
677: /**
678: * Set the value of field <code>name</code>within this.
679: * <p>
680: * This method requires the base object to be a {@link COSDictionary}.
681: *
682: * @param name
683: * The name of the field.
684: * @param value
685: * The new value of the field.
686: */
687: public void setFieldObject(COSName name, COSBasedObject value) {
688: if (value == null) {
689: cosRemoveField(name);
690: } else {
691: cosSetField(name, value.cosGetObject());
692: }
693: }
694:
695: /*
696: * (non-Javadoc)
697: *
698: * @see java.lang.Object#toString()
699: */
700: public String toString() {
701: return cosGetObject().toString();
702: }
703: }
|