123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439 |
- /* ====================================================================
- Licensed to the Apache Software Foundation (ASF) under one or more
- contributor license agreements. See the NOTICE file distributed with
- this work for additional information regarding copyright ownership.
- The ASF licenses this file to You under the Apache License, Version 2.0
- (the "License"); you may not use this file except in compliance with
- the License. You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- ==================================================================== */
-
- package org.apache.poi.hpsf;
-
- import java.io.UnsupportedEncodingException;
- import java.util.HashMap;
- import java.util.Map;
-
- import org.apache.poi.util.HexDump;
- import org.apache.poi.util.LittleEndian;
- import org.apache.poi.util.POILogFactory;
- import org.apache.poi.util.POILogger;
-
- /**
- * <p>A property in a {@link Section} of a {@link PropertySet}.</p>
- *
- * <p>The property's <strong>ID</strong> gives the property a meaning
- * in the context of its {@link Section}. Each {@link Section} spans
- * its own name space of property IDs.</p>
- *
- * <p>The property's <strong>type</strong> determines how its
- * <strong>value </strong> is interpreted. For example, if the type is
- * {@link Variant#VT_LPSTR} (byte string), the value consists of a
- * DWord telling how many bytes the string contains. The bytes follow
- * immediately, including any null bytes that terminate the
- * string. The type {@link Variant#VT_I4} denotes a four-byte integer
- * value, {@link Variant#VT_FILETIME} some date and time (of a
- * file).</p>
- *
- * <p>Please note that not all {@link Variant} types yet. This might change
- * over time but largely depends on your feedback so that the POI team knows
- * which variant types are really needed. So please feel free to submit error
- * reports or patches for the types you need.</p>
- *
- * <p>Microsoft documentation: <a
- * href="http://msdn.microsoft.com/library/en-us/stg/stg/property_set_display_name_dictionary.asp?frame=true">
- * Property Set Display Name Dictionary</a>.
- *
- * @author Rainer Klute <a
- * href="mailto:klute@rainer-klute.de"><klute@rainer-klute.de></a>
- * @author Drew Varner (Drew.Varner InAndAround sc.edu)
- * @see Section
- * @see Variant
- */
- public class Property
- {
-
- /** <p>The property's ID.</p> */
- protected long id;
-
-
- /**
- * <p>Returns the property's ID.</p>
- *
- * @return The ID value
- */
- public long getID()
- {
- return id;
- }
-
-
-
- /** <p>The property's type.</p> */
- protected long type;
-
-
- /**
- * <p>Returns the property's type.</p>
- *
- * @return The type value
- */
- public long getType()
- {
- return type;
- }
-
-
-
- /** <p>The property's value.</p> */
- protected Object value;
-
-
- /**
- * <p>Returns the property's value.</p>
- *
- * @return The property's value
- */
- public Object getValue()
- {
- return value;
- }
-
-
-
- /**
- * <p>Creates a property.</p>
- *
- * @param id the property's ID.
- * @param type the property's type, see {@link Variant}.
- * @param value the property's value. Only certain types are allowed, see
- * {@link Variant}.
- */
- public Property(final long id, final long type, final Object value)
- {
- this.id = id;
- this.type = type;
- this.value = value;
- }
-
-
-
- /**
- * <p>Creates a {@link Property} instance by reading its bytes
- * from the property set stream.</p>
- *
- * @param id The property's ID.
- * @param src The bytes the property set stream consists of.
- * @param offset The property's type/value pair's offset in the
- * section.
- * @param length The property's type/value pair's length in bytes.
- * @param codepage The section's and thus the property's
- * codepage. It is needed only when reading string values.
- * @exception UnsupportedEncodingException if the specified codepage is not
- * supported.
- */
- public Property(final long id, final byte[] src, final long offset,
- final int length, final int codepage)
- throws UnsupportedEncodingException
- {
- this.id = id;
-
- /*
- * ID 0 is a special case since it specifies a dictionary of
- * property IDs and property names.
- */
- if (id == 0)
- {
- value = readDictionary(src, offset, length, codepage);
- return;
- }
-
- int o = (int) offset;
- type = LittleEndian.getUInt(src, o);
- o += LittleEndian.INT_SIZE;
-
- try
- {
- value = VariantSupport.read(src, o, length, (int) type, codepage);
- }
- catch (UnsupportedVariantTypeException ex)
- {
- VariantSupport.writeUnsupportedTypeMessage(ex);
- value = ex.getValue();
- }
- }
-
-
-
- /**
- * <p>Creates an empty property. It must be filled using the set method to
- * be usable.</p>
- */
- protected Property()
- { }
-
-
-
- /**
- * <p>Reads a dictionary.</p>
- *
- * @param src The byte array containing the bytes making out the dictionary.
- * @param offset At this offset within <var>src </var> the dictionary
- * starts.
- * @param length The dictionary contains at most this many bytes.
- * @param codepage The codepage of the string values.
- * @return The dictonary
- * @throws UnsupportedEncodingException if the dictionary's codepage is not
- * (yet) supported.
- */
- protected Map readDictionary(final byte[] src, final long offset,
- final int length, final int codepage)
- throws UnsupportedEncodingException
- {
- /* Check whether "offset" points into the "src" array". */
- if (offset < 0 || offset > src.length)
- throw new HPSFRuntimeException
- ("Illegal offset " + offset + " while HPSF stream contains " +
- length + " bytes.");
- int o = (int) offset;
-
- /*
- * Read the number of dictionary entries.
- */
- final long nrEntries = LittleEndian.getUInt(src, o);
- o += LittleEndian.INT_SIZE;
-
- final Map m = new HashMap((int) nrEntries, (float) 1.0);
-
- try
- {
- for (int i = 0; i < nrEntries; i++)
- {
- /* The key. */
- final Long id = Long.valueOf(LittleEndian.getUInt(src, o));
- o += LittleEndian.INT_SIZE;
-
- /* The value (a string). The length is the either the
- * number of (two-byte) characters if the character set is Unicode
- * or the number of bytes if the character set is not Unicode.
- * The length includes terminating 0x00 bytes which we have to strip
- * off to create a Java string. */
- long sLength = LittleEndian.getUInt(src, o);
- o += LittleEndian.INT_SIZE;
-
- /* Read the string. */
- final StringBuffer b = new StringBuffer();
- switch (codepage)
- {
- case -1:
- {
- /* Without a codepage the length is equal to the number of
- * bytes. */
- b.append(new String(src, o, (int) sLength));
- break;
- }
- case Constants.CP_UNICODE:
- {
- /* The length is the number of characters, i.e. the number
- * of bytes is twice the number of the characters. */
- final int nrBytes = (int) (sLength * 2);
- final byte[] h = new byte[nrBytes];
- for (int i2 = 0; i2 < nrBytes; i2 += 2)
- {
- h[i2] = src[o + i2 + 1];
- h[i2 + 1] = src[o + i2];
- }
- b.append(new String(h, 0, nrBytes,
- VariantSupport.codepageToEncoding(codepage)));
- break;
- }
- default:
- {
- /* For encodings other than Unicode the length is the number
- * of bytes. */
- b.append(new String(src, o, (int) sLength,
- VariantSupport.codepageToEncoding(codepage)));
- break;
- }
- }
-
- /* Strip 0x00 characters from the end of the string: */
- while (b.length() > 0 && b.charAt(b.length() - 1) == 0x00)
- b.setLength(b.length() - 1);
- if (codepage == Constants.CP_UNICODE)
- {
- if (sLength % 2 == 1)
- sLength++;
- o += (sLength + sLength);
- }
- else
- o += sLength;
- m.put(id, b.toString());
- }
- }
- catch (RuntimeException ex)
- {
- final POILogger l = POILogFactory.getLogger(getClass());
- l.log(POILogger.WARN,
- "The property set's dictionary contains bogus data. "
- + "All dictionary entries starting with the one with ID "
- + id + " will be ignored.", ex);
- }
- return m;
- }
-
-
-
- /**
- * <p>Returns the property's size in bytes. This is always a multiple of
- * 4.</p>
- *
- * @return the property's size in bytes
- *
- * @exception WritingNotSupportedException if HPSF does not yet support the
- * property's variant type.
- */
- protected int getSize() throws WritingNotSupportedException
- {
- int length = VariantSupport.getVariantLength(type);
- if (length >= 0)
- return length; /* Fixed length */
- if (length == -2)
- /* Unknown length */
- throw new WritingNotSupportedException(type, null);
-
- /* Variable length: */
- final int PADDING = 4; /* Pad to multiples of 4. */
- switch ((int) type)
- {
- case Variant.VT_LPSTR:
- {
- int l = ((String) value).length() + 1;
- int r = l % PADDING;
- if (r > 0)
- l += PADDING - r;
- length += l;
- break;
- }
- case Variant.VT_EMPTY:
- break;
- default:
- throw new WritingNotSupportedException(type, value);
- }
- return length;
- }
-
-
-
- /**
- * <p>Compares two properties.</p> <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 dictionary. Another special case are strings: Two properties
- * may have the different types Variant.VT_LPSTR and Variant.VT_LPWSTR;</p>
- *
- * @see Object#equals(java.lang.Object)
- */
- public boolean equals(final Object o)
- {
- if (!(o instanceof Property)) {
- return false;
- }
- final Property p = (Property) o;
- final Object pValue = p.getValue();
- final long pId = p.getID();
- if (id != pId || (id != 0 && !typesAreEqual(type, p.getType())))
- return false;
- if (value == null && pValue == null)
- return true;
- if (value == null || pValue == null)
- return false;
-
- /* It's clear now that both values are non-null. */
- final Class<?> valueClass = value.getClass();
- final Class<?> pValueClass = pValue.getClass();
- if (!(valueClass.isAssignableFrom(pValueClass)) &&
- !(pValueClass.isAssignableFrom(valueClass)))
- return false;
-
- if (value instanceof byte[])
- return Util.equal((byte[]) value, (byte[]) pValue);
-
- return value.equals(pValue);
- }
-
-
-
- private boolean typesAreEqual(final long t1, final long t2)
- {
- if (t1 == t2 ||
- (t1 == Variant.VT_LPSTR && t2 == Variant.VT_LPWSTR) ||
- (t2 == Variant.VT_LPSTR && t1 == Variant.VT_LPWSTR)) {
- return true;
- }
- return false;
- }
-
-
-
- /**
- * @see Object#hashCode()
- */
- public int hashCode()
- {
- long hashCode = 0;
- hashCode += id;
- hashCode += type;
- if (value != null)
- hashCode += value.hashCode();
- final int returnHashCode = (int) (hashCode & 0x0ffffffffL );
- return returnHashCode;
-
- }
-
-
-
- /**
- * @see Object#toString()
- */
- public String toString()
- {
- final StringBuffer b = new StringBuffer();
- b.append(getClass().getName());
- b.append('[');
- b.append("id: ");
- b.append(getID());
- b.append(", type: ");
- b.append(getType());
- final Object value = getValue();
- b.append(", value: ");
- b.append(value.toString());
- if (value instanceof String)
- {
- final String s = (String) value;
- final int l = s.length();
- final byte[] bytes = new byte[l * 2];
- for (int i = 0; i < l; i++)
- {
- final char c = s.charAt(i);
- final byte high = (byte) ((c & 0x00ff00) >> 8);
- final byte low = (byte) ((c & 0x0000ff) >> 0);
- bytes[i * 2] = high;
- bytes[i * 2 + 1] = low;
- }
- final String hex = HexDump.dump(bytes, 0L, 0);
- b.append(" [");
- b.append(hex);
- b.append("]");
- }
- b.append(']');
- return b.toString();
- }
-
- }
|