You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@poi.apache.org by ki...@apache.org on 2016/11/27 20:19:19 UTC

svn commit: r1771640 [2/4] - in /poi: site/src/documentation/content/xdocs/ trunk/src/java/org/apache/poi/hpsf/ trunk/src/java/org/apache/poi/hpsf/extractor/ trunk/src/java/org/apache/poi/util/ trunk/src/testcases/org/apache/poi/hpsf/basic/

Modified: poi/trunk/src/java/org/apache/poi/hpsf/MutableSection.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/hpsf/MutableSection.java?rev=1771640&r1=1771639&r2=1771640&view=diff
==============================================================================
--- poi/trunk/src/java/org/apache/poi/hpsf/MutableSection.java (original)
+++ poi/trunk/src/java/org/apache/poi/hpsf/MutableSection.java Sun Nov 27 20:19:18 2016
@@ -17,683 +17,27 @@
 
 package org.apache.poi.hpsf;
 
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.Date;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.ListIterator;
-import java.util.Map;
+import java.io.UnsupportedEncodingException;
 
-import org.apache.poi.hpsf.wellknown.PropertyIDMap;
-import org.apache.poi.util.CodePageUtil;
-import org.apache.poi.util.LittleEndian;
+import org.apache.poi.util.Removal;
 
 /**
  * <p>Adds writing capability to the {@link Section} class.</p>
  *
  * <p>Please be aware that this class' functionality will be merged into the
  * {@link Section} class at a later time, so the API will change.</p>
+ *
+ * @deprecated POI 3.16 - use Section as base class instead
  */
-public class MutableSection extends Section
-{
-    /**
-     * <p>If the "dirty" flag is true, the section's size must be
-     * (re-)calculated before the section is written.</p>
-     */
-    private boolean dirty = true;
-
-
-
-    /**
-     * <p>List to assemble the properties. Unfortunately a wrong
-     * decision has been taken when specifying the "properties" field
-     * as an Property[]. It should have been a {@link java.util.List}.</p>
-     */
-    private List<Property> preprops;
-
-
-
-    /**
-     * <p>Contains the bytes making out the section. This byte array is
-     * established when the section's size is calculated and can be reused
-     * later. It is valid only if the "dirty" flag is false.</p>
-     */
-    private byte[] sectionBytes;
-
-
-
-    /**
-     * <p>Creates an empty mutable section.</p>
-     */
-    public MutableSection()
-    {
-        dirty = true;
-        formatID = null;
-        offset = -1;
-        preprops = new LinkedList<Property>();
-    }
-
-
-
-    /**
-     * <p>Constructs a <code>MutableSection</code> by doing a deep copy of an
-     * existing <code>Section</code>. All nested <code>Property</code>
-     * instances, will be their mutable counterparts in the new
-     * <code>MutableSection</code>.</p>
-     *
-     * @param s The section set to copy
-     */
-    public MutableSection(final Section s)
-    {
-        setFormatID(s.getFormatID());
-        final Property[] pa = s.getProperties();
-        final MutableProperty[] mpa = new MutableProperty[pa.length];
-        for (int i = 0; i < pa.length; i++)
-            mpa[i] = new MutableProperty(pa[i]);
-        setProperties(mpa);
-        setDictionary(s.getDictionary());
-    }
-
-
-
-    /**
-     * <p>Sets the section's format ID.</p>
-     *
-     * @param formatID The section's format ID
-     *
-     * @see #setFormatID(byte[])
-     * @see Section#getFormatID
-     */
-    public void setFormatID(final ClassID formatID)
-    {
-        this.formatID = formatID;
-    }
-
-
-
-    /**
-     * <p>Sets the section's format ID.</p>
-     *
-     * @param formatID The section's format ID as a byte array. It components
-     * are in big-endian format.
-     *
-     * @see #setFormatID(ClassID)
-     * @see Section#getFormatID
-     */
-    public void setFormatID(final byte[] formatID)
-    {
-        ClassID fid = getFormatID();
-        if (fid == null)
-        {
-            fid = new ClassID();
-            setFormatID(fid);
-        }
-        fid.setBytes(formatID);
-    }
-
-
-
-    /**
-     * <p>Sets this section's properties. Any former values are overwritten.</p>
-     *
-     * @param properties This section's new properties.
-     */
-    public void setProperties(final Property[] properties)
-    {
-        this.properties = properties;
-        preprops = new LinkedList<Property>();
-        for (int i = 0; i < properties.length; i++)
-            preprops.add(properties[i]);
-        dirty = true;
-    }
-
-
-
-    /**
-     * <p>Sets the string value of the property with the specified ID.</p>
-     *
-     * @param id The property's ID
-     * @param value The property's value. It will be written as a Unicode
-     * string.
-     *
-     * @see #setProperty(int, long, Object)
-     * @see #getProperty
-     */
-    public void setProperty(final int id, final String value)
-    {
-        setProperty(id, Variant.VT_LPWSTR, value);
-        dirty = true;
-    }
-
-
-
-    /**
-     * <p>Sets the int value of the property with the specified ID.</p>
-     *
-     * @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 int value)
-    {
-        setProperty(id, Variant.VT_I4, Integer.valueOf(value));
-        dirty = true;
-    }
-
-
-
-    /**
-     * <p>Sets the long value of the property with the specified ID.</p>
-     *
-     * @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));
-        dirty = true;
-    }
-
-
-
-    /**
-     * <p>Sets the boolean value of the property with the specified ID.</p>
-     *
-     * @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));
-        dirty = true;
-    }
-
-
-
-    /**
-     * <p>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.</p>
-     *
-     * @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)
-    {
-        final MutableProperty p = new MutableProperty();
-        p.setID(id);
-        p.setType(variantType);
-        p.setValue(value);
-        setProperty(p);
-        dirty = true;
-    }
-
-
-
-    /**
-     * <p>Sets a property.</p>
-     *
-     * @param p The property to be set.
-     *
-     * @see #setProperty(int, long, Object)
-     * @see #getProperty
-     * @see Variant
-     */
-    public void setProperty(final Property p)
-    {
-        final long id = p.getID();
-        removeProperty(id);
-        preprops.add(p);
-        dirty = true;
-    }
-
-
-
-    /**
-     * <p>Removes a property.</p>
-     *
-     * @param id The ID of the property to be removed
-     */
-    public void removeProperty(final long id)
-    {
-        for (final Iterator<Property> i = preprops.iterator(); i.hasNext();)
-            if (i.next().getID() == id)
-            {
-                i.remove();
-                break;
-            }
-        dirty = true;
-    }
-
-
-
-    /**
-     * <p>Sets the value of the boolean property with the specified
-     * ID.</p>
-     *
-     * @param id The property's ID
-     * @param value The property's value
-     *
-     * @see #setProperty(int, long, Object)
-     * @see #getProperty
-     * @see Variant
-     */
-    protected void setPropertyBooleanValue(final int id, final boolean value)
-    {
-        setProperty(id, Variant.VT_BOOL, Boolean.valueOf(value));
-    }
-
-
-
-    /**
-     * <p>Returns the section's size.</p>
-     *
-     * @return the section's size.
-     */
-    public int getSize()
-    {
-        if (dirty)
-        {
-            try
-            {
-                size = calcSize();
-                dirty = false;
-            }
-            catch (HPSFRuntimeException ex)
-            {
-                throw ex;
-            }
-            catch (Exception ex)
-            {
-                throw new HPSFRuntimeException(ex);
-            }
-        }
-        return size;
-    }
-
-
-
-    /**
-     * <p>Calculates the section's size. It is the sum of the lengths of the
-     * section's header (8), the properties list (16 times the number of
-     * properties) and the properties themselves.</p>
-     *
-     * @return the section's length in bytes.
-     * @throws WritingNotSupportedException
-     * @throws IOException
-     */
-    private int calcSize() throws WritingNotSupportedException, IOException
-    {
-        final ByteArrayOutputStream out = new ByteArrayOutputStream();
-        write(out);
-        out.close();
-        /* Pad to multiple of 4 bytes so that even the Windows shell (explorer)
-         * shows custom properties. */
-        sectionBytes = Util.pad4(out.toByteArray());
-        return sectionBytes.length;
-    }
-
-
-
-    /**
-     * <p>Writes this section into an output stream.</p>
-     *
-     * <p>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.</p>
-     *
-     * @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();
-        }
-
-        /* Sort the property list by their property IDs: */
-        Collections.sort(preprops, new Comparator<Property>()
-            {
-                public int compare(final Property p1, final Property p2)
-                {
-                    if (p1.getID() < p2.getID())
-                        return -1;
-                    else if (p1.getID() == p2.getID())
-                        return 0;
-                    else
-                        return 1;
-                }
-            });
-
-        /* Write the properties and the property list into their respective
-         * streams: */
-        for (final ListIterator<Property> i = preprops.listIterator(); i.hasNext();)
-        {
-            final MutableProperty p = (MutableProperty) i.next();
-            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;
-    }
-
-
-
-    /**
-     * <p>Writes the section's dictionary.</p>
-     *
-     * @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<Long,String> dictionary, final int codepage)
-        throws IOException
-    {
-        int length = TypeWriter.writeUIntToStream(out, dictionary.size());
-        for (Map.Entry<Long,String> ls : dictionary.entrySet()) {
-            final Long key = ls.getKey();
-            final String value = ls.getValue();
-
-            if (codepage == CodePageUtil.CP_UNICODE)
-            {
-                /* Write the dictionary item in Unicode. */
-                int sLength = value.length() + 1;
-                if ((sLength & 1) == 1) {
-                    sLength++;
-                }
-                length += TypeWriter.writeUIntToStream(out, key.longValue());
-                length += TypeWriter.writeUIntToStream(out, sLength);
-                final byte[] ca = CodePageUtil.getBytesInCodePage(value, codepage);
-                for (int j = 2; j < ca.length; j += 2)
-                {
-                    out.write(ca[j+1]);
-                    out.write(ca[j]);
-                    length += 2;
-                }
-                sLength -= value.length();
-                while (sLength > 0)
-                {
-                    out.write(0x00);
-                    out.write(0x00);
-                    length += 2;
-                    sLength--;
-                }
-            }
-            else
-            {
-                /* Write the dictionary item in another codepage than
-                 * Unicode. */
-                length += TypeWriter.writeUIntToStream(out, key.longValue());
-                length += TypeWriter.writeUIntToStream(out, value.length() + 1);
-                final byte[] ba = CodePageUtil.getBytesInCodePage(value, codepage);
-                for (int j = 0; j < ba.length; j++)
-                {
-                    out.write(ba[j]);
-                    length++;
-                }
-                out.write(0x00);
-                length++;
-            }
-        }
-        return length;
-    }
-
-
-
-    /**
-     * <p>Overwrites the super class' method to cope with a redundancy:
-     * the property count is maintained in a separate member variable, but
-     * shouldn't.</p>
-     *
-     * @return The number of properties in this section
-     */
-    public int getPropertyCount()
-    {
-        return preprops.size();
-    }
-
-
-
-    /**
-     * <p>Gets this section's properties.</p>
-     *
-     * @return this section's properties.
-     */
-    public Property[] getProperties()
-    {
-        properties = preprops.toArray(new Property[0]);
-        return properties;
-    }
-
-
-
-    /**
-     * <p>Gets a property.</p>
-     *
-     * @param id The ID of the property to get
-     * @return The property or <code>null</code> if there is no such property
-     */
-    public Object getProperty(final long id)
-    {
-        /* Calling getProperties() ensures that properties and preprops are in
-         * sync.</p> */
-        getProperties();
-        return super.getProperty(id);
-    }
-
-
-
-    /**
-     * <p>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.</p>
-     *
-     * @param dictionary The dictionary
-     *
-     * @exception IllegalPropertySetDataException if the dictionary's key and
-     * value types are not correct.
-     *
-     * @see Section#getDictionary()
-     */
-    public void setDictionary(final Map<Long,String> dictionary)
-        throws IllegalPropertySetDataException
-    {
-        if (dictionary != null)
-        {
-            this.dictionary = dictionary;
-
-            /* Set the dictionary property (ID 0). Please note that the second
-             * parameter in the method call below is unused because dictionaries
-             * don't have a type. */
-            setProperty(PropertyIDMap.PID_DICTIONARY, -1, dictionary);
-
-            /* If the codepage property (ID 1) for the strings (keys and
-             * values) used in the dictionary is not yet defined, set it to
-             * Unicode. */
-            final Integer codepage =
-                (Integer) getProperty(PropertyIDMap.PID_CODEPAGE);
-            if (codepage == null)
-                setProperty(PropertyIDMap.PID_CODEPAGE, Variant.VT_I2,
-                            Integer.valueOf(CodePageUtil.CP_UNICODE));
-        }
-        else
-            /* Setting the dictionary to null means to remove property 0.
-             * However, it does not mean to remove property 1 (codepage). */
-            removeProperty(PropertyIDMap.PID_DICTIONARY);
-    }
-
-
-
-    /**
-     * <p>Sets a property.</p>
-     *
-     * @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() + ".");
-    }
-
-
-
-    /**
-     * <p>Removes all properties from the section including 0 (dictionary) and
-     * 1 (codepage).</p>
-     */
-    public void clear()
-    {
-        final Property[] properties = getProperties();
-        for (int i = 0; i < properties.length; i++)
-        {
-            final Property p = properties[i];
-            removeProperty(p.getID());
-        }
-    }
-
-    /**
-     * <p>Sets the codepage.</p>
-     *
-     * @param codepage the codepage
-     */
-    public void setCodepage(final int codepage)
-    {
-        setProperty(PropertyIDMap.PID_CODEPAGE, Variant.VT_I2,
-                Integer.valueOf(codepage));
+@Removal(version="3.18")
+public class MutableSection extends Section {
+    public MutableSection() {}
+
+    public MutableSection(final Section s) {
+    	super(s);
+    }
+    
+    public MutableSection(final byte[] src, final int offset) throws UnsupportedEncodingException {
+        super(src,offset);
     }
 }

Modified: poi/trunk/src/java/org/apache/poi/hpsf/NoFormatIDException.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/hpsf/NoFormatIDException.java?rev=1771640&r1=1771639&r2=1771640&view=diff
==============================================================================
--- poi/trunk/src/java/org/apache/poi/hpsf/NoFormatIDException.java (original)
+++ poi/trunk/src/java/org/apache/poi/hpsf/NoFormatIDException.java Sun Nov 27 20:19:18 2016
@@ -18,53 +18,47 @@
 package org.apache.poi.hpsf;
 
 /**
- * <p>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 {
 
     /**
-     * <p>Constructor</p>
+     * Constructor
      */
-    public NoFormatIDException()
-    {
+    public NoFormatIDException() {
         super();
     }
 
 
     /**
-     * <p>Constructor</p>
+     * Constructor
      * 
      * @param msg The exception's message string
      */
-    public NoFormatIDException(final String msg)
-    {
+    public NoFormatIDException(final String msg) {
         super(msg);
     }
 
 
     /**
-     * <p>Constructor</p>
+     * Constructor
      * 
      * @param reason This exception's underlying reason
      */
-    public NoFormatIDException(final Throwable reason)
-    {
+    public NoFormatIDException(final Throwable reason) {
         super(reason);
     }
 
 
     /**
-     * <p>Constructor</p>
+     * 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);
     }
 

Modified: poi/trunk/src/java/org/apache/poi/hpsf/Property.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/hpsf/Property.java?rev=1771640&r1=1771639&r2=1771640&view=diff
==============================================================================
--- poi/trunk/src/java/org/apache/poi/hpsf/Property.java (original)
+++ poi/trunk/src/java/org/apache/poi/hpsf/Property.java Sun Nov 27 20:19:18 2016
@@ -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;
 
 /**
- * <p>A property in a {@link Section} of a {@link PropertySet}.</p>
+ * A property in a {@link Section} of a {@link PropertySet}.<p>
  *
- * <p>The property's <strong>ID</strong> 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.</p>
+ * 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
+ * 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).</p>
+ * 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
+ * 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>.
+ * reports or patches for the types you need.
  *
  * @see Section
  * @see Variant
+ * @see <a href="https://msdn.microsoft.com/en-us/library/dd942421.aspx">
+ * [MS-OLEPS]: Object Linking and Embedding (OLE) Property Set Data Structures</a>
  */
-public class Property
-{
+public class Property {
 
-    /** <p>The property's ID.</p> */
-    protected long id;
+    /** The property's ID. */
+    private long id;
 
+    /** The property's type. */
+    private long type;
 
-    /**
-     * <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;
+    /** The property's value. */
+    protected Object value;
 
 
     /**
-     * <p>Returns the property's type.</p>
-     *
-     * @return The type value
+     * Creates an empty property. It must be filled using the set method to be usable.
      */
-    public long getType()
-    {
-        return type;
+    public Property() {
     }
 
-
-
-    /** <p>The property's value.</p> */
-    protected Object value;
-
-
     /**
-     * <p>Returns the property's value.</p>
+     * Creates a {@code Property} as a copy of an existing {@code Property}.
      *
-     * @return The property's value
+     * @param p The property to copy.
      */
-    public Object getValue()
-    {
-        return value;
+    public Property(Property p) {
+        this(p.id, p.type, p.value);
     }
-
-
-
+    
     /**
-     * <p>Creates a property.</p>
+     * 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;
     }
 
-
-
     /**
-     * <p>Creates a {@link Property} instance by reading its bytes
-     * from the property set stream.</p>
+     * 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
 
 
     /**
-     * <p>Creates an empty property. It must be filled using the set method to
-     * be usable.</p>
+     * Returns the property's ID.
+     *
+     * @return The ID value
      */
-    protected Property()
-    { }
+    public long getID() {
+        return id;
+    }
 
+    /**
+     * 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;
+    }
 
     /**
-     * <p>Reads a dictionary.</p>
+     * 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 <var>src </var> 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 Map<Object, Object> m = new LinkedHashMap<Object, Object>(
-                (int) nrEntries, (float) 1.0 );
+        final Map<Object, Object> m = new LinkedHashMap<Object, Object>((int) nrEntries, (float) 1.0 );
 
-        try
-        {
-            for (int i = 0; i < nrEntries; i++)
-            {
+        try {
+            for (int i = 0; i < nrEntries; i++) {
                 /* The key. */
                 final Long id = Long.valueOf(LittleEndian.getUInt(src, o));
                 o += LittleEndian.INT_SIZE;
@@ -230,17 +242,13 @@ public class Property
 
                 /* Read the string. */
                 final StringBuffer b = new StringBuffer();
-                switch (codepage)
-                {
+                switch (codepage) {
                     case -1:
-                    {
                         /* Without a codepage the length is equal to the number of
                          * bytes. */
                         b.append(new String(src, o, (int) sLength, Charset.forName("ASCII")));
                         break;
-                    }
                     case CodePageUtil.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);
@@ -250,36 +258,30 @@ public class Property
                             h[i2] = src[o + i2 + 1];
                             h[i2 + 1] = src[o + i2];
                         }
-                        b.append(new String(h, 0, nrBytes,
-                                CodePageUtil.codepageToEncoding(codepage)));
+                        b.append(new String(h, 0, nrBytes, CodePageUtil.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)));
+                        b.append(new String(src, o, (int) sLength, CodePageUtil.codepageToEncoding(codepage)));
                         break;
-                    }
                 }
 
                 /* Strip 0x00 characters from the end of the string: */
-                while (b.length() > 0 && b.charAt(b.length() - 1) == 0x00)
+                while (b.length() > 0 && b.charAt(b.length() - 1) == 0x00) {
                     b.setLength(b.length() - 1);
-                if (codepage == CodePageUtil.CP_UNICODE)
-                {
-                    if (sLength % 2 == 1)
+                }
+                if (codepage == CodePageUtil.CP_UNICODE) {
+                    if (sLength % 2 == 1) {
                         sLength++;
+                    }
                     o += (sLength + sLength);
-                }
-                else
+                } else {
                     o += sLength;
+                }
                 m.put(id, b.toString());
             }
-        }
-        catch (RuntimeException ex)
-        {
+        } catch (RuntimeException ex) {
             final POILogger l = POILogFactory.getLogger(getClass());
             l.log(POILogger.WARN,
                     "The property set's dictionary contains bogus data. "
@@ -292,8 +294,7 @@ public class Property
 
 
     /**
-     * <p>Returns the property's size in bytes. This is always a multiple of
-     * 4.</p>
+     * 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
 
 
     /**
-     * <p>Compares two properties.</p> <p>Please beware that a property with
+     * Compares two properties.<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>
+     * 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;
+    }
+    
 }

Modified: poi/trunk/src/java/org/apache/poi/hpsf/PropertySet.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/hpsf/PropertySet.java?rev=1771640&r1=1771639&r2=1771640&view=diff
==============================================================================
--- poi/trunk/src/java/org/apache/poi/hpsf/PropertySet.java (original)
+++ poi/trunk/src/java/org/apache/poi/hpsf/PropertySet.java Sun Nov 27 20:19:18 2016
@@ -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;
 
 /**
- * <p>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.</p>
+ * document.<p>
  *
- * <p>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.</p>
+ * the information its needs by calling appropriate methods.<p>
  *
- * <p>{@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.</p>
+ * exception is thrown.<p>
  *
- * <p>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.</p> 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.<p>
+ * 
+ * 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 {
     /**
-     * <p>The "byteOrder" field must equal this value.</p>
+     * If the OS version field holds this value the property set stream was
+     * created on a 16-bit Windows system.
      */
-    static final byte[] BYTE_ORDER_ASSERTION =
-            {(byte) 0xFE, (byte) 0xFF};
+    public static final int OS_WIN16     = 0x0000;
 
     /**
-     * <p>Specifies this {@link PropertySet}'s byte order. See the
-     * HPFS documentation for details!</p>
+     * If the OS version field holds this value the property set stream was
+     * created on a Macintosh system.
      */
-    protected int byteOrder;
+    public static final int OS_MACINTOSH = 0x0001;
 
     /**
-     * <p>Returns the property set stream's low-level "byte order"
-     * field. It is always <tt>0xFFFE</tt> .</p>
-     *
-     * @return The property set stream's low-level "byte order" field.
+     * If the OS version field holds this value the property set stream was
+     * created on a 32-bit Windows system.
      */
-    public int getByteOrder()
-    {
-        return byteOrder;
-    }
-
-
+    public static final int OS_WIN32     = 0x0002;
 
     /**
-     * <p>The "format" field must equal this value.</p>
+     * The "byteOrder" field must equal this value.
      */
-    static final byte[] FORMAT_ASSERTION =
-            {(byte) 0x00, (byte) 0x00};
+    private static final int BYTE_ORDER_ASSERTION = 0xFFFE;
 
     /**
-     * <p>Specifies this {@link PropertySet}'s format. See the HPFS
-     * documentation for details!</p>
+     * The "format" field must equal this value.
      */
-    protected int format;
+    private static final int FORMAT_ASSERTION = 0x0000;
 
     /**
-     * <p>Returns the property set stream's low-level "format"
-     * field. It is always <tt>0x0000</tt> .</p>
-     *
-     * @return The property set stream's low-level "format" field.
+     * The length of the property set stream header.
      */
-    public int getFormat()
-    {
-        return format;
-    }
+    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 */
 
-
- 
+    
     /**
-     * <p>Specifies the version of the operating system that created
-     * this {@link PropertySet}. See the HPFS documentation for
-     * details!</p>
+     * Specifies this {@link PropertySet}'s byte order. See the
+     * HPFS documentation for details!
      */
-    protected int osVersion;
-
+    private int byteOrder;
 
     /**
-     * <p>If the OS version field holds this value the property set stream was
-     * created on a 16-bit Windows system.</p>
+     * Specifies this {@link PropertySet}'s format. See the HPFS
+     * documentation for details!
      */
-    public static final int OS_WIN16     = 0x0000;
-
+    private int format;
+    
     /**
-     * <p>If the OS version field holds this value the property set stream was
-     * created on a Macintosh system.</p>
+     * Specifies the version of the operating system that created this
+     * {@link PropertySet}. See the HPFS documentation for details!
      */
-    public static final int OS_MACINTOSH = 0x0001;
+    private int osVersion;
 
     /**
-     * <p>If the OS version field holds this value the property set stream was
-     * created on a 32-bit Windows system.</p>
+     * Specifies this {@link PropertySet}'s "classID" field. See
+     * the HPFS documentation for details!
      */
-    public static final int OS_WIN32     = 0x0002;
+    private ClassID classID;
 
     /**
-     * <p>Returns the property set stream's low-level "OS version"
-     * field.</p>
-     *
-     * @return The property set stream's low-level "OS version" field.
+     * The sections in this {@link PropertySet}.
      */
-    public int getOSVersion()
-    {
-        return osVersion;
-    }
-
-
-
-    /**
-     * <p>Specifies this {@link PropertySet}'s "classID" field. See
-     * the HPFS documentation for details!</p>
-     */
-    protected ClassID classID;
-
-    /**
-     * <p>Returns the property set stream's low-level "class ID"
-     * field.</p>
-     *
-     * @return The property set stream's low-level "class ID" field.
-     */
-    public ClassID getClassID()
-    {
-        return classID;
-    }
-
-
+    private final List<Section> sections = new LinkedList<Section>();
 
+    
     /**
-     * <p>Returns the number of {@link Section}s in the property
-     * set.</p>
-     *
-     * @return The number of {@link Section}s in the property set.
+     * Constructs a {@code PropertySet} instance. Its
+     * primary task is to initialize the field with their proper values.
+     * It also sets fields that might change to reasonable defaults.
      */
-    public int getSectionCount()
-    {
-        return sections.size();
-    }
+    public PropertySet() {
+        /* Initialize the "byteOrder" field. */
+        byteOrder = BYTE_ORDER_ASSERTION;
 
+        /* Initialize the "format" field. */
+        format = FORMAT_ASSERTION;
 
+        /* Initialize "osVersion" field as if the property has been created on
+         * a Win32 platform, whether this is the case or not. */
+        osVersion = (OS_WIN32 << 16) | 0x0A04;
 
-    /**
-     * <p>The sections in this {@link PropertySet}.</p>
-     */
-    protected List<Section> sections;
+        /* Initialize the "classID" field. */
+        classID = new ClassID();
 
-    /**
-     * <p>Returns the {@link Section}s in the property set.</p>
-     *
-     * @return The {@link Section}s in the property set.
-     */
-    public List<Section> getSections()
-    {
-        return sections;
+        /* Initialize the sections. Since property set must have at least
+         * one section it is added right here. */
+        addSection(new MutableSection());
     }
 
 
 
     /**
-     * <p>Creates an empty (uninitialized) {@link PropertySet}.</p>
+     * Creates a {@link PropertySet} instance from an {@link
+     * InputStream} in the Horrible Property Set Format.<p>
      *
-     * <p><strong>Please note:</strong> 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.</p>
-     */
-    protected PropertySet()
-    { }
-
-
-
-    /**
-     * <p>Creates a {@link PropertySet} instance from an {@link
-     * InputStream} in the Horrible Property Set Format.</p>
-     *
-     * <p>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.</p>
+     * 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);
     }
 
 
 
     /**
-     * <p>Creates a {@link PropertySet} instance from a byte array
-     * that represents a stream in the Horrible Property Set
-     * Format.</p>
+     * 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 <var>stream</var> 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 <var>offset</var> 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);
     }
 
-
-
     /**
-     * <p>Creates a {@link PropertySet} instance from a byte array
-     * that represents a stream in the Horrible Property Set
-     * Format.</p>
+     * 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;
+    }
 
     /**
-     * <p>Checks whether an {@link InputStream} is in the Horrible
-     * Property Set Format.</p>
+     * @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 List<Section> getSections() {
+        return Collections.unmodifiableList(sections);
+    }
+
+
+
+    /**
+     * 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.
-     * @return <code>true</code> if the stream is a property set
-     * stream, else <code>false</code>.
+     * @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;
+        }
     }
 
 
 
     /**
-     * <p>Checks whether a byte array is in the Horrible Property Set
-     * Format.</p>
+     * 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.
-     * @return <code>true</code> if the byte array is a property set
-     * stream, <code>false</code> 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);
     }
 
 
 
     /**
-     * <p>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.</p>
+     * 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 <var>src</var>
+     * 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 ArrayList<Section>( sectionCount );
 
         /*
          * Loop over the section descriptor array. Each descriptor
          * consists of a ClassID and a DWord, and we have to increment
          * "offset" accordingly.
          */
-        for (int i = 0; i < sectionCount; i++)
-        {
-            final Section s = new Section(src, o);
+        for (int i = 0; i < sectionCount; i++) {
+            final Section s = new MutableSection(src, o);
             o += ClassID.LENGTH + LittleEndian.INT_SIZE;
             sections.add(s);
         }
     }
 
+    /**
+     * Writes the property set to an output stream.
+     *
+     * @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 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();
+    }
 
     /**
-     * <p>Checks whether this {@link PropertySet} represents a Summary
-     * Information.</p>
+     * Writes a property set to a document in a POI filesystem directory.
+     *
+     * @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.
      *
-     * @return <code>true</code> if this {@link PropertySet}
-     * represents a Summary Information, else <code>false</code>.
+     * @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 isSummaryInformation()
-    {
-        if (sections.size() <= 0)
-            return false;
-        return Util.equal(sections.get(0).getFormatID().getBytes(),
-                          SectionIDMap.SUMMARY_INFORMATION_ID);
+    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);
+    }
 
+    /**
+     * 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();
+    }
 
     /**
-     * <p>Checks whether this {@link PropertySet} is a Document
-     * Summary Information.</p>
+     * Checks whether this {@link PropertySet} represents a Summary Information.
      *
-     * @return <code>true</code> if this {@link PropertySet}
-     * represents a Document Summary Information, else <code>false</code>.
+     * @return {@code true} if this {@link PropertySet}
+     * represents a Summary Information, else {@code false}.
      */
-    public boolean isDocumentSummaryInformation()
-    {
-        if (sections.size() <= 0)
-            return false;
-        return Util.equal(sections.get(0).getFormatID().getBytes(),
-                          SectionIDMap.DOCUMENT_SUMMARY_INFORMATION_ID[0]);
+    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); 
+    }
+    
+    
 
     /**
-     * <p>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}.</p>
+     * 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();
     }
 
 
 
     /**
-     * <p>Convenience method returning the value of the property with
-     * the specified ID. If the property is not available,
-     * <code>null</code> is returned and a subsequent call to {@link
-     * #wasNull} will return <code>true</code> .</p>
+     * Convenience method returning 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 {@code true}.
      *
      * @param id The property ID
      * @return The property value
      * @throws NoSingleSectionException if the {@link PropertySet} has
      * more or less than one {@link Section}.
      */
-    protected Object getProperty(final int id) throws NoSingleSectionException
-    {
+    protected Object getProperty(final int id) throws NoSingleSectionException {
         return getFirstSection().getProperty(id);
     }
 
 
 
     /**
-     * <p>Convenience method returning the value of a boolean property
-     * with the specified ID. If the property is not available,
-     * <code>false</code> is returned. A subsequent call to {@link
-     * #wasNull} will return <code>true</code> to let the caller
-     * distinguish that case from a real property value of
-     * <code>false</code>.</p>
+     * Convenience method returning the value of a boolean property with the
+     * specified ID. If the property is not available, {@code false} is returned.
+     * A subsequent call to {@link #wasNull} will return {@code true} to let the
+     * caller distinguish that case from a real property value of {@code false}.
      *
      * @param id The property ID
      * @return The property value
      * @throws NoSingleSectionException if the {@link PropertySet} has
      * more or less than one {@link Section}.
      */
-    protected boolean getPropertyBooleanValue(final int id)
-        throws NoSingleSectionException
-    {
+    protected boolean getPropertyBooleanValue(final int id) throws NoSingleSectionException {
         return getFirstSection().getPropertyBooleanValue(id);
     }
 
 
 
     /**
-     * <p>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 <code>true</code> to let the caller distinguish
-     * that case from a real property value of 0.</p>
+     * will return {@code true} to let the caller distinguish
+     * that case from a real property value of 0.
      *
      * @param id The property ID
      * @return The propertyIntValue value
      * @throws NoSingleSectionException if the {@link PropertySet} has
      * more or less than one {@link Section}.
      */
-    protected int getPropertyIntValue(final int id)
-        throws NoSingleSectionException
-    {
+    protected int getPropertyIntValue(final int id) throws NoSingleSectionException {
         return getFirstSection().getPropertyIntValue(id);
     }
 
 
 
     /**
-     * <p>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.</p>
+     * property's real value of 0.
      *
-     * @return <code>true</code> 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 <code>false</code>.
+     * 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();
     }
 
 
 
     /**
-     * <p>Gets the {@link PropertySet}'s first section.</p>
+     * 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);
     }
 
 
 
     /**
-     * <p>If the {@link PropertySet} has only a single section this
-     * method returns it.</p>
+     * 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);
     }
 
 
 
     /**
-     * <p>Returns <code>true</code> if the <code>PropertySet</code> is equal
-     * to the specified parameter, else <code>false</code>.</p>
+     * Returns {@code true} if the {@code PropertySet} is equal
+     * to the specified parameter, else {@code false}.
      *
-     * @param o the object to compare this <code>PropertySet</code> with
+     * @param o the object to compare this {@code PropertySet} with
      * 
-     * @return <code>true</code> if the objects are equal, <code>false</code>
+     * @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);
+    }
 }



---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@poi.apache.org
For additional commands, e-mail: commits-help@poi.apache.org