KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > etymon > pjx > util > PdfFieldTree


1 package com.etymon.pjx.util;
2
3 import java.io.*;
4 import java.util.*;
5 import com.etymon.pjx.*;
6
7 /**
8    Provides methods for retrieving and modifying the field tree of a
9    PDF document. This class is synchronized.
10    @author Nassib Nassar
11 */

12 public class PdfFieldTree {
13
14     /**
15        A stack for holding nested levels of field nodes.
16     */

17     protected Stack _nested;
18
19     /**
20        The manager associated with this document.
21     */

22     protected PdfManager _m;
23
24     /**
25        The catalog associated with this document.
26     */

27     protected PdfCatalog _catalog;
28
29     /**
30        Defines the set of inheritable field attributes.
31     */

32     protected static Set _inheritable;
33
34     /**
35        Defines the set of field attributes that are inheritable
36        from the AcroForm.
37     */

38     protected static Set _inheritableAcroForm;
39
40     protected static final PdfName PDFNAME_ACROFORM = new PdfName("AcroForm");
41     protected static final PdfName PDFNAME_KIDS = new PdfName("Kids");
42     protected static final PdfName PDFNAME_FIELDS = new PdfName("Fields");
43     protected static final PdfName PDFNAME_PARENT = new PdfName("Parent");
44     protected static final PdfName PDFNAME_T = new PdfName("T");
45     protected static final PdfName PDFNAME_TYPE = new PdfName("Type");
46
47     /**
48        Constructs a <code>PdfFieldTree</code> instance based on a
49        specified <code>PdfManager</code>.
50      */

51     public PdfFieldTree(PdfManager manager) {
52
53         _m = manager;
54         _catalog = new PdfCatalog(manager);
55
56         _inheritable = new HashSet(13);
57         _inheritable.add(new PdfName("FT"));
58         _inheritable.add(new PdfName("Ff"));
59         _inheritable.add(new PdfName("V"));
60         _inheritable.add(new PdfName("DV"));
61         _inheritable.add(new PdfName("DR"));
62         _inheritable.add(new PdfName("DA"));
63         _inheritable.add(new PdfName("Q"));
64         _inheritable.add(new PdfName("Opt"));
65         _inheritable.add(new PdfName("MaxLen"));
66         _inheritable.add(new PdfName("TI"));
67         _inheritable.add(new PdfName("I"));
68         _inheritable.add(new PdfName("Filter"));
69         _inheritable.add(new PdfName("Flags"));
70         
71         _inheritableAcroForm = new HashSet(3);
72         _inheritableAcroForm.add(new PdfName("DR"));
73         _inheritableAcroForm.add(new PdfName("DA"));
74         _inheritableAcroForm.add(new PdfName("Q"));
75
76     }
77
78     // the following should be made public once the API is stable
79
/**
80        Returns the interactive form dictionary of the document.
81        This method returns a <code>PdfDictionary</code> (the
82        AcroForm dictionary), a <code>PdfReference</code> (an
83        indirect reference to the AcroForm dictionary), or
84        <code>null</code> if there is no AcroForm dictionary
85        present.
86        @return the AcroForm dictionary (direct object or indirect
87        reference) or <code>null</code>.
88        @throws IOException
89        @throws PdfFormatException
90      */

91     protected PdfObject getAcroForm() throws IOException, PdfFormatException {
92         synchronized (this) {
93             synchronized (_m) {
94
95                 Object JavaDoc obj = _m.getObjectIndirect(_catalog.getCatalog());
96                 if ( !(obj instanceof PdfDictionary) ) {
97                     throw new PdfFormatException(
98                         "Catalog is not a dictionary.");
99                 }
100                 PdfDictionary catalog = (PdfDictionary)obj;
101
102                 obj = catalog.getMap().get(PDFNAME_ACROFORM);
103                 if (obj == null) {
104                     return null;
105                 }
106                 if ( ( !(obj instanceof PdfReference) ) &&
107                      ( !(obj instanceof PdfDictionary) ) ) {
108                     throw new PdfFormatException(
109                         "AcroForm is not a dictionary or indirect reference.");
110                 }
111                 return (PdfObject)obj;
112
113             }
114         }
115     }
116
117     /**
118        Determines the fully qualified field name of a specified
119        field.
120        @param field the field dictionary.
121        @return the fully qualified field name.
122        @throws IOException
123        @throws PdfFormatException
124      */

125     public String JavaDoc getFullyQualifiedName(PdfDictionary field) throws IOException, PdfFormatException {
126         synchronized (this) {
127             synchronized (_m) {
128
129                 // keep track of the approximate
130
// length of the fully qualified field
131
// name to use later when constructing
132
// the StringBuffer
133
int approxStringLength = 0;
134
135                 // the partial names will be stored in
136
// a list
137
ArrayList names = new ArrayList();
138
139                 // the first node we examine is the
140
// specified dictionary
141
PdfDictionary fieldNode = field;
142                 
143                 boolean done = false;
144                 do {
145
146                     Map fieldMap = fieldNode.getMap();
147                     
148                     // get the node's partial field name
149
Object JavaDoc obj = fieldMap.get(PDFNAME_T);
150                     if (PdfNull.isNull(obj) == false) {
151                         if ( !(obj instanceof PdfObject) ) {
152                             throw new PdfFormatException(
153                                 "Field name (T) is not a PDF object.");
154                         }
155                         obj = _m.getObjectIndirect((PdfObject)obj);
156                         if (PdfNull.isNull(obj) == false) {
157                             if ( !(obj instanceof PdfString) ) {
158                                 throw new PdfFormatException(
159                                     "Field name (T) is not a string.");
160                             }
161                             // add name to
162
// running list
163
String JavaDoc fieldName = ((PdfString)obj).getString();
164                             if (fieldName.length() > 0) {
165                                 names.add(fieldName);
166                                 approxStringLength += fieldName.length() + 1;
167                             }
168                         }
169                     }
170
171                     // ascend to the parent node
172
obj = fieldMap.get(PDFNAME_PARENT);
173                     if (PdfNull.isNull(obj) == true) {
174                         done = true;
175                     } else {
176                         if ( !(obj instanceof PdfObject) ) {
177                             throw new PdfFormatException(
178                                 "Field parent is not a PDF object.");
179                         }
180                         obj = _m.getObjectIndirect((PdfObject)obj);
181                         if (PdfNull.isNull(obj) == true) {
182                             done = true;
183                         } else {
184                             if ( !(obj instanceof PdfDictionary) ) {
185                                 throw new PdfFormatException(
186                                     "Field parent is not a dictionary.");
187                             }
188                             fieldNode = (PdfDictionary)obj;
189                         }
190                     }
191                     
192                 } while (!done);
193
194                 // now string the partial names together
195
StringBuffer JavaDoc sb = new StringBuffer JavaDoc(approxStringLength);
196                 boolean first = true;
197                 int namesSize = names.size();
198                 for (int x = namesSize - 1; x >= 0; x--) {
199                     String JavaDoc name = (String JavaDoc)names.get(x);
200                     // append the name
201
if (first) {
202                         first = false;
203                     } else {
204                         sb.append('.');
205                     }
206                     sb.append(name);
207                 }
208
209                 return sb.toString();
210                 
211             }
212         }
213     }
214     
215     /**
216        Adds inherited attributes to a specified field dictionary
217        object. The field object is cloned and the inherited
218        attributes are made explicit in the cloned object's
219        dictionary. The inherited attributes are retrieved by
220        ascending the field tree and looking for inheritable
221        attributes (if any) that are missing from the specified
222        field dictionary. The interactive form dictionary is also
223        checked (if necessary) for document-wide default values.
224        @param field the field dictionary to be filled in with
225        inherited attributes.
226        @return a clone of the specified field dictionary, with all
227        inherited attributes filled in.
228        @throws IOException
229        @throws PdfFormatException
230      */

231     public PdfDictionary inheritAttributes(PdfDictionary field) throws IOException, PdfFormatException {
232         synchronized (this) {
233             synchronized (_m) {
234
235                 Map fieldM = field.getMap();
236                 
237                 // define new dictionary map
238
Map newMap = new HashMap(field.getMap());
239         
240                 // start out looking for all inheritable attributes
241
// that are not present in this field
242
Set unused = new HashSet(_inheritable.size());
243                 for (Iterator t = _inheritable.iterator(); t.hasNext(); ) {
244                     
245                     PdfName attr = (PdfName)t.next();
246                     Object JavaDoc obj = fieldM.get(attr);
247                     
248                     if ( (obj == null) || (obj instanceof PdfNull) ) {
249                         unused.add(attr);
250                     }
251                     
252                 }
253                 
254                 boolean done = false;
255                 
256                 do {
257                     
258                     // if all the inheritable attributes have been
259
// filled, there is no need to continue
260
// ascending the tree
261
if (unused.isEmpty()) {
262                         done = true;
263                         break;
264                     }
265                     
266                     // get the Parent node
267
Object JavaDoc obj = fieldM.get(PDFNAME_PARENT);
268                     if (obj == null) {
269                         // we are done, but we
270
// need to do one more
271
// round of
272
// inheritance from
273
// the AcroForm
274
done = true;
275                         obj = getAcroForm();
276                         if (obj == null) {
277                             break;
278                         }
279                         // remove all elements
280
// from the unused set
281
// except for
282
// AcroForm-inheritable fields
283
unused.retainAll(_inheritableAcroForm);
284                         if (unused.isEmpty()) {
285                             break;
286                         }
287                     }
288                     if ( !(obj instanceof PdfObject) ) {
289                         throw new PdfFormatException(
290                             "Parent object is not a PDF object.");
291                     }
292                     obj = _m.getObjectIndirect((PdfObject)obj);
293                     if ( !(obj instanceof PdfDictionary) ) {
294                         throw new PdfFormatException(
295                             "Parent object is not a dictionary.");
296                     }
297                     fieldM = ((PdfDictionary)obj).getMap();
298                     
299                     // now examine the parent node
300
for (Iterator t = unused.iterator(); t.hasNext(); ) {
301                         
302                         PdfName attr = (PdfName)t.next();
303                         
304                         // check if the attribute is present
305
obj = fieldM.get(attr);
306                         if ( (obj != null) && ( !(obj instanceof PdfNull) ) ) {
307                             t.remove();
308                             newMap.put(attr, obj);
309                         }
310                         
311                     }
312                     
313                 } while ( !done );
314
315                 return new PdfDictionary(newMap);
316
317             }
318         }
319     }
320     
321     /**
322        Returns an iterator over the terminal field objects in this
323        document's field tree. Note that terminal field objects do
324        not include inherited attributes; {@link
325        #inheritAttributes(PdfDictionary)
326        inheritAttributes(PdfDictionary)} should be used to obtain
327        inherited attributes.
328        @return the iterator over the terminal field objects.
329        @throws IOException
330        @throws PdfFormatException
331      */

332     public PdfFieldTreeIterator getIterator() throws IOException, PdfFormatException {
333         return new FieldTreeIterator(this, _m);
334     }
335
336     /**
337        An iterator over the tree of field dictionaries in a PDF document.
338        @author Nassib Nassar
339      */

340     protected class FieldTreeIterator implements PdfFieldTreeIterator {
341
342         /**
343            The manager associated with this iterator.
344         */

345         protected PdfManager _m;
346
347         /**
348            The field tree associated with this iterator.
349         */

350         protected PdfFieldTree _ft;
351
352         /**
353            Constructs an iterator over a field tree.
354            @param ft the field tree to iterate over.
355            @param m the associated document manager.
356            @throws IOException
357            @throws PdfFormatException
358          */

359         public FieldTreeIterator(PdfFieldTree ft, PdfManager m) throws IOException, PdfFormatException {
360
361             _ft = ft;
362             _m = m;
363             _nested = new Stack();
364
365             // get the AcroForm
366
Object JavaDoc obj = m.getObjectIndirect(getAcroForm());
367
368             if (obj != null) {
369                 
370                 if ( !(obj instanceof PdfDictionary) ) {
371                     throw new PdfFormatException(
372                         "AcroForm is not a dictionary.");
373                 }
374                 PdfDictionary acroForm = (PdfDictionary)obj;
375                 
376                 // get the Fields array from the AcroForm
377
obj = acroForm.getMap().get(PDFNAME_FIELDS);
378                 if ( !(obj instanceof PdfObject) ) {
379                     throw new PdfFormatException(
380                         "Fields array is not a PDF object.");
381                 }
382                 obj = m.getObjectIndirect((PdfObject)obj);
383                 if ( !(obj instanceof PdfArray) ) {
384                     throw new PdfFormatException(
385                         "Fields array is not an array.");
386                 }
387                 List fields = ((PdfArray)obj).getList();
388                 
389                 _nested.push(newList(fields));
390
391             }
392             
393         }
394
395         protected List newList(List list) {
396             return new ArrayList(list);
397         }
398
399         /**
400            Descends the left-edge of the tree until reaching a
401            terminal node and returns its reference. As the
402            tree is descended, this method pushes field lists
403            onto the stack.
404            @return an indirect reference to the terminal node
405            @throws IOException
406            @throws PdfFormatException
407          */

408         protected PdfReference descendTree() throws IOException, PdfFormatException {
409
410             List kidsM;
411             PdfReference node;
412             
413             do {
414
415                 // get the first element of the field
416
// list in the top element of the
417
// stack; this assumes there is a
418
// field list on the stack, and that
419
// the field list contains at least
420
// one element
421
List fieldsM = (List)_nested.peek();
422                 node = (PdfReference)fieldsM.get(0);
423
424                 // remove the element
425
fieldsM.remove(0);
426
427                 // retrieve the referenced node
428
Object JavaDoc obj = _m.getObjectIndirect(node);
429                 if ( !(obj instanceof PdfDictionary) ) {
430                     throw new PdfFormatException(
431                         "Field node is not a dictionary.");
432                 }
433                 Map fieldNode = ((PdfDictionary)obj).getMap();
434
435                 // determine if there are any children
436

437                 obj = fieldNode.get(PDFNAME_KIDS);
438
439                 if (obj == null) {
440
441                     kidsM = null;
442
443                 } else {
444
445                     if ( !(obj instanceof PdfObject) ) {
446                         throw new PdfFormatException(
447                             "Kids array is not a PDF object.");
448                     }
449                     obj = _m.getObjectIndirect((PdfObject)obj);
450                     if ( !(obj instanceof PdfArray) ) {
451                         throw new PdfFormatException(
452                             "Kids array is not an array object.");
453                     }
454                     List kidsL = ((PdfArray)obj).getList();
455                     if (kidsL.isEmpty()) {
456                         throw new PdfFormatException(
457                             "Kids array is empty.");
458                     }
459
460                     kidsM = newList(kidsL);
461
462                 }
463                 // now kidsM contains a modifiable
464
// version of the kids array, or it
465
// equals null if there is no kids
466
// array
467

468                 // push the kids onto the stack and
469
// continue to the next level of
470
// descent
471
if (kidsM != null) {
472                     _nested.push(kidsM);
473                 }
474
475             } while (kidsM != null);
476             // at this point, kidsM == null, which means
477
// that fieldNode is a terminal node
478

479             return node;
480             
481         }
482
483         /**
484            Removes any empty lists from the top of the stack.
485          */

486         protected void cleanUp() {
487
488             while ( ( _nested.empty() == false ) &&
489                 ( ((List)_nested.peek()).isEmpty() ) ) {
490
491                 _nested.pop();
492                 
493             }
494
495         }
496         
497         // Clean-up, Check Null Stack
498
public boolean hasNext() throws PdfFormatException {
499             synchronized (_m) {
500                 synchronized (_ft) {
501
502                     cleanUp();
503                     
504                     return !(_nested.empty());
505                     
506                 }
507             }
508         }
509
510         /**
511            @throws IOException
512            @throws PdfFormatException
513         */

514         public PdfReference next() throws NoSuchElementException, IOException, PdfFormatException {
515             synchronized (_m) {
516                 synchronized (_ft) {
517
518                     // throw an exception if there
519
// are no more elements
520
if ( !hasNext() ) {
521                         throw new NoSuchElementException();
522                     }
523
524                     // descend the tree to a
525
// terminal node
526
return descendTree();
527                     
528                 }
529             }
530         }
531
532     }
533     
534 }
535
Popular Tags