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.util.ArrayList;
033: import java.util.HashSet;
034: import java.util.Iterator;
035: import java.util.List;
036: import java.util.Map;
037: import java.util.Set;
038:
039: /**
040: * A COSArray represents an indexed collection of {@link COSDocumentElement}
041: * instances.
042: * <p>
043: * Using the standard access methods, always "dereferenced" {@link COSObject}
044: * instances are returned. Use the "basic" falvor of methods to access the
045: * optional {@link COSIndirectObject}.
046: */
047: public class COSArray extends COSCompositeObject {
048: /** the list of document elements contained */
049: private final List objects;
050:
051: protected COSArray() {
052: super ();
053: this .objects = new ArrayList();
054: }
055:
056: protected COSArray(int size) {
057: super ();
058: this .objects = new ArrayList(size);
059: }
060:
061: protected COSArray(COSArray array) {
062: super (array);
063: this .objects = new ArrayList(array.objects);
064: }
065:
066: /**
067: * Create an empty {@link COSArray}.
068: *
069: * @return Create an empty {@link COSArray}.
070: */
071: public static COSArray create() {
072: return new COSArray();
073: }
074:
075: /**
076: * Create an empty {@link COSArray} with a preallocated size.
077: *
078: * @return Create an empty {@link COSArray}.
079: */
080: public static COSArray create(int size) {
081: return new COSArray(size);
082: }
083:
084: /**
085: * Shortcut for fast creation of rectangle arrays
086: *
087: * @param a
088: * @param b
089: * @param c
090: * @param d
091: * @return a new COSArray
092: */
093: public static COSArray createWith(float a, float b, float c, float d) {
094: COSArray result = new COSArray(4);
095: result.basicAddSilent(COSFixed.create(a));
096: result.basicAddSilent(COSFixed.create(b));
097: result.basicAddSilent(COSFixed.create(c));
098: result.basicAddSilent(COSFixed.create(d));
099: return result;
100: }
101:
102: /**
103: * Shortcut for fast creation of matrix arrays
104: *
105: * @param a
106: * @param b
107: * @param c
108: * @param d
109: * @param e
110: * @param f
111: * @return a new COSArray
112: */
113: public static COSArray createWith(float a, float b, float c,
114: float d, float e, float f) {
115: COSArray result = new COSArray(6);
116: result.basicAddSilent(COSFixed.create(a));
117: result.basicAddSilent(COSFixed.create(b));
118: result.basicAddSilent(COSFixed.create(c));
119: result.basicAddSilent(COSFixed.create(d));
120: result.basicAddSilent(COSFixed.create(e));
121: result.basicAddSilent(COSFixed.create(f));
122: return result;
123: }
124:
125: /**
126: * A copy of all COSObject's in this.
127: *
128: * <p>
129: * Indirect objects and dangling references are handled by this method.
130: * </p>
131: *
132: * @return A copy of all COSObject's in this.
133: */
134: public List getObjects() {
135: int size = size();
136: List result = new ArrayList(size);
137: for (int i = 0; i < size; i++) {
138: result.add(get(i));
139: }
140: return result;
141: }
142:
143: /*
144: * (non-Javadoc)
145: *
146: * @see de.intarsys.pdf.cos.COSObject#accept(de.intarsys.pdf.cos.ICOSObjectVisitor)
147: */
148: public java.lang.Object accept(ICOSObjectVisitor visitor)
149: throws COSVisitorException {
150: return visitor.visitFromArray(this );
151: }
152:
153: /**
154: * Add a {@link COSObject} to the collection.
155: *
156: * <p>
157: * This method takes care of change propagation for incremental writing.
158: * </p>
159: *
160: * <p>
161: * this method should be used by the application level programmer to ensure
162: * he deals not with references.
163: * </p>
164: *
165: * @param object
166: * the object to be added
167: *
168: * @return this
169: */
170: public COSArray add(COSObject object) {
171: willChange(this );
172: basicAddPropagate(object);
173: if (objectListeners != null) {
174: triggerChanged(objects.size(), COSNull.NULL, object);
175: }
176: return this ;
177: }
178:
179: /**
180: * Add a {@link COSObject} to the collection.
181: *
182: * <p>
183: * This method takes care of change propagation for incremental writing.
184: * </p>
185: *
186: * <p>
187: * this method should be used by the application level programmer to ensure
188: * he deals not with references.
189: * </p>
190: *
191: * @param index
192: * The index where to insert <code>object</code>
193: * @param object
194: * the object to be added
195: *
196: * @return this
197: */
198: public COSArray add(int index, COSObject object) {
199: willChange(this );
200: basicAddPropagate(index, object);
201: if (objectListeners != null) {
202: triggerChanged(index, COSNull.NULL, object);
203: }
204: return this ;
205: }
206:
207: /**
208: * add a document element (an object or a reference) to the collection.
209: *
210: * <p>
211: * this method should only be used for low level programming (parser).
212: * </p>
213: *
214: * @param index
215: * The index where to insert <code>object</code>
216: * @param element
217: * the element to be added
218: *
219: * @return this
220: */
221: protected COSArray basicAddPropagate(int index,
222: COSDocumentElement element) {
223: COSObject dereferenced = element.dereference();
224: COSDocumentElement containable = element.containable();
225: willChange(dereferenced);
226: ICOSContainer newContainer = containable.addContainer(this );
227: objects.add(index, containable);
228: dereferenced.triggerChanged(COSObject.SLOT_CONTAINER, null,
229: newContainer);
230: return this ;
231: }
232:
233: /**
234: * Add a document element (an object or a reference) to the collection.
235: *
236: * <p>
237: * This method should only be used for low level programming (parser).
238: * </p>
239: *
240: * @param element
241: * the element to be added
242: *
243: * @return The receiver.
244: */
245: protected COSArray basicAddPropagate(COSDocumentElement element) {
246: COSObject dereferenced = element.dereference();
247: COSDocumentElement containable = element.containable();
248: willChange(dereferenced);
249: ICOSContainer newContainer = containable.addContainer(this );
250: objects.add(containable);
251: dereferenced.triggerChanged(COSObject.SLOT_CONTAINER, null,
252: newContainer);
253: return this ;
254: }
255:
256: /**
257: * Add a document element (an object or a reference) to the collection.
258: *
259: * <p>
260: * The change is not propagated.
261: * </p>
262: * This should not be used by the application level programmer. It is public
263: * for package visibility reasons.
264: *
265: *
266: * @param element
267: * the element to be added
268: *
269: * @return The receiver.
270: */
271: public COSArray basicAddSilent(COSDocumentElement element) {
272: COSDocumentElement containable = element.containable();
273: containable.addContainer(this );
274: objects.add(containable);
275: return this ;
276: }
277:
278: /**
279: * Remove all elements from the receiver.
280: *
281: */
282: protected void basicClearPropagate() {
283: List oldObjects = new ArrayList(objects);
284: objects.clear();
285: for (Iterator i = oldObjects.iterator(); i.hasNext();) {
286: COSDocumentElement element = (COSDocumentElement) i.next();
287: COSObject dereferenced = element.dereference();
288: willChange(dereferenced);
289: ICOSContainer newContainer = element.removeContainer(this );
290: dereferenced.triggerChanged(COSObject.SLOT_CONTAINER, null,
291: newContainer);
292: }
293: }
294:
295: /**
296: * Get the {@link COSDocumentElement} (an object or a reference) from this
297: * at the specified index.
298: * <p>
299: * This method should only be used for low level programming.
300: *
301: * @param index
302: * The index into this
303: *
304: * @return Get the {@link COSDocumentElement} (an object or a reference)
305: * from this at the specified index.
306: */
307: public COSDocumentElement basicGet(int index) {
308: return (COSDocumentElement) objects.get(index);
309: }
310:
311: /**
312: * The index within this where <code>element</code> can be found or -1.
313: *
314: * @param element
315: * The element to be searched within this.
316: * @return The index of <code>element</code> or -1 if nothing found.
317: */
318: protected int basicIndexOf(COSDocumentElement element) {
319: COSDocumentElement containable = element.containable();
320: int i = 0;
321: for (Iterator it = basicIterator(); it.hasNext();) {
322: COSDocumentElement current = (COSDocumentElement) it.next();
323: if (containable == current) {
324: return i;
325: }
326: i++;
327: }
328: return -1;
329: }
330:
331: /**
332: * An iterator that returns all contained {@link COSDocumentElement}
333: * instances without dereferencing.
334: *
335: * <p>
336: * This should be used in low level programming.
337: * </p>
338: *
339: * @return An iterator that returns all contained {@link COSDocumentElement}
340: * instances without dereferencing.
341: */
342: public Iterator basicIterator() {
343: return objects.iterator();
344: }
345:
346: /**
347: * Remove <code>otherElement</code> from this.
348: *
349: * @param otherElement
350: * The element to be removed.
351: * @return <code>true</code> if element was removed.
352: */
353: protected boolean basicRemovePropagate(
354: COSDocumentElement otherElement) {
355: COSObject dereferenced = otherElement.dereference();
356: COSDocumentElement containable = otherElement.containable();
357: for (Iterator i = basicIterator(); i.hasNext();) {
358: COSDocumentElement element = (COSDocumentElement) i.next();
359: if (containable == element) {
360: i.remove();
361: willChange(dereferenced);
362: ICOSContainer newContainer = containable
363: .removeContainer(this );
364: dereferenced.triggerChanged(COSObject.SLOT_CONTAINER,
365: null, newContainer);
366: return true;
367: }
368: }
369: return false;
370: }
371:
372: /**
373: * Remove element at <code>index</code>.
374: *
375: * @param index
376: * The index within this to be removed.
377: * @return The {@link COSDocumentElement} that was removed at the specified
378: * index.
379: */
380: protected COSDocumentElement basicRemovePropagate(int index) {
381: COSDocumentElement removed = (COSDocumentElement) objects
382: .remove(index);
383: COSObject dereferenced = removed.dereference();
384: willChange(dereferenced);
385: ICOSContainer newContainer = removed.removeContainer(this );
386: dereferenced.triggerChanged(COSObject.SLOT_CONTAINER, null,
387: newContainer);
388: return removed;
389: }
390:
391: /**
392: * replace the object at index <<code>i</code> with <code>element</code>.
393: *
394: * @param i
395: * the index
396: * @param element
397: * the object to put at the specified index
398: *
399: * @return The previously contained object
400: */
401: protected COSDocumentElement basicSetPropagate(int i,
402: COSDocumentElement element) {
403: COSObject dereferenced = element.dereference();
404: COSDocumentElement containable = element.containable();
405: //
406: willChange(dereferenced);
407: ICOSContainer newContainer = containable.addContainer(this );
408: COSDocumentElement oldContainable = (COSDocumentElement) objects
409: .set(i, containable);
410: dereferenced.triggerChanged(COSObject.SLOT_CONTAINER, null,
411: newContainer);
412: //
413: if (oldContainable != containable) {
414: COSObject oldDereferenced = oldContainable.dereference();
415: willChange(oldDereferenced);
416: newContainer = oldContainable.removeContainer(this );
417: oldDereferenced.triggerChanged(COSObject.SLOT_CONTAINER,
418: null, newContainer);
419: }
420: return oldContainable;
421: }
422:
423: /*
424: * (non-Javadoc)
425: *
426: * @see de.intarsys.pdf.cos.COSObject#basicToString()
427: */
428: protected String basicToString() {
429: return objects.toString();
430: }
431:
432: /**
433: * Remove all elements from this.
434: */
435: public void clear() {
436: willChange(this );
437: basicClearPropagate();
438: if (objectListeners != null) {
439: triggerChanged(-1, null, null);
440: }
441: }
442:
443: /*
444: * (non-Javadoc)
445: *
446: * @see de.intarsys.pdf.cos.COSObject#copyShallow()
447: */
448: public COSObject copyShallow() {
449: COSArray result = (COSArray) super .copyShallow();
450: for (Iterator i = basicIterator(); i.hasNext();) {
451: COSDocumentElement element = (COSDocumentElement) i.next();
452: result.basicAddSilent(element.copyShallowNested());
453: }
454: return result;
455: }
456:
457: /*
458: * (non-Javadoc)
459: *
460: * @see java.lang.Object#equals(java.lang.Object)
461: */
462: public boolean equals(Object o) {
463: return this .equals(o, new HashSet());
464: }
465:
466: /**
467: * The {@link COSObject} at the given index. Any index outisde the valid
468: * array range results in COSNull (compare Adobe Core ApI Reference).
469: *
470: * @param index
471: * The index of the {@link COSObject} to select from this.
472: *
473: * @return The {@link COSObject} at the given index or {@link COSNull}.
474: */
475: public COSObject get(int index) {
476: try {
477: return ((COSDocumentElement) objects.get(index))
478: .dereference();
479: } catch (IndexOutOfBoundsException e) {
480: return COSNull.NULL;
481: }
482: }
483:
484: /**
485: * ATTENTION: this implementation returns a hash code that does not remain
486: * constant when manipulating the arrays content
487: *
488: * @see Object#hashCode()
489: */
490: public int hashCode() {
491: int result = 17;
492: for (Iterator iThis = this .basicIterator(); iThis.hasNext();) {
493: COSDocumentElement oThis = (COSDocumentElement) iThis
494: .next();
495: if (oThis.isReference()) {
496: result = (result + oThis.hashCode()) * 34;
497: } else if (((COSObject) oThis).isPrimitive()) {
498: // do not descend
499: result = (result + oThis.hashCode()) * 34;
500: } else {
501: result = (result + 17) * 34;
502: }
503: }
504: return result;
505: }
506:
507: /**
508: * The index of <code>object</code> within this or -1 if not found.
509: *
510: * @param object
511: * The object to be searched within this.
512: * @return The index of <code>object</code> within this or -1 if not
513: * found.
514: */
515: public int indexOf(COSObject object) {
516: return basicIndexOf(object);
517: }
518:
519: /*
520: * (non-Javadoc)
521: *
522: * @see de.intarsys.pdf.cos.COSObject#iterator()
523: */
524: public java.util.Iterator iterator() {
525: return new Iterator() {
526: private int index = 0;
527:
528: private int size = size();
529:
530: public boolean hasNext() {
531: return index < size;
532: }
533:
534: public Object next() {
535: return get(index++);
536: }
537:
538: public void remove() {
539: throw new UnsupportedOperationException();
540: }
541: };
542: }
543:
544: /*
545: * (non-Javadoc)
546: *
547: * @see de.intarsys.pdf.cos.COSCompositeObject#referenceIndirect(de.intarsys.pdf.cos.COSObject)
548: */
549: public COSIndirectObject referenceIndirect(COSObject object) {
550: COSIndirectObject ref = super .referenceIndirect(object);
551: int index = getObjects().indexOf(object);
552: if (index >= 0) {
553: objects.set(index, ref);
554: }
555: return ref;
556: }
557:
558: /**
559: * Remove <code>object</code> from this. If <code>object</code> is not
560: * contained, nothing happens.
561: *
562: * <p>
563: * This method cycles all elements wich may cause heavy lazy loading.
564: * </p>
565: *
566: * @param object
567: * The object to remove from this.
568: * @return <code>true</code> if <code>object</code> was removed.
569: */
570: public boolean remove(COSObject object) {
571: willChange(this );
572: boolean result = basicRemovePropagate(object);
573: if ((objectListeners != null) && result) {
574: triggerChanged(-1, object, COSNull.NULL);
575: }
576: return result;
577: }
578:
579: /**
580: * Remove the object at <code>index</code> from the collection.
581: *
582: * @param index
583: * The index of the object to remove from the collection.
584: * @return The object previously stored at the index.
585: */
586: public COSObject remove(int index) {
587: willChange(this );
588: COSDocumentElement element = basicRemovePropagate(index);
589: COSObject object = element.dereference();
590: if (objectListeners != null) {
591: triggerChanged(index, object, COSNull.NULL);
592: }
593: return object;
594: }
595:
596: /**
597: * Replace the object at index <code>i</code> with <code>object</code>.
598: *
599: * @param i
600: * The index
601: * @param object
602: * The object to put at the specified index
603: *
604: * @return The previuosly referenced object
605: */
606: public COSObject set(int i, COSObject object) {
607: willChange(this );
608: COSDocumentElement element = basicSetPropagate(i, object);
609: COSObject oldObject = element.dereference();
610: if (objectListeners != null) {
611: triggerChanged(i, oldObject, object);
612: }
613: return oldObject;
614: }
615:
616: /**
617: * The number of elements in this.
618: *
619: * @return The number of elements in this.
620: */
621: public int size() {
622: return objects.size();
623: }
624:
625: /**
626: * <code>true</code> if <code>this.size() == 0</code>.
627: *
628: * @return <code>true</code> if <code>this.size() == 0</code>.
629: */
630: public boolean isEmpty() {
631: return objects.size() == 0;
632: }
633:
634: /*
635: * (non-Javadoc)
636: *
637: * @see de.intarsys.pdf.cos.COSObject#copyBasic(de.intarsys.pdf.cos.COSDocument)
638: */
639: protected COSObject copyBasic() {
640: return create(size());
641: }
642:
643: /*
644: * (non-Javadoc)
645: *
646: * @see de.intarsys.pdf.cos.COSObject#copyDeep(java.util.Map)
647: */
648: public COSObject copyDeep(Map copied) {
649: COSArray result = (COSArray) super .copyDeep(copied);
650: for (Iterator i = basicIterator(); i.hasNext();) {
651: COSDocumentElement element = (COSDocumentElement) i.next();
652: COSObject copy = element.copyDeep(copied);
653: result.basicAddSilent(copy);
654: }
655: return result;
656: }
657:
658: /*
659: * (non-Javadoc)
660: *
661: * @see de.intarsys.pdf.cos.COSObject#copyNet(java.util.Map)
662: */
663: protected COSObject copySubGraph(Map copied) {
664: COSArray result = (COSArray) super .copySubGraph(copied);
665:
666: // for (Iterator i = basicIterator(); i.hasNext();) {
667: // COSDocumentElement element = (COSDocumentElement) i.next();
668: // COSObject copy = null;
669: // if (element.isReference()) {
670: // copy = (COSObject) copied.get(element);
671: // if (copy == null) {
672: // if (value.hasNavigationPathTo(this)) {
673: // copy = value.copySubGraph(copied);
674: // } else {
675: // copy = value;
676: // copied.put(value.getIndirectObject(), copy);
677: // }
678: // }
679: // } else {
680: // copy = value.copySubGraph(copied);
681: // }
682: // result.basicAdd(copy);
683: // }
684: return result;
685: }
686:
687: protected boolean equals(Object o, Set visited) {
688: if (isIndirect()) {
689: if (visited.contains(getIndirectObject())) {
690: return true;
691: }
692: visited.add(getIndirectObject());
693: }
694:
695: if (!(o instanceof COSArray)) {
696: return false;
697: }
698:
699: COSArray other = (COSArray) o;
700: if (size() != other.size()) {
701: return false;
702: }
703:
704: Iterator iThis = this .basicIterator();
705: Iterator iOther = other.basicIterator();
706: for (; iThis.hasNext() && iOther.hasNext();) {
707: COSDocumentElement eThis = (COSDocumentElement) iThis
708: .next();
709: COSObject oThis = eThis.dereference();
710: COSDocumentElement eOther = (COSDocumentElement) iOther
711: .next();
712: COSObject oOther = eOther.dereference();
713: if ((oThis == null) && (oOther != null)) {
714: return false;
715: }
716: if (!oThis.equals(oOther, visited)) {
717: return false;
718: }
719: }
720: if (iThis.hasNext() || iOther.hasNext()) {
721: return false;
722: }
723: return true;
724: }
725:
726: /*
727: * (non-Javadoc)
728: *
729: * @see de.intarsys.pdf.cos.COSObject#restoreState(java.lang.Object)
730: */
731: public void restoreState(Object object) {
732: super .restoreState(object);
733: objects.clear();
734: objects.addAll(((COSArray) object).objects);
735: if (objectListeners != null) {
736: triggerChanged(-1, null, null);
737: }
738: }
739:
740: /*
741: * (non-Javadoc)
742: *
743: * @see de.intarsys.tools.objectsession.ISaveStateSupport#saveState()
744: */
745: public Object saveState() {
746: return new COSArray(this );
747: }
748:
749: protected void triggerChanged(int slot, COSObject oldValue,
750: COSObject newValue) {
751: if (objectListeners == null) {
752: return;
753: }
754: Integer slotObject = new Integer(slot);
755: for (Iterator it = objectListeners.iterator(); it.hasNext();) {
756: ICOSObjectListener listener = (ICOSObjectListener) it
757: .next();
758: listener.changed(this , slotObject, oldValue, newValue);
759: }
760: }
761:
762: /*
763: * (non-Javadoc)
764: *
765: * @see de.intarsys.pdf.cos.COSObject#getCOSArray()
766: */
767: public COSArray asArray() {
768: return this;
769: }
770: }
|