KickJava   Java API By Example, From Geeks To Geeks.

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


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

17         
18 package org.apache.poi.hpsf;
19
20 import java.io.IOException JavaDoc;
21 import java.io.InputStream JavaDoc;
22 import java.io.UnsupportedEncodingException JavaDoc;
23 import java.util.ArrayList JavaDoc;
24 import java.util.List JavaDoc;
25
26 import org.apache.poi.hpsf.wellknown.SectionIDMap;
27 import org.apache.poi.util.LittleEndian;
28
29 /**
30  * <p>Represents a property set in the Horrible Property Set Format
31  * (HPSF). These are usually metadata of a Microsoft Office
32  * document.</p>
33  *
34  * <p>An application that wants to access these metadata should create
35  * an instance of this class or one of its subclasses by calling the
36  * factory method {@link PropertySetFactory#create} and then retrieve
37  * the information its needs by calling appropriate methods.</p>
38  *
39  * <p>{@link PropertySetFactory#create} does its work by calling one
40  * of the constructors {@link PropertySet#PropertySet(InputStream)} or
41  * {@link PropertySet#PropertySet(byte[])}. If the constructor's
42  * argument is not in the Horrible Property Set Format, i.e. not a
43  * property set stream, or if any other error occurs, an appropriate
44  * exception is thrown.</p>
45  *
46  * <p>A {@link PropertySet} has a list of {@link Section}s, and each
47  * {@link Section} has a {@link Property} array. Use {@link
48  * #getSections} to retrieve the {@link Section}s, then call {@link
49  * Section#getProperties} for each {@link Section} to get hold of the
50  * {@link Property} arrays.</p> Since the vast majority of {@link
51  * PropertySet}s contains only a single {@link Section}, the
52  * convenience method {@link #getProperties} returns the properties of
53  * a {@link PropertySet}'s {@link Section} (throwing a {@link
54  * NoSingleSectionException} if the {@link PropertySet} contains more
55  * (or less) than exactly one {@link Section}).</p>
56  *
57  * @author Rainer Klute <a
58  * HREF="mailto:klute@rainer-klute.de">&lt;klute@rainer-klute.de&gt;</a>
59  * @author Drew Varner (Drew.Varner hanginIn sc.edu)
60  * @version $Id: PropertySet.java,v 1.19 2004/08/31 17:59:49 klute Exp $
61  * @since 2002-02-09
62  */

63 public class PropertySet
64 {
65
66     /**
67      * <p>The "byteOrder" field must equal this value.</p>
68      */

69     static final byte[] BYTE_ORDER_ASSERTION =
70         new byte[] {(byte) 0xFE, (byte) 0xFF};
71
72     /**
73      * <p>Specifies this {@link PropertySet}'s byte order. See the
74      * HPFS documentation for details!</p>
75      */

76     protected int byteOrder;
77
78     /**
79      * <p>Returns the property set stream's low-level "byte order"
80      * field. It is always <tt>0xFFFE</tt> .</p>
81      *
82      * @return The property set stream's low-level "byte order" field.
83      */

84     public int getByteOrder()
85     {
86         return byteOrder;
87     }
88
89
90
91     /**
92      * <p>The "format" field must equal this value.</p>
93      */

94     static final byte[] FORMAT_ASSERTION =
95         new byte[]{(byte) 0x00, (byte) 0x00};
96
97     /**
98      * <p>Specifies this {@link PropertySet}'s format. See the HPFS
99      * documentation for details!</p>
100      */

101     protected int format;
102
103     /**
104      * <p>Returns the property set stream's low-level "format"
105      * field. It is always <tt>0x0000</tt> .</p>
106      *
107      * @return The property set stream's low-level "format" field.
108      */

109     public int getFormat()
110     {
111         return format;
112     }
113
114
115  
116     /**
117      * <p>Specifies the version of the operating system that created
118      * this {@link PropertySet}. See the HPFS documentation for
119      * details!</p>
120      */

121     protected int osVersion;
122
123
124     /**
125      * <p>If the OS version field holds this value the property set stream was
126      * created on a 16-bit Windows system.</p>
127      */

128     public static final int OS_WIN16 = 0x0000;
129
130     /**
131      * <p>If the OS version field holds this value the property set stream was
132      * created on a Macintosh system.</p>
133      */

134     public static final int OS_MACINTOSH = 0x0001;
135
136     /**
137      * <p>If the OS version field holds this value the property set stream was
138      * created on a 32-bit Windows system.</p>
139      */

140     public static final int OS_WIN32 = 0x0002;
141
142     /**
143      * <p>Returns the property set stream's low-level "OS version"
144      * field.</p>
145      *
146      * @return The property set stream's low-level "OS version" field.
147      */

148     public int getOSVersion()
149     {
150         return osVersion;
151     }
152
153
154
155     /**
156      * <p>Specifies this {@link PropertySet}'s "classID" field. See
157      * the HPFS documentation for details!</p>
158      */

159     protected ClassID classID;
160
161     /**
162      * <p>Returns the property set stream's low-level "class ID"
163      * field.</p>
164      *
165      * @return The property set stream's low-level "class ID" field.
166      */

167     public ClassID getClassID()
168     {
169         return classID;
170     }
171
172
173
174     /**
175      * <p>Returns the number of {@link Section}s in the property
176      * set.</p>
177      *
178      * @return The number of {@link Section}s in the property set.
179      */

180     public int getSectionCount()
181     {
182         return sections.size();
183     }
184
185
186
187     /**
188      * <p>The sections in this {@link PropertySet}.</p>
189      */

190     protected List JavaDoc sections;
191
192
193     /**
194      * <p>Returns the {@link Section}s in the property set.</p>
195      *
196      * @return The {@link Section}s in the property set.
197      */

198     public List JavaDoc getSections()
199     {
200         return sections;
201     }
202
203
204
205     /**
206      * <p>Creates an empty (uninitialized) {@link PropertySet}.</p>
207      *
208      * <p><strong>Please note:</strong> For the time being this
209      * constructor is protected since it is used for internal purposes
210      * only, but expect it to become public once the property set's
211      * writing functionality is implemented.</p>
212      */

213     protected PropertySet()
214     { }
215
216
217
218     /**
219      * <p>Creates a {@link PropertySet} instance from an {@link
220      * InputStream} in the Horrible Property Set Format.</p>
221      *
222      * <p>The constructor reads the first few bytes from the stream
223      * and determines whether it is really a property set stream. If
224      * it is, it parses the rest of the stream. If it is not, it
225      * resets the stream to its beginning in order to let other
226      * components mess around with the data and throws an
227      * exception.</p>
228      *
229      * @param stream Holds the data making out the property set
230      * stream.
231      * @throws MarkUnsupportedException if the stream does not support
232      * the {@link InputStream#markSupported} method.
233      * @throws IOException if the {@link InputStream} cannot not be
234      * accessed as needed.
235      * @exception NoPropertySetStreamException if the input stream does not
236      * contain a property set.
237      * @exception UnsupportedEncodingException if a character encoding is not
238      * supported.
239      */

240     public PropertySet(final InputStream JavaDoc stream)
241         throws NoPropertySetStreamException, MarkUnsupportedException,
242                IOException JavaDoc, UnsupportedEncodingException JavaDoc
243     {
244         if (isPropertySetStream(stream))
245         {
246             final int avail = stream.available();
247             final byte[] buffer = new byte[avail];
248             stream.read(buffer, 0, buffer.length);
249             init(buffer, 0, buffer.length);
250         }
251         else
252             throw new NoPropertySetStreamException();
253     }
254
255
256
257     /**
258      * <p>Creates a {@link PropertySet} instance from a byte array
259      * that represents a stream in the Horrible Property Set
260      * Format.</p>
261      *
262      * @param stream The byte array holding the stream data.
263      * @param offset The offset in <var>stream</var> where the stream
264      * data begin. If the stream data begin with the first byte in the
265      * array, the <var>offset</var> is 0.
266      * @param length The length of the stream data.
267      * @throws NoPropertySetStreamException if the byte array is not a
268      * property set stream.
269      *
270      * @exception UnsupportedEncodingException if the codepage is not supported.
271      */

272     public PropertySet(final byte[] stream, final int offset, final int length)
273         throws NoPropertySetStreamException, UnsupportedEncodingException JavaDoc
274     {
275         if (isPropertySetStream(stream, offset, length))
276             init(stream, offset, length);
277         else
278             throw new NoPropertySetStreamException();
279     }
280
281
282
283     /**
284      * <p>Creates a {@link PropertySet} instance from a byte array
285      * that represents a stream in the Horrible Property Set
286      * Format.</p>
287      *
288      * @param stream The byte array holding the stream data. The
289      * complete byte array contents is the stream data.
290      * @throws NoPropertySetStreamException if the byte array is not a
291      * property set stream.
292      *
293      * @exception UnsupportedEncodingException if the codepage is not supported.
294      */

295     public PropertySet(final byte[] stream)
296     throws NoPropertySetStreamException, UnsupportedEncodingException JavaDoc
297     {
298         this(stream, 0, stream.length);
299     }
300
301
302
303     /**
304      * <p>Checks whether an {@link InputStream} is in the Horrible
305      * Property Set Format.</p>
306      *
307      * @param stream The {@link InputStream} to check. In order to
308      * perform the check, the method reads the first bytes from the
309      * stream. After reading, the stream is reset to the position it
310      * had before reading. The {@link InputStream} must support the
311      * {@link InputStream#mark} method.
312      * @return <code>true</code> if the stream is a property set
313      * stream, else <code>false</code>.
314      * @throws MarkUnsupportedException if the {@link InputStream}
315      * does not support the {@link InputStream#mark} method.
316      * @exception IOException if an I/O error occurs
317      */

318     public static boolean isPropertySetStream(final InputStream JavaDoc stream)
319         throws MarkUnsupportedException, IOException JavaDoc
320     {
321         /*
322          * Read at most this many bytes.
323          */

324         final int BUFFER_SIZE = 50;
325
326         /*
327          * Mark the current position in the stream so that we can
328          * reset to this position if the stream does not contain a
329          * property set.
330          */

331         if (!stream.markSupported())
332             throw new MarkUnsupportedException(stream.getClass().getName());
333         stream.mark(BUFFER_SIZE);
334
335         /*
336          * Read a couple of bytes from the stream.
337          */

338         final byte[] buffer = new byte[BUFFER_SIZE];
339         final int bytes =
340             stream.read(buffer, 0,
341                         Math.min(buffer.length, stream.available()));
342         final boolean isPropertySetStream =
343             isPropertySetStream(buffer, 0, bytes);
344         stream.reset();
345         return isPropertySetStream;
346     }
347
348
349
350     /**
351      * <p>Checks whether a byte array is in the Horrible Property Set
352      * Format.</p>
353      *
354      * @param src The byte array to check.
355      * @param offset The offset in the byte array.
356      * @param length The significant number of bytes in the byte
357      * array. Only this number of bytes will be checked.
358      * @return <code>true</code> if the byte array is a property set
359      * stream, <code>false</code> if not.
360      */

361     public static boolean isPropertySetStream(final byte[] src,
362                                               final int offset,
363                                               final int length)
364     {
365         /* FIXME (3): Ensure that at most "length" bytes are read. */
366
367         /*
368          * Read the header fields of the stream. They must always be
369          * there.
370          */

371         int o = offset;
372         final int byteOrder = LittleEndian.getUShort(src, o);
373         o += LittleEndian.SHORT_SIZE;
374         byte[] temp = new byte[LittleEndian.SHORT_SIZE];
375         LittleEndian.putShort(temp, (short) byteOrder);
376         if (!Util.equal(temp, BYTE_ORDER_ASSERTION))
377             return false;
378         final int format = LittleEndian.getUShort(src, o);
379         o += LittleEndian.SHORT_SIZE;
380         temp = new byte[LittleEndian.SHORT_SIZE];
381         LittleEndian.putShort(temp, (short) format);
382         if (!Util.equal(temp, FORMAT_ASSERTION))
383             return false;
384         // final long osVersion = LittleEndian.getUInt(src, offset);
385
o += LittleEndian.INT_SIZE;
386         // final ClassID classID = new ClassID(src, offset);
387
o += ClassID.LENGTH;
388         final long sectionCount = LittleEndian.getUInt(src, o);
389         o += LittleEndian.INT_SIZE;
390         if (sectionCount < 1)
391             return false;
392         return true;
393     }
394
395
396
397     /**
398      * <p>Initializes this {@link PropertySet} instance from a byte
399      * array. The method assumes that it has been checked already that
400      * the byte array indeed represents a property set stream. It does
401      * no more checks on its own.</p>
402      *
403      * @param src Byte array containing the property set stream
404      * @param offset The property set stream starts at this offset
405      * from the beginning of <var>src</src>
406      * @param length Length of the property set stream.
407      */

408     private void init(final byte[] src, final int offset, final int length)
409     throws UnsupportedEncodingException JavaDoc
410     {
411         /* FIXME (3): Ensure that at most "length" bytes are read. */
412         
413         /*
414          * Read the stream's header fields.
415          */

416         int o = offset;
417         byteOrder = LittleEndian.getUShort(src, o);
418         o += LittleEndian.SHORT_SIZE;
419         format = LittleEndian.getUShort(src, o);
420         o += LittleEndian.SHORT_SIZE;
421         osVersion = (int) LittleEndian.getUInt(src, o);
422         o += LittleEndian.INT_SIZE;
423         classID = new ClassID(src, o);
424         o += ClassID.LENGTH;
425         final int sectionCount = LittleEndian.getInt(src, o);
426         o += LittleEndian.INT_SIZE;
427         if (sectionCount <= 0)
428             throw new HPSFRuntimeException("Section count " + sectionCount +
429                                            " must be greater than 0.");
430
431         /*
432          * Read the sections, which are following the header. They
433          * start with an array of section descriptions. Each one
434          * consists of a format ID telling what the section contains
435          * and an offset telling how many bytes from the start of the
436          * stream the section begins.
437          */

438         /*
439          * Most property sets have only one section. The Document
440          * Summary Information stream has 2. Everything else is a rare
441          * exception and is no longer fostered by Microsoft.
442          */

443         sections = new ArrayList JavaDoc(sectionCount);
444
445         /*
446          * Loop over the section descriptor array. Each descriptor
447          * consists of a ClassID and a DWord, and we have to increment
448          * "offset" accordingly.
449          */

450         for (int i = 0; i < sectionCount; i++)
451         {
452             final Section s = new Section(src, o);
453             o += ClassID.LENGTH + LittleEndian.INT_SIZE;
454             sections.add(s);
455         }
456     }
457
458
459
460     /**
461      * <p>Checks whether this {@link PropertySet} represents a Summary
462      * Information.</p>
463      *
464      * @return <code>true</code> if this {@link PropertySet}
465      * represents a Summary Information, else <code>false</code>.
466      */

467     public boolean isSummaryInformation()
468     {
469         return Util.equal(((Section) sections.get(0)).getFormatID().getBytes(),
470                           SectionIDMap.SUMMARY_INFORMATION_ID);
471     }
472
473
474
475     /**
476      * <p>Checks whether this {@link PropertySet} is a Document
477      * Summary Information.</p>
478      *
479      * @return <code>true</code> if this {@link PropertySet}
480      * represents a Document Summary Information, else <code>false</code>.
481      */

482     public boolean isDocumentSummaryInformation()
483     {
484         return Util.equal(((Section) sections.get(0)).getFormatID().getBytes(),
485                           SectionIDMap.DOCUMENT_SUMMARY_INFORMATION_ID);
486     }
487
488
489
490     /**
491      * <p>Convenience method returning the {@link Property} array
492      * contained in this property set. It is a shortcut for getting
493      * the {@link PropertySet}'s {@link Section}s list and then
494      * getting the {@link Property} array from the first {@link
495      * Section}. However, it can only be used if the {@link
496      * PropertySet} contains exactly one {@link Section}, so check
497      * {@link #getSectionCount} first!</p>
498      *
499      * @return The properties of the only {@link Section} of this
500      * {@link PropertySet}.
501      * @throws NoSingleSectionException if the {@link PropertySet} has
502      * more or less than one {@link Section}.
503      */

504     public Property[] getProperties()
505         throws NoSingleSectionException
506     {
507         return getSingleSection().getProperties();
508     }
509
510
511
512     /**
513      * <p>Convenience method returning the value of the property with
514      * the specified ID. If the property is not available,
515      * <code>null</code> is returned and a subsequent call to {@link
516      * #wasNull} will return <code>true</code> .</p>
517      *
518      * @param id The property ID
519      * @return The property value
520      * @throws NoSingleSectionException if the {@link PropertySet} has
521      * more or less than one {@link Section}.
522      */

523     protected Object JavaDoc getProperty(final int id) throws NoSingleSectionException
524     {
525         return getSingleSection().getProperty(id);
526     }
527
528
529
530     /**
531      * <p>Convenience method returning the value of a boolean property
532      * with the specified ID. If the property is not available,
533      * <code>false</code> is returned. A subsequent call to {@link
534      * #wasNull} will return <code>true</code> to let the caller
535      * distinguish that case from a real property value of
536      * <code>false</code>.</p>
537      *
538      * @param id The property ID
539      * @return The property value
540      * @throws NoSingleSectionException if the {@link PropertySet} has
541      * more or less than one {@link Section}.
542      */

543     protected boolean getPropertyBooleanValue(final int id)
544         throws NoSingleSectionException
545     {
546         return getSingleSection().getPropertyBooleanValue(id);
547     }
548
549
550
551     /**
552      * <p>Convenience method returning the value of the numeric
553      * property with the specified ID. If the property is not
554      * available, 0 is returned. A subsequent call to {@link #wasNull}
555      * will return <code>true</code> to let the caller distinguish
556      * that case from a real property value of 0.</p>
557      *
558      * @param id The property ID
559      * @return The propertyIntValue value
560      * @throws NoSingleSectionException if the {@link PropertySet} has
561      * more or less than one {@link Section}.
562      */

563     protected int getPropertyIntValue(final int id)
564         throws NoSingleSectionException
565     {
566         return getSingleSection().getPropertyIntValue(id);
567     }
568
569
570
571     /**
572      * <p>Checks whether the property which the last call to {@link
573      * #getPropertyIntValue} or {@link #getProperty} tried to access
574      * was available or not. This information might be important for
575      * callers of {@link #getPropertyIntValue} since the latter
576      * returns 0 if the property does not exist. Using {@link
577      * #wasNull}, the caller can distiguish this case from a
578      * property's real value of 0.</p>
579      *
580      * @return <code>true</code> if the last call to {@link
581      * #getPropertyIntValue} or {@link #getProperty} tried to access a
582      * property that was not available, else <code>false</code>.
583      * @throws NoSingleSectionException if the {@link PropertySet} has
584      * more than one {@link Section}.
585      */

586     public boolean wasNull() throws NoSingleSectionException
587     {
588         return getSingleSection().wasNull();
589     }
590
591
592
593     /**
594      * <p>If the {@link PropertySet} has only a single section this
595      * method returns it.</p>
596      *
597      * @return The singleSection value
598      */

599     public Section getSingleSection()
600     {
601         final int sectionCount = getSectionCount();
602         if (sectionCount != 1)
603             throw new NoSingleSectionException
604                 ("Property set contains " + sectionCount + " sections.");
605         return ((Section) sections.get(0));
606     }
607
608
609
610     /**
611      * <p>Returns <code>true</code> if the <code>PropertySet</code> is equal
612      * to the specified parameter, else <code>false</code>.</p>
613      *
614      * @param o the object to compare this <code>PropertySet</code> with
615      *
616      * @return <code>true</code> if the objects are equal, <code>false</code>
617      * if not
618      */

619     public boolean equals(final Object JavaDoc o)
620     {
621         if (o == null || !(o instanceof PropertySet))
622             return false;
623         final PropertySet ps = (PropertySet) o;
624         int byteOrder1 = ps.getByteOrder();
625         int byteOrder2 = getByteOrder();
626         ClassID classID1 = ps.getClassID();
627         ClassID classID2 = getClassID();
628         int format1 = ps.getFormat();
629         int format2 = getFormat();
630         int osVersion1 = ps.getOSVersion();
631         int osVersion2 = getOSVersion();
632         int sectionCount1 = ps.getSectionCount();
633         int sectionCount2 = getSectionCount();
634         if (byteOrder1 != byteOrder2 ||
635             !classID1.equals(classID2) ||
636             format1 != format2 ||
637             osVersion1 != osVersion2 ||
638             sectionCount1 != sectionCount2)
639             return false;
640
641         /* Compare the sections: */
642         return Util.equals(getSections(), ps.getSections());
643     }
644
645
646
647     /**
648      * @see Object#hashCode()
649      */

650     public int hashCode()
651     {
652         throw new UnsupportedOperationException JavaDoc("FIXME: Not yet implemented.");
653     }
654
655
656
657     /**
658      * @see Object#toString()
659      */

660     public String JavaDoc toString()
661     {
662         final StringBuffer JavaDoc b = new StringBuffer JavaDoc();
663         final int sectionCount = getSectionCount();
664         b.append(getClass().getName());
665         b.append('[');
666         b.append("byteOrder: ");
667         b.append(getByteOrder());
668         b.append(", classID: ");
669         b.append(getClassID());
670         b.append(", format: ");
671         b.append(getFormat());
672         b.append(", OSVersion: ");
673         b.append(getOSVersion());
674         b.append(", sectionCount: ");
675         b.append(sectionCount);
676         b.append(", sections: [\n");
677         final List JavaDoc sections = getSections();
678         for (int i = 0; i < sectionCount; i++)
679             b.append(((Section) sections.get(i)).toString());
680         b.append(']');
681         b.append(']');
682         return b.toString();
683     }
684 }
685
Popular Tags