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.io.IOException;
033: import java.util.ArrayList;
034: import java.util.HashMap;
035: import java.util.Iterator;
036: import java.util.List;
037: import java.util.Map;
038: import java.util.NoSuchElementException;
039: import de.intarsys.pdf.crypt.AccessPermissionsFull;
040: import de.intarsys.pdf.crypt.IAccessPermissions;
041: import de.intarsys.pdf.parser.COSLoadError;
042: import de.intarsys.pdf.parser.COSLoadException;
043: import de.intarsys.pdf.st.EnumWriteMode;
044: import de.intarsys.pdf.st.STDocType;
045: import de.intarsys.pdf.st.STDocument;
046: import de.intarsys.tools.attribute.IAttributeSupport;
047: import de.intarsys.tools.locator.ILocator;
048: import de.intarsys.tools.locator.ILocatorSupport;
049:
050: /**
051: * This is a COS level representation of a pdf document. A COS document is made
052: * up of a collection of {@link COSObject} instances. These objects are arranged
053: * according to the PDF file format specification.
054: *
055: * <p>
056: * See PDF File Format Specification [PDF].
057: * </p>
058: */
059: public class COSDocument implements ICOSContainer,
060: ICOSExceptionHandler, IAttributeSupport, ILocatorSupport {
061: public static final Object SLOT_DIRTY = new Object();
062:
063: public static final Object SLOT_TRAILER = new Object();
064:
065: public static final Object SLOT_LOCATOR = new Object();
066:
067: /**
068: * Create a COSDocument based on a Locator.
069: *
070: * @param locator
071: * The ILocater referencing the documents data stream.
072: * @return A new COSDocument.
073: * @throws COSLoadException
074: * @throws IOException
075: */
076: public static COSDocument createFromLocator(ILocator locator)
077: throws IOException, COSLoadException {
078: STDocument stDoc = STDocument.createFromLocator(locator);
079: return createFromST(stDoc);
080: }
081:
082: /**
083: * Create a COSDocument based on a STDocument.
084: *
085: * @param doc
086: * The storage layer document.
087: * @return A new COSDocument.
088: * @throws COSLoadException
089: */
090: public static COSDocument createFromST(STDocument doc)
091: throws COSLoadException {
092: COSDocument result = new COSDocument(doc);
093: result.initializeFromST();
094: result.checkConsistency();
095: return result;
096: }
097:
098: /**
099: * Create a new empty pdf COSDocument.
100: *
101: * @return A new empty pdf COSDocument
102: */
103: public static COSDocument createNew() {
104: return createNew(STDocument.DOCTYPE_PDF);
105: }
106:
107: /**
108: * Create a new empty COSDocument.
109: *
110: * @return A new empty COSDocument
111: */
112: public static COSDocument createNew(STDocType docType) {
113: STDocument stDoc = STDocument.createNew(docType);
114: COSDocument doc = new COSDocument(stDoc);
115: doc.initializeFromScratch();
116: return doc;
117: }
118:
119: /** The abstraction of the document storage layer. */
120: private STDocument stDoc;
121:
122: private ICOSExceptionHandler exceptionHandler;
123:
124: /**
125: * The list of listeners interested in document change events.
126: */
127: private List documentListeners;
128:
129: /**
130: * The list of listeners interested in session events.
131: */
132: private List monitors;
133:
134: /**
135: * document access permissions
136: */
137: private IAccessPermissions accessPermissions;
138:
139: /**
140: * Generic attribute support
141: */
142: private Map attributes;
143:
144: /**
145: * Create a new empty COSDocument.
146: * <p>
147: * This one does no initialization, use the factory method.
148: */
149: protected COSDocument() {
150: this (STDocument.createNew());
151: }
152:
153: /**
154: * Create a new COSDocument based on a STDocument.
155: * <p>
156: * This one does no initialization, use the factory method.
157: *
158: * @param storage
159: * The storage level document
160: */
161: protected COSDocument(STDocument doc) {
162: this .stDoc = doc;
163: doc.setDoc(this );
164: }
165:
166: /**
167: * This should not be used by the application programmer.
168: * <code>public</code> for package visibility reasons.
169: *
170: * @param element
171: */
172: public void add(COSDocumentElement element) {
173: COSDocumentElement containable = element.containable();
174: containable.addContainer(this );
175: }
176:
177: public void addMonitor(ICOSMonitor listener) {
178: List newMonitors;
179: if (monitors == null) {
180: newMonitors = new ArrayList();
181: } else {
182: newMonitors = new ArrayList(monitors);
183: }
184: newMonitors.add(listener);
185: monitors = newMonitors;
186: }
187:
188: /**
189: * Add an {@link ICOSDocumentListener} to be informed about the documents
190: * events.
191: *
192: * @param listener
193: * THe new listener
194: */
195: public void addDocumentListener(ICOSDocumentListener listener) {
196: List newListeners;
197: if (documentListeners == null) {
198: newListeners = new ArrayList();
199: } else {
200: newListeners = new ArrayList(documentListeners);
201: }
202: newListeners.add(listener);
203: documentListeners = newListeners;
204: }
205:
206: /**
207: * This method should not be used by the application programmer. This is
208: * called in the {@link COSObject} lifecycle to ensure internal consistency.
209: */
210: public ICOSContainer associate(ICOSContainer newContainer,
211: COSObject object) {
212: if (newContainer == this ) {
213: // error ?
214: return this ;
215: }
216:
217: // sorry, this is an error
218: throw new IllegalStateException(
219: "object may only be contained once (use indirect object)"); //$NON-NLS-1$
220: }
221:
222: protected void checkConsistency() throws COSLoadError {
223: if (getCatalog() == null) {
224: throw new COSLoadError("Catalog missing"); //$NON-NLS-1$
225: }
226: }
227:
228: /**
229: * Close the document. Accessing a documents content is undefined after
230: * <code>close</code>.
231: *
232: * @throws IOException
233: */
234: public void close() throws IOException {
235: stDoc.close();
236: }
237:
238: /**
239: * This method should not be used by the application programmer. This is
240: * called in the {@link COSObject} lifecycle to ensure internal consistency.
241: */
242: public COSDocumentElement containable(COSObject object) {
243: return object;
244: }
245:
246: /**
247: * Make a deep copy of the receiver. The newly created document has the same
248: * content as this, but does not share any object. The structure of the ST
249: * level is built from scratch.
250: *
251: * @return A deep copy of this.
252: */
253: public COSDocument copyDeep() {
254: try {
255: return COSDocument.createFromST(stGetDoc().copyDeep());
256: } catch (COSLoadException e) {
257: throw new COSRuntimeException(e);
258: }
259: }
260:
261: /**
262: * This method should not be used by the application programmer. This is
263: * called in the {@link COSObject} lifecycle to ensure internal consistency.
264: */
265: public ICOSContainer disassociate(ICOSContainer oldContainer,
266: COSObject object) {
267: if (oldContainer == this ) {
268: // object removed from container
269: object.basicSetContainer(COSObject.NULL_CONTAINER);
270: return COSObject.NULL_CONTAINER;
271: }
272:
273: // sorry, this is an error
274: throw new IllegalStateException("association inconsistent"); //$NON-NLS-1$
275: }
276:
277: /**
278: * If a document contains a permissions dictionary, it is "pushed" to this
279: * by the parser. Otherwise the document will have full permissions set.
280: *
281: * @return The document access permissions
282: */
283: public IAccessPermissions getAccessPermissions() {
284: if (accessPermissions == null) {
285: accessPermissions = AccessPermissionsFull.getActive();
286: }
287: return accessPermissions;
288: }
289:
290: /*
291: * (non-Javadoc)
292: *
293: * @see de.intarsys.tools.component.IAttributeSupport#getAttribute(java.lang.Object)
294: */
295: synchronized public Object getAttribute(Object key) {
296: if (attributes == null) {
297: return null;
298: }
299: return attributes.get(key);
300: }
301:
302: /**
303: * Get the root object (the catalog) for the document.
304: *
305: * @return The root object (the catalog) for the document.
306: */
307: public COSCatalog getCatalog() {
308: COSDictionary cosCatalog = stGetDoc().cosGetTrailer().get(
309: COSTrailer.DK_Root).asDictionary();
310: return (COSCatalog) COSCatalog.META.createFromCos(cosCatalog);
311: }
312:
313: /**
314: * This method should not be used by the application programmer. This is
315: * called in the {@link COSObject} lifecycle to ensure internal consistency.
316: */
317: public COSDocument getDoc() {
318: return this ;
319: }
320:
321: /**
322: * The number of versions created for this document so far.
323: *
324: * @return The number of versions created for this document so far.
325: */
326: public int getIncrementalCount() {
327: return stGetDoc().getIncrementalCount();
328: }
329:
330: /**
331: * Get the info dictionary containing metadata.
332: *
333: * @return The info dictionary containing metadata.
334: */
335: public COSInfoDict getInfoDict() {
336: return stGetDoc().getTrailer().getInfoDict();
337: }
338:
339: /**
340: * The {@link ILocator} for this document. The {@link ILocator} designates
341: * the physical storage for the PDF data.
342: *
343: * @return The {@link ILocator} for this document.
344: */
345: public ILocator getLocator() {
346: return stDoc.getLocator();
347: }
348:
349: /**
350: * The document name. This is derived from the associated {@link ILocator}.
351: *
352: * @return The document name.
353: */
354: public String getName() {
355: return stDoc.getName();
356: }
357:
358: /**
359: * The write mode to be used when the document is written the next time. If
360: * defined this overrides any hint that is used when saving the document.
361: * The write mode is reset after each "save".
362: *
363: * @return The write mode to be used when the document is written.
364: */
365: public EnumWriteMode getWriteModeHint() {
366: return stDoc.getWriteModeHint();
367: }
368:
369: /*
370: * (non-Javadoc)
371: *
372: * @see de.intarsys.pdf.cos.ICOSExceptionHandler#error(de.intarsys.pdf.cos.COSRuntimeException)
373: */
374: public void handleException(COSRuntimeException ex)
375: throws COSRuntimeException {
376: if (exceptionHandler != null) {
377: exceptionHandler.handleException(ex);
378: } else {
379: throw ex;
380: }
381: }
382:
383: protected void initializeFromScratch() {
384: //
385: }
386:
387: protected void initializeFromST() {
388: if (stGetDoc().getSystemSecurityHandler() != null) {
389: setAccessPermissions(stGetDoc().getSystemSecurityHandler()
390: .getSecurityHandler().getAccessPermissions());
391: }
392: }
393:
394: /**
395: * Answer <code>true</code> if the document has changes to be commited.
396: *
397: * @return Answer <code>true</code> if the document has changes to be
398: * commited.
399: */
400: public boolean isDirty() {
401: return stGetDoc().isDirty();
402: }
403:
404: /**
405: * Answer <code>true</code> if the document is encrypted.
406: *
407: * @return Answer <code>true</code> if the document is encrypted.
408: */
409: public boolean isEncrypted() {
410: return stGetDoc().isEncrypted();
411: }
412:
413: /**
414: * Answer <code>true</code> if the document is new, i.e. not yet written.
415: *
416: * @return Answer <code>true</code> if the document is new, i.e. not yet
417: * written.
418: */
419: public boolean isNew() {
420: return stDoc.isNew();
421: }
422:
423: /**
424: * Answer <code>true</code> if the document is read only. To save the
425: * document and its changes you have to define another {@link ILocator} when
426: * saving.
427: *
428: * @return Answer <code>true</code> if the document is read only.
429: */
430: public boolean isReadOnly() {
431: return stDoc.isReadOnly();
432: }
433:
434: /**
435: * An iterator on all COSObject instances of this that are managed as
436: * indirect objects in the storage layer.
437: * <p>
438: * ATTENTION: This iterator may (and on incremental documents most often
439: * will) return objects that are no longer used (referenced) in the
440: * document.
441: *
442: * @return An iterator on all COSObject instances od this that are managed
443: * as indirect objects in the storage layer.
444: */
445: public Iterator objects() {
446: Iterator iteratorObjects = new Iterator() {
447: /**
448: * The iterator on all indirect objects in the storage layer. This
449: * also includes garbage or purely structural objects like x ref
450: * streams.
451: */
452: private Iterator indirectObjects = stGetDoc().objects();
453:
454: COSIndirectObject io = null;
455:
456: public boolean hasNext() {
457: if (io != null) {
458: return true;
459: }
460: while (indirectObjects.hasNext()) {
461: COSIndirectObject current = (COSIndirectObject) indirectObjects
462: .next();
463: if (!current.dereference().isDangling()) {
464: io = current;
465: break;
466: }
467: }
468: return io != null;
469: }
470:
471: public Object next() {
472: if (!hasNext()) {
473: throw new NoSuchElementException();
474: }
475: COSIndirectObject result = io;
476: io = null;
477: return result.dereference();
478: }
479:
480: public void remove() {
481: throw new UnsupportedOperationException();
482: }
483: };
484: return iteratorObjects;
485: }
486:
487: /**
488: * This method should not be used by the application programmer. This is
489: * called in the {@link COSObject} lifecycle to ensure internal consistency.
490: */
491: public int referenceCount() {
492: return 1;
493: }
494:
495: /**
496: * This method should not be used by the application programmer. This is
497: * called in the {@link COSObject} lifecycle to ensure internal consistency.
498: */
499: public COSIndirectObject referenceIndirect(COSObject object) {
500: // i contain the trailer - that will never be indirect
501: throw new IllegalStateException(
502: "document can not have indirect references"); //$NON-NLS-1$
503: }
504:
505: /**
506: * This method should not be used by the application programmer. This is
507: * called in the {@link COSObject} lifecycle to ensure internal consistency.
508: */
509: public void register(COSDocumentElement object) {
510: object.registerWith(this );
511: }
512:
513: /*
514: * (non-Javadoc)
515: *
516: * @see de.intarsys.tools.component.IAttributeSupport#removeAttribute(java.lang.Object)
517: */
518: synchronized public Object removeAttribute(Object key) {
519: if (attributes != null) {
520: return attributes.remove(key);
521: }
522: return null;
523: }
524:
525: /*
526: * (non-Javadoc)
527: *
528: * @see de.intarsys.tools.objectsession.IChangeListenerSupport#removeChangeListener(de.intarsys.tools.objectsession.IChangeListener)
529: */
530: public void removeMonitor(ICOSMonitor monitor) {
531: if (monitors == null) {
532: return;
533: }
534: List newMonitors = new ArrayList(monitors);
535: newMonitors.remove(monitor);
536: monitors = newMonitors;
537: }
538:
539: /**
540: * Remove an {@link ICOSDocumentListener}.
541: *
542: * @param listener
543: * The listener to be removed
544: */
545: public void removeDocumentListener(ICOSDocumentListener listener) {
546: if (documentListeners == null) {
547: return;
548: }
549: List newListeners = new ArrayList(documentListeners);
550: newListeners.remove(listener);
551: documentListeners = newListeners;
552: }
553:
554: /**
555: * Restore this from a locator. The {@link ILocator} must reference a data
556: * stream that was previously used to parse the document.
557: *
558: * @param locator
559: * The {@link ILocator} defining the new physical content.
560: * @throws IOException
561: * @throws COSLoadException
562: */
563: public void restore(ILocator locator) throws IOException,
564: COSLoadException {
565: stDoc.restore(locator);
566: }
567:
568: /**
569: * This method should not be used by the application programmer. This is
570: * called in the {@link COSObject} lifecycle to ensure internal consistency.
571: */
572: public ICOSContainer restoreStateContainer(ICOSContainer container) {
573: return container;
574: }
575:
576: /**
577: * Save the document to its current {@link ILocator}.
578: *
579: * @throws IOException
580: */
581: public void save() throws IOException {
582: save(getLocator(), null);
583: }
584:
585: /**
586: * Save the document nto a new {@link ILocator}.
587: *
588: * @param locator
589: * The {@link ILocator} defining the new data location.
590: * @throws IOException
591: */
592: public void save(ILocator locator) throws IOException {
593: save(locator, null);
594: }
595:
596: /**
597: * Save the document to an optional new {@link ILocator} using the
598: * <code>options</code> to control specific serializing behavior such as
599: * "incremental writing".
600: *
601: * @param locator
602: * @param options
603: * @throws IOException
604: */
605: public void save(ILocator locator, Map options) throws IOException {
606: Object oldValue = getLocator();
607: stDoc.save(locator, options);
608: triggerChangedLocator(oldValue, locator);
609: }
610:
611: /**
612: * This method should not be used by the application programmer. This is
613: * called in the {@link COSObject} lifecycle to ensure internal consistency.
614: */
615: public ICOSContainer saveStateContainer() {
616: return this ;
617: }
618:
619: /**
620: * Assign the {@link IAccessPermissions} for the document.
621: *
622: * @param accessPermissions
623: * The new {@link IAccessPermissions}.
624: */
625: protected void setAccessPermissions(
626: IAccessPermissions accessPermissions) {
627: this .accessPermissions = accessPermissions;
628: }
629:
630: /*
631: * (non-Javadoc)
632: *
633: * @see de.intarsys.tools.attribute.IAttributeSupport#setAttribute(java.lang.Object,
634: * java.lang.Object)
635: */
636: synchronized public Object setAttribute(Object key, Object value) {
637: if (attributes == null) {
638: attributes = new HashMap();
639: }
640: return attributes.put(key, value);
641: }
642:
643: protected void setDirty(boolean b) {
644: boolean oldValue = stGetDoc().isDirty();
645: stGetDoc().setDirty(b);
646: if (oldValue != b) {
647: triggerChangedDirty();
648: }
649: }
650:
651: /**
652: * Set the info dictionary containing metadata.
653: *
654: * @param infoDict
655: * The info dictionary containing metadata.
656: */
657: public void setInfoDict(COSInfoDict infoDict) {
658: stGetDoc().cosGetTrailer().put(COSTrailer.DK_Info,
659: infoDict.cosGetObject());
660: }
661:
662: /**
663: * Assign a new name to the document.
664: *
665: * @param name
666: * The new name.
667: */
668: public void setName(String name) {
669: stDoc.setName(name);
670: triggerChangedLocator(getLocator(), getLocator());
671: }
672:
673: /**
674: * The write mode to be used when the document is written the next time. If
675: * defined this overrides any hint that is used when saving the document.
676: * The write mode is reset after each "save".
677: *
678: * @param writeMode
679: * The write mode to be used when the document is written.
680: */
681: public void setWriteModeHint(EnumWriteMode writeMode) {
682: stDoc.setWriteModeHint(writeMode);
683: }
684:
685: /**
686: * The storage layer document.
687: *
688: * @return The storage layer document.
689: */
690: public STDocument stGetDoc() {
691: return stDoc;
692: }
693:
694: protected void triggerChanged(Object slot, Object oldValue,
695: Object newValue) {
696: if (documentListeners == null) {
697: return;
698: }
699: for (Iterator it = documentListeners.iterator(); it.hasNext();) {
700: ICOSDocumentListener listener = (ICOSDocumentListener) it
701: .next();
702: listener.changed(this , slot, oldValue, newValue);
703: }
704: }
705:
706: /**
707: * This method should not be used by the application programmer. This is
708: * called in the {@link COSObject} lifecycle to ensure internal consistency.
709: */
710: public void triggerChangedAll() {
711: // todo 1 @mit review API
712: triggerChanged(SLOT_TRAILER, null, null);
713: }
714:
715: protected void triggerChangedDirty() {
716: // todo 1 @mit review API
717: Boolean newValue = Boolean.valueOf(isDirty());
718: Boolean oldValue = Boolean.valueOf(!isDirty());
719: triggerChanged(SLOT_DIRTY, oldValue, newValue);
720: }
721:
722: protected void triggerChangedLocator(Object oldValue,
723: Object newValue) {
724: triggerChanged(SLOT_LOCATOR, oldValue, newValue);
725: }
726:
727: /**
728: * This method should not be used by the application programmer. This is
729: * called in the {@link COSObject} lifecycle to ensure internal consistency.
730: */
731: public void willChange(COSObject change) {
732: setDirty(true);
733: if (monitors == null) {
734: return;
735: }
736: for (Iterator iter = monitors.iterator(); iter.hasNext();) {
737: ICOSMonitor monitor = (ICOSMonitor) iter.next();
738: monitor.willChange(change);
739: }
740: }
741: }
|