From 6ea58e94bc28a51013ba622fc94ca7bd2b218e30 Mon Sep 17 00:00:00 2001
From: Andreas Beeker Maintains the instances of {@link CustomProperty} that belong to a
+ * Maintains the instances of {@link CustomProperty} that belong to a
* {@link DocumentSummaryInformation}. The class maintains the names of the
* custom properties in a dictionary. It implements the {@link Map} interface
* and by this provides a simplified view on custom properties: A property's
* name is the key that maps to a typed value. This implementation hides
* property IDs from the developer and regards the property names as keys to
- * typed values.
*
- * While this class provides a simple API to custom properties, it ignores
+ * While this class provides a simple API to custom properties, it ignores
* the fact that not names, but IDs are the real keys to properties. Under the
* hood this class maintains a 1:1 relationship between IDs and names. Therefore
* you should not use this class to process property sets with several IDs
* mapping to the same name or with properties without a name: the result will
* contain only a subset of the original properties. If you really need to deal
- * such property sets, use HPSF's low-level access methods.
*
- * An application can call the {@link #isPure} method to check whether a
+ * An application can call the {@link #isPure} method to check whether a
* property set parsed by {@link CustomProperties} is still pure (i.e.
- * unmodified) or whether one or more properties have been dropped.
*
- * This class is not thread-safe; concurrent access to instances of this
- * class must be synchronized.
*
- * While this class is roughly HashMap<Long,CustomProperty>, that's the
- * internal representation. To external calls, it should appear as
- * HashMap<String,Object> mapping between Names and Custom Property Values.
Sets the section's dictionary. All keys in the dictionary must be - * {@link java.lang.Long} instances, all values must be - * {@link java.lang.String}s. This method overwrites the properties with IDs - * 0 and 1 since they are reserved for the dictionary and the dictionary's - * codepage. Setting these properties explicitly might have surprising - * effects. An application should never do this but always use this - * method.
- * - * @param dictionary The dictionary - * - * @exception IllegalPropertySetDataException if the dictionary's key and - * value types are not correct. - * - * @see Section#getDictionary() - */ - public void setDictionary(final MapSets a property.
- * - * @param id The property ID. - * @param value The property's value. The value's class must be one of those - * supported by HPSF. - */ - public void setProperty(final int id, final Object value) - { - if (value instanceof String) - setProperty(id, (String) value); - else if (value instanceof Long) - setProperty(id, ((Long) value).longValue()); - else if (value instanceof Integer) - setProperty(id, ((Integer) value).intValue()); - else if (value instanceof Short) - setProperty(id, ((Short) value).intValue()); - else if (value instanceof Boolean) - setProperty(id, ((Boolean) value).booleanValue()); - else if (value instanceof Date) - setProperty(id, Variant.VT_FILETIME, value); - else - throw new HPSFRuntimeException( - "HPSF does not support properties of type " + - value.getClass().getName() + "."); - } - - - - /** - *Removes all properties from the section including 0 (dictionary) and - * 1 (codepage).
- */ - public void clear() - { - final Property[] properties = getProperties(); - for (int i = 0; i < properties.length; i++) - { - final Property p = properties[i]; - removeProperty(p.getID()); - } - } - - /** - *Sets the codepage.
- * - * @param codepage the codepage - */ - public void setCodepage(final int codepage) - { - setProperty(PropertyIDMap.PID_CODEPAGE, Variant.VT_I2, - Integer.valueOf(codepage)); + + public MutableSection(final byte[] src, final int offset) throws UnsupportedEncodingException { + super(src,offset); } } diff --git a/src/java/org/apache/poi/hpsf/NoFormatIDException.java b/src/java/org/apache/poi/hpsf/NoFormatIDException.java index f1a09438e..51f76c348 100644 --- a/src/java/org/apache/poi/hpsf/NoFormatIDException.java +++ b/src/java/org/apache/poi/hpsf/NoFormatIDException.java @@ -18,53 +18,47 @@ package org.apache.poi.hpsf; /** - *This exception is thrown if a {@link MutablePropertySet} is to be written - * but does not have a formatID set (see {@link - * MutableSection#setFormatID(ClassID)} or - * {@link org.apache.poi.hpsf.MutableSection#setFormatID(byte[])}. + * This exception is thrown if a {@link PropertySet} is to be written + * but does not have a formatID set (see {@link Section#setFormatID(ClassID)} or + * {@link org.apache.poi.hpsf.Section#setFormatID(byte[])}. */ -public class NoFormatIDException extends HPSFRuntimeException -{ +public class NoFormatIDException extends HPSFRuntimeException { /** - *
Constructor
+ * Constructor */ - public NoFormatIDException() - { + public NoFormatIDException() { super(); } /** - *Constructor
+ * Constructor * * @param msg The exception's message string */ - public NoFormatIDException(final String msg) - { + public NoFormatIDException(final String msg) { super(msg); } /** - *Constructor
+ * Constructor * * @param reason This exception's underlying reason */ - public NoFormatIDException(final Throwable reason) - { + public NoFormatIDException(final Throwable reason) { super(reason); } /** - *Constructor
+ * Constructor * * @param msg The exception's message string * @param reason This exception's underlying reason */ - public NoFormatIDException(final String msg, final Throwable reason) - { + public NoFormatIDException(final String msg, final Throwable reason) { super(msg, reason); } diff --git a/src/java/org/apache/poi/hpsf/Property.java b/src/java/org/apache/poi/hpsf/Property.java index 14dbee027..b9841dbcc 100644 --- a/src/java/org/apache/poi/hpsf/Property.java +++ b/src/java/org/apache/poi/hpsf/Property.java @@ -17,8 +17,11 @@ package org.apache.poi.hpsf; +import java.io.IOException; +import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.nio.charset.Charset; +import java.util.Arrays; import java.util.LinkedHashMap; import java.util.Map; @@ -29,104 +32,74 @@ import org.apache.poi.util.POILogFactory; import org.apache.poi.util.POILogger; /** - *A property in a {@link Section} of a {@link PropertySet}.
+ * A property in a {@link Section} of a {@link PropertySet}.* - *
The property's ID gives the property a meaning + * The property's {@code ID} gives the property a meaning * in the context of its {@link Section}. Each {@link Section} spans - * its own name space of property IDs.
+ * its own name space of property IDs.* - *
The property's type determines how its - * value is interpreted. For example, if the type is + * The property's {@code type} determines how its + * {@code value} 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).
+ * value, {@link Variant#VT_FILETIME} some date and time (of a file).* - *
Please note that not all {@link Variant} types yet. This might change + * 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.
- * - *Microsoft documentation: - * Property Set Display Name Dictionary. + * reports or patches for the types you need. * * @see Section * @see Variant + * @see + * [MS-OLEPS]: Object Linking and Embedding (OLE) Property Set Data Structures */ -public class Property -{ +public class Property { - /**
The property's ID.
*/ - protected long id; + /** The property's ID. */ + private long id; + /** The property's type. */ + private long type; - /** - *Returns the property's ID.
- * - * @return The ID value - */ - public long getID() - { - return id; - } - - - - /**The property's type.
*/ - protected long type; - - - /** - *Returns the property's type.
- * - * @return The type value - */ - public long getType() - { - return type; - } - - - - /**The property's value.
*/ + /** The property's value. */ protected Object value; /** - *Returns the property's value.
- * - * @return The property's value + * Creates an empty property. It must be filled using the set method to be usable. */ - public Object getValue() - { - return value; + public Property() { } - - /** - *Creates a property.
+ * Creates a {@code Property} as a copy of an existing {@code Property}. + * + * @param p The property to copy. + */ + public Property(Property p) { + this(p.id, p.type, p.value); + } + + /** + * Creates a property. * * @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) - { + public Property(final long id, final long type, final Object value) { this.id = id; this.type = type; this.value = value; } - - /** - *Creates a {@link Property} instance by reading its bytes - * from the property set stream.
+ * Creates a {@link Property} instance by reading its bytes + * from the property set stream. * * @param id The property's ID. * @param src The bytes the property set stream consists of. @@ -138,18 +111,15 @@ public class Property * @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 - { + 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) - { + if (id == 0) { value = readDictionary(src, offset, length, codepage); return; } @@ -158,12 +128,9 @@ public class Property type = LittleEndian.getUInt(src, o); o += LittleEndian.INT_SIZE; - try - { + try { value = VariantSupport.read(src, o, length, (int) type, codepage); - } - catch (UnsupportedVariantTypeException ex) - { + } catch (UnsupportedVariantTypeException ex) { VariantSupport.writeUnsupportedTypeMessage(ex); value = ex.getValue(); } @@ -172,19 +139,68 @@ public class Property /** - *Creates an empty property. It must be filled using the set method to - * be usable.
+ * Returns the property's ID. + * + * @return The ID value */ - protected Property() - { } - - + public long getID() { + return id; + } /** - *Reads a dictionary.
+ * Sets the property's ID. + * + * @param id the ID + */ + public void setID(final long id) { + this.id = id; + } + + /** + * Returns the property's type. + * + * @return The type value + */ + public long getType() { + return type; + } + + /** + * Sets the property's type. + * + * @param type the property's type + */ + public void setType(final long type) { + this.type = type; + } + + /** + * Returns the property's value. + * + * @return The property's value + */ + public Object getValue() { + return value; + } + + /** + * Sets the property's value. + * + * @param value the property's value + */ + public void setValue(final Object value) { + this.value = value; + } + + + + + + /** + * Reads a dictionary. * * @param src The byte array containing the bytes making out the dictionary. - * @param offset At this offset within src the dictionary + * @param offset At this offset within {@code src} the dictionary * starts. * @param length The dictionary contains at most this many bytes. * @param codepage The codepage of the string values. @@ -192,15 +208,14 @@ public class Property * @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 - { + 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) + if (offset < 0 || offset > src.length) { throw new HPSFRuntimeException ("Illegal offset " + offset + " while HPSF stream contains " + length + " bytes."); + } int o = (int) offset; /* @@ -209,13 +224,10 @@ public class Property final long nrEntries = LittleEndian.getUInt(src, o); o += LittleEndian.INT_SIZE; - final MapReturns the property's size in bytes. This is always a multiple of - * 4.
+ * Returns the property's size in bytes. This is always a multiple of 4. * * @return the property's size in bytes * @@ -303,18 +304,18 @@ public class Property protected int getSize() throws WritingNotSupportedException { int length = VariantSupport.getVariantLength(type); - if (length >= 0) + if (length >= 0) { return length; /* Fixed length */ - if (length == -2) + } + 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: - { + switch ((int) type) { + case Variant.VT_LPSTR: { int l = ((String) value).length() + 1; int r = l % PADDING; if (r > 0) @@ -333,51 +334,53 @@ public class Property /** - *Compares two properties.
Please beware that a property with + * Compares two properties.
+ * + * 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;
+ * may have the different types Variant.VT_LPSTR and Variant.VT_LPWSTR; * * @see Object#equals(java.lang.Object) */ - public boolean equals(final Object o) - { + 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()))) + if (id != pId || (id != 0 && !typesAreEqual(type, p.getType()))) { return false; - if (value == null && pValue == null) + } + if (value == null && pValue == null) { return true; - if (value == null || pValue == null) + } + 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))) + !(pValueClass.isAssignableFrom(valueClass))) { return false; + } - if (value instanceof byte[]) - return Util.equal((byte[]) value, (byte[]) pValue); + if (value instanceof byte[]) { + return Arrays.equals((byte[]) value, (byte[]) pValue); + } return value.equals(pValue); } - private boolean typesAreEqual(final long t1, final long t2) - { - if (t1 == t2 || + private boolean typesAreEqual(final long t1, final long t2) { + return (t1 == t2 || (t1 == Variant.VT_LPSTR && t2 == Variant.VT_LPWSTR) || - (t2 == Variant.VT_LPSTR && t1 == Variant.VT_LPWSTR)) { - return true; - } - return false; + (t2 == Variant.VT_LPSTR && t1 == Variant.VT_LPWSTR)); } @@ -385,15 +388,14 @@ public class Property /** * @see Object#hashCode() */ - public int hashCode() - { + public int hashCode() { long hashCode = 0; hashCode += id; hashCode += type; - if (value != null) + if (value != null) { hashCode += value.hashCode(); - final int returnHashCode = (int) (hashCode & 0x0ffffffffL ); - return returnHashCode; + } + return (int) (hashCode & 0x0ffffffffL ); } @@ -402,8 +404,7 @@ public class Property /** * @see Object#toString() */ - public String toString() - { + public String toString() { final StringBuffer b = new StringBuffer(); b.append(getClass().getName()); b.append('['); @@ -413,14 +414,12 @@ public class Property b.append(getType()); final Object value = getValue(); b.append(", value: "); - if (value instanceof String) - { + if (value instanceof String) { b.append(value.toString()); final String s = (String) value; final int l = s.length(); final byte[] bytes = new byte[l * 2]; - for (int i = 0; i < l; i++) - { + 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); @@ -433,21 +432,43 @@ public class Property b.append(hex); } b.append("]"); - } - else if (value instanceof byte[]) - { + } else if (value instanceof byte[]) { byte[] bytes = (byte[])value; if(bytes.length > 0) { String hex = HexDump.dump(bytes, 0L, 0); b.append(hex); } - } - else - { + } else { b.append(value.toString()); } b.append(']'); return b.toString(); } + /** + * Writes the property to an output stream. + * + * @param out The output stream to write to. + * @param codepage The codepage to use for writing non-wide strings + * @return the number of bytes written to the stream + * + * @exception IOException if an I/O error occurs + * @exception WritingNotSupportedException if a variant type is to be + * written that is not yet supported + */ + public int write(final OutputStream out, final int codepage) + throws IOException, WritingNotSupportedException { + int length = 0; + long variantType = getType(); + + /* Ensure that wide strings are written if the codepage is Unicode. */ + if (codepage == CodePageUtil.CP_UNICODE && variantType == Variant.VT_LPSTR) { + variantType = Variant.VT_LPWSTR; + } + + length += TypeWriter.writeUIntToStream(out, variantType); + length += VariantSupport.write(out, variantType, getValue(), codepage); + return length; + } + } diff --git a/src/java/org/apache/poi/hpsf/PropertySet.java b/src/java/org/apache/poi/hpsf/PropertySet.java index 9c9b1876a..30a636ffc 100644 --- a/src/java/org/apache/poi/hpsf/PropertySet.java +++ b/src/java/org/apache/poi/hpsf/PropertySet.java @@ -17,246 +17,195 @@ package org.apache.poi.hpsf; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; import java.io.UnsupportedEncodingException; -import java.util.ArrayList; +import java.nio.charset.Charset; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedList; import java.util.List; +import org.apache.poi.EmptyFileException; +import org.apache.poi.hpsf.wellknown.PropertyIDMap; import org.apache.poi.hpsf.wellknown.SectionIDMap; +import org.apache.poi.poifs.filesystem.DirectoryEntry; +import org.apache.poi.poifs.filesystem.Entry; import org.apache.poi.util.IOUtils; import org.apache.poi.util.LittleEndian; +import org.apache.poi.util.LittleEndianConsts; /** - *Represents a property set in the Horrible Property Set Format + * Represents a property set in the Horrible Property Set Format * (HPSF). These are usually metadata of a Microsoft Office - * document.
+ * document.* - *
An application that wants to access these metadata should create + * An application that wants to access these metadata should create * an instance of this class or one of its subclasses by calling the * factory method {@link PropertySetFactory#create} and then retrieve - * the information its needs by calling appropriate methods.
+ * the information its needs by calling appropriate methods.* - *
{@link PropertySetFactory#create} does its work by calling one + * {@link PropertySetFactory#create} does its work by calling one * of the constructors {@link PropertySet#PropertySet(InputStream)} or * {@link PropertySet#PropertySet(byte[])}. If the constructor's * argument is not in the Horrible Property Set Format, i.e. not a * property set stream, or if any other error occurs, an appropriate - * exception is thrown.
+ * exception is thrown.* - *
A {@link PropertySet} has a list of {@link Section}s, and each + * A {@link PropertySet} has a list of {@link Section}s, and each * {@link Section} has a {@link Property} array. Use {@link * #getSections} to retrieve the {@link Section}s, then call {@link * Section#getProperties} for each {@link Section} to get hold of the - * {@link Property} arrays.
Since the vast majority of {@link - * PropertySet}s contains only a single {@link Section}, the - * convenience method {@link #getProperties} returns the properties of - * a {@link PropertySet}'s {@link Section} (throwing a {@link - * NoSingleSectionException} if the {@link PropertySet} contains more - * (or less) than exactly one {@link Section}). + * {@link Property} arrays.+ * + * Since the vast majority of {@link PropertySet}s contains only a single + * {@link Section}, the convenience method {@link #getProperties} returns + * the properties of a {@link PropertySet}'s {@link Section} (throwing a + * {@link NoSingleSectionException} if the {@link PropertySet} contains + * more (or less) than exactly one {@link Section}). */ -public class PropertySet -{ - +public class PropertySet { /** - *
The "byteOrder" field must equal this value.
- */ - static final byte[] BYTE_ORDER_ASSERTION = - {(byte) 0xFE, (byte) 0xFF}; - - /** - *Specifies this {@link PropertySet}'s byte order. See the - * HPFS documentation for details!
- */ - protected int byteOrder; - - /** - *Returns the property set stream's low-level "byte order" - * field. It is always 0xFFFE .
- * - * @return The property set stream's low-level "byte order" field. - */ - public int getByteOrder() - { - return byteOrder; - } - - - - /** - *The "format" field must equal this value.
- */ - static final byte[] FORMAT_ASSERTION = - {(byte) 0x00, (byte) 0x00}; - - /** - *Specifies this {@link PropertySet}'s format. See the HPFS - * documentation for details!
- */ - protected int format; - - /** - *Returns the property set stream's low-level "format" - * field. It is always 0x0000 .
- * - * @return The property set stream's low-level "format" field. - */ - public int getFormat() - { - return format; - } - - - - /** - *Specifies the version of the operating system that created - * this {@link PropertySet}. See the HPFS documentation for - * details!
- */ - protected int osVersion; - - - /** - *If the OS version field holds this value the property set stream was - * created on a 16-bit Windows system.
+ * If the OS version field holds this value the property set stream was + * created on a 16-bit Windows system. */ public static final int OS_WIN16 = 0x0000; /** - *If the OS version field holds this value the property set stream was - * created on a Macintosh system.
+ * If the OS version field holds this value the property set stream was + * created on a Macintosh system. */ public static final int OS_MACINTOSH = 0x0001; /** - *If the OS version field holds this value the property set stream was - * created on a 32-bit Windows system.
+ * If the OS version field holds this value the property set stream was + * created on a 32-bit Windows system. */ public static final int OS_WIN32 = 0x0002; /** - *Returns the property set stream's low-level "OS version" - * field.
- * - * @return The property set stream's low-level "OS version" field. + * The "byteOrder" field must equal this value. */ - public int getOSVersion() - { - return osVersion; + private static final int BYTE_ORDER_ASSERTION = 0xFFFE; + + /** + * The "format" field must equal this value. + */ + private static final int FORMAT_ASSERTION = 0x0000; + + /** + * The length of the property set stream header. + */ + private static final int OFFSET_HEADER = + LittleEndianConsts.SHORT_SIZE + /* Byte order */ + LittleEndianConsts.SHORT_SIZE + /* Format */ + LittleEndianConsts.INT_SIZE + /* OS version */ + ClassID.LENGTH + /* Class ID */ + LittleEndianConsts.INT_SIZE; /* Section count */ + + + /** + * Specifies this {@link PropertySet}'s byte order. See the + * HPFS documentation for details! + */ + private int byteOrder; + + /** + * Specifies this {@link PropertySet}'s format. See the HPFS + * documentation for details! + */ + private int format; + + /** + * Specifies the version of the operating system that created this + * {@link PropertySet}. See the HPFS documentation for details! + */ + private int osVersion; + + /** + * Specifies this {@link PropertySet}'s "classID" field. See + * the HPFS documentation for details! + */ + private ClassID classID; + + /** + * The sections in this {@link PropertySet}. + */ + private final ListSpecifies this {@link PropertySet}'s "classID" field. See - * the HPFS documentation for details!
- */ - protected ClassID classID; - - /** - *Returns the property set stream's low-level "class ID" - * field.
+ * Creates a {@link PropertySet} instance from an {@link + * InputStream} in the Horrible Property Set Format.* - * @return The property set stream's low-level "class ID" field. - */ - public ClassID getClassID() - { - return classID; - } - - - - /** - *
Returns the number of {@link Section}s in the property - * set.
- * - * @return The number of {@link Section}s in the property set. - */ - public int getSectionCount() - { - return sections.size(); - } - - - - /** - *The sections in this {@link PropertySet}.
- */ - protected ListReturns the {@link Section}s in the property set.
- * - * @return The {@link Section}s in the property set. - */ - public ListCreates an empty (uninitialized) {@link PropertySet}.
- * - *Please note: For the time being this - * constructor is protected since it is used for internal purposes - * only, but expect it to become public once the property set's - * writing functionality is implemented.
- */ - protected PropertySet() - { } - - - - /** - *Creates a {@link PropertySet} instance from an {@link - * InputStream} in the Horrible Property Set Format.
- * - *The constructor reads the first few bytes from the stream + * The constructor reads the first few bytes from the stream * and determines whether it is really a property set stream. If * it is, it parses the rest of the stream. If it is not, it * resets the stream to its beginning in order to let other * components mess around with the data and throws an - * exception.
+ * exception. * * @param stream Holds the data making out the property set * stream. - * @throws MarkUnsupportedException if the stream does not support - * the {@link InputStream#markSupported} method. - * @throws IOException if the {@link InputStream} cannot be - * accessed as needed. - * @exception NoPropertySetStreamException if the input stream does not - * contain a property set. - * @exception UnsupportedEncodingException if a character encoding is not - * supported. + * @throws MarkUnsupportedException + * if the stream does not support the {@link InputStream#markSupported} method. + * @throws IOException + * if the {@link InputStream} cannot be accessed as needed. + * @exception NoPropertySetStreamException + * if the input stream does not contain a property set. + * @exception UnsupportedEncodingException + * if a character encoding is not supported. */ public PropertySet(final InputStream stream) - throws NoPropertySetStreamException, MarkUnsupportedException, - IOException, UnsupportedEncodingException - { - if (isPropertySetStream(stream)) - { - final int avail = stream.available(); - final byte[] buffer = new byte[avail]; - IOUtils.readFully(stream, buffer); - init(buffer, 0, buffer.length); - } - else + throws NoPropertySetStreamException, MarkUnsupportedException, + IOException, UnsupportedEncodingException { + if (!isPropertySetStream(stream)) { throw new NoPropertySetStreamException(); + } + + final byte[] buffer = IOUtils.toByteArray(stream); + init(buffer, 0, buffer.length); } /** - *Creates a {@link PropertySet} instance from a byte array - * that represents a stream in the Horrible Property Set - * Format.
+ * Creates a {@link PropertySet} instance from a byte array that + * represents a stream in the Horrible Property Set Format. * * @param stream The byte array holding the stream data. - * @param offset The offset in stream where the stream + * @param offset The offset in {@code stream} where the stream * data begin. If the stream data begin with the first byte in the - * array, the offset is 0. + * array, the {@code offset} is 0. * @param length The length of the stream data. * @throws NoPropertySetStreamException if the byte array is not a * property set stream. @@ -264,20 +213,16 @@ public class PropertySet * @exception UnsupportedEncodingException if the codepage is not supported. */ public PropertySet(final byte[] stream, final int offset, final int length) - throws NoPropertySetStreamException, UnsupportedEncodingException - { - if (isPropertySetStream(stream, offset, length)) - init(stream, offset, length); - else + throws NoPropertySetStreamException, UnsupportedEncodingException { + if (!isPropertySetStream(stream, offset, length)) { throw new NoPropertySetStreamException(); + } + init(stream, offset, length); } - - /** - *Creates a {@link PropertySet} instance from a byte array - * that represents a stream in the Horrible Property Set - * Format.
+ * Creates a {@link PropertySet} instance from a byte array + * that represents a stream in the Horrible Property Set Format. * * @param stream The byte array holding the stream data. The * complete byte array contents is the stream data. @@ -287,75 +232,185 @@ public class PropertySet * @exception UnsupportedEncodingException if the codepage is not supported. */ public PropertySet(final byte[] stream) - throws NoPropertySetStreamException, UnsupportedEncodingException - { + throws NoPropertySetStreamException, UnsupportedEncodingException { this(stream, 0, stream.length); } + + /** + * Constructs a {@code PropertySet} by doing a deep copy of + * an existing {@code PropertySet}. All nested elements, i.e. + * {@code Section}s and {@code Property} instances, will be their + * counterparts in the new {@code PropertySet}. + * + * @param ps The property set to copy + */ + public PropertySet(PropertySet ps) { + setByteOrder(ps.getByteOrder()); + setFormat(ps.getFormat()); + setOSVersion(ps.getOSVersion()); + setClassID(ps.getClassID()); + for (final Section section : ps.getSections()) { + sections.add(new MutableSection(section)); + } + } + + + /** + * @return The property set stream's low-level "byte order" field. It is always {@code 0xFFFE}. + */ + public int getByteOrder() { + return byteOrder; + } + + /** + * Returns the property set stream's low-level "byte order" field. + * + * @param byteOrder The property set stream's low-level "byte order" field. + */ + public void setByteOrder(int byteOrder) { + this.byteOrder = byteOrder; + } + + /** + * @return The property set stream's low-level "format" field. It is always {@code 0x0000}. + */ + public int getFormat() { + return format; + } + + /** + * Sets the property set stream's low-level "format" field. + * + * @param format The property set stream's low-level "format" field. + */ + public void setFormat(int format) { + this.format = format; + } + + /** + * @return The property set stream's low-level "OS version" field. + */ + public int getOSVersion() { + return osVersion; + } + + /** + * Sets the property set stream's low-level "OS version" field. + * + * @param osVersion The property set stream's low-level "OS version" field. + */ + public void setOSVersion(int osVersion) { + this.osVersion = osVersion; + } + + + /** + * @return The property set stream's low-level "class ID" field. + */ + public ClassID getClassID() { + return classID; + } + + /** + * Sets the property set stream's low-level "class ID" field. + * + * @param classID The property set stream's low-level "class ID" field. + */ + public void setClassID(ClassID classID) { + this.classID = classID; + } + + /** + * @return The number of {@link Section}s in the property set. + */ + public int getSectionCount() { + return sections.size(); + } + + /** + * @return The unmodifiable list of {@link Section}s in the property set. + */ + public ListChecks whether an {@link InputStream} is in the Horrible - * Property Set Format.
+ * Adds a section to this property set. + * + * @param section The {@link Section} to add. It will be appended + * after any sections that are already present in the property set + * and thus become the last section. + */ + public void addSection(final Section section) { + sections.add(section); + } + + /** + * Removes all sections from this property set. + */ + public void clearSections() { + sections.clear(); + } + + /** + * The id to name mapping of the properties in this set. + * + * @return the id to name mapping of the properties in this set or {@code null} if not applicable + */ + public PropertyIDMap getPropertySetIDMap() { + return null; + } + + + /** + * Checks whether an {@link InputStream} is in the Horrible + * Property Set Format. * * @param stream The {@link InputStream} to check. In order to * perform the check, the method reads the first bytes from the * stream. After reading, the stream is reset to the position it * had before reading. The {@link InputStream} must support the * {@link InputStream#mark} method. - * @returntrue
if the stream is a property set
- * stream, else false
.
+ * @return {@code true} if the stream is a property set
+ * stream, else {@code false}.
* @throws MarkUnsupportedException if the {@link InputStream}
* does not support the {@link InputStream#mark} method.
* @exception IOException if an I/O error occurs
*/
public static boolean isPropertySetStream(final InputStream stream)
- throws MarkUnsupportedException, IOException
- {
+ throws MarkUnsupportedException, IOException {
/*
* Read at most this many bytes.
*/
final int BUFFER_SIZE = 50;
- /*
- * Mark the current position in the stream so that we can
- * reset to this position if the stream does not contain a
- * property set.
- */
- if (!stream.markSupported())
- throw new MarkUnsupportedException(stream.getClass().getName());
- stream.mark(BUFFER_SIZE);
-
/*
* Read a couple of bytes from the stream.
*/
- final byte[] buffer = new byte[BUFFER_SIZE];
- final int bytes =
- stream.read(buffer, 0,
- Math.min(buffer.length, stream.available()));
- final boolean isPropertySetStream =
- isPropertySetStream(buffer, 0, bytes);
- stream.reset();
- return isPropertySetStream;
+ try {
+ final byte[] buffer = IOUtils.peekFirstNBytes(stream, BUFFER_SIZE);
+ final boolean isPropertySetStream = isPropertySetStream(buffer, 0, buffer.length);
+ return isPropertySetStream;
+ } catch (EmptyFileException e) {
+ return false;
+ }
}
/**
- * Checks whether a byte array is in the Horrible Property Set - * Format.
+ * Checks whether a byte array is in the Horrible Property Set Format. * * @param src The byte array to check. * @param offset The offset in the byte array. * @param length The significant number of bytes in the byte * array. Only this number of bytes will be checked. - * @returntrue
if the byte array is a property set
- * stream, false
if not.
+ * @return {@code true} if the byte array is a property set
+ * stream, {@code false} if not.
*/
- public static boolean isPropertySetStream(final byte[] src,
- final int offset,
- final int length)
- {
+ public static boolean isPropertySetStream(final byte[] src, final int offset, final int length) {
/* FIXME (3): Ensure that at most "length" bytes are read. */
/*
@@ -365,45 +420,39 @@ public class PropertySet
int o = offset;
final int byteOrder = LittleEndian.getUShort(src, o);
o += LittleEndian.SHORT_SIZE;
- byte[] temp = new byte[LittleEndian.SHORT_SIZE];
- LittleEndian.putShort(temp, 0, (short) byteOrder);
- if (!Util.equal(temp, BYTE_ORDER_ASSERTION))
+ if (byteOrder != BYTE_ORDER_ASSERTION) {
return false;
+ }
final int format = LittleEndian.getUShort(src, o);
o += LittleEndian.SHORT_SIZE;
- temp = new byte[LittleEndian.SHORT_SIZE];
- LittleEndian.putShort(temp, 0, (short) format);
- if (!Util.equal(temp, FORMAT_ASSERTION))
+ if (format != FORMAT_ASSERTION) {
return false;
+ }
// final long osVersion = LittleEndian.getUInt(src, offset);
o += LittleEndian.INT_SIZE;
// final ClassID classID = new ClassID(src, offset);
o += ClassID.LENGTH;
final long sectionCount = LittleEndian.getUInt(src, o);
- o += LittleEndian.INT_SIZE;
- if (sectionCount < 0)
- return false;
- return true;
+ return (sectionCount >= 0);
}
/**
- * Initializes this {@link PropertySet} instance from a byte + * Initializes this {@link PropertySet} instance from a byte * array. The method assumes that it has been checked already that * the byte array indeed represents a property set stream. It does - * no more checks on its own.
+ * no more checks on its own. * * @param src Byte array containing the property set stream * @param offset The property set stream starts at this offset - * from the beginning of src + * from the beginning of {@code src} * @param length Length of the property set stream. * @throws UnsupportedEncodingException if HPSF does not (yet) support the * property set's character encoding. */ private void init(final byte[] src, final int offset, final int length) - throws UnsupportedEncodingException - { + throws UnsupportedEncodingException { /* FIXME (3): Ensure that at most "length" bytes are read. */ /* @@ -420,9 +469,9 @@ public class PropertySet o += ClassID.LENGTH; final int sectionCount = LittleEndian.getInt(src, o); o += LittleEndian.INT_SIZE; - if (sectionCount < 0) - throw new HPSFRuntimeException("Section count " + sectionCount + - " is negative."); + if (sectionCount < 0) { + throw new HPSFRuntimeException("Section count " + sectionCount + " is negative."); + } /* * Read the sections, which are following the header. They @@ -430,209 +479,334 @@ public class PropertySet * consists of a format ID telling what the section contains * and an offset telling how many bytes from the start of the * stream the section begins. - */ - /* + * * Most property sets have only one section. The Document * Summary Information stream has 2. Everything else is a rare * exception and is no longer fostered by Microsoft. */ - sections = new ArrayListChecks whether this {@link PropertySet} represents a Summary - * Information.
+ * Writes the property set to an output stream. * - * @returntrue
if this {@link PropertySet}
- * represents a Summary Information, else false
.
+ * @param out the output stream to write the section to
+ * @exception IOException if an error when writing to the output stream
+ * occurs
+ * @exception WritingNotSupportedException if HPSF does not yet support
+ * writing a property's variant type.
*/
- public boolean isSummaryInformation()
- {
- if (sections.size() <= 0)
- return false;
- return Util.equal(sections.get(0).getFormatID().getBytes(),
- SectionIDMap.SUMMARY_INFORMATION_ID);
+ public void write(final OutputStream out)
+ throws WritingNotSupportedException, IOException {
+ /* Write the number of sections in this property set stream. */
+ final int nrSections = getSectionCount();
+
+ /* Write the property set's header. */
+ TypeWriter.writeToStream(out, (short) getByteOrder());
+ TypeWriter.writeToStream(out, (short) getFormat());
+ TypeWriter.writeToStream(out, getOSVersion());
+ TypeWriter.writeToStream(out, getClassID());
+ TypeWriter.writeToStream(out, nrSections);
+ int offset = OFFSET_HEADER;
+
+ /* Write the section list, i.e. the references to the sections. Each
+ * entry in the section list consist of the section's class ID and the
+ * section's offset relative to the beginning of the stream. */
+ offset += nrSections * (ClassID.LENGTH + LittleEndianConsts.INT_SIZE);
+ final int sectionsBegin = offset;
+ for (final Section section : getSections()) {
+ final ClassID formatID = section.getFormatID();
+ if (formatID == null) {
+ throw new NoFormatIDException();
+ }
+ TypeWriter.writeToStream(out, section.getFormatID());
+ TypeWriter.writeUIntToStream(out, offset);
+ try {
+ offset += section.getSize();
+ } catch (HPSFRuntimeException ex) {
+ final Throwable cause = ex.getReason();
+ if (cause instanceof UnsupportedEncodingException) {
+ throw new IllegalPropertySetDataException(cause);
+ }
+ throw ex;
+ }
+ }
+
+ /* Write the sections themselves. */
+ offset = sectionsBegin;
+ for (final Section section : getSections()) {
+ offset += section.write(out);
+ }
+
+ /* Indicate that we're done */
+ out.close();
}
-
-
/**
- * Checks whether this {@link PropertySet} is a Document - * Summary Information.
+ * Writes a property set to a document in a POI filesystem directory. * - * @returntrue
if this {@link PropertySet}
- * represents a Document Summary Information, else false
.
+ * @param dir The directory in the POI filesystem to write the document to.
+ * @param name The document's name. If there is already a document with the
+ * same name in the directory the latter will be overwritten.
+ *
+ * @throws WritingNotSupportedException if the filesystem doesn't support writing
+ * @throws IOException if the old entry can't be deleted or the new entry be written
*/
- public boolean isDocumentSummaryInformation()
- {
- if (sections.size() <= 0)
- return false;
- return Util.equal(sections.get(0).getFormatID().getBytes(),
- SectionIDMap.DOCUMENT_SUMMARY_INFORMATION_ID[0]);
+ public void write(final DirectoryEntry dir, final String name)
+ throws WritingNotSupportedException, IOException {
+ /* If there is already an entry with the same name, remove it. */
+ if (dir.hasEntry(name)) {
+ final Entry e = dir.getEntry(name);
+ e.delete();
+ }
+
+ /* Create the new entry. */
+ dir.createDocument(name, toInputStream());
}
-
+ /**
+ * Returns the contents of this property set stream as an input stream.
+ * The latter can be used for example to write the property set into a POIFS
+ * document. The input stream represents a snapshot of the property set.
+ * If the latter is modified while the input stream is still being
+ * read, the modifications will not be reflected in the input stream but in
+ * the {@link MutablePropertySet} only.
+ *
+ * @return the contents of this property set stream
+ *
+ * @throws WritingNotSupportedException if HPSF does not yet support writing
+ * of a property's variant type.
+ * @throws IOException if an I/O exception occurs.
+ */
+ public InputStream toInputStream() throws IOException, WritingNotSupportedException {
+ final ByteArrayOutputStream psStream = new ByteArrayOutputStream();
+ try {
+ write(psStream);
+ } finally {
+ psStream.close();
+ }
+ final byte[] streamData = psStream.toByteArray();
+ return new ByteArrayInputStream(streamData);
+ }
/**
- * Convenience method returning the {@link Property} array - * contained in this property set. It is a shortcut for getting - * the {@link PropertySet}'s {@link Section}s list and then - * getting the {@link Property} array from the first {@link - * Section}.
+ * Fetches the property with the given ID, then does its + * best to return it as a String + * + * @param propertyId the property id + * + * @return The property as a String, or null if unavailable + */ + protected String getPropertyStringValue(final int propertyId) { + Object propertyValue = getProperty(propertyId); + return getPropertyStringValue(propertyValue); + } + + /** + * Return the string representation of a property value + * + * @param propertyValue the property value + * + * @return The property value as a String, or null if unavailable + */ + public static String getPropertyStringValue(final Object propertyValue) { + // Normal cases + if (propertyValue == null) { + return null; + } + if (propertyValue instanceof String) { + return (String)propertyValue; + } + + // Do our best with some edge cases + if (propertyValue instanceof byte[]) { + byte[] b = (byte[])propertyValue; + switch (b.length) { + case 0: + return ""; + case 1: + return Byte.toString(b[0]); + case 2: + return Integer.toString( LittleEndian.getUShort(b) ); + case 4: + return Long.toString( LittleEndian.getUInt(b) ); + default: + // Maybe it's a string? who knows! + return new String(b, Charset.forName("ASCII")); + } + } + return propertyValue.toString(); + } + + /** + * Checks whether this {@link PropertySet} represents a Summary Information. + * + * @return {@code true} if this {@link PropertySet} + * represents a Summary Information, else {@code false}. + */ + public boolean isSummaryInformation() { + return matchesSummary(SectionIDMap.SUMMARY_INFORMATION_ID); + } + + /** + * Checks whether this {@link PropertySet} is a Document Summary Information. + * + * @return {@code true} if this {@link PropertySet} + * represents a Document Summary Information, else {@code false}. + */ + public boolean isDocumentSummaryInformation() { + return matchesSummary(SectionIDMap.DOCUMENT_SUMMARY_INFORMATION_ID[0]); + } + + private boolean matchesSummary(byte[] summaryBytes) { + return !sections.isEmpty() && + Arrays.equals(getFirstSection().getFormatID().getBytes(), summaryBytes); + } + + + + /** + * Convenience method returning the {@link Property} array contained in this + * property set. It is a shortcut for getting he {@link PropertySet}'s + * {@link Section}s list and then getting the {@link Property} array from the + * first {@link Section}. * * @return The properties of the only {@link Section} of this * {@link PropertySet}. * @throws NoSingleSectionException if the {@link PropertySet} has * more or less than one {@link Section}. */ - public Property[] getProperties() - throws NoSingleSectionException - { + public Property[] getProperties() throws NoSingleSectionException { return getFirstSection().getProperties(); } /** - *Convenience method returning the value of the property with
- * the specified ID. If the property is not available,
- * null
is returned and a subsequent call to {@link
- * #wasNull} will return true
.
Convenience method returning the value of a boolean property
- * with the specified ID. If the property is not available,
- * false
is returned. A subsequent call to {@link
- * #wasNull} will return true
to let the caller
- * distinguish that case from a real property value of
- * false
.
Convenience method returning the value of the numeric
+ * Convenience method returning the value of the numeric
* property with the specified ID. If the property is not
* available, 0 is returned. A subsequent call to {@link #wasNull}
- * will return true
to let the caller distinguish
- * that case from a real property value of 0.
Checks whether the property which the last call to {@link + * Checks whether the property which the last call to {@link * #getPropertyIntValue} or {@link #getProperty} tried to access * was available or not. This information might be important for * callers of {@link #getPropertyIntValue} since the latter * returns 0 if the property does not exist. Using {@link * #wasNull}, the caller can distiguish this case from a - * property's real value of 0.
+ * property's real value of 0. * - * @returntrue
if the last call to {@link
+ * @return {@code true} if the last call to {@link
* #getPropertyIntValue} or {@link #getProperty} tried to access a
- * property that was not available, else false
.
+ * property that was not available, else {@code false}.
* @throws NoSingleSectionException if the {@link PropertySet} has
* more than one {@link Section}.
*/
- public boolean wasNull() throws NoSingleSectionException
- {
+ public boolean wasNull() throws NoSingleSectionException {
return getFirstSection().wasNull();
}
/**
- * Gets the {@link PropertySet}'s first section.
+ * Gets the {@link PropertySet}'s first section. * * @return The {@link PropertySet}'s first section. */ - public Section getFirstSection() - { - if (getSectionCount() < 1) + public Section getFirstSection() { + if (sections.isEmpty()) { throw new MissingSectionException("Property set does not contain any sections."); + } return sections.get(0); } /** - *If the {@link PropertySet} has only a single section this - * method returns it.
+ * If the {@link PropertySet} has only a single section this method returns it. * * @return The singleSection value */ - public Section getSingleSection() - { + public Section getSingleSection() { final int sectionCount = getSectionCount(); - if (sectionCount != 1) - throw new NoSingleSectionException - ("Property set contains " + sectionCount + " sections."); + if (sectionCount != 1) { + throw new NoSingleSectionException("Property set contains " + sectionCount + " sections."); + } return sections.get(0); } /** - *Returns true
if the PropertySet
is equal
- * to the specified parameter, else false
.
PropertySet
with
+ * @param o the object to compare this {@code PropertySet} with
*
- * @return true
if the objects are equal, false
+ * @return {@code true} if the objects are equal, {@code false}
* if not
*/
@Override
- public boolean equals(final Object o)
- {
- if (o == null || !(o instanceof PropertySet))
+ public boolean equals(final Object o) {
+ if (o == null || !(o instanceof PropertySet)) {
return false;
+ }
final PropertySet ps = (PropertySet) o;
int byteOrder1 = ps.getByteOrder();
int byteOrder2 = getByteOrder();
@@ -648,11 +822,12 @@ public class PropertySet
!classID1.equals(classID2) ||
format1 != format2 ||
osVersion1 != osVersion2 ||
- sectionCount1 != sectionCount2)
+ sectionCount1 != sectionCount2) {
return false;
+ }
/* Compare the sections: */
- return Util.equals(getSections(), ps.getSections());
+ return getSections().containsAll(ps.getSections());
}
@@ -660,8 +835,7 @@ public class PropertySet
/**
* @see Object#hashCode()
*/
- public int hashCode()
- {
+ public int hashCode() {
throw new UnsupportedOperationException("FIXME: Not yet implemented.");
}
@@ -670,8 +844,7 @@ public class PropertySet
/**
* @see Object#toString()
*/
- public String toString()
- {
+ public String toString() {
final StringBuilder b = new StringBuilder();
final int sectionCount = getSectionCount();
b.append(getClass().getName());
@@ -687,10 +860,32 @@ public class PropertySet
b.append(", sectionCount: ");
b.append(sectionCount);
b.append(", sections: [\n");
- for (Section section: getSections())
+ for (Section section: getSections()) {
b.append(section);
+ }
b.append(']');
b.append(']');
return b.toString();
}
+
+
+ protected void remove1stProperty(long id) {
+ getFirstSection().removeProperty(id);
+ }
+
+ protected void set1stProperty(long id, String value) {
+ getFirstSection().setProperty((int)id, value);
+ }
+
+ protected void set1stProperty(long id, int value) {
+ getFirstSection().setProperty((int)id, value);
+ }
+
+ protected void set1stProperty(long id, boolean value) {
+ getFirstSection().setProperty((int)id, value);
+ }
+
+ protected void set1stProperty(long id, byte[] value) {
+ getFirstSection().setProperty((int)id, value);
+ }
}
diff --git a/src/java/org/apache/poi/hpsf/PropertySetFactory.java b/src/java/org/apache/poi/hpsf/PropertySetFactory.java
index 4d4b4e638..db0a73d13 100644
--- a/src/java/org/apache/poi/hpsf/PropertySetFactory.java
+++ b/src/java/org/apache/poi/hpsf/PropertySetFactory.java
@@ -22,24 +22,22 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
-import org.apache.poi.hpsf.wellknown.SectionIDMap;
import org.apache.poi.poifs.filesystem.DirectoryEntry;
import org.apache.poi.poifs.filesystem.DocumentEntry;
import org.apache.poi.poifs.filesystem.DocumentInputStream;
/**
- * Factory class to create instances of {@link SummaryInformation}, - * {@link DocumentSummaryInformation} and {@link PropertySet}.
+ * Factory class to create instances of {@link SummaryInformation}, + * {@link DocumentSummaryInformation} and {@link PropertySet}. */ -public class PropertySetFactory -{ +public class PropertySetFactory { /** - *Creates the most specific {@link PropertySet} from an entry + * Creates the most specific {@link PropertySet} from an entry * in the specified POIFS Directory. This is preferrably a {@link * DocumentSummaryInformation} or a {@link SummaryInformation}. If * the specified entry does not contain a property set stream, an * exception is thrown. If no entry is found with the given name, - * an exception is thrown.
+ * an exception is thrown. * * @param dir The directory to find the PropertySet in * @param name The name of the entry containing the PropertySet @@ -52,55 +50,53 @@ public class PropertySetFactory * supported. */ public static PropertySet create(final DirectoryEntry dir, final String name) - throws FileNotFoundException, NoPropertySetStreamException, - IOException, UnsupportedEncodingException - { + throws FileNotFoundException, NoPropertySetStreamException, IOException, UnsupportedEncodingException { InputStream inp = null; try { DocumentEntry entry = (DocumentEntry)dir.getEntry(name); inp = new DocumentInputStream(entry); try { return create(inp); - } catch (MarkUnsupportedException e) { return null; } + } catch (MarkUnsupportedException e) { + return null; + } } finally { - if (inp != null) inp.close(); + if (inp != null) { + inp.close(); + } } } /** - *Creates the most specific {@link PropertySet} from an {@link + * Creates the most specific {@link PropertySet} from an {@link * InputStream}. This is preferrably a {@link * DocumentSummaryInformation} or a {@link SummaryInformation}. If * the specified {@link InputStream} does not contain a property * set stream, an exception is thrown and the {@link InputStream} - * is repositioned at its beginning.
+ * is repositioned at its beginning. * * @param stream Contains the property set stream's data. * @return The created {@link PropertySet}. * @throws NoPropertySetStreamException if the stream does not * contain a property set. * @throws MarkUnsupportedException if the stream does not support - * themark
operation.
+ * the {@code mark} operation.
* @throws IOException if some I/O problem occurs.
* @exception UnsupportedEncodingException if the specified codepage is not
* supported.
*/
public static PropertySet create(final InputStream stream)
- throws NoPropertySetStreamException, MarkUnsupportedException,
- UnsupportedEncodingException, IOException
- {
+ throws NoPropertySetStreamException, MarkUnsupportedException, UnsupportedEncodingException, IOException {
final PropertySet ps = new PropertySet(stream);
- try
- {
- if (ps.isSummaryInformation())
+ try {
+ if (ps.isSummaryInformation()) {
return new SummaryInformation(ps);
- else if (ps.isDocumentSummaryInformation())
+ } else if (ps.isDocumentSummaryInformation()) {
return new DocumentSummaryInformation(ps);
- else
+ } else {
return ps;
- }
- catch (UnexpectedPropertySetTypeException ex)
- {
+ }
+ } catch (UnexpectedPropertySetTypeException ex) {
/* This exception will never be throws because we already checked
* explicitly for this case above. */
throw new IllegalStateException(ex);
@@ -108,44 +104,20 @@ public class PropertySetFactory
}
/**
- * Creates a new summary information.
+ * Creates a new summary information. * * @return the new summary information. */ - public static SummaryInformation newSummaryInformation() - { - final MutablePropertySet ps = new MutablePropertySet(); - final MutableSection s = (MutableSection) ps.getFirstSection(); - s.setFormatID(SectionIDMap.SUMMARY_INFORMATION_ID); - try - { - return new SummaryInformation(ps); - } - catch (UnexpectedPropertySetTypeException ex) - { - /* This should never happen. */ - throw new HPSFRuntimeException(ex); - } + public static SummaryInformation newSummaryInformation() { + return new SummaryInformation(); } /** - *Creates a new document summary information.
+ * Creates a new document summary information. * * @return the new document summary information. */ - public static DocumentSummaryInformation newDocumentSummaryInformation() - { - final MutablePropertySet ps = new MutablePropertySet(); - final MutableSection s = (MutableSection) ps.getFirstSection(); - s.setFormatID(SectionIDMap.DOCUMENT_SUMMARY_INFORMATION_ID[0]); - try - { - return new DocumentSummaryInformation(ps); - } - catch (UnexpectedPropertySetTypeException ex) - { - /* This should never happen. */ - throw new HPSFRuntimeException(ex); - } + public static DocumentSummaryInformation newDocumentSummaryInformation() { + return new DocumentSummaryInformation(); } } \ No newline at end of file diff --git a/src/java/org/apache/poi/hpsf/Section.java b/src/java/org/apache/poi/hpsf/Section.java index c54fe51ab..a27e9485c 100644 --- a/src/java/org/apache/poi/hpsf/Section.java +++ b/src/java/org/apache/poi/hpsf/Section.java @@ -17,127 +17,96 @@ package org.apache.poi.hpsf; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; import java.io.UnsupportedEncodingException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; +import java.util.Date; import java.util.Map; +import java.util.TreeMap; +import org.apache.commons.collections4.bidimap.TreeBidiMap; import org.apache.poi.hpsf.wellknown.PropertyIDMap; import org.apache.poi.hpsf.wellknown.SectionIDMap; +import org.apache.poi.util.CodePageUtil; import org.apache.poi.util.LittleEndian; /** - *Represents a section in a {@link PropertySet}.
+ * Represents a section in a {@link PropertySet}. */ -public class Section -{ +public class Section { /** - *Maps property IDs to section-private PID strings. These - * strings can be found in the property with ID 0.
+ * Maps property IDs to section-private PID strings. These + * strings can be found in the property with ID 0. */ - protected MapThe section's format ID, {@link #getFormatID}.
+ * The section's format ID, {@link #getFormatID}. */ - protected ClassID formatID; - + private ClassID formatID; + /** + * If the "dirty" flag is true, the section's size must be + * (re-)calculated before the section is written. + */ + private boolean dirty = true; /** - *Returns the format ID. The format ID is the "type" of the
- * section. For example, if the format ID of the first {@link
- * Section} contains the bytes specified by
- * org.apache.poi.hpsf.wellknown.SectionIDMap.SUMMARY_INFORMATION_ID
- * the section (and thus the property set) is a SummaryInformation.
Returns the offset of the section in the stream.
- * - * @return The offset of the section in the stream. - */ - public long getOffset() - { - return offset; - } - - - - /** - * @see #getSize - */ - protected int size; - - - /** - *Returns the section's size in bytes.
- * - * @return The section's size in bytes. - */ - public int getSize() - { - return size; - } - - - - /** - *Returns the number of properties in this section.
- * - * @return The number of properties in this section. - */ - public int getPropertyCount() - { - return properties.length; - } - - - - /** - * @see #getProperties - */ - protected Property[] properties; - - - /** - *Returns this section's properties.
- * - * @return This section's properties. - */ - public Property[] getProperties() - { - return properties; - } - - - - /** - *Creates an empty and uninitialized {@link Section}. - */ - protected Section() - { } - - - - /** - *
Creates a {@link Section} instance from a byte array.
+ * Creates a {@link Section} instance from a byte array. * * @param src Contains the complete property set stream. * @param offset The position in the stream that points to the @@ -147,9 +116,7 @@ public class Section * supported. */ @SuppressWarnings("unchecked") - public Section(final byte[] src, final int offset) - throws UnsupportedEncodingException - { + public Section(final byte[] src, final int offset) throws UnsupportedEncodingException { int o1 = offset; /* @@ -201,82 +168,59 @@ public class Section * one looks for property ID 1 and extracts the codepage number. The * seconds pass reads the other properties. */ - properties = new Property[propertyCount]; - /* Pass 1: Read the property list. */ int pass1Offset = o1; - final ListRepresents an entry in the property list and holds a property's ID and - * its offset from the section's beginning.
+ * Retrieves the length of the given property (by key) + * + * @param offset2Id the offset to id map + * @param entryOffset the current entry key + * @param maxSize the maximum offset/size of the section stream + * @return the length of the current property */ - static class PropertyListEntry implements ComparableCompares this {@link PropertyListEntry} with another one by their - * offsets. A {@link PropertyListEntry} is "smaller" than another one if - * its offset from the section's begin is smaller.
- * - * @see Comparable#compareTo(java.lang.Object) - */ - public int compareTo(final PropertyListEntry o) - { - final int otherOffset = o.offset; - if (offset < otherOffset) - return -1; - else if (offset == otherOffset) - return 0; - else - return 1; - } - - - - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + id; - result = prime * result + length; - result = prime * result + offset; - return result; - } - - - - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - PropertyListEntry other = (PropertyListEntry) obj; - if (id != other.id) { - return false; - } - if (length != other.length) { - return false; - } - if (offset != other.offset) { - return false; - } - return true; - } - - - - public String toString() - { - final StringBuffer b = new StringBuffer(); - b.append(getClass().getName()); - b.append("[id="); - b.append(id); - b.append(", offset="); - b.append(offset); - b.append(", length="); - b.append(length); - b.append(']'); - return b.toString(); - } + private static int propLen( + TreeBidiMapReturns the value of the property with the specified ID. If
- * the property is not available, null
is returned
+ * Sets the section's format ID.
+ *
+ * @param formatID The section's format ID
+ */
+ public void setFormatID(final ClassID formatID) {
+ this.formatID = formatID;
+ }
+
+ /**
+ * Sets the section's format ID.
+ *
+ * @param formatID The section's format ID as a byte array. It components
+ * are in big-endian format.
+ */
+ public void setFormatID(final byte[] formatID) {
+ ClassID fid = getFormatID();
+ if (fid == null) {
+ fid = new ClassID();
+ setFormatID(fid);
+ }
+ fid.setBytes(formatID);
+ }
+
+ /**
+ * Returns the offset of the section in the stream.
+ *
+ * @return The offset of the section in the stream.
+ */
+ public long getOffset() {
+ return offset;
+ }
+
+ /**
+ * Returns the number of properties in this section.
+ *
+ * @return The number of properties in this section.
+ */
+ public int getPropertyCount() {
+ return properties.size();
+ }
+
+ /**
+ * Returns this section's properties.
+ *
+ * @return This section's properties.
+ */
+ public Property[] getProperties() {
+ return properties.values().toArray(new Property[properties.size()]);
+ }
+
+ /**
+ * Sets this section's properties. Any former values are overwritten.
+ *
+ * @param properties This section's new properties.
+ */
+ public void setProperties(final Property[] properties) {
+ this.properties.clear();
+ for (Property p : properties) {
+ this.properties.put(p.getID(), p);
+ }
+ dirty = true;
+ }
+
+ /**
+ * Returns the value of the property with the specified ID. If
+ * the property is not available, {@code null} is returned
* and a subsequent call to {@link #wasNull} will return
- * true
.
Returns the value of the numeric property with the specified
+ * Sets the long value of the property with the specified ID.
+ *
+ * @param id The property's ID
+ * @param value The property's value.
+ *
+ * @see #setProperty(int, long, Object)
+ * @see #getProperty
+ */
+ public void setProperty(final int id, final long value) {
+ setProperty(id, Variant.VT_I8, Long.valueOf(value));
+ }
+
+
+
+ /**
+ * Sets the boolean value of the property with the specified ID.
+ *
+ * @param id The property's ID
+ * @param value The property's value.
+ *
+ * @see #setProperty(int, long, Object)
+ * @see #getProperty
+ */
+ public void setProperty(final int id, final boolean value) {
+ setProperty(id, Variant.VT_BOOL, Boolean.valueOf(value));
+ }
+
+
+
+ /**
+ * Sets the value and the variant type of the property with the
+ * specified ID. If a property with this ID is not yet present in
+ * the section, it will be added. An already present property with
+ * the specified ID will be overwritten. A default mapping will be
+ * used to choose the property's type.
+ *
+ * @param id The property's ID.
+ * @param variantType The property's variant type.
+ * @param value The property's value.
+ *
+ * @see #setProperty(int, String)
+ * @see #getProperty
+ * @see Variant
+ */
+ public void setProperty(final int id, final long variantType, final Object value) {
+ setProperty(new Property(id, variantType, value));
+ }
+
+
+
+ /**
+ * Sets a property.
+ *
+ * @param p The property to be set.
+ *
+ * @see #setProperty(int, long, Object)
+ * @see #getProperty
+ * @see Variant
+ */
+ public void setProperty(final Property p) {
+ Property old = properties.get(p.getID());
+ if (old == null || !old.equals(p)) {
+ properties.put(p.getID(), p);
+ dirty = true;
+ }
+ }
+
+ /**
+ * Sets a property.
+ *
+ * @param id The property ID.
+ * @param value The property's value. The value's class must be one of those
+ * supported by HPSF.
+ */
+ public void setProperty(final int id, final Object value) {
+ if (value instanceof String) {
+ setProperty(id, (String) value);
+ } else if (value instanceof Long) {
+ setProperty(id, ((Long) value).longValue());
+ } else if (value instanceof Integer) {
+ setProperty(id, ((Integer) value).intValue());
+ } else if (value instanceof Short) {
+ setProperty(id, ((Short) value).intValue());
+ } else if (value instanceof Boolean) {
+ setProperty(id, ((Boolean) value).booleanValue());
+ } else if (value instanceof Date) {
+ setProperty(id, Variant.VT_FILETIME, value);
+ } else {
+ throw new HPSFRuntimeException(
+ "HPSF does not support properties of type " +
+ value.getClass().getName() + ".");
+ }
+ }
+
+ /**
+ * Returns the value of the numeric property with the specified
* ID. If the property is not available, 0 is returned. A
* subsequent call to {@link #wasNull} will return
- * true
to let the caller distinguish that case from
- * a real property value of 0.
Returns the value of the boolean property with the specified
- * ID. If the property is not available, false
is
+ * Returns the value of the boolean property with the specified
+ * ID. If the property is not available, {@code false} is
* returned. A subsequent call to {@link #wasNull} will return
- * true
to let the caller distinguish that case from
- * a real property value of false
.
This member is true
if the last call to {@link
- * #getPropertyIntValue} or {@link #getProperty} tried to access a
- * property that was not available, else false
.
Checks whether the property which the last call to {@link + * Checks whether the property which the last call to {@link * #getPropertyIntValue} or {@link #getProperty} tried to access * was available or not. This information might be important for * callers of {@link #getPropertyIntValue} since the latter * returns 0 if the property does not exist. Using {@link * #wasNull} the caller can distiguish this case from a property's - * real value of 0.
+ * real value of 0. * - * @returntrue
if the last call to {@link
+ * @return {@code true} if the last call to {@link
* #getPropertyIntValue} or {@link #getProperty} tried to access a
- * property that was not available, else false
.
+ * property that was not available, else {@code false}.
*/
- public boolean wasNull()
- {
+ public boolean wasNull() {
return wasNull;
}
/**
- * Returns the PID string associated with a property ID. The ID + * Returns the PID string associated with a property ID. The ID * is first looked up in the {@link Section}'s private * dictionary. If it is not found there, the method calls {@link - * SectionIDMap#getPIDString}.
+ * SectionIDMap#getPIDString}. * * @param pid The property ID * * @return The property ID's string value */ - public String getPIDString(final long pid) - { + public String getPIDString(final long pid) { String s = null; if (dictionary != null) { s = dictionary.get(Long.valueOf(pid)); @@ -491,39 +600,65 @@ public class Section return s; } + /** + * Removes all properties from the section including 0 (dictionary) and + * 1 (codepage). + */ + public void clear() + { + final Property[] properties = getProperties(); + for (int i = 0; i < properties.length; i++) + { + final Property p = properties[i]; + removeProperty(p.getID()); + } + } + + /** + * Sets the codepage. + * + * @param codepage the codepage + */ + public void setCodepage(final int codepage) + { + setProperty(PropertyIDMap.PID_CODEPAGE, Variant.VT_I2, + Integer.valueOf(codepage)); + } + /** - *Checks whether this section is equal to another object. The result is
- * false
if one of the the following conditions holds:
The other object is not a {@link Section}.
The format IDs of the two sections are not equal.
The sections have a different number of properties. However, - * properties with ID 1 (codepage) are not counted.
The other object is not a {@link Section}.
The properties have different values. The order of the properties - * is irrelevant.
true
if the objects are equal, false
if
+ * @return {@code true} if the objects are equal, {@code false} if
* not
*/
- public boolean equals(final Object o)
- {
- if (o == null || !(o instanceof Section))
+ public boolean equals(final Object o) {
+ if (o == null || !(o instanceof Section)) {
return false;
+ }
final Section s = (Section) o;
- if (!s.getFormatID().equals(getFormatID()))
+ if (!s.getFormatID().equals(getFormatID())) {
return false;
+ }
/* Compare all properties except 0 and 1 as they must be handled
* specially. */
@@ -536,34 +671,26 @@ public class Section
* arrays. */
Property p10 = null;
Property p20 = null;
- for (int i = 0; i < pa1.length; i++)
- {
+ for (int i = 0; i < pa1.length; i++) {
final long id = pa1[i].getID();
- if (id == 0)
- {
+ if (id == 0) {
p10 = pa1[i];
pa1 = remove(pa1, i);
i--;
}
- if (id == 1)
- {
- // p11 = pa1[i];
+ if (id == 1) {
pa1 = remove(pa1, i);
i--;
}
}
- for (int i = 0; i < pa2.length; i++)
- {
+ for (int i = 0; i < pa2.length; i++) {
final long id = pa2[i].getID();
- if (id == 0)
- {
+ if (id == 0) {
p20 = pa2[i];
pa2 = remove(pa2, i);
i--;
}
- if (id == 1)
- {
- // p21 = pa2[i];
+ if (id == 1) {
pa2 = remove(pa2, i);
i--;
}
@@ -571,52 +698,266 @@ public class Section
/* If the number of properties (not counting property 1) is unequal the
* sections are unequal. */
- if (pa1.length != pa2.length)
+ if (pa1.length != pa2.length) {
return false;
+ }
/* If the dictionaries are unequal the sections are unequal. */
boolean dictionaryEqual = true;
- if (p10 != null && p20 != null)
+ if (p10 != null && p20 != null) {
dictionaryEqual = p10.getValue().equals(p20.getValue());
- else if (p10 != null || p20 != null)
+ } else if (p10 != null || p20 != null) {
dictionaryEqual = false;
+ }
if (dictionaryEqual) {
return Util.equals(pa1, pa2);
}
return false;
}
-
+ /**
+ * Removes a property.
+ *
+ * @param id The ID of the property to be removed
+ */
+ public void removeProperty(final long id) {
+ dirty |= (properties.remove(id) != null);
+ }
/**
- * Removes a field from a property array. The resulting array is - * compactified and returned.
+ * Removes a field from a property array. The resulting array is + * compactified and returned. * * @param pa The property array. * @param i The index of the field to be removed. * @return the compactified array. */ - private Property[] remove(final Property[] pa, final int i) - { + private Property[] remove(final Property[] pa, final int i) { final Property[] h = new Property[pa.length - 1]; - if (i > 0) + if (i > 0) { System.arraycopy(pa, 0, h, 0, i); + } System.arraycopy(pa, i + 1, h, i, h.length - i); return h; } + /** + * Writes this section into an output stream.
+ *
+ * Internally this is done by writing into three byte array output
+ * streams: one for the properties, one for the property list and one for
+ * the section as such. The two former are appended to the latter when they
+ * have received all their data.
+ *
+ * @param out The stream to write into.
+ *
+ * @return The number of bytes written, i.e. the section's size.
+ * @exception IOException if an I/O error occurs
+ * @exception WritingNotSupportedException if HPSF does not yet support
+ * writing a property's variant type.
+ */
+ public int write(final OutputStream out) throws WritingNotSupportedException, IOException {
+ /* Check whether we have already generated the bytes making out the
+ * section. */
+ if (!dirty && sectionBytes != null) {
+ out.write(sectionBytes);
+ return sectionBytes.length;
+ }
+
+ /* The properties are written to this stream. */
+ final ByteArrayOutputStream propertyStream = new ByteArrayOutputStream();
+
+ /* The property list is established here. After each property that has
+ * been written to "propertyStream", a property list entry is written to
+ * "propertyListStream". */
+ final ByteArrayOutputStream propertyListStream = new ByteArrayOutputStream();
+
+ /* Maintain the current position in the list. */
+ int position = 0;
+
+ /* Increase the position variable by the size of the property list so
+ * that it points behind the property list and to the beginning of the
+ * properties themselves. */
+ position += 2 * LittleEndian.INT_SIZE + getPropertyCount() * 2 * LittleEndian.INT_SIZE;
+
+ /* Writing the section's dictionary it tricky. If there is a dictionary
+ * (property 0) the codepage property (property 1) must be set, too. */
+ int codepage = -1;
+ if (getProperty(PropertyIDMap.PID_DICTIONARY) != null) {
+ final Object p1 = getProperty(PropertyIDMap.PID_CODEPAGE);
+ if (p1 != null) {
+ if (!(p1 instanceof Integer)) {
+ throw new IllegalPropertySetDataException
+ ("The codepage property (ID = 1) must be an " +
+ "Integer object.");
+ }
+ } else {
+ /* Warning: The codepage property is not set although a
+ * dictionary is present. In order to cope with this problem we
+ * add the codepage property and set it to Unicode. */
+ setProperty(PropertyIDMap.PID_CODEPAGE, Variant.VT_I2,
+ Integer.valueOf(CodePageUtil.CP_UNICODE));
+ }
+ codepage = getCodepage();
+ }
+
+ /* Write the properties and the property list into their respective
+ * streams: */
+ for (Property p : properties.values()) {
+ final long id = p.getID();
+
+ /* Write the property list entry. */
+ TypeWriter.writeUIntToStream(propertyListStream, p.getID());
+ TypeWriter.writeUIntToStream(propertyListStream, position);
+
+ /* If the property ID is not equal 0 we write the property and all
+ * is fine. However, if it equals 0 we have to write the section's
+ * dictionary which has an implicit type only and an explicit
+ * value. */
+ if (id != 0)
+ /* Write the property and update the position to the next
+ * property. */
+ position += p.write(propertyStream, getCodepage());
+ else
+ {
+ if (codepage == -1)
+ throw new IllegalPropertySetDataException
+ ("Codepage (property 1) is undefined.");
+ position += writeDictionary(propertyStream, dictionary,
+ codepage);
+ }
+ }
+ propertyStream.close();
+ propertyListStream.close();
+
+ /* Write the section: */
+ byte[] pb1 = propertyListStream.toByteArray();
+ byte[] pb2 = propertyStream.toByteArray();
+
+ /* Write the section's length: */
+ TypeWriter.writeToStream(out, LittleEndian.INT_SIZE * 2 +
+ pb1.length + pb2.length);
+
+ /* Write the section's number of properties: */
+ TypeWriter.writeToStream(out, getPropertyCount());
+
+ /* Write the property list: */
+ out.write(pb1);
+
+ /* Write the properties: */
+ out.write(pb2);
+
+ int streamLength = LittleEndian.INT_SIZE * 2 + pb1.length + pb2.length;
+ return streamLength;
+ }
+
+
+
+ /**
+ * Writes the section's dictionary.
+ *
+ * @param out The output stream to write to.
+ * @param dictionary The dictionary.
+ * @param codepage The codepage to be used to write the dictionary items.
+ * @return The number of bytes written
+ * @exception IOException if an I/O exception occurs.
+ */
+ private static int writeDictionary(final OutputStream out, final Map Gets the section's dictionary. A dictionary allows an application to
+ * Gets the section's dictionary. A dictionary allows an application to
* use human-readable property names instead of numeric property IDs. It
* contains mappings from property IDs to their associated string
* values. The dictionary is stored as the property with ID 0. The codepage
- * for the strings in the dictionary is defined by property with ID 1. Gets the section's codepage, if any. Abstract superclass for the convenience classes {@link
- * SummaryInformation} and {@link DocumentSummaryInformation}.
*
- * The motivation behind this class is quite nasty if you look
- * behind the scenes, but it serves the application programmer well by
- * providing him with the easy-to-use {@link SummaryInformation} and
- * {@link DocumentSummaryInformation} classes. When parsing the data a
- * property set stream consists of (possibly coming from an {@link
- * java.io.InputStream}) we want to read and process each byte only
- * once. Since we don't know in advance which kind of property set we
- * have, we can expect only the most general {@link
- * PropertySet}. Creating a special subclass should be as easy as
- * calling the special subclass' constructor and pass the general
- * {@link PropertySet} in. To make things easy internally, the special
- * class just holds a reference to the general {@link PropertySet} and
- * delegates all method calls to it. A cleaner implementation would have been like this: The {@link
- * PropertySetFactory} parses the stream data into some internal
- * object first. Then it finds out whether the stream is a {@link
- * SummaryInformation}, a {@link DocumentSummaryInformation} or a
- * general {@link PropertySet}. However, the current implementation
- * went the other way round historically: the convenience classes came
- * only late to my mind. The "real" property set Creates a Creates a Convenience class representing a Summary Information stream in a
- * Microsoft Office document. The document name a summary information stream usually has in a POIFS
- * filesystem. Creates a {@link SummaryInformation} from a given {@link
- * PropertySet}. Returns the title (or Sets the title. Removes the title. Returns the subject (or Sets the subject. Removes the subject. Returns the author (or Sets the author. Removes the author. Returns the keywords (or Sets the keywords. Removes the keywords. Returns the comments (or Sets the comments. Removes the comments. Returns the template (or Sets the template. Removes the template. Returns the last author (or Sets the last author. Removes the last author. Returns the revision number (or Sets the revision number. Removes the revision number. Returns the total time spent in editing the document (or
- * Sets the total time spent in editing the document. Remove the total time spent in editing the document. Returns the last printed time (or Sets the lastPrinted. Removes the lastPrinted. Returns the creation time (or Sets the creation time. Removes the creation time. Returns the last save time (or Sets the total time spent in editing the document. Remove the total time spent in editing the document. Returns the page count or 0 if the {@link SummaryInformation} does
- * not contain a page count. Sets the page count. Removes the page count. Returns the word count or 0 if the {@link SummaryInformation} does
- * not contain a word count. Sets the word count. Removes the word count. Returns the character count or 0 if the {@link SummaryInformation}
- * does not contain a char count. Sets the character count. Removes the character count. Returns the thumbnail (or
*
- * To process this data, you may wish to make use of the
+ * To process this data, you may wish to make use of the
* {@link Thumbnail} class. The raw data is generally
- * an image in WMF or Clipboard (BMP?) format Returns the thumbnail (or Sets the thumbnail. Removes the thumbnail. Returns the application name (or Sets the application name. Removes the application name. Returns a security code which is one of the following values: 0 if the {@link SummaryInformation} does not contain a
+ * 1 if the document is password protected 2 if the document is read-only recommended 4 if the document is read-only enforced 8 if the document is locked for annotations Sets the security code. Removes the security code. Provides various static utility methods. Checks whether two byte arrays a and b
- * are equal. They are equal if they have the same length and if for each i with
- * i >= 0 and
- * i < a.length holds
- * a[i] == b[i]. Copies a part of a byte array into another byte array. Concatenates the contents of several byte arrays into a
- * single one. Copies bytes from a source byte array into a new byte
- * array. The difference between the Windows epoch (1601-01-01
* 00:00:00) and the Unix epoch (1970-01-01 00:00:00) in
@@ -189,36 +96,6 @@ public class Util
}
- /**
- * Checks whether two collections are equal. Two collections
- * C1 and C2 are equal, if the following conditions
- * are true: For each c1i (element of C1) there
- * is a c2j (element of C2), and
- * c1i equals c2j. For each c2i (element of C2) there
- * is a c1j (element of C1) and
- * c2i equals c1j. Compares to object arrays with regarding the objects' order. For
@@ -231,24 +108,17 @@ public class Util
*/
public static boolean equals(Object[] c1, Object[] c2)
{
- final Object[] o1 = c1.clone();
- final Object[] o2 = c2.clone();
- return internalEquals(o1, o2);
- }
-
- private static boolean internalEquals(Object[] o1, Object[] o2)
- {
- for (int i1 = 0; i1 < o1.length; i1++)
+ for (int i1 = 0; i1 < c1.length; i1++)
{
- final Object obj1 = o1[i1];
+ final Object obj1 = c1[i1];
boolean matchFound = false;
- for (int i2 = 0; !matchFound && i2 < o1.length; i2++)
+ for (int i2 = 0; !matchFound && i2 < c1.length; i2++)
{
- final Object obj2 = o2[i2];
+ final Object obj2 = c2[i2];
if (obj1.equals(obj2))
{
matchFound = true;
- o2[i2] = null;
+ c2[i2] = null;
}
}
if (!matchFound)
@@ -257,8 +127,6 @@ public class Util
return true;
}
-
-
/**
* Pads a byte array with 0x00 bytes so that its length is a multiple of
* 4. Pads a character array with 0x0000 characters so that its length is a
- * multiple of 4. Pads a string with 0x0000 characters so that its length is a
- * multiple of 4. Returns a textual representation of a {@link Throwable}, including a
* stacktrace. Supports reading and writing of variant data.
*
- * FIXME (3): Reading and writing should be made more
- * uniform than it is now. The following items should be resolved:
+ * FIXME (3): Reading and writing should be made more
+ * uniform than it is now. The following items should be resolved:
*
* Reading requires a length parameter that is 4 byte greater than the
- * actual data, because the variant type field is included. Reading reads from a byte array while writing writes to an byte array
- * output stream. Specifies whether warnings about unsupported variant types are to be
- * written to Checks whether logging of unsupported variant types warning is turned
- * on or off. Keeps a list of the variant types an "unsupported" message has already
- * been issued for. Writes a warning to HPSF is able to read these {@link Variant} types. Checks whether HPSF supports the specified variant type. Unsupported
+ * Checks whether HPSF supports the specified variant type. Unsupported
* types should be implemented included in the {@link #SUPPORTED_TYPES}
- * array. Reads a variant type from a byte array. Turns a codepage number into the equivalent character encoding's
- * name. Writes a variant value to an output stream. This method ensures that
- * always a multiple of 4 bytes is written.
*
- * If the codepage is UTF-16, which is encouraged, strings
+ * If the codepage is UTF-16, which is encouraged, strings
* must always be written as {@link Variant#VT_LPWSTR}
* strings, not as {@link Variant#VT_LPSTR} strings. This method ensure this
- * by converting strings appropriately, if needed.null
if the section does not have
+ * @return the dictionary or {@code null} if the section does not have
* a dictionary.
*/
- public MapSpecialPropertySet
- * delegates to.SpecialPropertySet
.
- *
- * @param ps The property set to be encapsulated by the
- * SpecialPropertySet
- */
- public SpecialPropertySet(final PropertySet ps)
- {
- delegate = new MutablePropertySet(ps);
+@Removal(version="3.18")
+public class SpecialPropertySet extends MutablePropertySet {
+ public SpecialPropertySet() {
}
-
-
- /**
- * SpecialPropertySet
.
- *
- * @param ps The mutable property set to be encapsulated by the
- * SpecialPropertySet
- */
- public SpecialPropertySet(final MutablePropertySet ps)
- {
- delegate = ps;
+ public SpecialPropertySet(final PropertySet ps) throws UnexpectedPropertySetTypeException {
+ super(ps);
}
-
-
-
- /**
- * @see PropertySet#getByteOrder
- */
- @Override
- public int getByteOrder()
- {
- return delegate.getByteOrder();
- }
-
-
-
- /**
- * @see PropertySet#getFormat
- */
- @Override
- public int getFormat()
- {
- return delegate.getFormat();
- }
-
-
-
- /**
- * @see PropertySet#getOSVersion
- */
- @Override
- public int getOSVersion()
- {
- return delegate.getOSVersion();
- }
-
-
-
- /**
- * @see PropertySet#getClassID
- */
- @Override
- public ClassID getClassID()
- {
- return delegate.getClassID();
- }
-
-
-
- /**
- * @see PropertySet#getSectionCount
- */
- @Override
- public int getSectionCount()
- {
- return delegate.getSectionCount();
- }
-
-
-
- /**
- * @see PropertySet#getSections
- */
- @Override
- public Listnull
).null
+ * @return The title or {@code null}
*/
- public String getTitle()
- {
+ public String getTitle() {
return getPropertyStringValue(PropertyIDMap.PID_TITLE);
}
/**
- * null
).null
+ * @return The subject or {@code null}
*/
- public String getSubject()
- {
+ public String getSubject() {
return getPropertyStringValue(PropertyIDMap.PID_SUBJECT);
}
/**
- * null
).null
+ * @return The author or {@code null}
*/
- public String getAuthor()
- {
+ public String getAuthor() {
return getPropertyStringValue(PropertyIDMap.PID_AUTHOR);
}
/**
- * null
).null
+ * @return The keywords or {@code null}
*/
- public String getKeywords()
- {
+ public String getKeywords() {
return getPropertyStringValue(PropertyIDMap.PID_KEYWORDS);
}
/**
- * null
).null
+ * @return The comments or {@code null}
*/
- public String getComments()
- {
+ public String getComments() {
return getPropertyStringValue(PropertyIDMap.PID_COMMENTS);
}
/**
- * null
).null
+ * @return The template or {@code null}
*/
- public String getTemplate()
- {
+ public String getTemplate() {
return getPropertyStringValue(PropertyIDMap.PID_TEMPLATE);
}
/**
- * null
).null
+ * @return The last author or {@code null}
*/
- public String getLastAuthor()
- {
+ public String getLastAuthor() {
return getPropertyStringValue(PropertyIDMap.PID_LASTAUTHOR);
}
/**
- * null
). null
+ * @return The revision number or {@code null}
*/
- public String getRevNumber()
- {
+ public String getRevNumber() {
return getPropertyStringValue(PropertyIDMap.PID_REVNUMBER);
}
/**
- * 0
).null
).null
+ * @return The last printed time or {@code null}
*/
- public Date getLastPrinted()
- {
+ public Date getLastPrinted() {
return (Date) getProperty(PropertyIDMap.PID_LASTPRINTED);
}
/**
- * null
).null
+ * @return The creation time or {@code null}
*/
- public Date getCreateDateTime()
- {
+ public Date getCreateDateTime() {
return (Date) getProperty(PropertyIDMap.PID_CREATE_DTM);
}
/**
- * null
).null
+ * @return The last save time or {@code null}
*/
- public Date getLastSaveDateTime()
- {
+ public Date getLastSaveDateTime() {
return (Date) getProperty(PropertyIDMap.PID_LASTSAVE_DTM);
}
/**
- * null
+ * @return The word count or {@code null}
*/
- public int getWordCount()
- {
+ public int getWordCount() {
return getPropertyIntValue(PropertyIDMap.PID_WORDCOUNT);
}
/**
- * null
+ * @return The character count or {@code null}
*/
- public int getCharCount()
- {
+ public int getCharCount() {
return getPropertyIntValue(PropertyIDMap.PID_CHARCOUNT);
}
/**
- * null
) when this
+ * Returns the thumbnail (or {@code null}) when this
* method is implemented. Please note that the return type is likely to
- * change!null
+ * @return The thumbnail or {@code null}
*/
- public byte[] getThumbnail()
- {
+ public byte[] getThumbnail() {
return (byte[]) getProperty(PropertyIDMap.PID_THUMBNAIL);
}
/**
- * null
), processed
+ * Returns the thumbnail (or {@code null}), processed
* as an object which is (largely) able to unpack the thumbnail
- * image data.null
+ * @return The thumbnail or {@code null}
*/
- public Thumbnail getThumbnailThumbnail()
- {
+ public Thumbnail getThumbnailThumbnail() {
byte[] data = getThumbnail();
if (data == null) return null;
return new Thumbnail(data);
@@ -648,115 +578,100 @@ public final class SummaryInformation extends SpecialPropertySet {
/**
- * null
).null
+ * @return The application name or {@code null}
*/
- public String getApplicationName()
- {
+ public String getApplicationName() {
return getPropertyStringValue(PropertyIDMap.PID_APPNAME);
}
/**
- *
*
- *
null
+ * @return The security code or {@code null}
*/
- public int getSecurity()
- {
+ public int getSecurity() {
return getPropertyIntValue(PropertyIDMap.PID_SECURITY);
}
/**
- *
- *
- *
- *
- * @param a The first byte array
- * @param b The first byte array
- * @return true
if the byte arrays are equal, else
- * false
- */
- public static boolean equal(final byte[] a, final byte[] b)
- {
- if (a.length != b.length)
- return false;
- for (int i = 0; i < a.length; i++)
- if (a[i] != b[i])
- return false;
- return true;
- }
-
-
-
- /**
- *
- *
- *
- *
- * @param c1 the first collection
- * @param c2 the second collection
- * @return true
if the collections are equal, else
- * false
.
- */
- public static boolean equals(Collection> c1, Collection> c2)
- {
- Object[] o1 = c1.toArray();
- Object[] o2 = c2.toArray();
- return internalEquals(o1, o2);
- }
-
-
/**
*
*
- *
*/
-public class VariantSupport extends Variant
-{
- private static final POILogger logger = POILogFactory.getLogger(VariantSupport.class);
+public class VariantSupport extends Variant {
+ /**
+ * HPSF is able to read these {@link Variant} types.
+ */
+ public static final int[] SUPPORTED_TYPES = { Variant.VT_EMPTY,
+ Variant.VT_I2, Variant.VT_I4, Variant.VT_I8, Variant.VT_R8,
+ Variant.VT_FILETIME, Variant.VT_LPSTR, Variant.VT_LPWSTR,
+ Variant.VT_CF, Variant.VT_BOOL };
+
+
+ private static final POILogger logger = POILogFactory.getLogger(VariantSupport.class);
private static boolean logUnsupportedTypes = false;
/**
- * System.err
or not.true
warnings will be written,
- * if false
they won't.
+ * Keeps a list of the variant types an "unsupported" message has already
+ * been issued for.
*/
- public static void setLogUnsupportedTypes(final boolean logUnsupportedTypes)
- {
+ protected static Listtrue
if logging is turned on, else
- * false
.
+ * @return {@code true} if logging is turned on, else
+ * {@code false}.
*/
- public static boolean isLogUnsupportedTypes()
- {
+ public static boolean isLogUnsupportedTypes() {
return logUnsupportedTypes;
}
/**
- * System.err
that a variant type is
+ * Writes a warning to {@code System.err} that a variant type is
* unsupported by HPSF. Such a warning is written only once for each variant
- * type. Log messages can be turned on or off by true
if HPFS supports this type, else
- * false
+ * @return {@code true} if HPFS supports this type, else
+ * {@code false}
*/
- public boolean isSupportedType(final int variantType)
- {
+ public boolean isSupportedType(final int variantType) {
for (int i = 0; i < SUPPORTED_TYPES.length; i++)
if (variantType == SUPPORTED_TYPES[i])
return true;
@@ -136,7 +133,7 @@ public class VariantSupport extends Variant
/**
- *