KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > poi > hpsf > MutableSection


1 /* ====================================================================
2    Copyright 2002-2004 Apache Software Foundation
3
4    Licensed under the Apache License, Version 2.0 (the "License");
5    you may not use this file except in compliance with the License.
6    You may obtain a copy of the License at
7
8        http://www.apache.org/licenses/LICENSE-2.0
9
10    Unless required by applicable law or agreed to in writing, software
11    distributed under the License is distributed on an "AS IS" BASIS,
12    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13    See the License for the specific language governing permissions and
14    limitations under the License.
15 ==================================================================== */

16         
17 package org.apache.poi.hpsf;
18
19 import java.io.ByteArrayOutputStream JavaDoc;
20 import java.io.IOException JavaDoc;
21 import java.io.OutputStream JavaDoc;
22 import java.util.Collections JavaDoc;
23 import java.util.Comparator JavaDoc;
24 import java.util.Iterator JavaDoc;
25 import java.util.LinkedList JavaDoc;
26 import java.util.List JavaDoc;
27 import java.util.ListIterator JavaDoc;
28 import java.util.Map JavaDoc;
29
30 import org.apache.poi.hpsf.wellknown.PropertyIDMap;
31 import org.apache.poi.util.LittleEndian;
32
33 /**
34  * <p>Adds writing capability to the {@link Section} class.</p>
35  *
36  * <p>Please be aware that this class' functionality will be merged into the
37  * {@link Section} class at a later time, so the API will change.</p>
38  *
39  * @version $Id: MutableSection.java,v 1.13 2004/08/31 20:47:09 klute Exp $
40  * @since 2002-02-20
41  */

42 public class MutableSection extends Section
43 {
44     /**
45      * <p>If the "dirty" flag is true, the section's size must be
46      * (re-)calculated before the section is written.</p>
47      */

48     private boolean dirty = true;
49
50
51
52     /**
53      * <p>List to assemble the properties. Unfortunately a wrong
54      * decision has been taken when specifying the "properties" field
55      * as an Property[]. It should have been a {@link java.util.List}.</p>
56      */

57     private List JavaDoc preprops;
58
59
60
61     /**
62      * <p>Contains the bytes making out the section. This byte array is
63      * established when the section's size is calculated and can be reused
64      * later. It is valid only if the "dirty" flag is false.</p>
65      */

66     private byte[] sectionBytes;
67
68
69
70     /**
71      * <p>Creates an empty mutable section.</p>
72      */

73     public MutableSection()
74     {
75         dirty = true;
76         formatID = null;
77         offset = -1;
78         preprops = new LinkedList JavaDoc();
79     }
80
81
82
83     /**
84      * <p>Constructs a <code>MutableSection</code> by doing a deep copy of an
85      * existing <code>Section</code>. All nested <code>Property</code>
86      * instances, will be their mutable counterparts in the new
87      * <code>MutableSection</code>.</p>
88      *
89      * @param s The section set to copy
90      */

91     public MutableSection(final Section s)
92     {
93         setFormatID(s.getFormatID());
94         final Property[] pa = s.getProperties();
95         final MutableProperty[] mpa = new MutableProperty[pa.length];
96         for (int i = 0; i < pa.length; i++)
97             mpa[i] = new MutableProperty(pa[i]);
98         setProperties(mpa);
99         setDictionary(s.getDictionary());
100     }
101
102
103
104     /**
105      * <p>Sets the section's format ID.</p>
106      *
107      * @param formatID The section's format ID
108      *
109      * @see #setFormatID(byte[])
110      * @see #getFormatID
111      */

112     public void setFormatID(final ClassID formatID)
113     {
114         this.formatID = formatID;
115     }
116
117
118
119     /**
120      * <p>Sets the section's format ID.</p>
121      *
122      * @param formatID The section's format ID as a byte array. It components
123      * are in big-endian format.
124      *
125      * @see #setFormatID(ClassID)
126      * @see #getFormatID
127      */

128     public void setFormatID(final byte[] formatID)
129     {
130         ClassID fid = getFormatID();
131         if (fid == null)
132         {
133             fid = new ClassID();
134             setFormatID(fid);
135         }
136         fid.setBytes(formatID);
137     }
138
139
140
141     /**
142      * <p>Sets this section's properties. Any former values are overwritten.</p>
143      *
144      * @param properties This section's new properties.
145      */

146     public void setProperties(final Property[] properties)
147     {
148         this.properties = properties;
149         preprops = new LinkedList JavaDoc();
150         for (int i = 0; i < properties.length; i++)
151             preprops.add(properties[i]);
152         dirty = true;
153     }
154
155
156
157     /**
158      * <p>Sets the value of the property with the specified ID. If a
159      * property with this ID is not yet present in the section, it
160      * will be added. An already present property with the specified
161      * ID will be overwritten.</p>
162      *
163      * @param id The property's ID
164      * @param value The property's value. It will be written as a Unicode
165      * string.
166      *
167      * @see #setProperty(int, long, Object)
168      * @see #getProperty
169      */

170     public void setProperty(final int id, final String JavaDoc value)
171     {
172         setProperty(id, Variant.VT_LPWSTR, value);
173         dirty = true;
174     }
175
176
177
178     /**
179      * <p>Sets the value and the variant type of the property with the
180      * specified ID. If a property with this ID is not yet present in
181      * the section, it will be added. An already present property with
182      * the specified ID will be overwritten. A default mapping will be
183      * used to choose the property's type.</p>
184      *
185      * @param id The property's ID.
186      * @param variantType The property's variant type.
187      * @param value The property's value.
188      *
189      * @see #setProperty(int, String)
190      * @see #getProperty
191      * @see Variant
192      */

193     public void setProperty(final int id, final long variantType,
194                             final Object JavaDoc value)
195     {
196         final MutableProperty p = new MutableProperty();
197         p.setID(id);
198         p.setType(variantType);
199         p.setValue(value);
200         setProperty(p);
201         dirty = true;
202     }
203
204
205
206     /**
207      * <p>Sets a property. If a property with the same ID is not yet present in
208      * the section, the property will be added to the section. If there is
209      * already a property with the same ID present in the section, it will be
210      * overwritten.</p>
211      *
212      * @param p The property to be added to the section
213      *
214      * @see #setProperty(int, long, Object)
215      * @see #setProperty(int, String)
216      * @see #getProperty
217      * @see Variant
218      */

219     public void setProperty(final Property p)
220     {
221         final long id = p.getID();
222         removeProperty(id);
223         preprops.add(p);
224         dirty = true;
225     }
226
227
228
229     /**
230      * <p>Removes a property.</p>
231      *
232      * @param id The ID of the property to be removed
233      */

234     public void removeProperty(final long id)
235     {
236         for (final Iterator JavaDoc i = preprops.iterator(); i.hasNext();)
237             if (((Property) i.next()).getID() == id)
238             {
239                 i.remove();
240                 break;
241             }
242         dirty = true;
243     }
244
245
246
247     /**
248      * <p>Sets the value of the boolean property with the specified
249      * ID.</p>
250      *
251      * @param id The property's ID
252      * @param value The property's value
253      *
254      * @see #setProperty(int, long, Object)
255      * @see #getProperty
256      * @see Variant
257      */

258     protected void setPropertyBooleanValue(final int id, final boolean value)
259     {
260         setProperty(id, (long) Variant.VT_BOOL, new Boolean JavaDoc(value));
261     }
262
263
264
265     /**
266      * <p>Returns the section's size.</p>
267      *
268      * @return the section's size.
269      */

270     public int getSize()
271     {
272         if (dirty)
273         {
274             try
275             {
276                 size = calcSize();
277                 dirty = false;
278             }
279             catch (HPSFRuntimeException ex)
280             {
281                 throw ex;
282             }
283             catch (Exception JavaDoc ex)
284             {
285                 throw new HPSFRuntimeException(ex);
286             }
287         }
288         return size;
289     }
290
291
292
293     /**
294      * <p>Calculates the section's size. It is the sum of the lengths of the
295      * section's header (8), the properties list (16 times the number of
296      * properties) and the properties themselves.</p>
297      *
298      * @return the section's length in bytes.
299      */

300     private int calcSize() throws WritingNotSupportedException, IOException JavaDoc
301     {
302         final ByteArrayOutputStream JavaDoc out = new ByteArrayOutputStream JavaDoc();
303         write(out);
304         out.close();
305         /* Pad to multiple of 4 bytes so that even the Windows shell (explorer)
306          * shows custom properties. */

307         sectionBytes = Util.pad4(out.toByteArray());
308         return sectionBytes.length;
309     }
310
311
312
313     /**
314      * <p>Writes this section into an output stream.</p>
315      *
316      * <p>Internally this is done by writing into three byte array output
317      * streams: one for the properties, one for the property list and one for
318      * the section as such. The two former are appended to the latter when they
319      * have received all their data.</p>
320      *
321      * @param out The stream to write into.
322      *
323      * @return The number of bytes written, i.e. the section's size.
324      * @exception IOException if an I/O error occurs
325      * @exception WritingNotSupportedException if HPSF does not yet support
326      * writing a property's variant type.
327      */

328     public int write(final OutputStream JavaDoc out)
329         throws WritingNotSupportedException, IOException JavaDoc
330     {
331         /* Check whether we have already generated the bytes making out the
332          * section. */

333         if (!dirty && sectionBytes != null)
334         {
335             out.write(sectionBytes);
336             return sectionBytes.length;
337         }
338
339         /* The properties are written to this stream. */
340         final ByteArrayOutputStream JavaDoc propertyStream =
341             new ByteArrayOutputStream JavaDoc();
342
343         /* The property list is established here. After each property that has
344          * been written to "propertyStream", a property list entry is written to
345          * "propertyListStream". */

346         final ByteArrayOutputStream JavaDoc propertyListStream =
347             new ByteArrayOutputStream JavaDoc();
348  
349         /* Maintain the current position in the list. */
350         int position = 0;
351
352         /* Increase the position variable by the size of the property list so
353          * that it points behind the property list and to the beginning of the
354          * properties themselves. */

355         position += 2 * LittleEndian.INT_SIZE +
356                     getPropertyCount() * 2 * LittleEndian.INT_SIZE;
357
358         /* Writing the section's dictionary it tricky. If there is a dictionary
359          * (property 0) the codepage property (property 1) has to be set, too.
360          * Since HPSF supports Unicode only, the codepage must be 1200. */

361         int codepage = -1;
362         if (getProperty(PropertyIDMap.PID_DICTIONARY) != null)
363         {
364             final Object JavaDoc p1 = getProperty(PropertyIDMap.PID_CODEPAGE);
365             if (p1 != null)
366             {
367                 if (!(p1 instanceof Integer JavaDoc))
368                     throw new IllegalPropertySetDataException
369                         ("The codepage property (ID = 1) must be an " +
370                          "Integer object.");
371             }
372             else
373                 throw new IllegalPropertySetDataException
374                     ("The codepage property (ID = 1) must be set if the " +
375                      "section contains a dictionary.");
376             codepage = getCodepage();
377         }
378
379         /* Sort the property list by their property IDs: */
380         Collections.sort(preprops, new Comparator JavaDoc()
381             {
382                 public int compare(final Object JavaDoc o1, final Object JavaDoc o2)
383                 {
384                     final Property p1 = (Property) o1;
385                     final Property p2 = (Property) o2;
386                     if (p1.getID() < p2.getID())
387                         return -1;
388                     else if (p1.getID() == p2.getID())
389                         return 0;
390                     else
391                         return 1;
392                 }
393             });
394
395         /* Write the properties and the property list into their respective
396          * streams: */

397         for (final ListIterator JavaDoc i = preprops.listIterator(); i.hasNext();)
398         {
399             final MutableProperty p = (MutableProperty) i.next();
400             final long id = p.getID();
401             
402             /* Write the property list entry. */
403             TypeWriter.writeUIntToStream(propertyListStream, p.getID());
404             TypeWriter.writeUIntToStream(propertyListStream, position);
405
406             /* If the property ID is not equal 0 we write the property and all
407              * is fine. However, if it equals 0 we have to write the section's
408              * dictionary which has an implicit type only and an explicit
409              * value. */

410             if (id != 0)
411                 /* Write the property and update the position to the next
412                  * property. */

413                 position += p.write(propertyStream, getCodepage());
414             else
415             {
416                 if (codepage == -1)
417                     throw new IllegalPropertySetDataException
418                         ("Codepage (property 1) is undefined.");
419                 position += writeDictionary(propertyStream, dictionary,
420                                             codepage);
421             }
422         }
423         propertyStream.close();
424         propertyListStream.close();
425
426         /* Write the section: */
427         byte[] pb1 = propertyListStream.toByteArray();
428         byte[] pb2 = propertyStream.toByteArray();
429         
430         /* Write the section's length: */
431         TypeWriter.writeToStream(out, LittleEndian.INT_SIZE * 2 +
432                                       pb1.length + pb2.length);
433         
434         /* Write the section's number of properties: */
435         TypeWriter.writeToStream(out, getPropertyCount());
436         
437         /* Write the property list: */
438         out.write(pb1);
439         
440         /* Write the properties: */
441         out.write(pb2);
442
443         int streamLength = LittleEndian.INT_SIZE * 2 + pb1.length + pb2.length;
444         return streamLength;
445     }
446
447
448
449     /**
450      * <p>Writes the section's dictionary.</p>
451      *
452      * @param out The output stream to write to.
453      * @param dictionary The dictionary.
454      * @param codepage The codepage to be used to write the dictionary items.
455      * @return The number of bytes written
456      * @exception IOException if an I/O exception occurs.
457      */

458     private static int writeDictionary(final OutputStream JavaDoc out,
459                                        final Map JavaDoc dictionary, final int codepage)
460         throws IOException JavaDoc
461     {
462         int length = TypeWriter.writeUIntToStream(out, dictionary.size());
463         for (final Iterator JavaDoc i = dictionary.keySet().iterator(); i.hasNext();)
464         {
465             final Long JavaDoc key = (Long JavaDoc) i.next();
466             final String JavaDoc value = (String JavaDoc) dictionary.get(key);
467
468             if (codepage == Constants.CP_UNICODE)
469             {
470                 /* Write the dictionary item in Unicode. */
471                 int sLength = value.length() + 1;
472                 if (sLength % 2 == 1)
473                     sLength++;
474                 length += TypeWriter.writeUIntToStream(out, key.longValue());
475                 length += TypeWriter.writeUIntToStream(out, sLength);
476                 final char[] ca = value.toCharArray();
477                 for (int j = 0; j < ca.length; j++)
478                 {
479                     int high = (ca[j] & 0x0ff00) >> 8;
480                     int low = (ca[j] & 0x000ff);
481                     out.write(low);
482                     out.write(high);
483                     length += 2;
484                     sLength--;
485                 }
486                 while (sLength > 0)
487                 {
488                     out.write(0x00);
489                     out.write(0x00);
490                     length += 2;
491                     sLength--;
492                 }
493             }
494             else
495             {
496                 /* Write the dictionary item in another codepage than
497                  * Unicode. */

498                 length += TypeWriter.writeUIntToStream(out, key.longValue());
499                 length += TypeWriter.writeUIntToStream(out, value.length() + 1);
500                 final byte[] ba =
501                     value.getBytes(VariantSupport.codepageToEncoding(codepage));
502                 for (int j = 0; j < ba.length; j++)
503                 {
504                     out.write(ba[j]);
505                     length++;
506                 }
507                 out.write(0x00);
508                 length++;
509             }
510         }
511         return length;
512     }
513
514
515
516     /**
517      * <p>Overwrites the super class' method to cope with a redundancy:
518      * the property count is maintained in a separate member variable, but
519      * shouldn't.</p>
520      *
521      * @return The number of properties in this section
522      */

523     public int getPropertyCount()
524     {
525         return preprops.size();
526     }
527
528
529
530     /**
531      * <p>Gets this section's properties.</p>
532      *
533      * @return this section's properties.
534      */

535     public Property[] getProperties()
536     {
537         properties = (Property[]) preprops.toArray(new Property[0]);
538         return properties;
539     }
540
541
542
543     /**
544      * <p>Gets a property.</p>
545      *
546      * @param id The ID of the property to get
547      * @return The property or <code>null</code> if there is no such property
548      */

549     public Object JavaDoc getProperty(final long id)
550     {
551         /* Calling getProperties() ensures that properties and preprops are in
552          * sync.</p> */

553         getProperties();
554         return super.getProperty(id);
555     }
556
557
558
559     /**
560      * <p>Sets the section's dictionary. All keys in the dictionary must be
561      * {@link java.lang.Long} instances, all values must be
562      * {@link java.lang.String}s. This method overwrites the properties with IDs
563      * 0 and 1 since they are reserved for the dictionary and the dictionary's
564      * codepage. Setting these properties explicitly might have surprising
565      * effects. An application should never do this but always use this
566      * method.</p>
567      *
568      * @param dictionary The dictionary
569      *
570      * @exception IllegalPropertySetDataException if the dictionary's key and
571      * value types are not correct.
572      *
573      * @see Section#getDictionary()
574      */

575     public void setDictionary(final Map JavaDoc dictionary)
576         throws IllegalPropertySetDataException
577     {
578         if (dictionary != null)
579         {
580             for (final Iterator JavaDoc i = dictionary.keySet().iterator();
581                  i.hasNext();)
582                 if (!(i.next() instanceof Long JavaDoc))
583                     throw new IllegalPropertySetDataException
584                         ("Dictionary keys must be of type Long.");
585             for (final Iterator JavaDoc i = dictionary.values().iterator();
586                  i.hasNext();)
587                 if (!(i.next() instanceof String JavaDoc))
588                     throw new IllegalPropertySetDataException
589                         ("Dictionary values must be of type String.");
590             this.dictionary = dictionary;
591
592             /* Set the dictionary property (ID 0). Please note that the second
593              * parameter in the method call below is unused because dictionaries
594              * don't have a type. */

595             setProperty(PropertyIDMap.PID_DICTIONARY, -1, dictionary);
596
597             /* Set the codepage property (ID 1) for the strings used in the
598              * dictionary. HPSF always writes Unicode strings to the
599              * dictionary. */

600             setProperty(PropertyIDMap.PID_CODEPAGE, Variant.VT_I2,
601                         new Integer JavaDoc(Constants.CP_UNICODE));
602         }
603         else
604             /* Setting the dictionary to null means to remove property 0.
605              * However, it does not mean to remove property 1 (codepage). */

606             removeProperty(PropertyIDMap.PID_DICTIONARY);
607     }
608
609 }
610
Popular Tags