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.HashMap;
022: import java.util.Map;
023:
024: import org.apache.poi.util.HexDump;
025: import org.apache.poi.util.LittleEndian;
026:
027: /**
028: * <p>A property in a {@link Section} of a {@link PropertySet}.</p>
029: *
030: * <p>The property's <strong>ID</strong> gives the property a meaning
031: * in the context of its {@link Section}. Each {@link Section} spans
032: * its own name space of property IDs.</p>
033: *
034: * <p>The property's <strong>type</strong> determines how its
035: * <strong>value </strong> is interpreted. For example, if the type is
036: * {@link Variant#VT_LPSTR} (byte string), the value consists of a
037: * DWord telling how many bytes the string contains. The bytes follow
038: * immediately, including any null bytes that terminate the
039: * string. The type {@link Variant#VT_I4} denotes a four-byte integer
040: * value, {@link Variant#VT_FILETIME} some date and time (of a
041: * file).</p>
042: *
043: * <p>Please note that not all {@link Variant} types yet. This might change
044: * over time but largely depends on your feedback so that the POI team knows
045: * which variant types are really needed. So please feel free to submit error
046: * reports or patches for the types you need.</p>
047: *
048: * <p>Microsoft documentation: <a
049: * href="http://msdn.microsoft.com/library/en-us/stg/stg/property_set_display_name_dictionary.asp?frame=true">
050: * Property Set Display Name Dictionary</a>.
051: *
052: * @author Rainer Klute <a
053: * href="mailto:klute@rainer-klute.de"><klute@rainer-klute.de></a>
054: * @author Drew Varner (Drew.Varner InAndAround sc.edu)
055: * @see Section
056: * @see Variant
057: * @version $Id: Property.java 489730 2006-12-22 19:18:16Z bayard $
058: * @since 2002-02-09
059: */
060: public class Property {
061:
062: /** <p>The property's ID.</p> */
063: protected long id;
064:
065: /**
066: * <p>Returns the property's ID.</p>
067: *
068: * @return The ID value
069: */
070: public long getID() {
071: return id;
072: }
073:
074: /** <p>The property's type.</p> */
075: protected long type;
076:
077: /**
078: * <p>Returns the property's type.</p>
079: *
080: * @return The type value
081: */
082: public long getType() {
083: return type;
084: }
085:
086: /** <p>The property's value.</p> */
087: protected Object value;
088:
089: /**
090: * <p>Returns the property's value.</p>
091: *
092: * @return The property's value
093: */
094: public Object getValue() {
095: return value;
096: }
097:
098: /**
099: * <p>Creates a property.</p>
100: *
101: * @param id the property's ID.
102: * @param type the property's type, see {@link Variant}.
103: * @param value the property's value. Only certain types are allowed, see {@link Variant}.
104: */
105: public Property(final long id, final long type, final Object value) {
106: this .id = id;
107: this .type = type;
108: this .value = value;
109: }
110:
111: /**
112: * <p>Creates a {@link Property} instance by reading its bytes
113: * from the property set stream.</p>
114: *
115: * @param id The property's ID.
116: * @param src The bytes the property set stream consists of.
117: * @param offset The property's type/value pair's offset in the
118: * section.
119: * @param length The property's type/value pair's length in bytes.
120: * @param codepage The section's and thus the property's
121: * codepage. It is needed only when reading string values.
122: * @exception UnsupportedEncodingException if the specified codepage is not
123: * supported.
124: */
125: public Property(final long id, final byte[] src, final long offset,
126: final int length, final int codepage)
127: throws UnsupportedEncodingException {
128: this .id = id;
129:
130: /*
131: * ID 0 is a special case since it specifies a dictionary of
132: * property IDs and property names.
133: */
134: if (id == 0) {
135: value = readDictionary(src, offset, length, codepage);
136: return;
137: }
138:
139: int o = (int) offset;
140: type = LittleEndian.getUInt(src, o);
141: o += LittleEndian.INT_SIZE;
142:
143: try {
144: value = VariantSupport.read(src, o, length, (int) type,
145: codepage);
146: } catch (UnsupportedVariantTypeException ex) {
147: VariantSupport.writeUnsupportedTypeMessage(ex);
148: value = ex.getValue();
149: }
150: }
151:
152: /**
153: * <p>Creates an empty property. It must be filled using the set method to
154: * be usable.</p>
155: */
156: protected Property() {
157: }
158:
159: /**
160: * <p>Reads a dictionary.</p>
161: *
162: * @param src The byte array containing the bytes making out the dictionary.
163: * @param offset At this offset within <var>src </var> the dictionary
164: * starts.
165: * @param length The dictionary contains at most this many bytes.
166: * @param codepage The codepage of the string values.
167: * @return The dictonary
168: * @throws UnsupportedEncodingException if the dictionary's codepage is not
169: * (yet) supported.
170: */
171: protected Map readDictionary(final byte[] src, final long offset,
172: final int length, final int codepage)
173: throws UnsupportedEncodingException {
174: /* Check whether "offset" points into the "src" array". */
175: if (offset < 0 || offset > src.length)
176: throw new HPSFRuntimeException("Illegal offset " + offset
177: + " while HPSF stream contains " + length
178: + " bytes.");
179: int o = (int) offset;
180:
181: /*
182: * Read the number of dictionary entries.
183: */
184: final long nrEntries = LittleEndian.getUInt(src, o);
185: o += LittleEndian.INT_SIZE;
186:
187: final Map m = new HashMap((int) nrEntries, (float) 1.0);
188: for (int i = 0; i < nrEntries; i++) {
189: /* The key. */
190: final Long id = new Long(LittleEndian.getUInt(src, o));
191: o += LittleEndian.INT_SIZE;
192:
193: /* The value (a string). The length is the either the
194: * number of (two-byte) characters if the character set is Unicode
195: * or the number of bytes if the character set is not Unicode.
196: * The length includes terminating 0x00 bytes which we have to strip
197: * off to create a Java string. */
198: long sLength = LittleEndian.getUInt(src, o);
199: o += LittleEndian.INT_SIZE;
200:
201: /* Read the string. */
202: final StringBuffer b = new StringBuffer();
203: switch (codepage) {
204: case -1: {
205: /* Without a codepage the length is equal to the number of
206: * bytes. */
207: b.append(new String(src, o, (int) sLength));
208: break;
209: }
210: case Constants.CP_UNICODE: {
211: /* The length is the number of characters, i.e. the number
212: * of bytes is twice the number of the characters. */
213: final int nrBytes = (int) (sLength * 2);
214: final byte[] h = new byte[nrBytes];
215: for (int i2 = 0; i2 < nrBytes; i2 += 2) {
216: h[i2] = src[o + i2 + 1];
217: h[i2 + 1] = src[o + i2];
218: }
219: b.append(new String(h, 0, nrBytes, VariantSupport
220: .codepageToEncoding(codepage)));
221: break;
222: }
223: default: {
224: /* For encodings other than Unicode the length is the number
225: * of bytes. */
226: b.append(new String(src, o, (int) sLength,
227: VariantSupport.codepageToEncoding(codepage)));
228: break;
229: }
230: }
231:
232: /* Strip 0x00 characters from the end of the string: */
233: while (b.length() > 0 && b.charAt(b.length() - 1) == 0x00)
234: b.setLength(b.length() - 1);
235: if (codepage == Constants.CP_UNICODE) {
236: if (sLength % 2 == 1)
237: sLength++;
238: o += (sLength + sLength);
239: } else
240: o += sLength;
241: m.put(id, b.toString());
242: }
243: return m;
244: }
245:
246: /**
247: * <p>Returns the property's size in bytes. This is always a multiple of
248: * 4.</p>
249: *
250: * @return the property's size in bytes
251: *
252: * @exception WritingNotSupportedException if HPSF does not yet support the
253: * property's variant type.
254: */
255: protected int getSize() throws WritingNotSupportedException {
256: int length = VariantSupport.getVariantLength(type);
257: if (length >= 0)
258: return length; /* Fixed length */
259: if (length == -2)
260: /* Unknown length */
261: throw new WritingNotSupportedException(type, null);
262:
263: /* Variable length: */
264: final int PADDING = 4; /* Pad to multiples of 4. */
265: switch ((int) type) {
266: case Variant.VT_LPSTR: {
267: int l = ((String) value).length() + 1;
268: int r = l % PADDING;
269: if (r > 0)
270: l += PADDING - r;
271: length += l;
272: break;
273: }
274: case Variant.VT_EMPTY:
275: break;
276: default:
277: throw new WritingNotSupportedException(type, value);
278: }
279: return length;
280: }
281:
282: /**
283: * <p>Compares two properties.</p>
284: *
285: * <p>Please beware that a property with ID == 0 is a special case: It does not have a type, and its value is the section's
286: * dictionary. Another special case are strings: Two properties may have
287: * the different types Variant.VT_LPSTR and Variant.VT_LPWSTR;</p>
288: *
289: * @see Object#equals(java.lang.Object)
290: */
291: public boolean equals(final Object o) {
292: if (!(o instanceof Property))
293: return false;
294: final Property p = (Property) o;
295: final Object pValue = p.getValue();
296: final long pId = p.getID();
297: if (id != pId || (id != 0 && !typesAreEqual(type, p.getType())))
298: return false;
299: if (value == null && pValue == null)
300: return true;
301: if (value == null || pValue == null)
302: return false;
303:
304: /* It's clear now that both values are non-null. */
305: final Class valueClass = value.getClass();
306: final Class pValueClass = pValue.getClass();
307: if (!(valueClass.isAssignableFrom(pValueClass))
308: && !(pValueClass.isAssignableFrom(valueClass)))
309: return false;
310:
311: if (value instanceof byte[])
312: return Util.equal((byte[]) value, (byte[]) pValue);
313:
314: return value.equals(pValue);
315: }
316:
317: private boolean typesAreEqual(final long t1, final long t2) {
318: if (t1 == t2
319: || (t1 == Variant.VT_LPSTR && t2 == Variant.VT_LPWSTR)
320: || (t2 == Variant.VT_LPSTR && t1 == Variant.VT_LPWSTR))
321: return true;
322: else
323: return false;
324: }
325:
326: /**
327: * @see Object#hashCode()
328: */
329: public int hashCode() {
330: long hashCode = 0;
331: hashCode += id;
332: hashCode += type;
333: if (value != null)
334: hashCode += value.hashCode();
335: final int returnHashCode = (int) (hashCode & 0x0ffffffffL);
336: return returnHashCode;
337:
338: }
339:
340: /**
341: * @see Object#toString()
342: */
343: public String toString() {
344: final StringBuffer b = new StringBuffer();
345: b.append(getClass().getName());
346: b.append('[');
347: b.append("id: ");
348: b.append(getID());
349: b.append(", type: ");
350: b.append(getType());
351: final Object value = getValue();
352: b.append(", value: ");
353: b.append(value.toString());
354: if (value instanceof String) {
355: final String s = (String) value;
356: final int l = s.length();
357: final byte[] bytes = new byte[l * 2];
358: for (int i = 0; i < l; i++) {
359: final char c = s.charAt(i);
360: final byte high = (byte) ((c & 0x00ff00) >> 8);
361: final byte low = (byte) ((c & 0x0000ff) >> 0);
362: bytes[i * 2] = high;
363: bytes[i * 2 + 1] = low;
364: }
365: final String hex = HexDump.dump(bytes, 0L, 0);
366: b.append(" [");
367: b.append(hex);
368: b.append("]");
369: }
370: b.append(']');
371: return b.toString();
372: }
373:
374: }
|