001: /* ====================================================================
002: Licensed to the Apache Software Foundation (ASF) under one or more
003: contributor license agreements. See the NOTICE file distributed with
004: this work for additional information regarding copyright ownership.
005: The ASF licenses this file to You under the Apache License, Version 2.0
006: (the "License"); you may not use this file except in compliance with
007: the License. You may obtain a copy of the License at
008:
009: http://www.apache.org/licenses/LICENSE-2.0
010:
011: Unless required by applicable law or agreed to in writing, software
012: distributed under the License is distributed on an "AS IS" BASIS,
013: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: See the License for the specific language governing permissions and
015: limitations under the License.
016: ==================================================================== */
017:
018: package org.apache.poi.hpsf;
019:
020: import java.io.UnsupportedEncodingException;
021: import java.util.ArrayList;
022: import java.util.Collections;
023: import java.util.Iterator;
024: import java.util.List;
025: import java.util.Map;
026:
027: import org.apache.poi.hpsf.wellknown.PropertyIDMap;
028: import org.apache.poi.hpsf.wellknown.SectionIDMap;
029: import org.apache.poi.util.LittleEndian;
030:
031: /**
032: * <p>Represents a section in a {@link PropertySet}.</p>
033: *
034: * @author Rainer Klute <a
035: * href="mailto:klute@rainer-klute.de"><klute@rainer-klute.de></a>
036: * @author Drew Varner (Drew.Varner allUpIn sc.edu)
037: * @version $Id: Section.java 550021 2007-06-23 07:44:47Z klute $
038: * @since 2002-02-09
039: */
040: public class Section {
041:
042: /**
043: * <p>Maps property IDs to section-private PID strings. These
044: * strings can be found in the property with ID 0.</p>
045: */
046: protected Map dictionary;
047:
048: /**
049: * <p>The section's format ID, {@link #getFormatID}.</p>
050: */
051: protected ClassID formatID;
052:
053: /**
054: * <p>Returns the format ID. The format ID is the "type" of the
055: * section. For example, if the format ID of the first {@link
056: * Section} contains the bytes specified by
057: * <code>org.apache.poi.hpsf.wellknown.SectionIDMap.SUMMARY_INFORMATION_ID</code>
058: * the section (and thus the property set) is a SummaryInformation.</p>
059: *
060: * @return The format ID
061: */
062: public ClassID getFormatID() {
063: return formatID;
064: }
065:
066: /**
067: * @see #getOffset
068: */
069: protected long offset;
070:
071: /**
072: * <p>Returns the offset of the section in the stream.</p>
073: *
074: * @return The offset of the section in the stream.
075: */
076: public long getOffset() {
077: return offset;
078: }
079:
080: /**
081: * @see #getSize
082: */
083: protected int size;
084:
085: /**
086: * <p>Returns the section's size in bytes.</p>
087: *
088: * @return The section's size in bytes.
089: */
090: public int getSize() {
091: return size;
092: }
093:
094: /**
095: * <p>Returns the number of properties in this section.</p>
096: *
097: * @return The number of properties in this section.
098: */
099: public int getPropertyCount() {
100: return properties.length;
101: }
102:
103: /**
104: * @see #getProperties
105: */
106: protected Property[] properties;
107:
108: /**
109: * <p>Returns this section's properties.</p>
110: *
111: * @return This section's properties.
112: */
113: public Property[] getProperties() {
114: return properties;
115: }
116:
117: /**
118: * <p>Creates an empty and uninitialized {@link Section}.
119: */
120: protected Section() {
121: }
122:
123: /**
124: * <p>Creates a {@link Section} instance from a byte array.</p>
125: *
126: * @param src Contains the complete property set stream.
127: * @param offset The position in the stream that points to the
128: * section's format ID.
129: *
130: * @exception UnsupportedEncodingException if the section's codepage is not
131: * supported.
132: */
133: public Section(final byte[] src, final int offset)
134: throws UnsupportedEncodingException {
135: int o1 = offset;
136:
137: /*
138: * Read the format ID.
139: */
140: formatID = new ClassID(src, o1);
141: o1 += ClassID.LENGTH;
142:
143: /*
144: * Read the offset from the stream's start and positions to
145: * the section header.
146: */
147: this .offset = LittleEndian.getUInt(src, o1);
148: o1 = (int) this .offset;
149:
150: /*
151: * Read the section length.
152: */
153: size = (int) LittleEndian.getUInt(src, o1);
154: o1 += LittleEndian.INT_SIZE;
155:
156: /*
157: * Read the number of properties.
158: */
159: final int propertyCount = (int) LittleEndian.getUInt(src, o1);
160: o1 += LittleEndian.INT_SIZE;
161:
162: /*
163: * Read the properties. The offset is positioned at the first
164: * entry of the property list. There are two problems:
165: *
166: * 1. For each property we have to find out its length. In the
167: * property list we find each property's ID and its offset relative
168: * to the section's beginning. Unfortunately the properties in the
169: * property list need not to be in ascending order, so it is not
170: * possible to calculate the length as
171: * (offset of property(i+1) - offset of property(i)). Before we can
172: * that we first have to sort the property list by ascending offsets.
173: *
174: * 2. We have to read the property with ID 1 before we read other
175: * properties, at least before other properties containing strings.
176: * The reason is that property 1 specifies the codepage. If it is
177: * 1200, all strings are in Unicode. In other words: Before we can
178: * read any strings we have to know whether they are in Unicode or
179: * not. Unfortunately property 1 is not guaranteed to be the first in
180: * a section.
181: *
182: * The algorithm below reads the properties in two passes: The first
183: * one looks for property ID 1 and extracts the codepage number. The
184: * seconds pass reads the other properties.
185: */
186: properties = new Property[propertyCount];
187:
188: /* Pass 1: Read the property list. */
189: int pass1Offset = o1;
190: List propertyList = new ArrayList(propertyCount);
191: PropertyListEntry ple;
192: for (int i = 0; i < properties.length; i++) {
193: ple = new PropertyListEntry();
194:
195: /* Read the property ID. */
196: ple.id = (int) LittleEndian.getUInt(src, pass1Offset);
197: pass1Offset += LittleEndian.INT_SIZE;
198:
199: /* Offset from the section's start. */
200: ple.offset = (int) LittleEndian.getUInt(src, pass1Offset);
201: pass1Offset += LittleEndian.INT_SIZE;
202:
203: /* Add the entry to the property list. */
204: propertyList.add(ple);
205: }
206:
207: /* Sort the property list by ascending offsets: */
208: Collections.sort(propertyList);
209:
210: /* Calculate the properties' lengths. */
211: for (int i = 0; i < propertyCount - 1; i++) {
212: final PropertyListEntry ple1 = (PropertyListEntry) propertyList
213: .get(i);
214: final PropertyListEntry ple2 = (PropertyListEntry) propertyList
215: .get(i + 1);
216: ple1.length = ple2.offset - ple1.offset;
217: }
218: if (propertyCount > 0) {
219: ple = (PropertyListEntry) propertyList
220: .get(propertyCount - 1);
221: ple.length = size - ple.offset;
222: if (ple.length <= 0) {
223: final StringBuffer b = new StringBuffer();
224: b.append("The property set claims to have a size of ");
225: b.append(size);
226: b.append(" bytes. However, it exceeds ");
227: b.append(ple.offset);
228: b.append(" bytes.");
229: throw new IllegalPropertySetDataException(b.toString());
230: }
231: }
232:
233: /* Look for the codepage. */
234: int codepage = -1;
235: for (final Iterator i = propertyList.iterator(); codepage == -1
236: && i.hasNext();) {
237: ple = (PropertyListEntry) i.next();
238:
239: /* Read the codepage if the property ID is 1. */
240: if (ple.id == PropertyIDMap.PID_CODEPAGE) {
241: /* Read the property's value type. It must be
242: * VT_I2. */
243: int o = (int) (this .offset + ple.offset);
244: final long type = LittleEndian.getUInt(src, o);
245: o += LittleEndian.INT_SIZE;
246:
247: if (type != Variant.VT_I2)
248: throw new HPSFRuntimeException(
249: "Value type of property ID 1 is not VT_I2 but "
250: + type + ".");
251:
252: /* Read the codepage number. */
253: codepage = LittleEndian.getUShort(src, o);
254: }
255: }
256:
257: /* Pass 2: Read all properties - including the codepage property,
258: * if available. */
259: int i1 = 0;
260: for (final Iterator i = propertyList.iterator(); i.hasNext();) {
261: ple = (PropertyListEntry) i.next();
262: Property p = new Property(ple.id, src, this .offset
263: + ple.offset, ple.length, codepage);
264: if (p.getID() == PropertyIDMap.PID_CODEPAGE)
265: p = new Property(p.getID(), p.getType(), new Integer(
266: codepage));
267: properties[i1++] = p;
268: }
269:
270: /*
271: * Extract the dictionary (if available).
272: */
273: dictionary = (Map) getProperty(0);
274: }
275:
276: /**
277: * <p>Represents an entry in the property list and holds a property's ID and
278: * its offset from the section's beginning.</p>
279: */
280: class PropertyListEntry implements Comparable {
281: int id;
282: int offset;
283: int length;
284:
285: /**
286: * <p>Compares this {@link PropertyListEntry} with another one by their
287: * offsets. A {@link PropertyListEntry} is "smaller" than another one if
288: * its offset from the section's begin is smaller.</p>
289: *
290: * @see Comparable#compareTo(java.lang.Object)
291: */
292: public int compareTo(final Object o) {
293: if (!(o instanceof PropertyListEntry))
294: throw new ClassCastException(o.toString());
295: final int otherOffset = ((PropertyListEntry) o).offset;
296: if (offset < otherOffset)
297: return -1;
298: else if (offset == otherOffset)
299: return 0;
300: else
301: return 1;
302: }
303:
304: public String toString() {
305: final StringBuffer b = new StringBuffer();
306: b.append(getClass().getName());
307: b.append("[id=");
308: b.append(id);
309: b.append(", offset=");
310: b.append(offset);
311: b.append(", length=");
312: b.append(length);
313: b.append(']');
314: return b.toString();
315: }
316: }
317:
318: /**
319: * <p>Returns the value of the property with the specified ID. If
320: * the property is not available, <code>null</code> is returned
321: * and a subsequent call to {@link #wasNull} will return
322: * <code>true</code>.</p>
323: *
324: * @param id The property's ID
325: *
326: * @return The property's value
327: */
328: public Object getProperty(final long id) {
329: wasNull = false;
330: for (int i = 0; i < properties.length; i++)
331: if (id == properties[i].getID())
332: return properties[i].getValue();
333: wasNull = true;
334: return null;
335: }
336:
337: /**
338: * <p>Returns the value of the numeric property with the specified
339: * ID. If the property is not available, 0 is returned. A
340: * subsequent call to {@link #wasNull} will return
341: * <code>true</code> to let the caller distinguish that case from
342: * a real property value of 0.</p>
343: *
344: * @param id The property's ID
345: *
346: * @return The property's value
347: */
348: protected int getPropertyIntValue(final long id) {
349: final Number i;
350: final Object o = getProperty(id);
351: if (o == null)
352: return 0;
353: if (!(o instanceof Long || o instanceof Integer))
354: throw new HPSFRuntimeException(
355: "This property is not an integer type, but "
356: + o.getClass().getName() + ".");
357: i = (Number) o;
358: return i.intValue();
359: }
360:
361: /**
362: * <p>Returns the value of the boolean property with the specified
363: * ID. If the property is not available, <code>false</code> is
364: * returned. A subsequent call to {@link #wasNull} will return
365: * <code>true</code> to let the caller distinguish that case from
366: * a real property value of <code>false</code>.</p>
367: *
368: * @param id The property's ID
369: *
370: * @return The property's value
371: */
372: protected boolean getPropertyBooleanValue(final int id) {
373: final Boolean b = (Boolean) getProperty(id);
374: if (b != null)
375: return b.booleanValue();
376: else
377: return false;
378: }
379:
380: /**
381: * <p>This member is <code>true</code> if the last call to {@link
382: * #getPropertyIntValue} or {@link #getProperty} tried to access a
383: * property that was not available, else <code>false</code>.</p>
384: */
385: private boolean wasNull;
386:
387: /**
388: * <p>Checks whether the property which the last call to {@link
389: * #getPropertyIntValue} or {@link #getProperty} tried to access
390: * was available or not. This information might be important for
391: * callers of {@link #getPropertyIntValue} since the latter
392: * returns 0 if the property does not exist. Using {@link
393: * #wasNull} the caller can distiguish this case from a property's
394: * real value of 0.</p>
395: *
396: * @return <code>true</code> if the last call to {@link
397: * #getPropertyIntValue} or {@link #getProperty} tried to access a
398: * property that was not available, else <code>false</code>.
399: */
400: public boolean wasNull() {
401: return wasNull;
402: }
403:
404: /**
405: * <p>Returns the PID string associated with a property ID. The ID
406: * is first looked up in the {@link Section}'s private
407: * dictionary. If it is not found there, the method calls {@link
408: * SectionIDMap#getPIDString}.</p>
409: *
410: * @param pid The property ID
411: *
412: * @return The property ID's string value
413: */
414: public String getPIDString(final long pid) {
415: String s = null;
416: if (dictionary != null)
417: s = (String) dictionary.get(new Long(pid));
418: if (s == null)
419: s = SectionIDMap
420: .getPIDString(getFormatID().getBytes(), pid);
421: if (s == null)
422: s = SectionIDMap.UNDEFINED;
423: return s;
424: }
425:
426: /**
427: * <p>Checks whether this section is equal to another object. The result is
428: * <code>false</code> if one of the the following conditions holds:</p>
429: *
430: * <ul>
431: *
432: * <li><p>The other object is not a {@link Section}.</p></li>
433: *
434: * <li><p>The format IDs of the two sections are not equal.</p></li>
435: *
436: * <li><p>The sections have a different number of properties. However,
437: * properties with ID 1 (codepage) are not counted.</p></li>
438: *
439: * <li><p>The other object is not a {@link Section}.</p></li>
440: *
441: * <li><p>The properties have different values. The order of the properties
442: * is irrelevant.</p></li>
443: *
444: * </ul>
445: *
446: * @param o The object to compare this section with
447: * @return <code>true</code> if the objects are equal, <code>false</code> if
448: * not
449: */
450: public boolean equals(final Object o) {
451: if (o == null || !(o instanceof Section))
452: return false;
453: final Section s = (Section) o;
454: if (!s.getFormatID().equals(getFormatID()))
455: return false;
456:
457: /* Compare all properties except 0 and 1 as they must be handled
458: * specially. */
459: Property[] pa1 = new Property[getProperties().length];
460: Property[] pa2 = new Property[s.getProperties().length];
461: System.arraycopy(getProperties(), 0, pa1, 0, pa1.length);
462: System.arraycopy(s.getProperties(), 0, pa2, 0, pa2.length);
463:
464: /* Extract properties 0 and 1 and remove them from the copy of the
465: * arrays. */
466: Property p10 = null;
467: Property p20 = null;
468: for (int i = 0; i < pa1.length; i++) {
469: final long id = pa1[i].getID();
470: if (id == 0) {
471: p10 = pa1[i];
472: pa1 = remove(pa1, i);
473: i--;
474: }
475: if (id == 1) {
476: // p11 = pa1[i];
477: pa1 = remove(pa1, i);
478: i--;
479: }
480: }
481: for (int i = 0; i < pa2.length; i++) {
482: final long id = pa2[i].getID();
483: if (id == 0) {
484: p20 = pa2[i];
485: pa2 = remove(pa2, i);
486: i--;
487: }
488: if (id == 1) {
489: // p21 = pa2[i];
490: pa2 = remove(pa2, i);
491: i--;
492: }
493: }
494:
495: /* If the number of properties (not counting property 1) is unequal the
496: * sections are unequal. */
497: if (pa1.length != pa2.length)
498: return false;
499:
500: /* If the dictionaries are unequal the sections are unequal. */
501: boolean dictionaryEqual = true;
502: if (p10 != null && p20 != null)
503: dictionaryEqual = p10.getValue().equals(p20.getValue());
504: else if (p10 != null || p20 != null)
505: dictionaryEqual = false;
506: if (!dictionaryEqual)
507: return false;
508: else
509: return Util.equals(pa1, pa2);
510: }
511:
512: /**
513: * <p>Removes a field from a property array. The resulting array is
514: * compactified and returned.</p>
515: *
516: * @param pa The property array.
517: * @param i The index of the field to be removed.
518: * @return the compactified array.
519: */
520: private Property[] remove(final Property[] pa, final int i) {
521: final Property[] h = new Property[pa.length - 1];
522: if (i > 0)
523: System.arraycopy(pa, 0, h, 0, i);
524: System.arraycopy(pa, i + 1, h, i, h.length - i);
525: return h;
526: }
527:
528: /**
529: * @see Object#hashCode()
530: */
531: public int hashCode() {
532: long hashCode = 0;
533: hashCode += getFormatID().hashCode();
534: final Property[] pa = getProperties();
535: for (int i = 0; i < pa.length; i++)
536: hashCode += pa[i].hashCode();
537: final int returnHashCode = (int) (hashCode & 0x0ffffffffL);
538: return returnHashCode;
539: }
540:
541: /**
542: * @see Object#toString()
543: */
544: public String toString() {
545: final StringBuffer b = new StringBuffer();
546: final Property[] pa = getProperties();
547: b.append(getClass().getName());
548: b.append('[');
549: b.append("formatID: ");
550: b.append(getFormatID());
551: b.append(", offset: ");
552: b.append(getOffset());
553: b.append(", propertyCount: ");
554: b.append(getPropertyCount());
555: b.append(", size: ");
556: b.append(getSize());
557: b.append(", properties: [\n");
558: for (int i = 0; i < pa.length; i++) {
559: b.append(pa[i].toString());
560: b.append(",\n");
561: }
562: b.append(']');
563: b.append(']');
564: return b.toString();
565: }
566:
567: /**
568: * <p>Gets the section's dictionary. A dictionary allows an application to
569: * use human-readable property names instead of numeric property IDs. It
570: * contains mappings from property IDs to their associated string
571: * values. The dictionary is stored as the property with ID 0. The codepage
572: * for the strings in the dictionary is defined by property with ID 1.</p>
573: *
574: * @return the dictionary or <code>null</code> if the section does not have
575: * a dictionary.
576: */
577: public Map getDictionary() {
578: return dictionary;
579: }
580:
581: /**
582: * <p>Gets the section's codepage, if any.</p>
583: *
584: * @return The section's codepage if one is defined, else -1.
585: */
586: public int getCodepage() {
587: final Integer codepage = (Integer) getProperty(PropertyIDMap.PID_CODEPAGE);
588: if (codepage == null)
589: return -1;
590: int cp = codepage.intValue();
591: return cp;
592: }
593:
594: }
|