001: /*
002: Copyright © 2006,2007 Stefano Chizzolini. http://clown.stefanochizzolini.it
003:
004: Contributors:
005: * Stefano Chizzolini (original code developer, http://www.stefanochizzolini.it):
006: contributed code is Copyright © 2006,2007 by Stefano Chizzolini.
007:
008: This file should be part of the source code distribution of "PDF Clown library"
009: (the Program): see the accompanying README files for more info.
010:
011: This Program is free software; you can redistribute it and/or modify it under
012: the terms of the GNU General Public License as published by the Free Software
013: Foundation; either version 2 of the License, or (at your option) any later version.
014:
015: This Program is distributed in the hope that it will be useful, but WITHOUT ANY
016: WARRANTY, either expressed or implied; without even the implied warranty of
017: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the License for more details.
018:
019: You should have received a copy of the GNU General Public License along with this
020: Program (see README files); if not, go to the GNU website (http://www.gnu.org/).
021:
022: Redistribution and use, with or without modification, are permitted provided that such
023: redistributions retain the above copyright notice, license and disclaimer, along with
024: this list of conditions.
025: */
026:
027: package it.stefanochizzolini.clown.files;
028:
029: import it.stefanochizzolini.clown.objects.PdfDataObject;
030: import it.stefanochizzolini.clown.objects.PdfIndirectObject;
031: import it.stefanochizzolini.clown.tokens.XRefEntry;
032: import it.stefanochizzolini.clown.tokens.XRefEntryUsageEnum;
033: import it.stefanochizzolini.clown.util.NotImplementedException;
034:
035: import java.util.ArrayList;
036: import java.util.Collection;
037: import java.util.Hashtable;
038: import java.util.Iterator;
039: import java.util.List;
040: import java.util.ListIterator;
041: import java.util.NoSuchElementException;
042: import java.util.TreeMap;
043:
044: /**
045: Collection of the <b>alive indirect objects</b> available inside the file.
046: <h3>Remarks</h3>
047: <p>According to the PDF spec, <i>indirect object entries may be free
048: (no data object associated) or in-use (data object associated)</i>.</p>
049: <p>We can effectively subdivide indirect objects in two possibly-overriding
050: collections: the <b>original indirect objects</b> (coming from the associated
051: preexisting file) and the <b>newly-registered indirect objects</b> (coming
052: from new data objects or original indirect objects manipulated during the
053: current session).</p>
054: <p><i>To ensure that the modifications applied to an original indirect object
055: are committed to being persistent</i> is critical that the modified original
056: indirect object is newly-registered (practically overriding the original
057: indirect object).</p>
058: <p><b>Alive indirect objects</b> encompass all the newly-registered ones plus
059: not-overridden original ones.</p>
060: @version 0.0.5
061: @since 0.0.0
062: */
063: public class IndirectObjects implements List<PdfIndirectObject> {
064: // <class>
065: // <dynamic>
066: // <fields>
067: /**
068: Associated file.
069: */
070: private File file;
071:
072: /**
073: Map of matching references of imported indirect objects.
074: <h3>Remarks</h3>
075: <p>This collection is used to prevent duplications among imported indirect
076: objects.</p>
077: <p><code>Key</code> is the external indirect object hashcode, <code>Value</code> is the
078: matching internal indirect object.</p>
079: */
080: private Hashtable<Integer, PdfIndirectObject> importedObjects = new Hashtable<Integer, PdfIndirectObject>();
081: /**
082: Collection of newly-registered indirect objects.
083: */
084: private TreeMap<Integer, PdfIndirectObject> modifiedObjects = new TreeMap<Integer, PdfIndirectObject>();
085: /**
086: Collection of instantiated original indirect objects.
087: <h3>Remarks</h3>
088: <p>This collection is useful as a cache to avoid unconsistent parsing duplications.</p>
089: */
090: private TreeMap<Integer, PdfIndirectObject> wokenObjects = new TreeMap<Integer, PdfIndirectObject>();
091:
092: /**
093: Object counter.
094: */
095: private int lastObjectNumber = -1; // Empty.
096: /**
097: Offsets of the original indirect objects inside the associated file (to say:
098: implicit collection of the original indirect objects).
099: <h3>Remarks
100: <p>This information is vital to randomly retrieve the indirect-object persistent
101: representation inside the associated file.</p>
102: */
103: private XRefEntry[] xrefEntries;
104:
105: private UpdateModeEnum updateMode = UpdateModeEnum.Manual;
106:
107: // </fields>
108:
109: // <constructors>
110: IndirectObjects(File file, XRefEntry[] xrefEntries) {
111: this .file = file;
112: this .xrefEntries = xrefEntries;
113: // Are there original indirect objects?
114: if (this .xrefEntries == null) {
115: /*
116: [PDF:1.6:3.4.3] Mandatory head of the linked list of free objects
117: at object number 0.
118: */
119: // Register the leading free-object!
120: lastObjectNumber++;
121: modifiedObjects.put(lastObjectNumber,
122: new PdfIndirectObject(this .file, null,
123: new XRefEntry(lastObjectNumber, 65535, 0,
124: XRefEntryUsageEnum.Free)));
125: } else {
126: // Adjust the object counter!
127: lastObjectNumber = xrefEntries.length - 1;
128: }
129: }
130:
131: // </constructors>
132:
133: // <interface>
134: // <public>
135: public File getFile() {
136: return file;
137: }
138:
139: /**
140: Register an <b>internal data object</b>.
141: <h3>Remarks</h3>
142: <p>Alternatives:<ul>
143: <li>To register a modified internal indirect object, use
144: {@link #set(int,PdfIndirectObject) set(int,PdfIndirectObject)}.</li>
145: <li>To register an external indirect object, use
146: {@link #addExternal(PdfIndirectObject) addExternal(PdfIndirectObject)}.</li>
147: </ul></p>
148: */
149: public PdfIndirectObject add(PdfDataObject object) {
150: // Wrap the data object inside a new indirect object!
151: lastObjectNumber++;
152: PdfIndirectObject indirectObject = new PdfIndirectObject(file,
153: object, new XRefEntry(lastObjectNumber, 0, 0,
154: XRefEntryUsageEnum.InUse));
155: // Register the object!
156: modifiedObjects.put(lastObjectNumber, indirectObject);
157:
158: return indirectObject;
159: }
160:
161: /**
162: Registers and gets an <b>external indirect object</b>.
163: <h3>Remarks</h3>
164: <p>External indirect objects come from alien PDF files. <i>This is a powerful
165: way to import the content of one file into another</i>.</p>
166: <p>Alternatives:<ul>
167: <li>To register a modified internal indirect object, use
168: {@link #set(int,PdfIndirectObject) set(int,PdfIndirectObject)}.</li>
169: <li>To register an internal data object, use
170: {@link #add(PdfDataObject) add(PdfDataObject)}.</li></ul></p>
171: */
172: public PdfIndirectObject addExternal(PdfIndirectObject object) {
173: PdfIndirectObject indirectObject = importedObjects.get(object
174: .hashCode());
175: // Hasn't the external indirect object been imported yet?
176: if (indirectObject == null) {
177: // Register the clone of the data object corresponding to the external indirect object!
178: indirectObject = add((PdfDataObject) object.getDataObject()
179: .clone(file));
180: // Keep track of the imported indirect object!
181: importedObjects.put(object.hashCode(), indirectObject);
182: }
183:
184: return indirectObject;
185: }
186:
187: public Collection<? extends PdfIndirectObject> addAllExternal(
188: Collection<? extends PdfIndirectObject> objects) {
189: ArrayList<PdfIndirectObject> addedObjects = new ArrayList<PdfIndirectObject>(
190: objects.size());
191: for (PdfIndirectObject object : objects) {
192: addedObjects.add((PdfIndirectObject) addExternal(object));
193: }
194:
195: return addedObjects;
196: }
197:
198: // <List>
199: public void add(int index, PdfIndirectObject object) {
200: throw new UnsupportedOperationException();
201: }
202:
203: public boolean addAll(int index,
204: Collection<? extends PdfIndirectObject> objects) {
205: throw new UnsupportedOperationException();
206: }
207:
208: /**
209: Gets an indirect object with the specified object number.
210: @param index Object number of the indirect object to get.
211: @return Indirect object corresponding to the specified object number.
212: */
213: public PdfIndirectObject get(int index) {
214: // Try among the new objects!
215: PdfIndirectObject object = modifiedObjects.get(index);
216: // Is it among the original objects?
217: if (object == null) {
218: // Try among the woken original objects!
219: object = wokenObjects.get(index);
220: // Is it among the sleeping original objects?
221: if (object == null) {
222: try {
223: object = new PdfIndirectObject(file, null,
224: xrefEntries[index]);
225: } catch (Exception e) {
226: throw new RuntimeException(e);
227: }
228:
229: // Now it's awake!
230: /*
231: NOTE: This operation allows to keep a consistant state across the whole session,
232: avoiding multiple incoherent instantiations of the same original indirect object.
233: */
234: wokenObjects.put(index, object);
235:
236: // Early registration?
237: if (updateMode == UpdateModeEnum.Automatic) {
238: update(object); /* Force early registration. */
239: }
240: }
241: }
242:
243: return object;
244: }
245:
246: public int indexOf(Object object) {
247: // Is this indirect object associated to this file?
248: if (((PdfIndirectObject) object).getFile() != file)
249: return -1;
250:
251: return ((PdfIndirectObject) object).getReference()
252: .getObjectNumber();
253: }
254:
255: public int lastIndexOf(Object object) {
256: /*
257: NOTE: By definition, there's a bijective relation between indirect objects
258: and their indices.
259: */
260: return indexOf(object);
261: }
262:
263: public ListIterator<PdfIndirectObject> listIterator() {
264: throw new NotImplementedException();
265: }
266:
267: public ListIterator<PdfIndirectObject> listIterator(int index) {
268: throw new NotImplementedException();
269: }
270:
271: public PdfIndirectObject remove(int index) {
272: /*
273: NOTE: Acrobat 6.0 and later (PDF 1.5+) DO NOT use the free list to recycle object numbers;
274: new objects are assigned new numbers [PDF:1.6:H.3:16].
275: According to such an implementation note, we simply mark the removed object as 'not-reusable'
276: newly-freed entry (generation 65535), neglecting both to add it to the linked list of free
277: entries and to increment by 1 its generation number.
278: */
279: return update(new PdfIndirectObject(file, null, new XRefEntry(
280: index, 65535, // Not reusable.
281: 0, XRefEntryUsageEnum.Free)));
282: }
283:
284: /**
285: Sets an indirect object with the specified object number.
286: <h3>Contract</h3>
287: <ul>
288: <li>Preconditions:
289: <ol>
290: <li><code>index</code> value MUST be between 1 and (size() - 1); index 0
291: is reserved to the mandatory head of the linked list of free objects [PDF:1.6:3.4.3].</li>
292: </ol>
293: </li>
294: <li>Postconditions:<ol><li>(none).</li></ol></li>
295: <li>Invariants:<ol><li>(none).</li></ol></li>
296: <li>Side-effects:<ol><li>(none).</li></ol></li>
297: </ul>
298: <h3>Remarks</h3>
299: <p>This method is currently limited to <b>internal indirect
300: objects</b>: <i>use it to register modified internal indirect objects only</i>.
301: If you need to register alternative-type objects, consider the following
302: methods:</p>
303: <ul>
304: <li>to register an <b>external indirect object</b>, use
305: {@link #addExternal(PdfIndirectObject) addExternal(PdfIndirectObject)}.</li>
306: <li>to register an <b>internal data object</b>, use
307: {@link #add(PdfDataObject) add(PdfDataObject)}.</li>
308: </ul>
309: @param index Object number of the indirect object to set.
310: @param object Indirect object to set.
311: @return Replaced indirect object.
312: */
313: public PdfIndirectObject set(int index, PdfIndirectObject object) {
314: throw new UnsupportedOperationException();
315: }
316:
317: public List<PdfIndirectObject> subList(int fromIndex, int toIndex) {
318: throw new NotImplementedException();
319: }
320:
321: // <Collection>
322: /**
323: Registers an <b>external indirect object</b>.
324: <h3>Remarks</h3>
325: <p>External indirect objects come from alien PDF files. <i>This is a powerful
326: way to import the content of one file into another</i>.</p>
327: <p>Alternatives:<ul>
328: <li>To register and get an external indirect object, use
329: {@link #addExternal(PdfIndirectObject) addExternal(PdfIndirectObject)}.</li>
330: </ul></p>
331: */
332: public boolean add(PdfIndirectObject object) {
333: boolean changed = (addExternal(object) != null);
334:
335: return changed;
336: }
337:
338: /**
339: Registers <b>external indirect objects</b>.
340: <h3>Remarks</h3>
341: <p>External indirect objects come from alien PDF files. <i>This is a powerful
342: way to import the content of one file into another</i>.</p>
343: <p>Alternatives:<ul>
344: <li>To register and get external indirect object, use
345: {@link #addAllExternal(PdfIndirectObject) addAllExternal(Collection<? extends PdfIndirectObject>)}.</li>
346: </ul></p>
347: */
348: public boolean addAll(
349: Collection<? extends PdfIndirectObject> objects) {
350: boolean changed = false;
351: for (PdfIndirectObject object : objects) {
352: changed |= (addExternal(object) != null);
353: }
354:
355: return changed;
356: }
357:
358: public void clear() {
359: throw new UnsupportedOperationException();
360: }
361:
362: public boolean contains(Object object) {
363: throw new NotImplementedException();
364: }
365:
366: public boolean containsAll(Collection<?> objects) {
367: throw new NotImplementedException();
368: }
369:
370: public boolean equals(Object object) {
371: throw new NotImplementedException();
372: }
373:
374: public int hashCode() {
375: throw new NotImplementedException();
376: }
377:
378: public boolean isEmpty() {
379: /*
380: NOTE: Semantics of the indirect objects collection imply that the collection is considered
381: empty in any case no in-use object is available.
382: */
383: for (PdfIndirectObject object : this ) {
384: if (object.isInUse())
385: return false;
386: }
387:
388: return true;
389: }
390:
391: /**
392: @version 0.0.5
393: @since 0.0.0
394: */
395: public boolean remove(Object object) {
396: return (remove(((PdfIndirectObject) object).getReference()
397: .getObjectNumber()) != null);
398: }
399:
400: public boolean removeAll(Collection<?> objects) {
401: throw new NotImplementedException();
402: }
403:
404: public boolean retainAll(Collection<?> objects) {
405: throw new UnsupportedOperationException();
406: }
407:
408: /**
409: Gets the number of entries available (both in-use and free) in the
410: collection.
411: @return The number of entries available in the collection.
412: */
413: public int size() {
414: return (lastObjectNumber + 1);
415: }
416:
417: public PdfIndirectObject[] toArray() {
418: throw new NotImplementedException();
419: }
420:
421: public <PdfIndirectObject> PdfIndirectObject[] toArray(
422: PdfIndirectObject[] objects) {
423: throw new NotImplementedException();
424: }
425:
426: // <Iterable>
427: public Iterator<PdfIndirectObject> iterator() {
428: return new Iterator<PdfIndirectObject>() {
429: // <class>
430: // <dynamic>
431: // <fields>
432: /** Index of the next item. */
433: private int index = 0;
434:
435: // </fields>
436:
437: // <interface>
438: // <public>
439: // <Iterator>
440: public boolean hasNext() {
441: return (index < size());
442: }
443:
444: public PdfIndirectObject next() {
445: if (!hasNext())
446: throw new NoSuchElementException();
447:
448: return get(index++);
449: }
450:
451: public void remove() {
452: throw new UnsupportedOperationException();
453: }
454: // </Iterator>
455: // </public>
456: // </interface>
457: // </dynamic>
458: // </class>
459: };
460: }
461:
462: // </Iterable>
463: // </Collection>
464: // </List>
465: // </public>
466:
467: // <internal>
468: /**
469: <h3>Remarks</h3>
470: <p>For internal use only.</p>
471: */
472: public TreeMap<Integer, PdfIndirectObject> getModifiedObjects() {
473: return modifiedObjects;
474: }
475:
476: /**
477: <h3>Remarks</h3>
478: <p>For internal use only.</p>
479: */
480: public PdfIndirectObject update(PdfIndirectObject object) {
481: int index = object.getReference().getObjectNumber();
482:
483: // Get the old indirect object to be replaced!
484: PdfIndirectObject old = get(index);
485: if (old != object) {
486: old.dropFile(); /* Disconnect the old indirect object. */
487: }
488:
489: // Insert the new indirect object into the modified objects collection!
490: modifiedObjects.put(index, object);
491: // Remove old indirect object from cache!
492: wokenObjects.remove(index);
493: // Mark the new indirect object as modified!
494: object.dropOriginal();
495:
496: return old;
497: }
498: // </internal>
499: // </interface>
500: // </dynamic>
501: // </class>
502: }
|