001: package com.etymon.pjx.util;
002:
003: import java.io.*;
004: import java.util.*;
005: import com.etymon.pjx.*;
006:
007: /**
008: Provides methods for retrieving and modifying the field tree of a
009: PDF document. This class is synchronized.
010: @author Nassib Nassar
011: */
012: public class PdfFieldTree {
013:
014: /**
015: A stack for holding nested levels of field nodes.
016: */
017: protected Stack _nested;
018:
019: /**
020: The manager associated with this document.
021: */
022: protected PdfManager _m;
023:
024: /**
025: The catalog associated with this document.
026: */
027: protected PdfCatalog _catalog;
028:
029: /**
030: Defines the set of inheritable field attributes.
031: */
032: protected static Set _inheritable;
033:
034: /**
035: Defines the set of field attributes that are inheritable
036: from the AcroForm.
037: */
038: protected static Set _inheritableAcroForm;
039:
040: protected static final PdfName PDFNAME_ACROFORM = new PdfName(
041: "AcroForm");
042: protected static final PdfName PDFNAME_KIDS = new PdfName("Kids");
043: protected static final PdfName PDFNAME_FIELDS = new PdfName(
044: "Fields");
045: protected static final PdfName PDFNAME_PARENT = new PdfName(
046: "Parent");
047: protected static final PdfName PDFNAME_T = new PdfName("T");
048: protected static final PdfName PDFNAME_TYPE = new PdfName("Type");
049:
050: /**
051: Constructs a <code>PdfFieldTree</code> instance based on a
052: specified <code>PdfManager</code>.
053: */
054: public PdfFieldTree(PdfManager manager) {
055:
056: _m = manager;
057: _catalog = new PdfCatalog(manager);
058:
059: _inheritable = new HashSet(13);
060: _inheritable.add(new PdfName("FT"));
061: _inheritable.add(new PdfName("Ff"));
062: _inheritable.add(new PdfName("V"));
063: _inheritable.add(new PdfName("DV"));
064: _inheritable.add(new PdfName("DR"));
065: _inheritable.add(new PdfName("DA"));
066: _inheritable.add(new PdfName("Q"));
067: _inheritable.add(new PdfName("Opt"));
068: _inheritable.add(new PdfName("MaxLen"));
069: _inheritable.add(new PdfName("TI"));
070: _inheritable.add(new PdfName("I"));
071: _inheritable.add(new PdfName("Filter"));
072: _inheritable.add(new PdfName("Flags"));
073:
074: _inheritableAcroForm = new HashSet(3);
075: _inheritableAcroForm.add(new PdfName("DR"));
076: _inheritableAcroForm.add(new PdfName("DA"));
077: _inheritableAcroForm.add(new PdfName("Q"));
078:
079: }
080:
081: // the following should be made public once the API is stable
082: /**
083: Returns the interactive form dictionary of the document.
084: This method returns a <code>PdfDictionary</code> (the
085: AcroForm dictionary), a <code>PdfReference</code> (an
086: indirect reference to the AcroForm dictionary), or
087: <code>null</code> if there is no AcroForm dictionary
088: present.
089: @return the AcroForm dictionary (direct object or indirect
090: reference) or <code>null</code>.
091: @throws IOException
092: @throws PdfFormatException
093: */
094: protected PdfObject getAcroForm() throws IOException,
095: PdfFormatException {
096: synchronized (this ) {
097: synchronized (_m) {
098:
099: Object obj = _m
100: .getObjectIndirect(_catalog.getCatalog());
101: if (!(obj instanceof PdfDictionary)) {
102: throw new PdfFormatException(
103: "Catalog is not a dictionary.");
104: }
105: PdfDictionary catalog = (PdfDictionary) obj;
106:
107: obj = catalog.getMap().get(PDFNAME_ACROFORM);
108: if (obj == null) {
109: return null;
110: }
111: if ((!(obj instanceof PdfReference))
112: && (!(obj instanceof PdfDictionary))) {
113: throw new PdfFormatException(
114: "AcroForm is not a dictionary or indirect reference.");
115: }
116: return (PdfObject) obj;
117:
118: }
119: }
120: }
121:
122: /**
123: Determines the fully qualified field name of a specified
124: field.
125: @param field the field dictionary.
126: @return the fully qualified field name.
127: @throws IOException
128: @throws PdfFormatException
129: */
130: public String getFullyQualifiedName(PdfDictionary field)
131: throws IOException, PdfFormatException {
132: synchronized (this ) {
133: synchronized (_m) {
134:
135: // keep track of the approximate
136: // length of the fully qualified field
137: // name to use later when constructing
138: // the StringBuffer
139: int approxStringLength = 0;
140:
141: // the partial names will be stored in
142: // a list
143: ArrayList names = new ArrayList();
144:
145: // the first node we examine is the
146: // specified dictionary
147: PdfDictionary fieldNode = field;
148:
149: boolean done = false;
150: do {
151:
152: Map fieldMap = fieldNode.getMap();
153:
154: // get the node's partial field name
155: Object obj = fieldMap.get(PDFNAME_T);
156: if (PdfNull.isNull(obj) == false) {
157: if (!(obj instanceof PdfObject)) {
158: throw new PdfFormatException(
159: "Field name (T) is not a PDF object.");
160: }
161: obj = _m.getObjectIndirect((PdfObject) obj);
162: if (PdfNull.isNull(obj) == false) {
163: if (!(obj instanceof PdfString)) {
164: throw new PdfFormatException(
165: "Field name (T) is not a string.");
166: }
167: // add name to
168: // running list
169: String fieldName = ((PdfString) obj)
170: .getString();
171: if (fieldName.length() > 0) {
172: names.add(fieldName);
173: approxStringLength += fieldName
174: .length() + 1;
175: }
176: }
177: }
178:
179: // ascend to the parent node
180: obj = fieldMap.get(PDFNAME_PARENT);
181: if (PdfNull.isNull(obj) == true) {
182: done = true;
183: } else {
184: if (!(obj instanceof PdfObject)) {
185: throw new PdfFormatException(
186: "Field parent is not a PDF object.");
187: }
188: obj = _m.getObjectIndirect((PdfObject) obj);
189: if (PdfNull.isNull(obj) == true) {
190: done = true;
191: } else {
192: if (!(obj instanceof PdfDictionary)) {
193: throw new PdfFormatException(
194: "Field parent is not a dictionary.");
195: }
196: fieldNode = (PdfDictionary) obj;
197: }
198: }
199:
200: } while (!done);
201:
202: // now string the partial names together
203: StringBuffer sb = new StringBuffer(approxStringLength);
204: boolean first = true;
205: int namesSize = names.size();
206: for (int x = namesSize - 1; x >= 0; x--) {
207: String name = (String) names.get(x);
208: // append the name
209: if (first) {
210: first = false;
211: } else {
212: sb.append('.');
213: }
214: sb.append(name);
215: }
216:
217: return sb.toString();
218:
219: }
220: }
221: }
222:
223: /**
224: Adds inherited attributes to a specified field dictionary
225: object. The field object is cloned and the inherited
226: attributes are made explicit in the cloned object's
227: dictionary. The inherited attributes are retrieved by
228: ascending the field tree and looking for inheritable
229: attributes (if any) that are missing from the specified
230: field dictionary. The interactive form dictionary is also
231: checked (if necessary) for document-wide default values.
232: @param field the field dictionary to be filled in with
233: inherited attributes.
234: @return a clone of the specified field dictionary, with all
235: inherited attributes filled in.
236: @throws IOException
237: @throws PdfFormatException
238: */
239: public PdfDictionary inheritAttributes(PdfDictionary field)
240: throws IOException, PdfFormatException {
241: synchronized (this ) {
242: synchronized (_m) {
243:
244: Map fieldM = field.getMap();
245:
246: // define new dictionary map
247: Map newMap = new HashMap(field.getMap());
248:
249: // start out looking for all inheritable attributes
250: // that are not present in this field
251: Set unused = new HashSet(_inheritable.size());
252: for (Iterator t = _inheritable.iterator(); t.hasNext();) {
253:
254: PdfName attr = (PdfName) t.next();
255: Object obj = fieldM.get(attr);
256:
257: if ((obj == null) || (obj instanceof PdfNull)) {
258: unused.add(attr);
259: }
260:
261: }
262:
263: boolean done = false;
264:
265: do {
266:
267: // if all the inheritable attributes have been
268: // filled, there is no need to continue
269: // ascending the tree
270: if (unused.isEmpty()) {
271: done = true;
272: break;
273: }
274:
275: // get the Parent node
276: Object obj = fieldM.get(PDFNAME_PARENT);
277: if (obj == null) {
278: // we are done, but we
279: // need to do one more
280: // round of
281: // inheritance from
282: // the AcroForm
283: done = true;
284: obj = getAcroForm();
285: if (obj == null) {
286: break;
287: }
288: // remove all elements
289: // from the unused set
290: // except for
291: // AcroForm-inheritable fields
292: unused.retainAll(_inheritableAcroForm);
293: if (unused.isEmpty()) {
294: break;
295: }
296: }
297: if (!(obj instanceof PdfObject)) {
298: throw new PdfFormatException(
299: "Parent object is not a PDF object.");
300: }
301: obj = _m.getObjectIndirect((PdfObject) obj);
302: if (!(obj instanceof PdfDictionary)) {
303: throw new PdfFormatException(
304: "Parent object is not a dictionary.");
305: }
306: fieldM = ((PdfDictionary) obj).getMap();
307:
308: // now examine the parent node
309: for (Iterator t = unused.iterator(); t.hasNext();) {
310:
311: PdfName attr = (PdfName) t.next();
312:
313: // check if the attribute is present
314: obj = fieldM.get(attr);
315: if ((obj != null)
316: && (!(obj instanceof PdfNull))) {
317: t.remove();
318: newMap.put(attr, obj);
319: }
320:
321: }
322:
323: } while (!done);
324:
325: return new PdfDictionary(newMap);
326:
327: }
328: }
329: }
330:
331: /**
332: Returns an iterator over the terminal field objects in this
333: document's field tree. Note that terminal field objects do
334: not include inherited attributes; {@link
335: #inheritAttributes(PdfDictionary)
336: inheritAttributes(PdfDictionary)} should be used to obtain
337: inherited attributes.
338: @return the iterator over the terminal field objects.
339: @throws IOException
340: @throws PdfFormatException
341: */
342: public PdfFieldTreeIterator getIterator() throws IOException,
343: PdfFormatException {
344: return new FieldTreeIterator(this , _m);
345: }
346:
347: /**
348: An iterator over the tree of field dictionaries in a PDF document.
349: @author Nassib Nassar
350: */
351: protected class FieldTreeIterator implements PdfFieldTreeIterator {
352:
353: /**
354: The manager associated with this iterator.
355: */
356: protected PdfManager _m;
357:
358: /**
359: The field tree associated with this iterator.
360: */
361: protected PdfFieldTree _ft;
362:
363: /**
364: Constructs an iterator over a field tree.
365: @param ft the field tree to iterate over.
366: @param m the associated document manager.
367: @throws IOException
368: @throws PdfFormatException
369: */
370: public FieldTreeIterator(PdfFieldTree ft, PdfManager m)
371: throws IOException, PdfFormatException {
372:
373: _ft = ft;
374: _m = m;
375: _nested = new Stack();
376:
377: // get the AcroForm
378: Object obj = m.getObjectIndirect(getAcroForm());
379:
380: if (obj != null) {
381:
382: if (!(obj instanceof PdfDictionary)) {
383: throw new PdfFormatException(
384: "AcroForm is not a dictionary.");
385: }
386: PdfDictionary acroForm = (PdfDictionary) obj;
387:
388: // get the Fields array from the AcroForm
389: obj = acroForm.getMap().get(PDFNAME_FIELDS);
390: if (!(obj instanceof PdfObject)) {
391: throw new PdfFormatException(
392: "Fields array is not a PDF object.");
393: }
394: obj = m.getObjectIndirect((PdfObject) obj);
395: if (!(obj instanceof PdfArray)) {
396: throw new PdfFormatException(
397: "Fields array is not an array.");
398: }
399: List fields = ((PdfArray) obj).getList();
400:
401: _nested.push(newList(fields));
402:
403: }
404:
405: }
406:
407: protected List newList(List list) {
408: return new ArrayList(list);
409: }
410:
411: /**
412: Descends the left-edge of the tree until reaching a
413: terminal node and returns its reference. As the
414: tree is descended, this method pushes field lists
415: onto the stack.
416: @return an indirect reference to the terminal node
417: @throws IOException
418: @throws PdfFormatException
419: */
420: protected PdfReference descendTree() throws IOException,
421: PdfFormatException {
422:
423: List kidsM;
424: PdfReference node;
425:
426: do {
427:
428: // get the first element of the field
429: // list in the top element of the
430: // stack; this assumes there is a
431: // field list on the stack, and that
432: // the field list contains at least
433: // one element
434: List fieldsM = (List) _nested.peek();
435: node = (PdfReference) fieldsM.get(0);
436:
437: // remove the element
438: fieldsM.remove(0);
439:
440: // retrieve the referenced node
441: Object obj = _m.getObjectIndirect(node);
442: if (!(obj instanceof PdfDictionary)) {
443: throw new PdfFormatException(
444: "Field node is not a dictionary.");
445: }
446: Map fieldNode = ((PdfDictionary) obj).getMap();
447:
448: // determine if there are any children
449:
450: obj = fieldNode.get(PDFNAME_KIDS);
451:
452: if (obj == null) {
453:
454: kidsM = null;
455:
456: } else {
457:
458: if (!(obj instanceof PdfObject)) {
459: throw new PdfFormatException(
460: "Kids array is not a PDF object.");
461: }
462: obj = _m.getObjectIndirect((PdfObject) obj);
463: if (!(obj instanceof PdfArray)) {
464: throw new PdfFormatException(
465: "Kids array is not an array object.");
466: }
467: List kidsL = ((PdfArray) obj).getList();
468: if (kidsL.isEmpty()) {
469: throw new PdfFormatException(
470: "Kids array is empty.");
471: }
472:
473: kidsM = newList(kidsL);
474:
475: }
476: // now kidsM contains a modifiable
477: // version of the kids array, or it
478: // equals null if there is no kids
479: // array
480:
481: // push the kids onto the stack and
482: // continue to the next level of
483: // descent
484: if (kidsM != null) {
485: _nested.push(kidsM);
486: }
487:
488: } while (kidsM != null);
489: // at this point, kidsM == null, which means
490: // that fieldNode is a terminal node
491:
492: return node;
493:
494: }
495:
496: /**
497: Removes any empty lists from the top of the stack.
498: */
499: protected void cleanUp() {
500:
501: while ((_nested.empty() == false)
502: && (((List) _nested.peek()).isEmpty())) {
503:
504: _nested.pop();
505:
506: }
507:
508: }
509:
510: // Clean-up, Check Null Stack
511: public boolean hasNext() throws PdfFormatException {
512: synchronized (_m) {
513: synchronized (_ft) {
514:
515: cleanUp();
516:
517: return !(_nested.empty());
518:
519: }
520: }
521: }
522:
523: /**
524: @throws IOException
525: @throws PdfFormatException
526: */
527: public PdfReference next() throws NoSuchElementException,
528: IOException, PdfFormatException {
529: synchronized (_m) {
530: synchronized (_ft) {
531:
532: // throw an exception if there
533: // are no more elements
534: if (!hasNext()) {
535: throw new NoSuchElementException();
536: }
537:
538: // descend the tree to a
539: // terminal node
540: return descendTree();
541:
542: }
543: }
544: }
545:
546: }
547:
548: }
|