]> source.dussan.org Git - poi.git/commitdiff
#60331 - Remove deprecated classes - deprecate Mutable* property classes
authorAndreas Beeker <kiwiwings@apache.org>
Sun, 27 Nov 2016 20:19:18 +0000 (20:19 +0000)
committerAndreas Beeker <kiwiwings@apache.org>
Sun, 27 Nov 2016 20:19:18 +0000 (20:19 +0000)
sonarcube fix - make protected attributes private

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1771640 13f79535-47bb-0310-9956-ffa450edef68

19 files changed:
src/java/org/apache/poi/hpsf/CustomProperties.java
src/java/org/apache/poi/hpsf/CustomProperty.java
src/java/org/apache/poi/hpsf/DocumentSummaryInformation.java
src/java/org/apache/poi/hpsf/MutableProperty.java
src/java/org/apache/poi/hpsf/MutablePropertySet.java
src/java/org/apache/poi/hpsf/MutableSection.java
src/java/org/apache/poi/hpsf/NoFormatIDException.java
src/java/org/apache/poi/hpsf/Property.java
src/java/org/apache/poi/hpsf/PropertySet.java
src/java/org/apache/poi/hpsf/PropertySetFactory.java
src/java/org/apache/poi/hpsf/Section.java
src/java/org/apache/poi/hpsf/SpecialPropertySet.java
src/java/org/apache/poi/hpsf/SummaryInformation.java
src/java/org/apache/poi/hpsf/Util.java
src/java/org/apache/poi/hpsf/VariantSupport.java
src/java/org/apache/poi/hpsf/extractor/HPSFPropertiesExtractor.java
src/java/org/apache/poi/util/IOUtils.java
src/testcases/org/apache/poi/hpsf/basic/TestBasic.java
src/testcases/org/apache/poi/hpsf/basic/TestWrite.java

index f305836fa8198611f31990ed64c63428e406019f..c420f1d3a0fca089365d8ee22d7b666426688ae3 100644 (file)
@@ -19,132 +19,112 @@ package org.apache.poi.hpsf;
 
 import java.util.Date;
 import java.util.HashMap;
-import java.util.Iterator;
 import java.util.Map;
 import java.util.Set;
 
+import org.apache.commons.collections4.bidimap.TreeBidiMap;
 import org.apache.poi.hpsf.wellknown.PropertyIDMap;
 
 /**
- * <p>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.</p>
+ * typed values.<p>
  *
- * <p>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.</p>
+ * such property sets, use HPSF's low-level access methods.<p>
  *
- * <p>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.</p>
+ * unmodified) or whether one or more properties have been dropped.<p>
  *
- * <p>This class is not thread-safe; concurrent access to instances of this
- * class must be synchronized.</p>
+ * This class is not thread-safe; concurrent access to instances of this
+ * class must be synchronized.<p>
  *
- * <p>While this class is roughly HashMap&lt;Long,CustomProperty&gt;, that's the
- *  internal representation. To external calls, it should appear as
- *  HashMap&lt;String,Object&gt; mapping between Names and Custom Property Values.</p>
+ * While this class is roughly HashMap&lt;Long,CustomProperty&gt;, that's the
+ * internal representation. To external calls, it should appear as
+ * HashMap&lt;String,Object&gt; mapping between Names and Custom Property Values.
  */
 @SuppressWarnings("serial")
-public class CustomProperties extends HashMap<Object,CustomProperty>
-{
+public class CustomProperties extends HashMap<Long,CustomProperty> {
 
     /**
-     * <p>Maps property IDs to property names.</p>
+     * Maps property IDs to property names and vice versa.
      */
-    private final Map<Long,String> dictionaryIDToName = new HashMap<Long,String>();
+    private final TreeBidiMap<Long,String> dictionary = new TreeBidiMap<Long,String>();
 
     /**
-     * <p>Maps property names to property IDs.</p>
-     */
-    private final Map<String,Long> dictionaryNameToID = new HashMap<String,Long>();
-
-    /**
-     * <p>Tells whether this object is pure or not.</p>
+     * Tells whether this object is pure or not.
      */
     private boolean isPure = true;
 
 
     /**
-     * <p>Puts a {@link CustomProperty} into this map. It is assumed that the
+     * Puts a {@link CustomProperty} into this map. It is assumed that the
      * {@link CustomProperty} already has a valid ID. Otherwise use
-     * {@link #put(CustomProperty)}.</p>
+     * {@link #put(CustomProperty)}.
      * 
      * @param name the property name
      * @param cp the property
      * 
      * @return the previous property stored under this name
      */
-    public CustomProperty put(final String name, final CustomProperty cp)
-    {
-        if (name == null)
-        {
+    public CustomProperty put(final String name, final CustomProperty cp) {
+        if (name == null) {
             /* Ignoring a property without a name. */
             isPure = false;
             return null;
         }
-        if (!(name.equals(cp.getName())))
+        
+        if (!name.equals(cp.getName())) {
             throw new IllegalArgumentException("Parameter \"name\" (" + name +
                     ") and custom property's name (" + cp.getName() +
                     ") do not match.");
+        }
 
         /* Register name and ID in the dictionary. Mapping in both directions is possible. If there is already a  */
-        final Long idKey = Long.valueOf(cp.getID());
-        final Long oldID = dictionaryNameToID.get(name);
-        dictionaryIDToName.remove(oldID);
-        dictionaryNameToID.put(name, idKey);
-        dictionaryIDToName.put(idKey, name);
+        super.remove(dictionary.getKey(name));
+        dictionary.put(cp.getID(), name);
 
         /* Put the custom property into this map. */
-        final CustomProperty oldCp = super.remove(oldID);
-        super.put(idKey, cp);
-        return oldCp;
+        return super.put(cp.getID(), cp);
     }
 
 
 
     /**
-     * <p>Puts a {@link CustomProperty} that has not yet a valid ID into this
-     * map. The method will allocate a suitable ID for the custom property:</p>
+     * Puts a {@link CustomProperty} that has not yet a valid ID into this
+     * map. The method will allocate a suitable ID for the custom property:
      *
      * <ul>
+     * <li>If there is already a property with the same name, take the ID
+     * of that property.
      *
-     * <li><p>If there is already a property with the same name, take the ID
-     * of that property.</p></li>
-     *
-     * <li><p>Otherwise find the highest ID and use its value plus one.</p></li>
-     *
+     * <li>Otherwise find the highest ID and use its value plus one.
      * </ul>
      *
      * @param customProperty
-     * @return If the was already a property with the same name, the
+     * @return If there was already a property with the same name, the old property
      * @throws ClassCastException
      */
-    private Object put(final CustomProperty customProperty) throws ClassCastException
-    {
+    private Object put(final CustomProperty customProperty) throws ClassCastException {
         final String name = customProperty.getName();
 
         /* Check whether a property with this name is in the map already. */
-        final Long oldId = dictionaryNameToID.get(name);
-        if (oldId != null)
-            customProperty.setID(oldId.longValue());
-        else
-        {
-            long max = 1;
-            for (Long long1 : dictionaryIDToName.keySet()) {
-                final long id = long1.longValue();
-                if (id > max)
-                    max = id;
-            }
-            customProperty.setID(max + 1);
+        final Long oldId = (name == null) ? null :  dictionary.getKey(name);
+        if (oldId != null) {
+            customProperty.setID(oldId);
+        } else {
+            long lastKey = (dictionary.isEmpty()) ? 0 : dictionary.lastKey();
+            customProperty.setID(Math.max(lastKey,PropertyIDMap.PID_MAX) + 1);
         }
         return this.put(name, customProperty);
     }
@@ -152,123 +132,92 @@ public class CustomProperties extends HashMap<Object,CustomProperty>
 
 
     /**
-     * <p>Removes a custom property.</p>
+     * Removes a custom property.
      * @param name The name of the custom property to remove
-     * @return The removed property or <code>null</code> if the specified property was not found.
+     * @return The removed property or {@code null} if the specified property was not found.
      *
      * @see java.util.HashSet#remove(java.lang.Object)
      */
-    public Object remove(final String name)
-    {
-        final Long id = dictionaryNameToID.get(name);
-        if (id == null)
-            return null;
-        dictionaryIDToName.remove(id);
-        dictionaryNameToID.remove(name);
+    public Object remove(final String name) {
+        final Long id = dictionary.removeValue(name);
         return super.remove(id);
     }
 
     /**
-     * <p>Adds a named string property.</p>
+     * Adds a named string property.
      *
      * @param name The property's name.
      * @param value The property's value.
      * @return the property that was stored under the specified name before, or
-     *         <code>null</code> if there was no such property before.
+     *         {@code null} if there was no such property before.
      */
-    public Object put(final String name, final String value)
-    {
-        final MutableProperty p = new MutableProperty();
-        p.setID(-1);
-        p.setType(Variant.VT_LPWSTR);
-        p.setValue(value);
-        final CustomProperty cp = new CustomProperty(p, name);
-        return put(cp);
+    public Object put(final String name, final String value) {
+        final Property p = new Property(-1, Variant.VT_LPWSTR, value);
+        return put(new CustomProperty(p, name));
     }
 
     /**
-     * <p>Adds a named long property.</p>
+     * Adds a named long property.
      *
      * @param name The property's name.
      * @param value The property's value.
      * @return the property that was stored under the specified name before, or
-     *         <code>null</code> if there was no such property before.
+     *         {@code null} if there was no such property before.
      */
-    public Object put(final String name, final Long value)
-    {
-        final MutableProperty p = new MutableProperty();
-        p.setID(-1);
-        p.setType(Variant.VT_I8);
-        p.setValue(value);
-        final CustomProperty cp = new CustomProperty(p, name);
-        return put(cp);
+    public Object put(final String name, final Long value) {
+        final Property p = new Property(-1, Variant.VT_I8, value);
+        return put(new CustomProperty(p, name));
     }
 
     /**
-     * <p>Adds a named double property.</p>
+     * Adds a named double property.
      *
      * @param name The property's name.
      * @param value The property's value.
      * @return the property that was stored under the specified name before, or
-     *         <code>null</code> if there was no such property before.
+     *         {@code null} if there was no such property before.
      */
-    public Object put(final String name, final Double value)
-    {
-        final MutableProperty p = new MutableProperty();
-        p.setID(-1);
-        p.setType(Variant.VT_R8);
-        p.setValue(value);
-        final CustomProperty cp = new CustomProperty(p, name);
-        return put(cp);
+    public Object put(final String name, final Double value) {
+        final Property p = new Property(-1, Variant.VT_R8, value);
+        return put(new CustomProperty(p, name));
     }
 
     /**
-     * <p>Adds a named integer property.</p>
+     * Adds a named integer property.
      *
      * @param name The property's name.
      * @param value The property's value.
      * @return the property that was stored under the specified name before, or
-     *         <code>null</code> if there was no such property before.
+     *         {@code null} if there was no such property before.
      */
-    public Object put(final String name, final Integer value)
-    {
-        final MutableProperty p = new MutableProperty();
-        p.setID(-1);
-        p.setType(Variant.VT_I4);
-        p.setValue(value);
-        final CustomProperty cp = new CustomProperty(p, name);
-        return put(cp);
+    public Object put(final String name, final Integer value) {
+        final Property p = new Property(-1, Variant.VT_I4, value);
+        return put(new CustomProperty(p, name));
     }
 
     /**
-     * <p>Adds a named boolean property.</p>
+     * Adds a named boolean property.
      *
      * @param name The property's name.
      * @param value The property's value.
      * @return the property that was stored under the specified name before, or
-     *         <code>null</code> if there was no such property before.
+     *         {@code null} if there was no such property before.
      */
-    public Object put(final String name, final Boolean value)
-    {
-        final MutableProperty p = new MutableProperty();
-        p.setID(-1);
-        p.setType(Variant.VT_BOOL);
-        p.setValue(value);
-        final CustomProperty cp = new CustomProperty(p, name);
-        return put(cp);
+    public Object put(final String name, final Boolean value) {
+        final Property p = new Property(-1, Variant.VT_BOOL, value);
+        return put(new CustomProperty(p, name));
     }
 
 
     /**
-     * <p>Gets a named value from the custom properties.</p>
+     * Gets a named value from the custom properties.
      *
      * @param name the name of the value to get
-     * @return the value or <code>null</code> if a value with the specified
+     * @return the value or {@code null} if a value with the specified
      *         name is not found in the custom properties.
      */
-    public Object get(final String name)
-    {
-        final Long id = dictionaryNameToID.get(name);
+    public Object get(final String name) {
+        final Long id = dictionary.getKey(name);
         final CustomProperty cp = super.get(id);
         return cp != null ? cp.getValue() : null;
     }
@@ -276,21 +225,16 @@ public class CustomProperties extends HashMap<Object,CustomProperty>
 
 
     /**
-     * <p>Adds a named date property.</p>
+     * Adds a named date property.
      *
      * @param name The property's name.
      * @param value The property's value.
      * @return the property that was stored under the specified name before, or
-     *         <code>null</code> if there was no such property before.
+     *         {@code null} if there was no such property before.
      */
-    public Object put(final String name, final Date value)
-    {
-        final MutableProperty p = new MutableProperty();
-        p.setID(-1);
-        p.setType(Variant.VT_FILETIME);
-        p.setValue(value);
-        final CustomProperty cp = new CustomProperty(p, name);
-        return put(cp);
+    public Object put(final String name, final Date value) {
+        final Property p = new Property(-1, Variant.VT_FILETIME, value);
+        return put(new CustomProperty(p, name));
     }
 
     /**
@@ -302,7 +246,7 @@ public class CustomProperties extends HashMap<Object,CustomProperty>
     @Override
     @SuppressWarnings({ "rawtypes", "unchecked" })
     public Set keySet() {
-        return dictionaryNameToID.keySet();
+        return dictionary.values();
     }
 
     /**
@@ -311,7 +255,7 @@ public class CustomProperties extends HashMap<Object,CustomProperty>
      * @return a set of all the names of our custom properties
      */
     public Set<String> nameSet() {
-        return dictionaryNameToID.keySet();
+        return dictionary.values();
     }
 
     /**
@@ -320,21 +264,17 @@ public class CustomProperties extends HashMap<Object,CustomProperty>
      * @return a set of all the IDs of our custom properties
      */
     public Set<String> idSet() {
-        return dictionaryNameToID.keySet();
+        return dictionary.values();
     }
 
 
     /**
-     * <p>Sets the codepage.</p>
+     * Sets the codepage.
      *
      * @param codepage the codepage
      */
-    public void setCodepage(final int codepage)
-    {
-        final MutableProperty p = new MutableProperty();
-        p.setID(PropertyIDMap.PID_CODEPAGE);
-        p.setType(Variant.VT_I2);
-        p.setValue(Integer.valueOf(codepage));
+    public void setCodepage(final int codepage) {
+        Property p = new Property(PropertyIDMap.PID_CODEPAGE, Variant.VT_I2, codepage);
         put(new CustomProperty(p));
     }
 
@@ -346,84 +286,65 @@ public class CustomProperties extends HashMap<Object,CustomProperty>
      *
      * @return the dictionary.
      */
-    Map<Long,String> getDictionary()
-    {
-        return dictionaryIDToName;
+    Map<Long,String> getDictionary() {
+        return dictionary;
     }
 
 
     /**
      * Checks against both String Name and Long ID
      */
-   @Override
-   public boolean containsKey(Object key) {
-      if(key instanceof Long) {
-         return super.containsKey(key);
-      }
-      if(key instanceof String) {
-         return super.containsKey(dictionaryNameToID.get(key));
-      }
-      return false;
-   }
-
-   /**
-    * Checks against both the property, and its values. 
-    */
-   @Override
-   public boolean containsValue(Object value) {
-      if(value instanceof CustomProperty) {
-         return super.containsValue(value);
-      } else {
-         for(CustomProperty cp : super.values()) {
+    @Override
+    public boolean containsKey(Object key) {
+        return ((key instanceof Long && dictionary.containsKey(key)) || dictionary.containsValue(key));
+    }
+
+    /**
+     * Checks against both the property, and its values. 
+     */
+    @Override
+    public boolean containsValue(Object value) {
+        if(value instanceof CustomProperty) {
+            return super.containsValue(value);
+        }
+      
+        for(CustomProperty cp : super.values()) {
             if(cp.getValue() == value) {
-               return true;
+                return true;
             }
-         }
-      }
-      return false;
-   }
-
+        }
 
+        return false;
+    }
 
-   /**
-     * <p>Gets the codepage.</p>
+    /**
+     * Gets the codepage.
      *
      * @return the codepage or -1 if the codepage is undefined.
      */
-    public int getCodepage()
-    {
-        int codepage = -1;
-        for (final Iterator<CustomProperty> i = this.values().iterator(); codepage == -1 && i.hasNext();)
-        {
-            final CustomProperty cp = i.next();
-            if (cp.getID() == PropertyIDMap.PID_CODEPAGE)
-                codepage = ((Integer) cp.getValue()).intValue();
-        }
-        return codepage;
+    public int getCodepage() {
+        CustomProperty cp = get(PropertyIDMap.PID_CODEPAGE);
+        return (cp == null) ? -1 : (Integer)cp.getValue();
     }
 
-
-
     /**
-     * <p>Tells whether this {@link CustomProperties} instance is pure or one or
+     * Tells whether this {@link CustomProperties} instance is pure or one or
      * more properties of the underlying low-level property set has been
-     * dropped.</p>
+     * dropped.
      *
-     * @return <code>true</code> if the {@link CustomProperties} is pure, else
-     *         <code>false</code>.
+     * @return {@code true} if the {@link CustomProperties} is pure, else
+     *         {@code false}.
      */
-    public boolean isPure()
-    {
+    public boolean isPure() {
         return isPure;
     }
 
     /**
-     * <p>Sets the purity of the custom property set.</p>
+     * Sets the purity of the custom property set.
      *
      * @param isPure the purity
      */
-    public void setPure(final boolean isPure)
-    {
+    public void setPure(final boolean isPure) {
         this.isPure = isPure;
     }
 }
index a256d94d3ab6ab44e5f1b175eb5c98b770810c81..83511655abaa2ea991f5365976aaa777be672621 100644 (file)
 package org.apache.poi.hpsf;
 
 /**
- * <p>This class represents custom properties in the document summary
+ * This class represents custom properties in the document summary
  * information stream. The difference to normal properties is that custom
- * properties have an optional name. If the name is not <code>null</code> it
- * will be maintained in the section's dictionary.</p>
+ * properties have an optional name. If the name is not {@code null} it
+ * will be maintained in the section's dictionary.
  */
 public class CustomProperty extends MutableProperty
 {
@@ -29,80 +29,75 @@ public class CustomProperty extends MutableProperty
     private String name;
 
     /**
-     * <p>Creates an empty {@link CustomProperty}. The set methods must be
-     * called to make it usable.</p>
+     * Creates an empty {@link CustomProperty}. The set methods must be
+     * called to make it usable.
      */
-    public CustomProperty()
-    {
+    public CustomProperty() {
         this.name = null;
     }
 
     /**
-     * <p>Creates a {@link CustomProperty} without a name by copying the
-     * underlying {@link Property}' attributes.</p>
+     * Creates a {@link CustomProperty} without a name by copying the
+     * underlying {@link Property}' attributes.
      * 
      * @param property the property to copy
      */
-    public CustomProperty(final Property property)
-    {
+    public CustomProperty(final Property property) {
         this(property, null);
     }
 
     /**
-     * <p>Creates a {@link CustomProperty} with a name.</p>
+     * Creates a {@link CustomProperty} with a name.
      * 
      * @param property This property's attributes are copied to the new custom
      *        property.
      * @param name The new custom property's name.
      */
-    public CustomProperty(final Property property, final String name)
-    {
+    public CustomProperty(final Property property, final String name) {
         super(property);
         this.name = name;
     }
 
     /**
-     * <p>Gets the property's name.</p>
+     * Gets the property's name.
      *
      * @return the property's name.
      */
-    public String getName()
-    {
+    public String getName() {
         return name;
     }
 
     /**
-     * <p>Sets the property's name.</p>
+     * Sets the property's name.
      *
      * @param name The name to set.
      */
-    public void setName(final String name)
-    {
+    public void setName(final String name) {
         this.name = name;
     }
 
 
     /**
-     * <p>Compares two custom properties for equality. The method returns
-     * <code>true</code> if all attributes of the two custom properties are
-     * equal.</p>
+     * Compares two custom properties for equality. The method returns
+     * {@code true} if all attributes of the two custom properties are
+     * equal.
      * 
      * @param o The custom property to compare with.
-     * @return <code>true</code> if both custom properties are equal, else
-     *         <code>false</code>.
+     * @return {@code true} if both custom properties are equal, else
+     *         {@code false}.
      * 
      * @see java.util.AbstractSet#equals(java.lang.Object)
      */
-    public boolean equalsContents(final Object o)
-    {
+    public boolean equalsContents(final Object o) {
         final CustomProperty c = (CustomProperty) o;
         final String name1 = c.getName();
         final String name2 = this.getName();
         boolean equalNames = true;
-        if (name1 == null)
+        if (name1 == null) {
             equalNames = name2 == null;
-        else
+        } else {
             equalNames = name1.equals(name2);
+        }
         return equalNames && c.getID() == this.getID()
                 && c.getType() == this.getType()
                 && c.getValue().equals(this.getValue());
@@ -112,8 +107,7 @@ public class CustomProperty extends MutableProperty
      * @see java.util.AbstractSet#hashCode()
      */
     @Override
-    public int hashCode()
-    {
+    public int hashCode() {
         return (int) this.getID();
     }
 
index 0f8c629cf8b66a29f25ab14e2f9f08cc49102a97..f63ab8d92d321f46df665f11642bb1c9f4ca6226 100644 (file)
@@ -17,7 +17,8 @@
 
 package org.apache.poi.hpsf;
 
-import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
 import java.util.Map;
 
 import org.apache.poi.hpsf.wellknown.PropertyIDMap;
@@ -25,16 +26,15 @@ import org.apache.poi.hpsf.wellknown.SectionIDMap;
 import org.apache.poi.util.CodePageUtil;
 
 /**
- * <p>Convenience class representing a DocumentSummary Information stream in a
- * Microsoft Office document.</p>
+ * Convenience class representing a DocumentSummary Information stream in a
+ * Microsoft Office document.
  *
  * @see SummaryInformation
  */
-public class DocumentSummaryInformation extends SpecialPropertySet
-{
+public class DocumentSummaryInformation extends SpecialPropertySet {
     /**
-     * <p>The document name a document summary information stream
-     * usually has in a POIFS filesystem.</p>
+     * The document name a document summary information stream
+     * usually has in a POIFS filesystem.
      */
     public static final String DEFAULT_STREAM_NAME =
         "\005DocumentSummaryInformation";
@@ -46,351 +46,308 @@ public class DocumentSummaryInformation extends SpecialPropertySet
 
 
     /**
-     * <p>Creates a {@link DocumentSummaryInformation} from a given
-     * {@link PropertySet}.</p>
+     * Creates an empty {@link DocumentSummaryInformation}.
+     */
+    public DocumentSummaryInformation() {
+        getFirstSection().setFormatID(SectionIDMap.DOCUMENT_SUMMARY_INFORMATION_ID[0]);
+    }
+
+    
+    /**
+     * Creates a {@link DocumentSummaryInformation} from a given
+     * {@link PropertySet}.
      *
      * @param ps A property set which should be created from a
      * document summary information stream.
-     * @throws UnexpectedPropertySetTypeException if <var>ps</var>
+     * @throws UnexpectedPropertySetTypeException if {@code ps}
      * does not contain a document summary information stream.
      */
     public DocumentSummaryInformation(final PropertySet ps)
-        throws UnexpectedPropertySetTypeException
-    {
+    throws UnexpectedPropertySetTypeException {
         super(ps);
-        if (!isDocumentSummaryInformation())
-            throw new UnexpectedPropertySetTypeException
-                ("Not a " + getClass().getName());
+        if (!isDocumentSummaryInformation()) {
+            throw new UnexpectedPropertySetTypeException("Not a " + getClass().getName());
+        }
     }
 
     
     /**
-     * <p>Returns the category (or {@code null}).</p>
+     * Returns the category (or {@code null}).
      *
      * @return The category value
      */
-    public String getCategory()
-    {
+    public String getCategory() {
         return getPropertyStringValue(PropertyIDMap.PID_CATEGORY);
     }
 
     /**
-     * <p>Sets the category.</p>
+     * Sets the category.
      *
      * @param category The category to set.
      */
-    public void setCategory(final String category)
-    {
-        final MutableSection s = (MutableSection) getFirstSection();
-        s.setProperty(PropertyIDMap.PID_CATEGORY, category);
+    public void setCategory(final String category) {
+        getFirstSection().setProperty(PropertyIDMap.PID_CATEGORY, category);
     }
 
     /**
-     * <p>Removes the category.</p>
+     * Removes the category.
      */
-    public void removeCategory()
-    {
-        final MutableSection s = (MutableSection) getFirstSection();
-        s.removeProperty(PropertyIDMap.PID_CATEGORY);
+    public void removeCategory() {
+        remove1stProperty(PropertyIDMap.PID_CATEGORY);
     }
 
 
 
     /**
-     * <p>Returns the presentation format (or
-     * {@code null}).</p>
+     * Returns the presentation format (or
+     * {@code null}).
      *
      * @return The presentation format value
      */
-    public String getPresentationFormat()
-    {
+    public String getPresentationFormat() {
         return getPropertyStringValue(PropertyIDMap.PID_PRESFORMAT);
     }
 
     /**
-     * <p>Sets the presentation format.</p>
+     * Sets the presentation format.
      *
      * @param presentationFormat The presentation format to set.
      */
-    public void setPresentationFormat(final String presentationFormat)
-    {
-        final MutableSection s = (MutableSection) getFirstSection();
-        s.setProperty(PropertyIDMap.PID_PRESFORMAT, presentationFormat);
+    public void setPresentationFormat(final String presentationFormat) {
+        getFirstSection().setProperty(PropertyIDMap.PID_PRESFORMAT, presentationFormat);
     }
 
     /**
-     * <p>Removes the presentation format.</p>
+     * Removes the presentation format.
      */
-    public void removePresentationFormat()
-    {
-        final MutableSection s = (MutableSection) getFirstSection();
-        s.removeProperty(PropertyIDMap.PID_PRESFORMAT);
+    public void removePresentationFormat() {
+        remove1stProperty(PropertyIDMap.PID_PRESFORMAT);
     }
 
 
 
     /**
-     * <p>Returns the byte count or 0 if the {@link
-     * DocumentSummaryInformation} does not contain a byte count.</p>
+     * Returns the byte count or 0 if the {@link
+     * DocumentSummaryInformation} does not contain a byte count.
      *
      * @return The byteCount value
      */
-    public int getByteCount()
-    {
+    public int getByteCount() {
         return getPropertyIntValue(PropertyIDMap.PID_BYTECOUNT);
     }
 
     /**
-     * <p>Sets the byte count.</p>
+     * Sets the byte count.
      *
      * @param byteCount The byte count to set.
      */
-    public void setByteCount(final int byteCount)
-    {
-        final MutableSection s = (MutableSection) getFirstSection();
-        s.setProperty(PropertyIDMap.PID_BYTECOUNT, byteCount);
+    public void setByteCount(final int byteCount) {
+        set1stProperty(PropertyIDMap.PID_BYTECOUNT, byteCount);
     }
 
     /**
-     * <p>Removes the byte count.</p>
+     * Removes the byte count.
      */
-    public void removeByteCount()
-    {
-        final MutableSection s = (MutableSection) getFirstSection();
-        s.removeProperty(PropertyIDMap.PID_BYTECOUNT);
+    public void removeByteCount() {
+        remove1stProperty(PropertyIDMap.PID_BYTECOUNT);
     }
 
 
 
     /**
-     * <p>Returns the line count or 0 if the {@link
-     * DocumentSummaryInformation} does not contain a line count.</p>
+     * Returns the line count or 0 if the {@link
+     * DocumentSummaryInformation} does not contain a line count.
      *
      * @return The line count value
      */
-    public int getLineCount()
-    {
+    public int getLineCount() {
         return getPropertyIntValue(PropertyIDMap.PID_LINECOUNT);
     }
 
     /**
-     * <p>Sets the line count.</p>
+     * Sets the line count.
      *
      * @param lineCount The line count to set.
      */
-    public void setLineCount(final int lineCount)
-    {
-        final MutableSection s = (MutableSection) getFirstSection();
-        s.setProperty(PropertyIDMap.PID_LINECOUNT, lineCount);
+    public void setLineCount(final int lineCount) {
+        set1stProperty(PropertyIDMap.PID_LINECOUNT, lineCount);
     }
 
     /**
-     * <p>Removes the line count.</p>
+     * Removes the line count.
      */
-    public void removeLineCount()
-    {
-        final MutableSection s = (MutableSection) getFirstSection();
-        s.removeProperty(PropertyIDMap.PID_LINECOUNT);
+    public void removeLineCount() {
+        remove1stProperty(PropertyIDMap.PID_LINECOUNT);
     }
 
 
 
     /**
-     * <p>Returns the par count or 0 if the {@link
-     * DocumentSummaryInformation} does not contain a par count.</p>
+     * Returns the par count or 0 if the {@link
+     * DocumentSummaryInformation} does not contain a par count.
      *
      * @return The par count value
      */
-    public int getParCount()
-    {
+    public int getParCount() {
         return getPropertyIntValue(PropertyIDMap.PID_PARCOUNT);
     }
 
     /**
-     * <p>Sets the par count.</p>
+     * Sets the par count.
      *
      * @param parCount The par count to set.
      */
-    public void setParCount(final int parCount)
-    {
-        final MutableSection s = (MutableSection) getFirstSection();
-        s.setProperty(PropertyIDMap.PID_PARCOUNT, parCount);
+    public void setParCount(final int parCount) {
+        set1stProperty(PropertyIDMap.PID_PARCOUNT, parCount);
     }
 
     /**
-     * <p>Removes the par count.</p>
+     * Removes the par count.
      */
-    public void removeParCount()
-    {
-        final MutableSection s = (MutableSection) getFirstSection();
-        s.removeProperty(PropertyIDMap.PID_PARCOUNT);
+    public void removeParCount() {
+        remove1stProperty(PropertyIDMap.PID_PARCOUNT);
     }
 
 
 
     /**
-     * <p>Returns the slide count or 0 if the {@link
-     * DocumentSummaryInformation} does not contain a slide count.</p>
+     * Returns the slide count or 0 if the {@link
+     * DocumentSummaryInformation} does not contain a slide count.
      *
      * @return The slide count value
      */
-    public int getSlideCount()
-    {
+    public int getSlideCount() {
         return getPropertyIntValue(PropertyIDMap.PID_SLIDECOUNT);
     }
 
     /**
-     * <p>Sets the slideCount.</p>
+     * Sets the slideCount.
      *
      * @param slideCount The slide count to set.
      */
-    public void setSlideCount(final int slideCount)
-    {
-        final MutableSection s = (MutableSection) getFirstSection();
-        s.setProperty(PropertyIDMap.PID_SLIDECOUNT, slideCount);
+    public void setSlideCount(final int slideCount) {
+        set1stProperty(PropertyIDMap.PID_SLIDECOUNT, slideCount);
     }
 
     /**
-     * <p>Removes the slide count.</p>
+     * Removes the slide count.
      */
-    public void removeSlideCount()
-    {
-        final MutableSection s = (MutableSection) getFirstSection();
-        s.removeProperty(PropertyIDMap.PID_SLIDECOUNT);
+    public void removeSlideCount() {
+        remove1stProperty(PropertyIDMap.PID_SLIDECOUNT);
     }
 
 
 
     /**
-     * <p>Returns the note count or 0 if the {@link
-     * DocumentSummaryInformation} does not contain a note count.</p>
+     * Returns the note count or 0 if the {@link
+     * DocumentSummaryInformation} does not contain a note count.
      *
      * @return The note count value
      */
-    public int getNoteCount()
-    {
+    public int getNoteCount() {
         return getPropertyIntValue(PropertyIDMap.PID_NOTECOUNT);
     }
 
     /**
-     * <p>Sets the note count.</p>
+     * Sets the note count.
      *
      * @param noteCount The note count to set.
      */
-    public void setNoteCount(final int noteCount)
-    {
-        final MutableSection s = (MutableSection) getFirstSection();
-        s.setProperty(PropertyIDMap.PID_NOTECOUNT, noteCount);
+    public void setNoteCount(final int noteCount) {
+        set1stProperty(PropertyIDMap.PID_NOTECOUNT, noteCount);
     }
 
     /**
-     * <p>Removes the noteCount.</p>
+     * Removes the noteCount.
      */
-    public void removeNoteCount()
-    {
-        final MutableSection s = (MutableSection) getFirstSection();
-        s.removeProperty(PropertyIDMap.PID_NOTECOUNT);
+    public void removeNoteCount() {
+        remove1stProperty(PropertyIDMap.PID_NOTECOUNT);
     }
 
 
 
     /**
-     * <p>Returns the hidden count or 0 if the {@link
+     * Returns the hidden count or 0 if the {@link
      * DocumentSummaryInformation} does not contain a hidden
-     * count.</p>
+     * count.
      *
      * @return The hidden count value
      */
-    public int getHiddenCount()
-    {
+    public int getHiddenCount() {
         return getPropertyIntValue(PropertyIDMap.PID_HIDDENCOUNT);
     }
 
     /**
-     * <p>Sets the hidden count.</p>
+     * Sets the hidden count.
      *
      * @param hiddenCount The hidden count to set.
      */
-    public void setHiddenCount(final int hiddenCount)
-    {
-        final MutableSection s = (MutableSection) getSections().get(0);
-        s.setProperty(PropertyIDMap.PID_HIDDENCOUNT, hiddenCount);
+    public void setHiddenCount(final int hiddenCount) {
+        set1stProperty(PropertyIDMap.PID_HIDDENCOUNT, hiddenCount);
     }
 
     /**
-     * <p>Removes the hidden count.</p>
+     * Removes the hidden count.
      */
-    public void removeHiddenCount()
-    {
-        final MutableSection s = (MutableSection) getFirstSection();
-        s.removeProperty(PropertyIDMap.PID_HIDDENCOUNT);
+    public void removeHiddenCount() {
+        remove1stProperty(PropertyIDMap.PID_HIDDENCOUNT);
     }
 
 
 
     /**
-     * <p>Returns the mmclip count or 0 if the {@link
+     * Returns the mmclip count or 0 if the {@link
      * DocumentSummaryInformation} does not contain a mmclip
-     * count.</p>
+     * count.
      *
      * @return The mmclip count value
      */
-    public int getMMClipCount()
-    {
+    public int getMMClipCount() {
         return getPropertyIntValue(PropertyIDMap.PID_MMCLIPCOUNT);
     }
 
     /**
-     * <p>Sets the mmclip count.</p>
+     * Sets the mmclip count.
      *
      * @param mmClipCount The mmclip count to set.
      */
-    public void setMMClipCount(final int mmClipCount)
-    {
-        final MutableSection s = (MutableSection) getFirstSection();
-        s.setProperty(PropertyIDMap.PID_MMCLIPCOUNT, mmClipCount);
+    public void setMMClipCount(final int mmClipCount) {
+        set1stProperty(PropertyIDMap.PID_MMCLIPCOUNT, mmClipCount);
     }
 
     /**
-     * <p>Removes the mmclip count.</p>
+     * Removes the mmclip count.
      */
-    public void removeMMClipCount()
-    {
-        final MutableSection s = (MutableSection) getFirstSection();
-        s.removeProperty(PropertyIDMap.PID_MMCLIPCOUNT);
+    public void removeMMClipCount() {
+        remove1stProperty(PropertyIDMap.PID_MMCLIPCOUNT);
     }
 
 
 
     /**
-     * <p>Returns <code>true</code> when scaling of the thumbnail is
-     * desired, <code>false</code> if cropping is desired.</p>
+     * Returns {@code true} when scaling of the thumbnail is
+     * desired, {@code false} if cropping is desired.
      *
      * @return The scale value
      */
-    public boolean getScale()
-    {
+    public boolean getScale() {
         return getPropertyBooleanValue(PropertyIDMap.PID_SCALE);
     }
 
     /**
-     * <p>Sets the scale.</p>
+     * Sets the scale.
      *
      * @param scale The scale to set.
      */
-    public void setScale(final boolean scale)
-    {
-        final MutableSection s = (MutableSection) getFirstSection();
-        s.setProperty(PropertyIDMap.PID_SCALE, scale);
+    public void setScale(final boolean scale) {
+        set1stProperty(PropertyIDMap.PID_SCALE, scale);
     }
 
     /**
-     * <p>Removes the scale.</p>
+     * Removes the scale.
      */
-    public void removeScale()
-    {
-        final MutableSection s = (MutableSection) getFirstSection();
-        s.removeProperty(PropertyIDMap.PID_SCALE);
+    public void removeScale() {
+        remove1stProperty(PropertyIDMap.PID_SCALE);
     }
 
 
@@ -402,29 +359,25 @@ public class DocumentSummaryInformation extends SpecialPropertySet
      *
      * @return The heading pair value
      */
-    public byte[] getHeadingPair()
-    {
+    public byte[] getHeadingPair() {
         notYetImplemented("Reading byte arrays ");
         return (byte[]) getProperty(PropertyIDMap.PID_HEADINGPAIR);
     }
 
     /**
-     * <p>Sets the heading pair.</p>
+     * Sets the heading pair.
      *
      * @param headingPair The heading pair to set.
      */
-    public void setHeadingPair(final byte[] headingPair)
-    {
+    public void setHeadingPair(final byte[] headingPair) {
         notYetImplemented("Writing byte arrays ");
     }
 
     /**
-     * <p>Removes the heading pair.</p>
+     * Removes the heading pair.
      */
-    public void removeHeadingPair()
-    {
-        final MutableSection s = (MutableSection) getFirstSection();
-        s.removeProperty(PropertyIDMap.PID_HEADINGPAIR);
+    public void removeHeadingPair() {
+        remove1stProperty(PropertyIDMap.PID_HEADINGPAIR);
     }
 
 
@@ -436,8 +389,7 @@ public class DocumentSummaryInformation extends SpecialPropertySet
      *
      * @return The doc parts value
      */
-    public byte[] getDocparts()
-    {
+    public byte[] getDocparts() {
         notYetImplemented("Reading byte arrays");
         return (byte[]) getProperty(PropertyIDMap.PID_DOCPARTS);
     }
@@ -445,129 +397,110 @@ public class DocumentSummaryInformation extends SpecialPropertySet
 
 
     /**
-     * <p>Sets the doc parts.</p>
+     * Sets the doc parts.
      *
      * @param docparts The doc parts to set.
      */
-    public void setDocparts(final byte[] docparts)
-    {
+    public void setDocparts(final byte[] docparts) {
         notYetImplemented("Writing byte arrays");
     }
 
     /**
-     * <p>Removes the doc parts.</p>
+     * Removes the doc parts.
      */
-    public void removeDocparts()
-    {
-        final MutableSection s = (MutableSection) getFirstSection();
-        s.removeProperty(PropertyIDMap.PID_DOCPARTS);
+    public void removeDocparts() {
+        remove1stProperty(PropertyIDMap.PID_DOCPARTS);
     }
 
 
 
     /**
-     * <p>Returns the manager (or {@code null}).</p>
+     * Returns the manager (or {@code null}).
      *
      * @return The manager value
      */
-    public String getManager()
-    {
+    public String getManager() {
         return getPropertyStringValue(PropertyIDMap.PID_MANAGER);
     }
 
     /**
-     * <p>Sets the manager.</p>
+     * Sets the manager.
      *
      * @param manager The manager to set.
      */
-    public void setManager(final String manager)
-    {
-        final MutableSection s = (MutableSection) getFirstSection();
-        s.setProperty(PropertyIDMap.PID_MANAGER, manager);
+    public void setManager(final String manager) {
+        set1stProperty(PropertyIDMap.PID_MANAGER, manager);
     }
 
     /**
-     * <p>Removes the manager.</p>
+     * Removes the manager.
      */
-    public void removeManager()
-    {
-        final MutableSection s = (MutableSection) getFirstSection();
-        s.removeProperty(PropertyIDMap.PID_MANAGER);
+    public void removeManager() {
+        remove1stProperty(PropertyIDMap.PID_MANAGER);
     }
 
 
 
     /**
-     * <p>Returns the company (or {@code null}).</p>
+     * Returns the company (or {@code null}).
      *
      * @return The company value
      */
-    public String getCompany()
-    {
+    public String getCompany() {
         return getPropertyStringValue(PropertyIDMap.PID_COMPANY);
     }
 
     /**
-     * <p>Sets the company.</p>
+     * Sets the company.
      *
      * @param company The company to set.
      */
-    public void setCompany(final String company)
-    {
-        final MutableSection s = (MutableSection) getFirstSection();
-        s.setProperty(PropertyIDMap.PID_COMPANY, company);
+    public void setCompany(final String company) {
+        set1stProperty(PropertyIDMap.PID_COMPANY, company);
     }
 
     /**
-     * <p>Removes the company.</p>
+     * Removes the company.
      */
-    public void removeCompany()
-    {
-        final MutableSection s = (MutableSection) getFirstSection();
-        s.removeProperty(PropertyIDMap.PID_COMPANY);
+    public void removeCompany() {
+        remove1stProperty(PropertyIDMap.PID_COMPANY);
     }
 
 
     /**
-     * <p>Returns <code>true</code> if the custom links are dirty.</p> <p>
+     * Returns {@code true} if the custom links are dirty. <p>
      *
      * @return The links dirty value
      */
-    public boolean getLinksDirty()
-    {
+    public boolean getLinksDirty() {
         return getPropertyBooleanValue(PropertyIDMap.PID_LINKSDIRTY);
     }
 
     /**
-     * <p>Sets the linksDirty.</p>
+     * Sets the linksDirty.
      *
      * @param linksDirty The links dirty value to set.
      */
-    public void setLinksDirty(final boolean linksDirty)
-    {
-        final MutableSection s = (MutableSection) getFirstSection();
-        s.setProperty(PropertyIDMap.PID_LINKSDIRTY, linksDirty);
+    public void setLinksDirty(final boolean linksDirty) {
+        set1stProperty(PropertyIDMap.PID_LINKSDIRTY, linksDirty);
     }
 
     /**
-     * <p>Removes the links dirty.</p>
+     * Removes the links dirty.
      */
-    public void removeLinksDirty()
-    {
-        final MutableSection s = (MutableSection) getFirstSection();
-        s.removeProperty(PropertyIDMap.PID_LINKSDIRTY);
+    public void removeLinksDirty() {
+        remove1stProperty(PropertyIDMap.PID_LINKSDIRTY);
     }
     
     
     /**
-     * <p>Returns the character count including whitespace, or 0 if the 
-     *  {@link DocumentSummaryInformation} does not contain this char count.</p>
+     * Returns the character count including whitespace, or 0 if the 
+     *  {@link DocumentSummaryInformation} does not contain this char count.
      * <p>This is the whitespace-including version of {@link SummaryInformation#getCharCount()}
      *
      * @return The character count or {@code null}
      */
-    public int getCharCountWithSpaces()
-    {
+    public int getCharCountWithSpaces() {
         return getPropertyIntValue(PropertyIDMap.PID_CCHWITHSPACES);
     }
 
@@ -576,19 +509,15 @@ public class DocumentSummaryInformation extends SpecialPropertySet
      * 
      * @param count The character count to set.
      */
-    public void setCharCountWithSpaces(int count)
-    {
-        final MutableSection s = (MutableSection) getFirstSection();
-        s.setProperty(PropertyIDMap.PID_CCHWITHSPACES, count);
+    public void setCharCountWithSpaces(int count) {
+        set1stProperty(PropertyIDMap.PID_CCHWITHSPACES, count);
     }
     
     /**
      * Removes the character count
      */
-    public void removeCharCountWithSpaces()
-    {
-        final MutableSection s = (MutableSection) getFirstSection();
-        s.removeProperty(PropertyIDMap.PID_CCHWITHSPACES);
+    public void removeCharCountWithSpaces() {
+        remove1stProperty(PropertyIDMap.PID_CCHWITHSPACES);
     }
     
     
@@ -599,8 +528,7 @@ public class DocumentSummaryInformation extends SpecialPropertySet
      * 
      * @return true, if the hyperlinks should be updated on document load
      */
-    public boolean getHyperlinksChanged()
-    {
+    public boolean getHyperlinksChanged() {
         return getPropertyBooleanValue(PropertyIDMap.PID_HYPERLINKSCHANGED);
     }
     
@@ -610,20 +538,16 @@ public class DocumentSummaryInformation extends SpecialPropertySet
      *  
      * @param changed true, if the User Defined Property Set has been updated
      */
-    public void setHyperlinksChanged(boolean changed)
-    {
-        final MutableSection s = (MutableSection) getFirstSection();
-        s.setProperty(PropertyIDMap.PID_HYPERLINKSCHANGED, changed);
+    public void setHyperlinksChanged(boolean changed) {
+        set1stProperty(PropertyIDMap.PID_HYPERLINKSCHANGED, changed);
     }
     
     /**
      * Removes the flag for if the User Defined Property Set has been updated
      *  outside of the Application.
      */
-    public void removeHyperlinksChanged()
-    {
-        final MutableSection s = (MutableSection) getFirstSection();
-        s.removeProperty(PropertyIDMap.PID_HYPERLINKSCHANGED);
+    public void removeHyperlinksChanged() {
+        remove1stProperty(PropertyIDMap.PID_HYPERLINKSCHANGED);
     }
 
     
@@ -635,8 +559,7 @@ public class DocumentSummaryInformation extends SpecialPropertySet
      * 
      * @return the Application version
      */
-    public int getApplicationVersion()
-    {
+    public int getApplicationVersion() {
         return getPropertyIntValue(PropertyIDMap.PID_VERSION);
     }
     
@@ -647,19 +570,15 @@ public class DocumentSummaryInformation extends SpecialPropertySet
      *  
      * @param version the Application version
      */
-    public void setApplicationVersion(int version)
-    {
-        final MutableSection s = (MutableSection) getFirstSection();
-        s.setProperty(PropertyIDMap.PID_VERSION, version);
+    public void setApplicationVersion(int version) {
+        set1stProperty(PropertyIDMap.PID_VERSION, version);
     }
     
     /**
      * Removes the Application Version
      */
-    public void removeApplicationVersion()
-    {
-        final MutableSection s = (MutableSection) getFirstSection();
-        s.removeProperty(PropertyIDMap.PID_VERSION);
+    public void removeApplicationVersion() {
+        remove1stProperty(PropertyIDMap.PID_VERSION);
     }
 
     
@@ -669,8 +588,7 @@ public class DocumentSummaryInformation extends SpecialPropertySet
      * 
      * @return the VBA digital signature
      */
-    public byte[] getVBADigitalSignature()
-    {
+    public byte[] getVBADigitalSignature() {
         Object value = getProperty(PropertyIDMap.PID_DIGSIG);
         if (value != null && value instanceof byte[]) {
             return (byte[])value;
@@ -679,24 +597,20 @@ public class DocumentSummaryInformation extends SpecialPropertySet
     }
     
     /**
-     * <p>Sets the VBA digital signature for the VBA project 
-     *  embedded in the document.</p>
+     * Sets the VBA digital signature for the VBA project 
+     *  embedded in the document.
      *  
      * @param signature VBA Digital Signature for the project
      */
-    public void setVBADigitalSignature(byte[] signature)
-    {
-        final MutableSection s = (MutableSection) getFirstSection();
-        s.setProperty(PropertyIDMap.PID_DIGSIG, signature);
+    public void setVBADigitalSignature(byte[] signature) {
+        set1stProperty(PropertyIDMap.PID_DIGSIG, signature);
     }
     
     /**
      * Removes the VBA Digital Signature
      */
-    public void removeVBADigitalSignature()
-    {
-        final MutableSection s = (MutableSection) getFirstSection();
-        s.removeProperty(PropertyIDMap.PID_DIGSIG);
+    public void removeVBADigitalSignature() {
+        remove1stProperty(PropertyIDMap.PID_DIGSIG);
     }
 
     
@@ -705,8 +619,7 @@ public class DocumentSummaryInformation extends SpecialPropertySet
      * 
      * @return the content type of the file
      */
-    public String getContentType()
-    {
+    public String getContentType() {
         return getPropertyStringValue(PropertyIDMap.PID_CONTENTTYPE);
     }
     
@@ -715,19 +628,15 @@ public class DocumentSummaryInformation extends SpecialPropertySet
      * 
      * @param type the content type of the file
      */
-    public void setContentType(String type)
-    {
-        final MutableSection s = (MutableSection) getFirstSection();
-        s.setProperty(PropertyIDMap.PID_CONTENTTYPE, type);
+    public void setContentType(String type) {
+        set1stProperty(PropertyIDMap.PID_CONTENTTYPE, type);
     }
     
     /**
      * Removes the content type of the file
      */
-    public void removeContentType()
-    {
-        final MutableSection s = (MutableSection) getFirstSection();
-        s.removeProperty(PropertyIDMap.PID_CONTENTTYPE);
+    public void removeContentType() {
+        remove1stProperty(PropertyIDMap.PID_CONTENTTYPE);
     }
 
     
@@ -736,8 +645,7 @@ public class DocumentSummaryInformation extends SpecialPropertySet
      * 
      * @return the content status of the file
      */
-    public String getContentStatus()
-    {
+    public String getContentStatus() {
         return getPropertyStringValue(PropertyIDMap.PID_CONTENTSTATUS);
     }
     
@@ -746,19 +654,15 @@ public class DocumentSummaryInformation extends SpecialPropertySet
      * 
      * @param status the content status of the file
      */
-    public void setContentStatus(String status)
-    {
-        final MutableSection s = (MutableSection) getFirstSection();
-        s.setProperty(PropertyIDMap.PID_CONTENTSTATUS, status);
+    public void setContentStatus(String status) {
+        set1stProperty(PropertyIDMap.PID_CONTENTSTATUS, status);
     }
     
     /**
      * Removes the content status of the file
      */
-    public void removeContentStatus()
-    {
-        final MutableSection s = (MutableSection) getFirstSection();
-        s.removeProperty(PropertyIDMap.PID_CONTENTSTATUS);
+    public void removeContentStatus() {
+        remove1stProperty(PropertyIDMap.PID_CONTENTSTATUS);
     }
 
     
@@ -767,8 +671,7 @@ public class DocumentSummaryInformation extends SpecialPropertySet
      * 
      * @return the document language
      */
-    public String getLanguage()
-    {
+    public String getLanguage() {
         return getPropertyStringValue(PropertyIDMap.PID_LANGUAGE);
     }
     
@@ -777,30 +680,25 @@ public class DocumentSummaryInformation extends SpecialPropertySet
      * 
      * @param language the document language
      */
-    public void setLanguage(String language)
-    {
-        final MutableSection s = (MutableSection) getFirstSection();
-        s.setProperty(PropertyIDMap.PID_LANGUAGE, language);
+    public void setLanguage(String language) {
+        set1stProperty(PropertyIDMap.PID_LANGUAGE, language);
     }
     
     /**
      * Removes the document language
      */
-    public void removeLanguage()
-    {
-        final MutableSection s = (MutableSection) getFirstSection();
-        s.removeProperty(PropertyIDMap.PID_LANGUAGE);
+    public void removeLanguage() {
+        remove1stProperty(PropertyIDMap.PID_LANGUAGE);
     }
 
     
     /**
-     * <p>Gets the document version as a string, which is normally unset and empty
-     *  (or {@code null}).</p>
+     * Gets the document version as a string, which is normally unset and empty
+     *  (or {@code null}).
      *  
      *  @return the document verion
      */
-    public String getDocumentVersion()
-    {
+    public String getDocumentVersion() {
         return getPropertyStringValue(PropertyIDMap.PID_DOCVERSION);
     }
     
@@ -809,64 +707,56 @@ public class DocumentSummaryInformation extends SpecialPropertySet
      * 
      * @param version the document version string
      */
-    public void setDocumentVersion(String version)
-    {
-        final MutableSection s = (MutableSection) getFirstSection();
-        s.setProperty(PropertyIDMap.PID_DOCVERSION, version);
+    public void setDocumentVersion(String version) {
+        set1stProperty(PropertyIDMap.PID_DOCVERSION, version);
     }
     
     /**
      * Removes the document version string
      */
-    public void removeDocumentVersion()
-    {
-        final MutableSection s = (MutableSection) getFirstSection();
-        s.removeProperty(PropertyIDMap.PID_DOCVERSION);
+    public void removeDocumentVersion() {
+        remove1stProperty(PropertyIDMap.PID_DOCVERSION);
     }
 
     
     /**
-     * <p>Gets the custom properties.</p>
+     * Gets the custom properties.
      *
      * @return The custom properties.
      */
-    public CustomProperties getCustomProperties()
-    {
+    public CustomProperties getCustomProperties() {
         CustomProperties cps = null;
-        if (getSectionCount() >= 2)
-        {
+        if (getSectionCount() >= 2) {
             cps = new CustomProperties();
             final Section section = getSections().get(1);
             final Map<Long,String> dictionary = section.getDictionary();
             final Property[] properties = section.getProperties();
             int propertyCount = 0;
-            for (int i = 0; i < properties.length; i++)
-            {
+            for (int i = 0; i < properties.length; i++) {
                 final Property p = properties[i];
                 final long id = p.getID();
-                if (id != 0 && id != 1)
-                {
+                if (id != 0 && id != 1) {
                     propertyCount++;
                     final CustomProperty cp = new CustomProperty(p,
                             dictionary.get(Long.valueOf(id)));
                     cps.put(cp.getName(), cp);
                 }
             }
-            if (cps.size() != propertyCount)
+            if (cps.size() != propertyCount) {
                 cps.setPure(false);
+            }
         }
         return cps;
     }
 
     /**
-     * <p>Sets the custom properties.</p>
+     * Sets the custom properties.
      *
      * @param customProperties The custom properties
      */
-    public void setCustomProperties(final CustomProperties customProperties)
-    {
+    public void setCustomProperties(final CustomProperties customProperties) {
         ensureSection2();
-        final MutableSection section = (MutableSection) getSections().get(1);
+        final Section section = getSections().get(1);
         final Map<Long,String> dictionary = customProperties.getDictionary();
         section.clear();
 
@@ -874,55 +764,57 @@ public class DocumentSummaryInformation extends SpecialPropertySet
          * codepage, the codepage from the custom properties wins, else take the
          * one that is defined. If none is defined, take Unicode. */
         int cpCodepage = customProperties.getCodepage();
-        if (cpCodepage < 0)
+        if (cpCodepage < 0) {
             cpCodepage = section.getCodepage();
-        if (cpCodepage < 0)
+        }
+        if (cpCodepage < 0) {
             cpCodepage = CodePageUtil.CP_UNICODE;
+        }
         customProperties.setCodepage(cpCodepage);
         section.setCodepage(cpCodepage);
         section.setDictionary(dictionary);
-        for (final Iterator<CustomProperty> i = customProperties.values().iterator(); i.hasNext();)
-        {
-            final Property p = i.next();
+        for (CustomProperty p : customProperties.values()) {
             section.setProperty(p);
         }
     }
 
     /**
-     * <p>Creates section 2 if it is not already present.</p>
-     *
+     * Creates section 2 if it is not already present.
      */
-    private void ensureSection2()
-    {
-        if (getSectionCount() < 2)
-        {
-            MutableSection s2 = new MutableSection();
+    private void ensureSection2() {
+        if (getSectionCount() < 2) {
+            Section s2 = new MutableSection();
             s2.setFormatID(SectionIDMap.DOCUMENT_SUMMARY_INFORMATION_ID[1]);
             addSection(s2);
         }
     }
 
     /**
-     * <p>Removes the custom properties.</p>
+     * Removes the custom properties.
      */
-    public void removeCustomProperties()
-    {
-        if (getSectionCount() >= 2)
-            getSections().remove(1);
-        else
+    public void removeCustomProperties() {
+        if (getSectionCount() < 2) {
             throw new HPSFRuntimeException("Illegal internal format of Document SummaryInformation stream: second section is missing.");
-    }
-
+        }
 
+        List<Section> l = new LinkedList<Section>(getSections());
+        clearSections();
+        int idx = 0;
+        for (Section s : l) {
+            if (idx++ != 1) {
+                addSection(s);
+            }
+        }
+    }
+    
     /**
-     * <p>Throws an {@link UnsupportedOperationException} with a message text
-     * telling which functionality is not yet implemented.</p>
+     * Throws an {@link UnsupportedOperationException} with a message text
+     * telling which functionality is not yet implemented.
      *
      * @param msg text telling was leaves to be implemented, e.g.
      * "Reading byte arrays".
      */
-    private void notYetImplemented(final String msg)
-    {
+    private void notYetImplemented(final String msg) {
         throw new UnsupportedOperationException(msg + " is not yet implemented.");
     }
 }
index 9d77c0dd0817a66e65804df812592a4cd51e801e..10d86be0403ab643278d12ef3513686ddb090f76 100644 (file)
 
 package org.apache.poi.hpsf;
 
-import java.io.IOException;
-import java.io.OutputStream;
-
-import org.apache.poi.util.CodePageUtil;
+import org.apache.poi.util.Removal;
 
 /**
  * <p>Adds writing capability to the {@link Property} class.</p>
  *
  * <p>Please be aware that this class' functionality will be merged into the
  * {@link Property} class at a later time, so the API will change.</p>
+ * 
+ * @deprecated POI 3.16 - use Property as base class instead
  */
-public class MutableProperty extends Property
-{
-
-    /**
-     * <p>Creates an empty property. It must be filled using the set method to
-     * be usable.</p>
-     */
-    public MutableProperty()
-    { }
-
-
-
-    /**
-     * <p>Creates a <code>MutableProperty</code> as a copy of an existing
-     * <code>Property</code>.</p>
-     *
-     * @param p The property to copy.
-     */
-    public MutableProperty(final Property p)
-    {
-        setID(p.getID());
-        setType(p.getType());
-        setValue(p.getValue());
-    }
-
-
-    /**
-     * <p>Sets the property's ID.</p>
-     *
-     * @param id the ID
-     */
-    public void setID(final long id)
-    {
-        this.id = id;
-    }
-
-
-
-    /**
-     * <p>Sets the property's type.</p>
-     *
-     * @param type the property's type
-     */
-    public void setType(final long type)
-    {
-        this.type = type;
-    }
-
-
-
-    /**
-     * <p>Sets the property's value.</p>
-     *
-     * @param value the property's value
-     */
-    public void setValue(final Object value)
-    {
-        this.value = value;
-    }
-
-
-
-    /**
-     * <p>Writes the property to an output stream.</p>
-     *
-     * @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;
+@Removal(version="3.18")
+public class MutableProperty extends Property {
+    public MutableProperty() {}
 
-        length += TypeWriter.writeUIntToStream(out, variantType);
-        length += VariantSupport.write(out, variantType, getValue(), codepage);
-        return length;
+    public MutableProperty(final Property p) {
+       super(p);
     }
 
 }
index 5c7d386587c2eaaec7f8f4a6f18c0e64bf3e3b21..16978a4d6aee292c0ab22be04267bfb3f576e191 100644 (file)
 
 package org.apache.poi.hpsf;
 
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.UnsupportedEncodingException;
-import java.util.LinkedList;
-
-import org.apache.poi.poifs.filesystem.DirectoryEntry;
-import org.apache.poi.poifs.filesystem.Entry;
-import org.apache.poi.util.LittleEndian;
-import org.apache.poi.util.LittleEndianConsts;
+import org.apache.poi.util.Removal;
 
 /**
- * <p>Adds writing support to the {@link PropertySet} class.</p>
+ * dds writing support to the {@link PropertySet} class.<p>
+ *
+ * Please be aware that this class' functionality will be merged into the
+ * {@link PropertySet} class at a later time, so the API will change.
  *
- * <p>Please be aware that this class' functionality will be merged into the
- * {@link PropertySet} class at a later time, so the API will change.</p>
+ * @deprecated POI 3.16 - use PropertySet as base class instead
  */
-public class MutablePropertySet extends PropertySet
-{
-
-    /**
-     * <p>Constructs a <code>MutablePropertySet</code> instance. Its
-     * primary task is to initialize the immutable field with their proper
-     * values. It also sets fields that might change to reasonable defaults.</p>
-     */
-    public MutablePropertySet()
-    {
-        /* Initialize the "byteOrder" field. */
-        byteOrder = LittleEndian.getUShort(BYTE_ORDER_ASSERTION);
-
-        /* Initialize the "format" field. */
-        format = LittleEndian.getUShort(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;
-
-        /* Initailize the "classID" field. */
-        classID = new ClassID();
-
-        /* Initialize the sections. Since property set must have at least
-         * one section it is added right here. */
-        sections = new LinkedList<Section>();
-        sections.add(new MutableSection());
-    }
-
-
-
-    /**
-     * <p>Constructs a <code>MutablePropertySet</code> by doing a deep copy of
-     * an existing <code>PropertySet</code>. All nested elements, i.e.
-     * <code>Section</code>s and <code>Property</code> instances, will be their
-     * mutable counterparts in the new <code>MutablePropertySet</code>.</p>
-     *
-     * @param ps The property set to copy
-     */
-    public MutablePropertySet(final PropertySet ps)
-    {
-        byteOrder = ps.getByteOrder();
-        format = ps.getFormat();
-        osVersion = ps.getOSVersion();
-        setClassID(ps.getClassID());
-        clearSections();
-        if (sections == null)
-            sections = new LinkedList<Section>();
-        for (final Section section : ps.getSections())
-        {
-            final MutableSection s = new MutableSection(section);
-            addSection(s);
-        }
-    }
-
-
-
-    /**
-     * <p>The length of the property set stream header.</p>
-     */
-    private final static int OFFSET_HEADER =
-        BYTE_ORDER_ASSERTION.length + /* Byte order    */
-        FORMAT_ASSERTION.length +     /* Format        */
-        LittleEndianConsts.INT_SIZE + /* OS version    */
-        ClassID.LENGTH +              /* Class ID      */
-        LittleEndianConsts.INT_SIZE;  /* Section count */
-
-
-
-    /**
-     * <p>Sets the "byteOrder" property.</p>
-     *
-     * @param byteOrder the byteOrder value to set
-     */
-    public void setByteOrder(final int byteOrder)
-    {
-        this.byteOrder = byteOrder;
-    }
-
-
-
-    /**
-     * <p>Sets the "format" property.</p>
-     *
-     * @param format the format value to set
-     */
-    public void setFormat(final int format)
-    {
-        this.format = format;
-    }
-
-
-
-    /**
-     * <p>Sets the "osVersion" property.</p>
-     *
-     * @param osVersion the osVersion value to set
-     */
-    public void setOSVersion(final int osVersion)
-    {
-        this.osVersion = osVersion;
+@Removal(version="3.18")
+public class MutablePropertySet extends PropertySet {
+    public MutablePropertySet() {}
+       
+       public MutablePropertySet(final PropertySet ps) {
+        super(ps);
     }
-
-
-
-    /**
-     * <p>Sets the property set stream's low-level "class ID"
-     * field.</p>
-     *
-     * @param classID The property set stream's low-level "class ID" field.
-     *
-     * @see PropertySet#getClassID()
-     */
-    public void setClassID(final ClassID classID)
-    {
-        this.classID = classID;
-    }
-
-
-
-    /**
-     * <p>Removes all sections from this property set.</p>
-     */
-    public void clearSections()
-    {
-        sections = null;
-    }
-
-
-
-    /**
-     * <p>Adds a section to this property set.</p>
-     *
-     * @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)
-    {
-        if (sections == null)
-            sections = new LinkedList<Section>();
-        sections.add(section);
-    }
-
-
-
-    /**
-     * <p>Writes the property set to an output stream.</p>
-     *
-     * @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 = sections.size();
-
-        /* 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 : sections)
-        {
-            final MutableSection s = (MutableSection)section;
-            final ClassID formatID = s.getFormatID();
-            if (formatID == null)
-                throw new NoFormatIDException();
-            TypeWriter.writeToStream(out, s.getFormatID());
-            TypeWriter.writeUIntToStream(out, offset);
-            try
-            {
-                offset += s.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 : sections)
-        {
-            final MutableSection s = (MutableSection)section;
-            offset += s.write(out);
-        }
-        
-        /* Indicate that we're done */
-        out.close();
-    }
-
-
-
-    /**
-     * <p>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.</p>
-     *
-     * @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);
-    }
-
-    /**
-     * <p>Writes a property set to a document in a POI filesystem directory.</p>
-     *
-     * @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 void write(final DirectoryEntry dir, final String name)
-    throws WritingNotSupportedException, IOException
-    {
-        /* If there is already an entry with the same name, remove it. */
-        try
-        {
-            final Entry e = dir.getEntry(name);
-            e.delete();
-        }
-        catch (FileNotFoundException ex)
-        {
-            /* Entry not found, no need to remove it. */
-        }
-        /* Create the new entry. */
-        dir.createDocument(name, toInputStream());
-    }
-
 }
index b0d71acf0e622f999a272205b4a9d41e3c9aa8c3..c5f0d6e0c85278117275b0f29af0d8020631ba19 100644 (file)
 
 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;
-    }
-
+@Removal(version="3.18")
+public class MutableSection extends Section {
+    public MutableSection() {}
 
-
-    /**
-     * <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();
+    public MutableSection(final Section s) {
+       super(s);
     }
-
-
-
-    /**
-     * <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));
+    
+    public MutableSection(final byte[] src, final int offset) throws UnsupportedEncodingException {
+        super(src,offset);
     }
 }
index f1a09438e149d2d7316564b0839d0946a4e99737..51f76c3486912f41942b0078e47efcd1d5a06230 100644 (file)
 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);
     }
 
index 14dbee0276d5f364eae2bd73bd58dc699b5d3ef9..b9841dbcc906d59866eea0fe077bfccc92961268 100644 (file)
 
 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
+     */
+    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;
+    }
+
+    /**
+     * 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
      */
-    protected Property()
-    { }
+    public void setValue(final Object value) {
+        this.value = value;
+    }
+
 
 
+    
 
     /**
-     * <p>Reads a dictionary.</p>
+     * 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;
+    }
+    
 }
index 9c9b1876a07c9046d09a10736f851f87f479a180..30a636ffcebef2ceef7c3e8c14c7652439bda688 100644 (file)
 
 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
-{
-
-    /**
-     * <p>The "byteOrder" field must equal this value.</p>
-     */
-    static final byte[] BYTE_ORDER_ASSERTION =
-            {(byte) 0xFE, (byte) 0xFF};
-
-    /**
-     * <p>Specifies this {@link PropertySet}'s byte order. See the
-     * HPFS documentation for details!</p>
-     */
-    protected int byteOrder;
-
+public class PropertySet {
     /**
-     * <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 16-bit Windows system.
      */
-    public int getByteOrder()
-    {
-        return byteOrder;
-    }
-
-
+    public static final int OS_WIN16     = 0x0000;
 
     /**
-     * <p>The "format" field must equal this value.</p>
+     * If the OS version field holds this value the property set stream was
+     * created on a Macintosh system.
      */
-    static final byte[] FORMAT_ASSERTION =
-            {(byte) 0x00, (byte) 0x00};
+    public static final int OS_MACINTOSH = 0x0001;
 
     /**
-     * <p>Specifies this {@link PropertySet}'s format. See the HPFS
-     * documentation for details!</p>
+     * If the OS version field holds this value the property set stream was
+     * created on a 32-bit Windows system.
      */
-    protected int format;
+    public static final int OS_WIN32     = 0x0002;
 
     /**
-     * <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 "byteOrder" field must equal this value.
      */
-    public int getFormat()
-    {
-        return format;
-    }
+    private static final int BYTE_ORDER_ASSERTION = 0xFFFE;
 
-
     /**
-     * <p>Specifies the version of the operating system that created
-     * this {@link PropertySet}. See the HPFS documentation for
-     * details!</p>
+     * The "format" field must equal this value.
      */
-    protected int osVersion;
-
+    private static final int FORMAT_ASSERTION = 0x0000;
 
     /**
-     * <p>If the OS version field holds this value the property set stream was
-     * created on a 16-bit Windows system.</p>
+     * The length of the property set stream header.
      */
-    public static final int OS_WIN16     = 0x0000;
+    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>If the OS version field holds this value the property set stream was
-     * created on a Macintosh system.</p>
+     * Specifies this {@link PropertySet}'s byte order. See the
+     * HPFS documentation for details!
      */
-    public static final int OS_MACINTOSH = 0x0001;
+    private int byteOrder;
 
     /**
-     * <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 format. See the HPFS
+     * documentation for details!
      */
-    public static final int OS_WIN32     = 0x0002;
-
+    private int format;
+    
     /**
-     * <p>Returns the property set stream's low-level "OS version"
-     * field.</p>
-     *
-     * @return The property set stream's low-level "OS version" field.
+     * Specifies the version of the operating system that created this
+     * {@link PropertySet}. See the HPFS documentation for details!
      */
-    public int getOSVersion()
-    {
-        return osVersion;
-    }
-
-
+    private int osVersion;
 
     /**
-     * <p>Specifies this {@link PropertySet}'s "classID" field. See
-     * the HPFS documentation for details!</p>
+     * Specifies this {@link PropertySet}'s "classID" field. See
+     * the HPFS documentation for details!
      */
-    protected ClassID classID;
+    private 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.
+     * The sections in this {@link PropertySet}.
      */
-    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>
-     *
-     * <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>
+     * 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;
+    }
 
+    /**
+     * @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;
+    }
 
     /**
-     * <p>Checks whether an {@link InputStream} is in the Horrible
-     * Property Set Format.</p>
+     * @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.
      *
-     * @return <code>true</code> if this {@link PropertySet}
-     * represents a Summary Information, else <code>false</code>.
-     */
-    public boolean isSummaryInformation()
-    {
-        if (sections.size() <= 0)
-            return false;
-        return Util.equal(sections.get(0).getFormatID().getBytes(),
-                          SectionIDMap.SUMMARY_INFORMATION_ID);
+     * @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 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);
+    }
 
     /**
-     * <p>Checks whether this {@link PropertySet} is a Document
-     * Summary Information.</p>
+     * 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</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);
+    }
 }
index 4d4b4e6386ba2c6ec2d588717cb98f8027032f7e..db0a73d135419cc2c0e4b5de8e6949f875a50f77 100644 (file)
@@ -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;
 
 /**
- * <p>Factory class to create instances of {@link SummaryInformation},
- * {@link DocumentSummaryInformation} and {@link PropertySet}.</p>
+ * Factory class to create instances of {@link SummaryInformation},
+ * {@link DocumentSummaryInformation} and {@link PropertySet}.
  */
-public class PropertySetFactory
-{
+public class PropertySetFactory {
     /**
-     * <p>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.</p>
+     * 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();
+            }
         }
     }
 
     /**
-     * <p>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.</p>
+     * 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
-     * the <code>mark</code> 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
     }
 
     /**
-     * <p>Creates a new summary information.</p>
+     * 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();
     }
 
     /**
-     * <p>Creates a new document summary information.</p>
+     * 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
index c54fe51ab2b7e2dd787f941d5b87247620df7b65..a27e9485cd6e4f126951af9d9c19623b2001e3aa 100644 (file)
 
 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;
 
 /**
- * <p>Represents a section in a {@link PropertySet}.</p>
+ * Represents a section in a {@link PropertySet}.
  */
-public class Section
-{
+public class Section {
 
     /**
-     * <p>Maps property IDs to section-private PID strings. These
-     * strings can be found in the property with ID 0.</p>
+     * Maps property IDs to section-private PID strings. These
+     * strings can be found in the property with ID 0.
      */
-    protected Map<Long,String> dictionary;
+    private Map<Long,String> dictionary;
 
     /**
-     * <p>The section's format ID, {@link #getFormatID}.</p>
+     * The section's format ID, {@link #getFormatID}.
      */
-    protected ClassID formatID;
-
-
+    private ClassID formatID;
     /**
-     * <p>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
-     * <code>org.apache.poi.hpsf.wellknown.SectionIDMap.SUMMARY_INFORMATION_ID</code>
-     * the section (and thus the property set) is a SummaryInformation.</p>
-     *
-     * @return The format ID
+     * If the "dirty" flag is true, the section's size must be
+     * (re-)calculated before the section is written.
      */
-    public ClassID getFormatID()
-    {
-        return formatID;
-    }
-
-
+    private boolean dirty = true;
 
     /**
-     * @see #getOffset
+     * 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.
      */
-    protected long offset;
-
+    private byte[] sectionBytes;
 
     /**
-     * <p>Returns the offset of the section in the stream.</p>
-     *
-     * @return The offset of the section in the stream.
+     * The offset of the section in the stream.
      */
-    public long getOffset()
-    {
-        return offset;
-    }
-
-
+    private long offset = -1;
 
     /**
-     * @see #getSize
+     * The section's size in bytes.
      */
-    protected int size;
-
+    private int size;
 
     /**
-     * <p>Returns the section's size in bytes.</p>
-     *
-     * @return The section's size in bytes.
+     * This section's properties.
      */
-    public int getSize()
-    {
-        return size;
-    }
-
-
+    private final Map<Long,Property> properties = new TreeMap<Long,Property>();
 
     /**
-     * <p>Returns the number of properties in this section.</p>
-     *
-     * @return The number of properties in this section.
+     * This member is {@code true} if the last call to {@link
+     * #getPropertyIntValue} or {@link #getProperty} tried to access a
+     * property that was not available, else {@code false}.
      */
-    public int getPropertyCount()
-    {
-        return properties.length;
-    }
-
-
+    private boolean wasNull;
 
     /**
-     * @see #getProperties
+     * Creates an empty {@link Section}.
      */
-    protected Property[] properties;
-
+    public Section() {
+    }
 
     /**
-     * <p>Returns this section's properties.</p>
+     * Constructs a {@code Section} by doing a deep copy of an
+     * existing {@code Section}. All nested {@code Property}
+     * instances, will be their mutable counterparts in the new
+     * {@code MutableSection}.
      *
-     * @return This section's properties.
+     * @param s The section set to copy
      */
-    public Property[] getProperties()
-    {
-        return properties;
+    public Section(final Section s) {
+        setFormatID(s.getFormatID());
+        for (Property p : s.properties.values()) {
+            properties.put(p.getID(), new MutableProperty(p));
+        }
+        setDictionary(s.getDictionary());
     }
 
 
 
     /**
-     * <p>Creates an empty and uninitialized {@link Section}.
-     */
-    protected Section()
-    { }
-
-
-
-    /**
-     * <p>Creates a {@link Section} instance from a byte array.</p>
+     * 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 List<PropertyListEntry> propertyList = new ArrayList<PropertyListEntry>(propertyCount);
-        PropertyListEntry ple;
-        for (int i = 0; i < properties.length; i++)
-        {
-            ple = new PropertyListEntry();
-
+        long cpOffset = -1;
+        final TreeBidiMap<Long,Long> offset2Id = new TreeBidiMap<Long,Long>();
+        for (int i = 0; i < propertyCount; i++) {
             /* Read the property ID. */
-            ple.id = (int) LittleEndian.getUInt(src, pass1Offset);
+            long id = LittleEndian.getUInt(src, pass1Offset);
             pass1Offset += LittleEndian.INT_SIZE;
 
             /* Offset from the section's start. */
-            ple.offset = (int) LittleEndian.getUInt(src, pass1Offset);
+            long off = LittleEndian.getUInt(src, pass1Offset);
             pass1Offset += LittleEndian.INT_SIZE;
 
-            /* Add the entry to the property list. */
-            propertyList.add(ple);
-        }
-
-        /* Sort the property list by ascending offsets: */
-        Collections.sort(propertyList);
-
-        /* Calculate the properties' lengths. */
-        for (int i = 0; i < propertyCount - 1; i++)
-        {
-            PropertyListEntry ple1 = propertyList.get(i);
-            PropertyListEntry ple2 = propertyList.get(i + 1);
-            ple1.length = ple2.offset - ple1.offset;
-        }
-        if (propertyCount > 0)
-        {
-            ple = propertyList.get(propertyCount - 1);
-            ple.length = size - ple.offset;
+            offset2Id.put(off, id);
+            
+            if (id == PropertyIDMap.PID_CODEPAGE) {
+                cpOffset = off;
+            }
         }
 
         /* Look for the codepage. */
         int codepage = -1;
-        for (final Iterator<PropertyListEntry> i = propertyList.iterator();
-             codepage == -1 && i.hasNext();)
-        {
-            ple = i.next();
-
-            /* Read the codepage if the property ID is 1. */
-            if (ple.id == PropertyIDMap.PID_CODEPAGE)
-            {
-                /* Read the property's value type. It must be
-                 * VT_I2. */
-                int o = (int) (this.offset + ple.offset);
-                final long type = LittleEndian.getUInt(src, o);
-                o += LittleEndian.INT_SIZE;
-
-                if (type != Variant.VT_I2)
-                    throw new HPSFRuntimeException
-                        ("Value type of property ID 1 is not VT_I2 but " +
-                         type + ".");
-
-                /* Read the codepage number. */
-                codepage = LittleEndian.getUShort(src, o);
+        if (cpOffset != -1) {
+            /* Read the property's value type. It must be VT_I2. */
+            long o = this.offset + cpOffset;
+            final long type = LittleEndian.getUInt(src, (int)o);
+            o += LittleEndian.INT_SIZE;
+
+            if (type != Variant.VT_I2) {
+                throw new HPSFRuntimeException
+                    ("Value type of property ID 1 is not VT_I2 but " +
+                     type + ".");
             }
+
+            /* Read the codepage number. */
+            codepage = LittleEndian.getUShort(src, (int)o);
         }
+        
 
         /* Pass 2: Read all properties - including the codepage property,
          * if available. */
-        int i1 = 0;
-        for (final Iterator<PropertyListEntry> i = propertyList.iterator(); i.hasNext();)
-        {
-            ple = i.next();
-            Property p = new Property(ple.id, src,
-                    this.offset + ple.offset,
-                    ple.length, codepage);
-            if (p.getID() == PropertyIDMap.PID_CODEPAGE)
-                p = new Property(p.getID(), p.getType(), Integer.valueOf(codepage));
-            properties[i1++] = p;
+        for (Map.Entry<Long,Long> me : offset2Id.entrySet()) {
+            long off = me.getKey();
+            long id = me.getValue();
+            Property p;
+            if (id == PropertyIDMap.PID_CODEPAGE) {
+                p = new Property(PropertyIDMap.PID_CODEPAGE, Variant.VT_I2, codepage);
+            } else {
+                int pLen = propLen(offset2Id, off, size);
+                long o = this.offset + off;
+                p = new Property(id, src, o, pLen, codepage);
+            }
+            properties.put(id, p);
         }
 
         /*
@@ -285,202 +229,367 @@ public class Section
         dictionary = (Map<Long,String>) getProperty(0);
     }
 
+    /**
+     * 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
+     */
+    private static int propLen(
+        TreeBidiMap<Long,Long> offset2Id,
+        Long entryOffset,
+        long maxSize) {
+        Long nextKey = offset2Id.nextKey(entryOffset);
+        long begin = entryOffset;
+        long end = (nextKey != null) ? nextKey : maxSize;
+        return (int)(end - begin);
+    }
+
+
+    /**
+     * 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
+     * {@code org.apache.poi.hpsf.wellknown.SectionIDMap.SUMMARY_INFORMATION_ID}
+     * the section (and thus the property set) is a SummaryInformation.
+     *
+     * @return The format ID
+     */
+    public ClassID getFormatID() {
+        return formatID;
+    }
 
+    /**
+     * Sets the section's format ID.
+     *
+     * @param formatID The section's format ID
+     */
+    public void setFormatID(final ClassID formatID) {
+        this.formatID = formatID;
+    }
 
     /**
-     * <p>Represents an entry in the property list and holds a property's ID and
-     * its offset from the section's beginning.</p>
+     * Sets the section's format ID.
+     *
+     * @param formatID The section's format ID as a byte array. It components
+     * are in big-endian format.
      */
-    static class PropertyListEntry implements Comparable<PropertyListEntry>
-    {
-        int id;
-        int offset;
-        int length;
-
-        /**
-         * <p>Compares 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.</p>
-         *
-         * @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 void setFormatID(final byte[] formatID) {
+        ClassID fid = getFormatID();
+        if (fid == null) {
+            fid = new ClassID();
+            setFormatID(fid);
         }
+        fid.setBytes(formatID);
+    }
 
-        
-        
-        public int hashCode() {
-            final int prime = 31;
-            int result = 1;
-            result = prime * result + id;
-            result = prime * result + length;
-            result = prime * result + offset;
-            return result;
-        }
+    /**
+     * 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()]);
+    }
 
-        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;
+    /**
+     * 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
+     * {@code true}.
+     *
+     * @param id The property's ID
+     *
+     * @return The property's value
+     */
+    public Object getProperty(final long id) {
+        wasNull = !properties.containsKey(id);
+        return (wasNull) ? null : properties.get(id).getValue();
+    }
 
+    /**
+     * Sets the string value of the property with the specified ID.
+     *
+     * @param id The property's ID
+     * @param value The property's value. It will be written as a Unicode
+     * string.
+     */
+    public void setProperty(final int id, final String value) {
+        setProperty(id, Variant.VT_LPWSTR, value);
+    }
 
-        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();
-        }
+    /**
+     * Sets the int 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 int value) {
+        setProperty(id, Variant.VT_I4, Integer.valueOf(value));
     }
 
 
 
     /**
-     * <p>Returns 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>
+     * Sets the long value of the property with the specified ID.
      *
      * @param id The property's ID
+     * @param value The property's value.
      *
-     * @return The property's value
+     * @see #setProperty(int, long, Object)
+     * @see #getProperty
      */
-    public Object getProperty(final long id)
-    {
-        wasNull = false;
-        for (int i = 0; i < properties.length; i++)
-            if (id == properties[i].getID())
-                return properties[i].getValue();
-        wasNull = true;
-        return null;
+    public void setProperty(final int id, final long value) {
+        setProperty(id, Variant.VT_I8, Long.valueOf(value));
     }
 
 
 
     /**
-     * <p>Returns the value of the numeric property with the specified
+     * 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
-     * <code>true</code> to let the caller distinguish that case from
-     * a real property value of 0.</p>
+     * {@code true} to let the caller distinguish that case from
+     * a real property value of 0.
      *
      * @param id The property's ID
      *
      * @return The property's value
      */
-    protected int getPropertyIntValue(final long id)
-    {
+    protected int getPropertyIntValue(final long id) {
         final Number i;
         final Object o = getProperty(id);
-        if (o == null)
+        if (o == null) {
             return 0;
-        if (!(o instanceof Long || o instanceof Integer))
+        }
+        if (!(o instanceof Long || o instanceof Integer)) {
             throw new HPSFRuntimeException
                 ("This property is not an integer type, but " +
                  o.getClass().getName() + ".");
+        }
         i = (Number) o;
         return i.intValue();
     }
 
-
-
     /**
-     * <p>Returns the value of the boolean property with the specified
-     * ID. If the property is not available, <code>false</code> 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
-     * <code>true</code> to let the caller distinguish that case from
-     * a real property value of <code>false</code>.</p>
+     * {@code true} to let the caller distinguish that case from
+     * a real property value of {@code false}.
      *
      * @param id The property's ID
      *
      * @return The property's value
      */
-    protected boolean getPropertyBooleanValue(final int id)
-    {
+    protected boolean getPropertyBooleanValue(final int id) {
         final Boolean b = (Boolean) getProperty(id);
         if (b == null) {
             return false;
         }
         return b.booleanValue();
-        }
+    }
 
+    /**
+     * Sets the value of the boolean property with the specified
+     * ID.
+     *
+     * @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));
+    }
 
+    /**
+     * @return the section's size in bytes.
+     */
+    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>This member is <code>true</code> if the last call to {@link
-     * #getPropertyIntValue} or {@link #getProperty} tried to access a
-     * property that was not available, else <code>false</code>.</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.
+     *
+     * @return the section's length in bytes.
+     * @throws WritingNotSupportedException
+     * @throws IOException
      */
-    private boolean wasNull;
+    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>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>
+     * 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}.
      */
-    public boolean wasNull()
-    {
+    public boolean wasNull() {
         return wasNull;
     }
 
 
 
     /**
-     * <p>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}.</p>
+     * 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));
+    }
+
 
 
     /**
-     * <p>Checks whether this section is equal to another object. The result is
-     * <code>false</code> if one of the the following conditions holds:</p>
+     * Checks whether this section is equal to another object. The result is
+     * {@code false} if one of the the following conditions holds:
      *
      * <ul>
      *
-     * <li><p>The other object is not a {@link Section}.</p></li>
+     * <li>The other object is not a {@link Section}.</li>
      *
-     * <li><p>The format IDs of the two sections are not equal.</p></li>
+     * <li>The format IDs of the two sections are not equal.</li>
      *
-     * <li><p>The sections have a different number of properties. However,
-     * properties with ID 1 (codepage) are not counted.</p></li>
+     * <li>The sections have a different number of properties. However,
+     * properties with ID 1 (codepage) are not counted.</li>
      *
-     * <li><p>The other object is not a {@link Section}.</p></li>
+     * <li>The other object is not a {@link Section}.</li>
      *
-     * <li><p>The properties have different values. The order of the properties
-     * is irrelevant.</p></li>
+     * <li>The properties have different values. The order of the properties
+     * is irrelevant.</li>
      *
      * </ul>
      *
      * @param o The object to compare this section with
-     * @return <code>true</code> if the objects are equal, <code>false</code> 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);
+    }
 
     /**
-     * <p>Removes a field from a property array. The resulting array is
-     * compactified and returned.</p>
+     * 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.<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.
+     *
+     * @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<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;
+    }
+
+    /**
+     * 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 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);
+        }
+    }
 
 
 
     /**
      * @see Object#hashCode()
      */
-    public int hashCode()
-    {
+    public int hashCode() {
         long hashCode = 0;
         hashCode += getFormatID().hashCode();
         final Property[] pa = getProperties();
-        for (int i = 0; i < pa.length; i++)
+        for (int i = 0; i < pa.length; i++) {
             hashCode += pa[i].hashCode();
+        }
         final int returnHashCode = (int) (hashCode & 0x0ffffffffL);
         return returnHashCode;
     }
@@ -626,8 +967,7 @@ public class Section
     /**
      * @see Object#toString()
      */
-    public String toString()
-    {
+    public String toString() {
         final StringBuffer b = new StringBuffer();
         final Property[] pa = getProperties();
         b.append(getClass().getName());
@@ -641,8 +981,7 @@ public class Section
         b.append(", size: ");
         b.append(getSize());
         b.append(", properties: [\n");
-        for (int i = 0; i < pa.length; i++)
-        {
+        for (int i = 0; i < pa.length; i++) {
             b.append(pa[i].toString());
             b.append(",\n");
         }
@@ -654,35 +993,33 @@ public class Section
 
 
     /**
-     * <p>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.</p>
+     * for the strings in the dictionary is defined by property with ID 1.
      *
-     * @return the dictionary or <code>null</code> if the section does not have
+     * @return the dictionary or {@code null} if the section does not have
      * a dictionary.
      */
-    public Map<Long,String> getDictionary()
-    {
+    public Map<Long,String> getDictionary() {
         return dictionary;
     }
 
 
 
     /**
-     * <p>Gets the section's codepage, if any.</p>
+     * Gets the section's codepage, if any.
      *
      * @return The section's codepage if one is defined, else -1.
      */
     public int getCodepage()
     {
-        final Integer codepage =
-            (Integer) getProperty(PropertyIDMap.PID_CODEPAGE);
-        if (codepage == null)
+        final Integer codepage = (Integer) getProperty(PropertyIDMap.PID_CODEPAGE);
+        if (codepage == null) {
             return -1;
+        }
         int cp = codepage.intValue();
         return cp;
     }
-
 }
index aa8f8051d2a85116dcbe32590334617ae576a92e..843d681ebc012e8f7aac999f30612357c61894fb 100644 (file)
 
 package org.apache.poi.hpsf;
 
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.nio.charset.Charset;
-import java.util.List;
-
-import org.apache.poi.hpsf.wellknown.PropertyIDMap;
-import org.apache.poi.poifs.filesystem.DirectoryEntry;
-import org.apache.poi.util.LittleEndian;
+import org.apache.poi.util.Removal;
 
 /**
- * <p>Abstract superclass for the convenience classes {@link
- * SummaryInformation} and {@link DocumentSummaryInformation}.</p>
+ * Interface for the convenience classes {@link SummaryInformation}
+ * and {@link DocumentSummaryInformation}.<p>
  *
- * <p>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.</p>
+ * This used to be an abstract class to support late loading
+ * of the SummaryInformation classes, as their concrete instance can
+ * only be determined after the PropertySet has been loaded.
  *
- * <p>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.</p>
+ * @deprecated POI 3.16 - use PropertySet as base class instead
  */
-public abstract class SpecialPropertySet extends MutablePropertySet
-{
-    /**
-     * The id to name mapping of the properties in this set.
-     * 
-     * @return the id to name mapping of the properties in this set
-     */
-    public abstract PropertyIDMap getPropertySetIDMap();
-
-    /**
-     * <p>The "real" property set <code>SpecialPropertySet</code>
-     * delegates to.</p>
-     */
-    private final MutablePropertySet delegate;
-
-
-
-    /**
-     * <p>Creates a <code>SpecialPropertySet</code>.
-     *
-     * @param ps The property set to be encapsulated by the
-     * <code>SpecialPropertySet</code>
-     */
-    public SpecialPropertySet(final PropertySet ps)
-    {
-        delegate = new MutablePropertySet(ps);
-    }
-
-
-
-    /**
-     * <p>Creates a <code>SpecialPropertySet</code>.
-     *
-     * @param ps The mutable property set to be encapsulated by the
-     * <code>SpecialPropertySet</code>
-     */
-    public SpecialPropertySet(final MutablePropertySet ps)
-    {
-        delegate = 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 List<Section> getSections()
-    {
-        return delegate.getSections();
-    }
-
-
-
-    /**
-     * @see PropertySet#isSummaryInformation
-     */
-    @Override
-    public boolean isSummaryInformation()
-    {
-        return delegate.isSummaryInformation();
-    }
-
-
-
-    /**
-     * @see PropertySet#isDocumentSummaryInformation
-     */
-    @Override
-    public boolean isDocumentSummaryInformation()
-    {
-        return delegate.isDocumentSummaryInformation();
-    }
-
-
-
-    /**
-     * @see PropertySet#getSingleSection
-     */
-    @Override
-    public Section getFirstSection()
-    {
-        return delegate.getFirstSection();
-    }
-
-
-    /**
-     * @see org.apache.poi.hpsf.MutablePropertySet#addSection(org.apache.poi.hpsf.Section)
-     */
-    @Override
-    public void addSection(final Section section)
-    {
-        delegate.addSection(section);
-    }
-
-
-
-    /**
-     * @see org.apache.poi.hpsf.MutablePropertySet#clearSections()
-     */
-    @Override
-    public void clearSections()
-    {
-        delegate.clearSections();
-    }
-
-
-
-    /**
-     * @see org.apache.poi.hpsf.MutablePropertySet#setByteOrder(int)
-     */
-    @Override
-    public void setByteOrder(final int byteOrder)
-    {
-        delegate.setByteOrder(byteOrder);
-    }
-
-
-
-    /**
-     * @see org.apache.poi.hpsf.MutablePropertySet#setClassID(org.apache.poi.hpsf.ClassID)
-     */
-    @Override
-    public void setClassID(final ClassID classID)
-    {
-        delegate.setClassID(classID);
-    }
-
-
-
-    /**
-     * @see org.apache.poi.hpsf.MutablePropertySet#setFormat(int)
-     */
-    @Override
-    public void setFormat(final int format)
-    {
-        delegate.setFormat(format);
-    }
-
-
-
-    /**
-     * @see org.apache.poi.hpsf.MutablePropertySet#setOSVersion(int)
-     */
-    @Override
-    public void setOSVersion(final int osVersion)
-    {
-        delegate.setOSVersion(osVersion);
-    }
-
-
-
-    /**
-     * @see org.apache.poi.hpsf.MutablePropertySet#toInputStream()
-     */
-    @Override
-    public InputStream toInputStream() throws IOException, WritingNotSupportedException
-    {
-        return delegate.toInputStream();
-    }
-
-
-
-    /**
-     * @see org.apache.poi.hpsf.MutablePropertySet#write(org.apache.poi.poifs.filesystem.DirectoryEntry, java.lang.String)
-     */
-    @Override
-    public void write(final DirectoryEntry dir, final String name) throws WritingNotSupportedException, IOException
-    {
-        delegate.write(dir, name);
-    }
-
-    /**
-     * @see org.apache.poi.hpsf.MutablePropertySet#write(java.io.OutputStream)
-     */
-    @Override
-    public void write(final OutputStream out) throws WritingNotSupportedException, IOException
-    {
-        delegate.write(out);
-    }
-
-    /**
-     * @see org.apache.poi.hpsf.PropertySet#equals(java.lang.Object)
-     */
-    @Override
-    public boolean equals(final Object o)
-    {
-        return delegate.equals(o);
+@Removal(version="3.18")
+public class SpecialPropertySet extends MutablePropertySet {
+    public SpecialPropertySet() {
     }
 
-    /**
-     * @see org.apache.poi.hpsf.PropertySet#getProperties()
-     */
-    @Override
-    public Property[] getProperties() throws NoSingleSectionException
-    {
-        return delegate.getProperties();
+    public SpecialPropertySet(final PropertySet ps) throws UnexpectedPropertySetTypeException {
+        super(ps);
     }
-
-    /**
-     * @see org.apache.poi.hpsf.PropertySet#getProperty(int)
-     */
-    @Override
-    protected Object getProperty(final int id) throws NoSingleSectionException
-    {
-        return delegate.getProperty(id);
-    }
-
-
-
-    /**
-     * @see org.apache.poi.hpsf.PropertySet#getPropertyBooleanValue(int)
-     */
-    @Override
-    protected boolean getPropertyBooleanValue(final int id) throws NoSingleSectionException
-    {
-        return delegate.getPropertyBooleanValue(id);
-    }
-
-
-
-    /**
-     * @see org.apache.poi.hpsf.PropertySet#getPropertyIntValue(int)
-     */
-    @Override
-    protected int getPropertyIntValue(final int id) throws NoSingleSectionException
-    {
-        return delegate.getPropertyIntValue(id);
-    }
-
-
-    
-    /**
-     * 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);
-    }
-    protected 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;
-            if (b.length == 0) {
-                return "";
-            }
-            if (b.length == 1) {
-                return Byte.toString(b[0]);
-            }
-            if (b.length == 2) {
-                return Integer.toString( LittleEndian.getUShort(b) );
-            }
-            if (b.length == 4) {
-                return Long.toString( LittleEndian.getUInt(b) );
-            }
-            // Maybe it's a string? who knows!
-            return new String(b, Charset.forName("ASCII"));
-        }
-        return propertyValue.toString();
-    }
-
-
-    /**
-     * @see org.apache.poi.hpsf.PropertySet#hashCode()
-     */
-    @Override
-    public int hashCode()
-    {
-        return delegate.hashCode();
-    }
-
-
-
-    /**
-     * @see org.apache.poi.hpsf.PropertySet#toString()
-     */
-    @Override
-    public String toString()
-    {
-        return delegate.toString();
-    }
-
-
-
-    /**
-     * @see org.apache.poi.hpsf.PropertySet#wasNull()
-     */
-    @Override
-    public boolean wasNull() throws NoSingleSectionException
-    {
-        return delegate.wasNull();
-    }
-
 }
index 5c260187c08942206b56fe497d763f4f446845c9..856f566e752bc964f94f8389ed97dbac786427d2 100644 (file)
@@ -20,18 +20,18 @@ package org.apache.poi.hpsf;
 import java.util.Date;
 
 import org.apache.poi.hpsf.wellknown.PropertyIDMap;
+import org.apache.poi.hpsf.wellknown.SectionIDMap;
 
 /**
- * <p>Convenience class representing a Summary Information stream in a
- * Microsoft Office document.</p>
+ * Convenience class representing a Summary Information stream in a
+ * Microsoft Office document.
  *
  * @see DocumentSummaryInformation
  */
 public final class SummaryInformation extends SpecialPropertySet {
 
     /**
-     * <p>The document name a summary information stream usually has in a POIFS
-     * filesystem.</p>
+     * The document name a summary information stream usually has in a POIFS filesystem.
      */
     public static final String DEFAULT_STREAM_NAME = "\005SummaryInformation";
 
@@ -39,324 +39,291 @@ public final class SummaryInformation extends SpecialPropertySet {
        return PropertyIDMap.getSummaryInformationProperties();
     }
 
-
     /**
-     * <p>Creates a {@link SummaryInformation} from a given {@link
-     * PropertySet}.</p>
+     * Creates a {@link SummaryInformation} from a given {@link
+     * PropertySet}.
+     *
+     * @param ps A property set which should be created from a summary
+     *        information stream.
+     * @throws UnexpectedPropertySetTypeException if {@code ps} does not
+     *         contain a summary information stream.
+     */
+    public SummaryInformation() {
+        getFirstSection().setFormatID(SectionIDMap.SUMMARY_INFORMATION_ID);
+    }
+        
+    /**
+     * Creates a {@link SummaryInformation} from a given {@link
+     * PropertySet}.
      *
      * @param ps A property set which should be created from a summary
      *        information stream.
-     * @throws UnexpectedPropertySetTypeException if <var>ps</var> does not
+     * @throws UnexpectedPropertySetTypeException if {@code ps} does not
      *         contain a summary information stream.
      */
-    public SummaryInformation(final PropertySet ps)
-            throws UnexpectedPropertySetTypeException
-    {
+    public SummaryInformation(final PropertySet ps) throws UnexpectedPropertySetTypeException {
         super(ps);
-        if (!isSummaryInformation())
-            throw new UnexpectedPropertySetTypeException("Not a "
-                    + getClass().getName());
+        if (!isSummaryInformation()) {
+            throw new UnexpectedPropertySetTypeException("Not a " + getClass().getName());
+        }
     }
 
 
 
     /**
-     * <p>Returns the title (or <code>null</code>).</p>
-     *
-     * @return The title or <code>null</code>
+     * @return The title or {@code null}
      */
-    public String getTitle()
-    {
+    public String getTitle() {
         return getPropertyStringValue(PropertyIDMap.PID_TITLE);
     }
 
 
 
     /**
-     * <p>Sets the title.</p>
+     * Sets the title.
      *
      * @param title The title to set.
      */
-    public void setTitle(final String title)
-    {
-        final MutableSection s = (MutableSection) getFirstSection();
-        s.setProperty(PropertyIDMap.PID_TITLE, title);
+    public void setTitle(final String title) {
+        set1stProperty(PropertyIDMap.PID_TITLE, title);
     }
 
 
 
     /**
-     * <p>Removes the title.</p>
+     * Removes the title.
      */
-    public void removeTitle()
-    {
-        final MutableSection s = (MutableSection) getFirstSection();
-        s.removeProperty(PropertyIDMap.PID_TITLE);
+    public void removeTitle() {
+        remove1stProperty(PropertyIDMap.PID_TITLE);
     }
 
 
 
     /**
-     * <p>Returns the subject (or <code>null</code>).</p>
+     * Returns the subject (or {@code null}).
      *
-     * @return The subject or <code>null</code>
+     * @return The subject or {@code null}
      */
-    public String getSubject()
-    {
+    public String getSubject() {
         return getPropertyStringValue(PropertyIDMap.PID_SUBJECT);
     }
 
 
 
     /**
-     * <p>Sets the subject.</p>
+     * Sets the subject.
      *
      * @param subject The subject to set.
      */
-    public void setSubject(final String subject)
-    {
-        final MutableSection s = (MutableSection) getFirstSection();
-        s.setProperty(PropertyIDMap.PID_SUBJECT, subject);
+    public void setSubject(final String subject) {
+        set1stProperty(PropertyIDMap.PID_SUBJECT, subject);
     }
 
 
 
     /**
-     * <p>Removes the subject.</p>
+     * Removes the subject.
      */
-    public void removeSubject()
-    {
-        final MutableSection s = (MutableSection) getFirstSection();
-        s.removeProperty(PropertyIDMap.PID_SUBJECT);
+    public void removeSubject() {
+        remove1stProperty(PropertyIDMap.PID_SUBJECT);
     }
 
 
 
     /**
-     * <p>Returns the author (or <code>null</code>).</p>
+     * Returns the author (or {@code null}).
      *
-     * @return The author or <code>null</code>
+     * @return The author or {@code null}
      */
-    public String getAuthor()
-    {
+    public String getAuthor() {
         return getPropertyStringValue(PropertyIDMap.PID_AUTHOR);
     }
 
 
 
     /**
-     * <p>Sets the author.</p>
+     * Sets the author.
      *
      * @param author The author to set.
      */
-    public void setAuthor(final String author)
-    {
-        final MutableSection s = (MutableSection) getFirstSection();
-        s.setProperty(PropertyIDMap.PID_AUTHOR, author);
+    public void setAuthor(final String author) {
+        set1stProperty(PropertyIDMap.PID_AUTHOR, author);
     }
 
 
 
     /**
-     * <p>Removes the author.</p>
+     * Removes the author.
      */
-    public void removeAuthor()
-    {
-        final MutableSection s = (MutableSection) getFirstSection();
-        s.removeProperty(PropertyIDMap.PID_AUTHOR);
+    public void removeAuthor() {
+        remove1stProperty(PropertyIDMap.PID_AUTHOR);
     }
 
 
 
     /**
-     * <p>Returns the keywords (or <code>null</code>).</p>
+     * Returns the keywords (or {@code null}).
      *
-     * @return The keywords or <code>null</code>
+     * @return The keywords or {@code null}
      */
-    public String getKeywords()
-    {
+    public String getKeywords() {
         return getPropertyStringValue(PropertyIDMap.PID_KEYWORDS);
     }
 
 
 
     /**
-     * <p>Sets the keywords.</p>
+     * Sets the keywords.
      *
      * @param keywords The keywords to set.
      */
-    public void setKeywords(final String keywords)
-    {
-        final MutableSection s = (MutableSection) getFirstSection();
-        s.setProperty(PropertyIDMap.PID_KEYWORDS, keywords);
+    public void setKeywords(final String keywords) {
+        set1stProperty(PropertyIDMap.PID_KEYWORDS, keywords);
     }
 
 
 
     /**
-     * <p>Removes the keywords.</p>
+     * Removes the keywords.
      */
-    public void removeKeywords()
-    {
-        final MutableSection s = (MutableSection) getFirstSection();
-        s.removeProperty(PropertyIDMap.PID_KEYWORDS);
+    public void removeKeywords() {
+        remove1stProperty(PropertyIDMap.PID_KEYWORDS);
     }
 
 
 
     /**
-     * <p>Returns the comments (or <code>null</code>).</p>
+     * Returns the comments (or {@code null}).
      *
-     * @return The comments or <code>null</code>
+     * @return The comments or {@code null}
      */
-    public String getComments()
-    {
+    public String getComments() {
         return getPropertyStringValue(PropertyIDMap.PID_COMMENTS);
     }
 
 
 
     /**
-     * <p>Sets the comments.</p>
+     * Sets the comments.
      *
      * @param comments The comments to set.
      */
-    public void setComments(final String comments)
-    {
-        final MutableSection s = (MutableSection) getFirstSection();
-        s.setProperty(PropertyIDMap.PID_COMMENTS, comments);
+    public void setComments(final String comments) {
+        set1stProperty(PropertyIDMap.PID_COMMENTS, comments);
     }
 
 
 
     /**
-     * <p>Removes the comments.</p>
+     * Removes the comments.
      */
-    public void removeComments()
-    {
-        final MutableSection s = (MutableSection) getFirstSection();
-        s.removeProperty(PropertyIDMap.PID_COMMENTS);
+    public void removeComments() {
+        remove1stProperty(PropertyIDMap.PID_COMMENTS);
     }
 
 
 
     /**
-     * <p>Returns the template (or <code>null</code>).</p>
+     * Returns the template (or {@code null}).
      *
-     * @return The template or <code>null</code>
+     * @return The template or {@code null}
      */
-    public String getTemplate()
-    {
+    public String getTemplate() {
         return getPropertyStringValue(PropertyIDMap.PID_TEMPLATE);
     }
 
 
 
     /**
-     * <p>Sets the template.</p>
+     * Sets the template.
      *
      * @param template The template to set.
      */
-    public void setTemplate(final String template)
-    {
-        final MutableSection s = (MutableSection) getFirstSection();
-        s.setProperty(PropertyIDMap.PID_TEMPLATE, template);
+    public void setTemplate(final String template) {
+        set1stProperty(PropertyIDMap.PID_TEMPLATE, template);
     }
 
 
 
     /**
-     * <p>Removes the template.</p>
+     * Removes the template.
      */
-    public void removeTemplate()
-    {
-        final MutableSection s = (MutableSection) getFirstSection();
-        s.removeProperty(PropertyIDMap.PID_TEMPLATE);
+    public void removeTemplate() {
+        remove1stProperty(PropertyIDMap.PID_TEMPLATE);
     }
 
 
 
     /**
-     * <p>Returns the last author (or <code>null</code>).</p>
+     * Returns the last author (or {@code null}).
      *
-     * @return The last author or <code>null</code>
+     * @return The last author or {@code null}
      */
-    public String getLastAuthor()
-    {
+    public String getLastAuthor() {
         return getPropertyStringValue(PropertyIDMap.PID_LASTAUTHOR);
     }
 
 
 
     /**
-     * <p>Sets the last author.</p>
+     * Sets the last author.
      *
      * @param lastAuthor The last author to set.
      */
-    public void setLastAuthor(final String lastAuthor)
-    {
-        final MutableSection s = (MutableSection) getFirstSection();
-        s.setProperty(PropertyIDMap.PID_LASTAUTHOR, lastAuthor);
+    public void setLastAuthor(final String lastAuthor) {
+        set1stProperty(PropertyIDMap.PID_LASTAUTHOR, lastAuthor);
     }
 
 
 
     /**
-     * <p>Removes the last author.</p>
+     * Removes the last author.
      */
-    public void removeLastAuthor()
-    {
-        final MutableSection s = (MutableSection) getFirstSection();
-        s.removeProperty(PropertyIDMap.PID_LASTAUTHOR);
+    public void removeLastAuthor() {
+        remove1stProperty(PropertyIDMap.PID_LASTAUTHOR);
     }
 
 
 
     /**
-     * <p>Returns the revision number (or <code>null</code>). </p>
+     * Returns the revision number (or {@code null}). 
      *
-     * @return The revision number or <code>null</code>
+     * @return The revision number or {@code null}
      */
-    public String getRevNumber()
-    {
+    public String getRevNumber() {
         return getPropertyStringValue(PropertyIDMap.PID_REVNUMBER);
     }
 
 
 
     /**
-     * <p>Sets the revision number.</p>
+     * Sets the revision number.
      *
      * @param revNumber The revision number to set.
      */
-    public void setRevNumber(final String revNumber)
-    {
-        final MutableSection s = (MutableSection) getFirstSection();
-        s.setProperty(PropertyIDMap.PID_REVNUMBER, revNumber);
+    public void setRevNumber(final String revNumber) {
+        set1stProperty(PropertyIDMap.PID_REVNUMBER, revNumber);
     }
 
 
 
     /**
-     * <p>Removes the revision number.</p>
+     * Removes the revision number.
      */
-    public void removeRevNumber()
-    {
-        final MutableSection s = (MutableSection) getFirstSection();
-        s.removeProperty(PropertyIDMap.PID_REVNUMBER);
+    public void removeRevNumber() {
+        remove1stProperty(PropertyIDMap.PID_REVNUMBER);
     }
 
 
 
     /**
-     * <p>Returns the total time spent in editing the document (or
-     * <code>0</code>).</p>
+     * Returns the total time spent in editing the document (or
+     * {@code 0}).
      *
      * @return The total time spent in editing the document or 0 if the {@link
      *         SummaryInformation} does not contain this information.
      */
-    public long getEditTime()
-    {
+    public long getEditTime() {
         final Date d = (Date) getProperty(PropertyIDMap.PID_EDITTIME);
         if (d == null) {
             return 0;
@@ -367,124 +334,106 @@ public final class SummaryInformation extends SpecialPropertySet {
 
 
     /**
-     * <p>Sets the total time spent in editing the document.</p>
+     * Sets the total time spent in editing the document.
      *
      * @param time The time to set.
      */
-    public void setEditTime(final long time)
-    {
+    public void setEditTime(final long time) {
         final Date d = Util.filetimeToDate(time);
-        final MutableSection s = (MutableSection) getFirstSection();
-        s.setProperty(PropertyIDMap.PID_EDITTIME, Variant.VT_FILETIME, d);
+        getFirstSection().setProperty(PropertyIDMap.PID_EDITTIME, Variant.VT_FILETIME, d);
     }
 
 
 
     /**
-     * <p>Remove the total time spent in editing the document.</p>
+     * Remove the total time spent in editing the document.
      */
-    public void removeEditTime()
-    {
-        final MutableSection s = (MutableSection) getFirstSection();
-        s.removeProperty(PropertyIDMap.PID_EDITTIME);
+    public void removeEditTime() {
+        remove1stProperty(PropertyIDMap.PID_EDITTIME);
     }
 
 
 
     /**
-     * <p>Returns the last printed time (or <code>null</code>).</p>
+     * Returns the last printed time (or {@code null}).
      *
-     * @return The last printed time or <code>null</code>
+     * @return The last printed time or {@code null}
      */
-    public Date getLastPrinted()
-    {
+    public Date getLastPrinted() {
         return (Date) getProperty(PropertyIDMap.PID_LASTPRINTED);
     }
 
 
 
     /**
-     * <p>Sets the lastPrinted.</p>
+     * Sets the lastPrinted.
      *
      * @param lastPrinted The lastPrinted to set.
      */
-    public void setLastPrinted(final Date lastPrinted)
-    {
-        final MutableSection s = (MutableSection) getFirstSection();
-        s.setProperty(PropertyIDMap.PID_LASTPRINTED, Variant.VT_FILETIME,
-                lastPrinted);
+    public void setLastPrinted(final Date lastPrinted) {
+        getFirstSection().setProperty(PropertyIDMap.PID_LASTPRINTED, Variant.VT_FILETIME, lastPrinted);
     }
 
 
 
     /**
-     * <p>Removes the lastPrinted.</p>
+     * Removes the lastPrinted.
      */
-    public void removeLastPrinted()
-    {
-        final MutableSection s = (MutableSection) getFirstSection();
-        s.removeProperty(PropertyIDMap.PID_LASTPRINTED);
+    public void removeLastPrinted() {
+        remove1stProperty(PropertyIDMap.PID_LASTPRINTED);
     }
 
 
 
     /**
-     * <p>Returns the creation time (or <code>null</code>).</p>
+     * Returns the creation time (or {@code null}).
      *
-     * @return The creation time or <code>null</code>
+     * @return The creation time or {@code null}
      */
-    public Date getCreateDateTime()
-    {
+    public Date getCreateDateTime() {
         return (Date) getProperty(PropertyIDMap.PID_CREATE_DTM);
     }
 
 
 
     /**
-     * <p>Sets the creation time.</p>
+     * Sets the creation time.
      *
      * @param createDateTime The creation time to set.
      */
-    public void setCreateDateTime(final Date createDateTime)
-    {
-        final MutableSection s = (MutableSection) getFirstSection();
-        s.setProperty(PropertyIDMap.PID_CREATE_DTM, Variant.VT_FILETIME,
-                createDateTime);
+    public void setCreateDateTime(final Date createDateTime) {
+        getFirstSection().setProperty(PropertyIDMap.PID_CREATE_DTM, Variant.VT_FILETIME, createDateTime);
     }
 
 
 
     /**
-     * <p>Removes the creation time.</p>
+     * Removes the creation time.
      */
-    public void removeCreateDateTime()
-    {
-        final MutableSection s = (MutableSection) getFirstSection();
-        s.removeProperty(PropertyIDMap.PID_CREATE_DTM);
+    public void removeCreateDateTime() {
+        remove1stProperty(PropertyIDMap.PID_CREATE_DTM);
     }
 
 
 
     /**
-     * <p>Returns the last save time (or <code>null</code>).</p>
+     * Returns the last save time (or {@code null}).
      *
-     * @return The last save time or <code>null</code>
+     * @return The last save time or {@code null}
      */
-    public Date getLastSaveDateTime()
-    {
+    public Date getLastSaveDateTime() {
         return (Date) getProperty(PropertyIDMap.PID_LASTSAVE_DTM);
     }
 
 
 
     /**
-     * <p>Sets the total time spent in editing the document.</p>
+     * Sets the total time spent in editing the document.
      *
      * @param time The time to set.
      */
-    public void setLastSaveDateTime(final Date time)
-    {
-        final MutableSection s = (MutableSection) getFirstSection();
+    public void setLastSaveDateTime(final Date time) {
+        final Section s = getFirstSection();
         s
                 .setProperty(PropertyIDMap.PID_LASTSAVE_DTM,
                         Variant.VT_FILETIME, time);
@@ -493,153 +442,134 @@ public final class SummaryInformation extends SpecialPropertySet {
 
 
     /**
-     * <p>Remove the total time spent in editing the document.</p>
+     * Remove the total time spent in editing the document.
      */
-    public void removeLastSaveDateTime()
-    {
-        final MutableSection s = (MutableSection) getFirstSection();
-        s.removeProperty(PropertyIDMap.PID_LASTSAVE_DTM);
+    public void removeLastSaveDateTime() {
+        remove1stProperty(PropertyIDMap.PID_LASTSAVE_DTM);
     }
 
 
 
     /**
-     * <p>Returns the page count or 0 if the {@link SummaryInformation} does
-     * not contain a page count.</p>
+     * Returns the page count or 0 if the {@link SummaryInformation} does
+     * not contain a page count.
      *
      * @return The page count or 0 if the {@link SummaryInformation} does not
      *         contain a page count.
      */
-    public int getPageCount()
-    {
+    public int getPageCount() {
         return getPropertyIntValue(PropertyIDMap.PID_PAGECOUNT);
     }
 
 
 
     /**
-     * <p>Sets the page count.</p>
+     * Sets the page count.
      *
      * @param pageCount The page count to set.
      */
-    public void setPageCount(final int pageCount)
-    {
-        final MutableSection s = (MutableSection) getFirstSection();
-        s.setProperty(PropertyIDMap.PID_PAGECOUNT, pageCount);
+    public void setPageCount(final int pageCount) {
+        set1stProperty(PropertyIDMap.PID_PAGECOUNT, pageCount);
     }
 
 
 
     /**
-     * <p>Removes the page count.</p>
+     * Removes the page count.
      */
-    public void removePageCount()
-    {
-        final MutableSection s = (MutableSection) getFirstSection();
-        s.removeProperty(PropertyIDMap.PID_PAGECOUNT);
+    public void removePageCount() {
+        remove1stProperty(PropertyIDMap.PID_PAGECOUNT);
     }
 
 
 
     /**
-     * <p>Returns the word count or 0 if the {@link SummaryInformation} does
-     * not contain a word count.</p>
+     * Returns the word count or 0 if the {@link SummaryInformation} does
+     * not contain a word count.
      *
-     * @return The word count or <code>null</code>
+     * @return The word count or {@code null}
      */
-    public int getWordCount()
-    {
+    public int getWordCount() {
         return getPropertyIntValue(PropertyIDMap.PID_WORDCOUNT);
     }
 
 
 
     /**
-     * <p>Sets the word count.</p>
+     * Sets the word count.
      *
      * @param wordCount The word count to set.
      */
-    public void setWordCount(final int wordCount)
-    {
-        final MutableSection s = (MutableSection) getFirstSection();
-        s.setProperty(PropertyIDMap.PID_WORDCOUNT, wordCount);
+    public void setWordCount(final int wordCount) {
+        set1stProperty(PropertyIDMap.PID_WORDCOUNT, wordCount);
     }
 
 
 
     /**
-     * <p>Removes the word count.</p>
+     * Removes the word count.
      */
-    public void removeWordCount()
-    {
-        final MutableSection s = (MutableSection) getFirstSection();
-        s.removeProperty(PropertyIDMap.PID_WORDCOUNT);
+    public void removeWordCount() {
+        remove1stProperty(PropertyIDMap.PID_WORDCOUNT);
     }
 
 
 
     /**
-     * <p>Returns the character count or 0 if the {@link SummaryInformation}
-     * does not contain a char count.</p>
+     * Returns the character count or 0 if the {@link SummaryInformation}
+     * does not contain a char count.
      *
-     * @return The character count or <code>null</code>
+     * @return The character count or {@code null}
      */
-    public int getCharCount()
-    {
+    public int getCharCount() {
         return getPropertyIntValue(PropertyIDMap.PID_CHARCOUNT);
     }
 
 
 
     /**
-     * <p>Sets the character count.</p>
+     * Sets the character count.
      *
      * @param charCount The character count to set.
      */
-    public void setCharCount(final int charCount)
-    {
-        final MutableSection s = (MutableSection) getFirstSection();
-        s.setProperty(PropertyIDMap.PID_CHARCOUNT, charCount);
+    public void setCharCount(final int charCount) {
+        set1stProperty(PropertyIDMap.PID_CHARCOUNT, charCount);
     }
 
 
 
     /**
-     * <p>Removes the character count.</p>
+     * Removes the character count.
      */
-    public void removeCharCount()
-    {
-        final MutableSection s = (MutableSection) getFirstSection();
-        s.removeProperty(PropertyIDMap.PID_CHARCOUNT);
+    public void removeCharCount() {
+        remove1stProperty(PropertyIDMap.PID_CHARCOUNT);
     }
 
 
 
     /**
-     * <p>Returns the thumbnail (or <code>null</code>) <strong>when this
+     * Returns the thumbnail (or {@code null}) <strong>when this
      * method is implemented. Please note that the return type is likely to
-     * change!</strong></p>
+     * change!</strong><p>
      *
-     * <p>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</p>
+     *  an image in WMF or Clipboard (BMP?) format
      *
-     * @return The thumbnail or <code>null</code>
+     * @return The thumbnail or {@code null}
      */
-    public byte[] getThumbnail()
-    {
+    public byte[] getThumbnail() {
         return (byte[]) getProperty(PropertyIDMap.PID_THUMBNAIL);
     }
 
     /**
-     * <p>Returns the thumbnail (or <code>null</code>), processed
+     * Returns the thumbnail (or {@code null}), processed
      *  as an object which is (largely) able to unpack the thumbnail
-     *  image data.</p>
+     *  image data.
      *
-     * @return The thumbnail or <code>null</code>
+     * @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 {
 
 
     /**
-     * <p>Sets the thumbnail.</p>
+     * Sets the thumbnail.
      *
      * @param thumbnail The thumbnail to set.
      */
-    public void setThumbnail(final byte[] thumbnail)
-    {
-        final MutableSection s = (MutableSection) getFirstSection();
-        s.setProperty(PropertyIDMap.PID_THUMBNAIL, /* FIXME: */
-                Variant.VT_LPSTR, thumbnail);
+    public void setThumbnail(final byte[] thumbnail) {
+        getFirstSection().setProperty(PropertyIDMap.PID_THUMBNAIL, /* FIXME: */ Variant.VT_LPSTR, thumbnail);
     }
 
 
 
     /**
-     * <p>Removes the thumbnail.</p>
+     * Removes the thumbnail.
      */
-    public void removeThumbnail()
-    {
-        final MutableSection s = (MutableSection) getFirstSection();
-        s.removeProperty(PropertyIDMap.PID_THUMBNAIL);
+    public void removeThumbnail() {
+        remove1stProperty(PropertyIDMap.PID_THUMBNAIL);
     }
 
 
 
     /**
-     * <p>Returns the application name (or <code>null</code>).</p>
+     * Returns the application name (or {@code null}).
      *
-     * @return The application name or <code>null</code>
+     * @return The application name or {@code null}
      */
-    public String getApplicationName()
-    {
+    public String getApplicationName() {
         return getPropertyStringValue(PropertyIDMap.PID_APPNAME);
     }
 
 
 
     /**
-     * <p>Sets the application name.</p>
+     * Sets the application name.
      *
      * @param applicationName The application name to set.
      */
-    public void setApplicationName(final String applicationName)
-    {
-        final MutableSection s = (MutableSection) getFirstSection();
-        s.setProperty(PropertyIDMap.PID_APPNAME, applicationName);
+    public void setApplicationName(final String applicationName) {
+        set1stProperty(PropertyIDMap.PID_APPNAME, applicationName);
     }
 
 
 
     /**
-     * <p>Removes the application name.</p>
+     * Removes the application name.
      */
-    public void removeApplicationName()
-    {
-        final MutableSection s = (MutableSection) getFirstSection();
-        s.removeProperty(PropertyIDMap.PID_APPNAME);
+    public void removeApplicationName() {
+        remove1stProperty(PropertyIDMap.PID_APPNAME);
     }
 
 
 
     /**
-     * <p>Returns a security code which is one of the following values:</p>
+     * Returns a security code which is one of the following values:
      *
      * <ul>
      *
-     * <li><p>0 if the {@link SummaryInformation} does not contain a
+     * <li>0 if the {@link SummaryInformation} does not contain a
      * security field or if there is no security on the document. Use
      * {@link PropertySet#wasNull()} to distinguish between the two
-     * cases!</p></li>
+     * cases!
      *
-     * <li><p>1 if the document is password protected</p></li>
+     * <li>1 if the document is password protected
      *
-     * <li><p>2 if the document is read-only recommended</p></li>
+     * <li>2 if the document is read-only recommended
      *
-     * <li><p>4 if the document is read-only enforced</p></li>
+     * <li>4 if the document is read-only enforced
      *
-     * <li><p>8 if the document is locked for annotations</p></li>
+     * <li>8 if the document is locked for annotations
      *
      * </ul>
      *
-     * @return The security code or <code>null</code>
+     * @return The security code or {@code null}
      */
-    public int getSecurity()
-    {
+    public int getSecurity() {
         return getPropertyIntValue(PropertyIDMap.PID_SECURITY);
     }
 
 
 
     /**
-     * <p>Sets the security code.</p>
+     * Sets the security code.
      *
      * @param security The security code to set.
      */
-    public void setSecurity(final int security)
-    {
-        final MutableSection s = (MutableSection) getFirstSection();
-        s.setProperty(PropertyIDMap.PID_SECURITY, security);
+    public void setSecurity(final int security) {
+        set1stProperty(PropertyIDMap.PID_SECURITY, security);
     }
 
 
 
     /**
-     * <p>Removes the security code.</p>
+     * Removes the security code.
      */
-    public void removeSecurity()
-    {
-        final MutableSection s = (MutableSection) getFirstSection();
-        s.removeProperty(PropertyIDMap.PID_SECURITY);
+    public void removeSecurity() {
+        remove1stProperty(PropertyIDMap.PID_SECURITY);
     }
 
 }
index 5d75cb986f5eca7695418366f7a7bd934795c0ba..d8ab7315db6c574eac154c3afd886c5498b30942 100644 (file)
@@ -20,110 +20,17 @@ package org.apache.poi.hpsf;
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.io.StringWriter;
-import java.util.Collection;
 import java.util.Date;
 
+import org.apache.poi.util.Internal;
 import org.apache.poi.util.SuppressForbidden;
 
 /**
  * <p>Provides various static utility methods.</p>
  */
+@Internal
 public class Util
 {
-
-    /**
-     * <p>Checks whether two byte arrays <var>a</var> and <var>b</var>
-     * are equal. They are equal</p>
-     *
-     * <ul>
-     *
-     *  <li><p>if they have the same length and</p></li>
-     *
-     *  <li><p>if for each <var>i</var> with
-     *  <var>i</var>&nbsp;&gt;=&nbsp;0 and
-     *  <var>i</var>&nbsp;&lt;&nbsp;<var>a.length</var> holds
-     *  <var>a</var>[<var>i</var>]&nbsp;== <var>b</var>[<var>i</var>].</p></li>
-     *
-     * </ul>
-     *
-     * @param a The first byte array
-     * @param b The first byte array
-     * @return <code>true</code> if the byte arrays are equal, else
-     * <code>false</code>
-     */
-    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;
-    }
-
-
-
-    /**
-     * <p>Copies a part of a byte array into another byte array.</p>
-     *
-     * @param src The source byte array.
-     * @param srcOffset Offset in the source byte array.
-     * @param length The number of bytes to copy.
-     * @param dst The destination byte array.
-     * @param dstOffset Offset in the destination byte array.
-     */
-    public static void copy(final byte[] src, final int srcOffset,
-                            final int length, final byte[] dst,
-                            final int dstOffset)
-    {
-        for (int i = 0; i < length; i++)
-            dst[dstOffset + i] = src[srcOffset + i];
-    }
-
-
-
-    /**
-     * <p>Concatenates the contents of several byte arrays into a
-     * single one.</p>
-     *
-     * @param byteArrays The byte arrays to be concatened.
-     * @return A new byte array containing the concatenated byte
-     * arrays.
-     */
-    public static byte[] cat(final byte[][] byteArrays)
-    {
-        int capacity = 0;
-        for (int i = 0; i < byteArrays.length; i++)
-            capacity += byteArrays[i].length;
-        final byte[] result = new byte[capacity];
-        int r = 0;
-        for (int i = 0; i < byteArrays.length; i++)
-            for (int j = 0; j < byteArrays[i].length; j++)
-                result[r++] = byteArrays[i][j];
-        return result;
-    }
-
-
-
-    /**
-     * <p>Copies bytes from a source byte array into a new byte
-     * array.</p>
-     *
-     * @param src Copy from this byte array.
-     * @param offset Start copying here.
-     * @param length Copy this many bytes.
-     * @return The new byte array. Its length is number of copied bytes.
-     */
-    public static byte[] copy(final byte[] src, final int offset,
-                              final int length)
-    {
-        final byte[] result = new byte[length];
-        copy(src, offset, length, result, 0);
-        return result;
-    }
-
-
-
     /**
      * <p>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
     }
 
 
-    /**
-     * <p>Checks whether two collections are equal. Two collections
-     * C<sub>1</sub> and C<sub>2</sub> are equal, if the following conditions
-     * are true:</p>
-     *
-     * <ul>
-     *
-     * <li><p>For each c<sub>1<em>i</em></sub> (element of C<sub>1</sub>) there
-     * is a c<sub>2<em>j</em></sub> (element of C<sub>2</sub>), and
-     * c<sub>1<em>i</em></sub> equals c<sub>2<em>j</em></sub>.</p></li>
-     *
-     * <li><p>For each c<sub>2<em>i</em></sub> (element of C<sub>2</sub>) there
-     * is a c<sub>1<em>j</em></sub> (element of C<sub>1</sub>) and
-     * c<sub>2<em>i</em></sub> equals c<sub>1<em>j</em></sub>.</p></li>
-     *
-     * </ul>
-     *
-     * @param c1 the first collection
-     * @param c2 the second collection
-     * @return <code>true</code> if the collections are equal, else
-     * <code>false</code>.
-     */
-    public static boolean equals(Collection<?> c1, Collection<?> c2)
-    {
-        Object[] o1 = c1.toArray();
-        Object[] o2 = c2.toArray();
-        return internalEquals(o1, o2);
-    }
-
-
 
     /**
      * <p>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;
     }
 
-
-
     /**
      * <p>Pads a byte array with 0x00 bytes so that its length is a multiple of
      * 4.</p>
@@ -283,46 +151,6 @@ public class Util
     }
 
 
-
-    /**
-     * <p>Pads a character array with 0x0000 characters so that its length is a
-     * multiple of 4.</p>
-     *
-     * @param ca The character array to pad.
-     * @return The padded character array.
-     */
-    public static char[] pad4(final char[] ca)
-    {
-        final int PAD = 4;
-        final char[] result;
-        int l = ca.length % PAD;
-        if (l == 0)
-            result = ca;
-        else
-        {
-            l = PAD - l;
-            result = new char[ca.length + l];
-            System.arraycopy(ca, 0, result, 0, ca.length);
-        }
-        return result;
-    }
-
-
-
-    /**
-     * <p>Pads a string with 0x0000 characters so that its length is a
-     * multiple of 4.</p>
-     *
-     * @param s The string to pad.
-     * @return The padded string as a character array.
-     */
-    public static char[] pad4(final String s)
-    {
-        return pad4(s.toCharArray());
-    }
-
-
-
     /**
      * <p>Returns a textual representation of a {@link Throwable}, including a
      * stacktrace.</p>
index b4816eb912e705244c0b662ff0d9444d9f8d3a51..61fc30d883f9aa82d1dab9f568741d1c52763fab 100644 (file)
@@ -27,70 +27,77 @@ import java.util.List;
 import org.apache.poi.util.CodePageUtil;
 import org.apache.poi.util.POILogFactory;
 import org.apache.poi.util.POILogger;
+import org.apache.poi.util.Removal;
 
 /**
- * <p>Supports reading and writing of variant data.</p>
+ * Supports reading and writing of variant data.<p>
  *
- * <p><strong>FIXME (3):</strong> Reading and writing should be made more
- * uniform than it is now. The following items should be resolved:
+ * <strong>FIXME (3):</strong> Reading and writing should be made more
+ * uniform than it is now. The following items should be resolved:<p>
  *
  * <ul>
  *
- * <li><p>Reading requires a length parameter that is 4 byte greater than the
- * actual data, because the variant type field is included. </p></li>
+ * <li>Reading requires a length parameter that is 4 byte greater than the
+ * actual data, because the variant type field is included.
  *
- * <li><p>Reading reads from a byte array while writing writes to an byte array
- * output stream.</p></li>
+ * <li>Reading reads from a byte array while writing writes to an byte array
+ * output stream.
  *
  * </ul>
  */
-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;
 
     /**
-     * <p>Specifies whether warnings about unsupported variant types are to be
-     * written to <code>System.err</code> or not.</p>
+     * Keeps a list of the variant types an "unsupported" message has already
+     * been issued for.
+     */
+    protected static List<Long> unsupportedMessage;
+
+    
+    /**
+     * Specifies whether warnings about unsupported variant types are to be
+     * written to {@code System.err} or not.
      *
-     * @param logUnsupportedTypes If <code>true</code> warnings will be written,
-     * if <code>false</code> they won't.
+     * @param logUnsupportedTypes If {@code true} warnings will be written,
+     * if {@code false} they won't.
      */
-    public static void setLogUnsupportedTypes(final boolean logUnsupportedTypes)
-    {
+    public static void setLogUnsupportedTypes(final boolean logUnsupportedTypes) {
         VariantSupport.logUnsupportedTypes = logUnsupportedTypes;
     }
 
     /**
-     * <p>Checks whether logging of unsupported variant types warning is turned
-     * on or off.</p>
+     * Checks whether logging of unsupported variant types warning is turned
+     * on or off.
      *
-     * @return <code>true</code> if logging is turned on, else
-     * <code>false</code>.
+     * @return {@code true} if logging is turned on, else
+     * {@code false}.
      */
-    public static boolean isLogUnsupportedTypes()
-    {
+    public static boolean isLogUnsupportedTypes() {
         return logUnsupportedTypes;
     }
 
 
 
     /**
-     * <p>Keeps a list of the variant types an "unsupported" message has already
-     * been issued for.</p>
-     */
-    protected static List<Long> unsupportedMessage;
-
-    /**
-     * <p>Writes a warning to <code>System.err</code> 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 </p>
+     * type. Log messages can be turned on or off by
      *
      * @param ex The exception to log
      */
     protected static void writeUnsupportedTypeMessage
-        (final UnsupportedVariantTypeException ex)
-    {
+        (final UnsupportedVariantTypeException ex) {
         if (isLogUnsupportedTypes())
         {
             if (unsupportedMessage == null)
@@ -105,28 +112,18 @@ public class VariantSupport extends Variant
     }
 
 
-    /**
-     * <p>HPSF is able to read these {@link Variant} types.</p>
-     */
-    final static public 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 };
-
-
 
     /**
-     * <p>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.</p>
+     * array.
      *
      * @see Variant
      * @param variantType the variant type to check
-     * @return <code>true</code> if HPFS supports this type, else
-     *         <code>false</code>
+     * @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
 
 
     /**
-     * <p>Reads a variant type from a byte array.</p>
+     * Reads a variant type from a byte array.
      *
      * @param src The byte array
      * @param offset The offset in the byte array where the variant starts
@@ -154,66 +151,50 @@ public class VariantSupport extends Variant
      */
     public static Object read( final byte[] src, final int offset,
             final int length, final long type, final int codepage )
-            throws ReadingNotSupportedException, UnsupportedEncodingException
-    {
-        TypedPropertyValue typedPropertyValue = new TypedPropertyValue(
-                (int) type, null );
+    throws ReadingNotSupportedException, UnsupportedEncodingException {
+        TypedPropertyValue typedPropertyValue = new TypedPropertyValue( (int) type, null );
         int unpadded;
-        try
-        {
+        try {
             unpadded = typedPropertyValue.readValue( src, offset );
-        }
-        catch ( UnsupportedOperationException exc )
-        {
+        } catch ( UnsupportedOperationException exc ) {
             int propLength = Math.min( length, src.length - offset );
             final byte[] v = new byte[propLength];
             System.arraycopy( src, offset, v, 0, propLength );
             throw new ReadingNotSupportedException( type, v );
         }
 
-        switch ( (int) type )
-        {
-        case Variant.VT_EMPTY:
-        case Variant.VT_I4:
-        case Variant.VT_I8:
-        case Variant.VT_R8:
+        switch ( (int) type ) {
             /*
              * we have more property types that can be converted into Java
              * objects, but current API need to be preserved, and it returns
              * other types as byte arrays. In future major versions it shall be
              * changed -- sergey
              */
-            return typedPropertyValue.getValue();
+            case Variant.VT_EMPTY:
+            case Variant.VT_I4:
+            case Variant.VT_I8:
+            case Variant.VT_R8:
+                return typedPropertyValue.getValue();
 
-        case Variant.VT_I2:
-        {
             /*
              * also for backward-compatibility with prev. versions of POI
              * --sergey
              */
-            return Integer.valueOf( ( (Short) typedPropertyValue.getValue() )
-                    .intValue() );
-        }
-        case Variant.VT_FILETIME:
-        {
-            Filetime filetime = (Filetime) typedPropertyValue.getValue();
-            return Util.filetimeToDate( (int) filetime.getHigh(),
-                    (int) filetime.getLow() );
-        }
-        case Variant.VT_LPSTR:
-        {
-            CodePageString string = (CodePageString) typedPropertyValue
-                    .getValue();
-            return string.getJavaValue( codepage );
-        }
-        case Variant.VT_LPWSTR:
-        {
-            UnicodeString string = (UnicodeString) typedPropertyValue
-                    .getValue();
-            return string.toJavaString();
-        }
-        case Variant.VT_CF:
-        {
+            case Variant.VT_I2:
+                return ( (Short) typedPropertyValue.getValue() ).intValue();
+
+            case Variant.VT_FILETIME:
+                Filetime filetime = (Filetime) typedPropertyValue.getValue();
+                return Util.filetimeToDate( (int) filetime.getHigh(), (int) filetime.getLow() );
+
+            case Variant.VT_LPSTR:
+                CodePageString cpString = (CodePageString) typedPropertyValue.getValue();
+                return cpString.getJavaValue( codepage );
+
+            case Variant.VT_LPWSTR:
+                UnicodeString uniString = (UnicodeString) typedPropertyValue.getValue();
+                return uniString.toJavaString();
+
             // if(l1 < 0) {
             /**
              * YK: reading the ClipboardData packet (VT_CF) is not quite
@@ -223,7 +204,7 @@ public class VariantSupport extends Variant
              * 45583 clearly show that this approach does not always work. The
              * workaround below attempts to gracefully handle such cases instead
              * of throwing exceptions.
-             * 
+             *
              * August 20, 2009
              */
             // l1 = LittleEndian.getInt(src, o1); o1 += LittleEndian.INT_SIZE;
@@ -232,33 +213,28 @@ public class VariantSupport extends Variant
             // System.arraycopy(src, o1, v, 0, v.length);
             // value = v;
             // break;
-            ClipboardData clipboardData = (ClipboardData) typedPropertyValue
-                    .getValue();
-            return clipboardData.toByteArray();
-        }
+            case Variant.VT_CF:
+                ClipboardData clipboardData = (ClipboardData) typedPropertyValue.getValue();
+                return clipboardData.toByteArray();
 
-        case Variant.VT_BOOL:
-        {
-            VariantBool bool = (VariantBool) typedPropertyValue.getValue();
-            return Boolean.valueOf( bool.getValue() );
-        }
+            case Variant.VT_BOOL:
+                VariantBool bool = (VariantBool) typedPropertyValue.getValue();
+                return bool.getValue();
 
-        default:
-        {
             /*
              * it is not very good, but what can do without breaking current
              * API? --sergey
              */
-            final byte[] v = new byte[unpadded];
-            System.arraycopy( src, offset, v, 0, unpadded );
-            throw new ReadingNotSupportedException( type, v );
-        }
+            default:
+                final byte[] v = new byte[unpadded];
+                System.arraycopy( src, offset, v, 0, unpadded );
+                throw new ReadingNotSupportedException( type, v );
         }
     }
 
     /**
-     * <p>Turns a codepage number into the equivalent character encoding's
-     * name.</p>
+     * Turns a codepage number into the equivalent character encoding's
+     * name.
      *
      * @param codepage The codepage number
      *
@@ -269,7 +245,10 @@ public class VariantSupport extends Variant
      *
      * @exception UnsupportedEncodingException if the specified codepage is
      * less than zero.
+     *
+     * @deprecated POI 3.16 - use {@link CodePageUtil#codepageToEncoding(int)}
      */
+    @Removal(version="3.18")
     public static String codepageToEncoding(final int codepage)
     throws UnsupportedEncodingException
     {
@@ -278,13 +257,13 @@ public class VariantSupport extends Variant
 
 
     /**
-     * <p>Writes a variant value to an output stream. This method ensures that
-     * always a multiple of 4 bytes is written.</p>
+     * Writes a variant value to an output stream. This method ensures that
+     * always a multiple of 4 bytes is written.<p>
      *
-     * <p>If the codepage is UTF-16, which is encouraged, strings
+     * If the codepage is UTF-16, which is encouraged, strings
      * <strong>must</strong> 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.</p>
+     * by converting strings appropriately, if needed.
      *
      * @param out The stream to write the value to.
      * @param type The variant's type.
@@ -298,42 +277,31 @@ public class VariantSupport extends Variant
      */
     public static int write(final OutputStream out, final long type,
                             final Object value, final int codepage)
-        throws IOException, WritingNotSupportedException
-    {
+    throws IOException, WritingNotSupportedException {
         int length = 0;
-        switch ((int) type)
-        {
+        switch ((int) type) {
             case Variant.VT_BOOL:
-            {
-                if ( ( (Boolean) value ).booleanValue() )
-                {
+                if ( ( (Boolean) value ).booleanValue() ) {
                     out.write( 0xff );
                     out.write( 0xff );
-                }
-                else
-                {
+                } else {
                     out.write( 0x00 );
                     out.write( 0x00 );
                 }
                 length += 2;
                 break;
-            }
+
             case Variant.VT_LPSTR:
-            {
-                CodePageString codePageString = new CodePageString( (String) value,
-                        codepage );
+                CodePageString codePageString = new CodePageString( (String) value, codepage );
                 length += codePageString.write( out );
                 break;
-            }
+
             case Variant.VT_LPWSTR:
-            {
                 final int nrOfChars = ( (String) value ).length() + 1;
                 length += TypeWriter.writeUIntToStream( out, nrOfChars );
-                char[] s = ( (String) value ).toCharArray();
-                for ( int i = 0; i < s.length; i++ )
-                {
-                    final int high = ( ( s[i] & 0x0000ff00 ) >> 8 );
-                    final int low = ( s[i] & 0x000000ff );
+                for ( char s : ( (String) value ).toCharArray() ) {
+                    final int high = ( ( s & 0x0000ff00 ) >> 8 );
+                    final int low = ( s & 0x000000ff );
                     final byte highb = (byte) high;
                     final byte lowb = (byte) low;
                     out.write( lowb );
@@ -345,79 +313,63 @@ public class VariantSupport extends Variant
                 out.write( 0x00 );
                 length += 2;
                 break;
-            }
+
             case Variant.VT_CF:
-            {
-                final byte[] b = (byte[]) value;
-                out.write(b);
-                length = b.length;
+                final byte[] cf = (byte[]) value;
+                out.write(cf);
+                length = cf.length;
                 break;
-            }
+
             case Variant.VT_EMPTY:
-            {
                 length += TypeWriter.writeUIntToStream( out, Variant.VT_EMPTY );
                 break;
-            }
+
             case Variant.VT_I2:
-            {
-                length += TypeWriter.writeToStream( out,
-                        ( (Integer) value ).shortValue() );
+                length += TypeWriter.writeToStream( out, ( (Integer) value ).shortValue() );
                 break;
-            }
+
             case Variant.VT_I4:
-            {
-                if (!(value instanceof Integer))
-                {
+                if (!(value instanceof Integer)) {
                     throw new ClassCastException("Could not cast an object to "
                             + Integer.class.toString() + ": "
                             + value.getClass().toString() + ", "
                             + value.toString());
                 }
-                length += TypeWriter.writeToStream(out,
-                          ((Integer) value).intValue());
+                length += TypeWriter.writeToStream(out, ((Integer) value).intValue());
                 break;
-            }
+
             case Variant.VT_I8:
-            {
                 length += TypeWriter.writeToStream(out, ((Long) value).longValue());
                 break;
-            }
+
             case Variant.VT_R8:
-            {
-                length += TypeWriter.writeToStream(out,
-                          ((Double) value).doubleValue());
+                length += TypeWriter.writeToStream(out, ((Double) value).doubleValue());
                 break;
-            }
+
             case Variant.VT_FILETIME:
-            {
                 long filetime = Util.dateToFileTime((Date) value);
                 int high = (int) ((filetime >> 32) & 0x00000000FFFFFFFFL);
                 int low = (int) (filetime & 0x00000000FFFFFFFFL);
                 Filetime filetimeValue = new Filetime( low, high);
                 length += filetimeValue.write( out );
                 break;
-            }
+
             default:
-            {
                 /* The variant type is not supported yet. However, if the value
                  * is a byte array we can write it nevertheless. */
-                if (value instanceof byte[])
-                {
+                if (value instanceof byte[]) {
                     final byte[] b = (byte[]) value;
                     out.write(b);
                     length = b.length;
-                    writeUnsupportedTypeMessage
-                        (new WritingNotSupportedException(type, value));
-                }
-                else
+                    writeUnsupportedTypeMessage(new WritingNotSupportedException(type, value));
+                } else {
                     throw new WritingNotSupportedException(type, value);
+                }
                 break;
-            }
         }
 
         /* pad values to 4-bytes */
-        while ( ( length & 0x3 ) != 0 )
-        {
+        while ( ( length & 0x3 ) != 0 ) {
             out.write( 0x00 );
             length++;
         }
index fc526ee619d48c6e093f24dbffc0c0e33674086d..36c3f3a9cb67dc9cc9ee6cae18ba6f475e826205 100644 (file)
@@ -27,6 +27,7 @@ import org.apache.poi.hpsf.CustomProperties;
 import org.apache.poi.hpsf.DocumentSummaryInformation;
 import org.apache.poi.hpsf.HPSFPropertiesOnlyDocument;
 import org.apache.poi.hpsf.Property;
+import org.apache.poi.hpsf.PropertySet;
 import org.apache.poi.hpsf.SpecialPropertySet;
 import org.apache.poi.hpsf.SummaryInformation;
 import org.apache.poi.hpsf.wellknown.PropertyIDMap;
@@ -67,7 +68,7 @@ public class HPSFPropertiesExtractor extends POIOLE2TextExtractor {
         CustomProperties cps = dsi == null ? null : dsi.getCustomProperties();
         if (cps != null) {
             for (String key : cps.nameSet()) {
-                String val = HelperPropertySet.getPropertyValueText(cps.get(key));
+                String val = getPropertyValueText(cps.get(key));
                 text.append(key).append(" = ").append(val).append("\n");
             }
         }
@@ -86,7 +87,7 @@ public class HPSFPropertiesExtractor extends POIOLE2TextExtractor {
         return getPropertiesText(si);
     }
 
-    private static String getPropertiesText(SpecialPropertySet ps) {
+    private static String getPropertiesText(PropertySet ps) {
         if (ps == null) {
             // Not defined, oh well
             return "";
@@ -98,12 +99,12 @@ public class HPSFPropertiesExtractor extends POIOLE2TextExtractor {
         Property[] props = ps.getProperties();
         for (Property prop : props) {
             String type = Long.toString(prop.getID());
-            Object typeObj = idMap.get(prop.getID());
+            Object typeObj = (idMap == null) ? null : idMap.get(prop.getID());
             if (typeObj != null) {
                 type = typeObj.toString();
             }
 
-            String val = HelperPropertySet.getPropertyValueText(prop.getValue());
+            String val = getPropertyValueText(prop.getValue());
             text.append(type).append(" = ").append(val).append("\n");
         }
 
@@ -125,18 +126,12 @@ public class HPSFPropertiesExtractor extends POIOLE2TextExtractor {
         throw new IllegalStateException("You already have the Metadata Text Extractor, not recursing!");
     }
 
-    private static abstract class HelperPropertySet extends SpecialPropertySet {
-        public HelperPropertySet() {
-            super(null);
-        }
-        public static String getPropertyValueText(Object val) {
-            if (val == null) {
-                return "(not set)";
-            }
-            return SpecialPropertySet.getPropertyStringValue(val);
-        }
+    private static String getPropertyValueText(Object val) {
+        return (val == null) 
+            ? "(not set)"
+            : PropertySet.getPropertyStringValue(val);
     }
-
+    
     @Override
     public boolean equals(Object o) {
         return super.equals(o);
index 2f61fd1d73ba2a2f029990e061cdcabc67ec5d87..929ade06c18366e4229861e9cb9231e96bee6484 100644 (file)
@@ -45,26 +45,42 @@ public final class IOUtils {
      * @throws EmptyFileException if the stream is empty
      */
     public static byte[] peekFirst8Bytes(InputStream stream) throws IOException, EmptyFileException {
-        // We want to peek at the first 8 bytes
-        stream.mark(8);
+        return peekFirstNBytes(stream, 8);
+    }
 
-        byte[] header = new byte[8];
-        int read = IOUtils.readFully(stream, header);
+    /**
+     * Peeks at the first N bytes of the stream. Returns those bytes, but
+     *  with the stream unaffected. Requires a stream that supports mark/reset,
+     *  or a PushbackInputStream. If the stream has &gt;0 but &lt;N bytes, 
+     *  remaining bytes will be zero.
+     * @throws EmptyFileException if the stream is empty
+     */
+    public static byte[] peekFirstNBytes(InputStream stream, int limit) throws IOException, EmptyFileException {
+        stream.mark(limit);
+        ByteArrayOutputStream bos = new ByteArrayOutputStream(limit);
+        copy(new BoundedInputStream(stream, limit), bos);
 
-        if (read < 1)
+        int readBytes = bos.size();
+        if (readBytes == 0) {
             throw new EmptyFileException();
-
-        // Wind back those 8 bytes
+        }
+        
+        if (readBytes < limit) {
+            bos.write(new byte[limit-readBytes]);
+        }
+        byte peekedBytes[] = bos.toByteArray();
         if(stream instanceof PushbackInputStream) {
             PushbackInputStream pin = (PushbackInputStream)stream;
-            pin.unread(header, 0, read);
+            pin.unread(peekedBytes, 0, readBytes);
         } else {
             stream.reset();
         }
 
-        return header;
+        return peekedBytes;
     }
-
+    
+    
+    
     /**
      * Reads all the data from the input stream, and returns the bytes read.
      */
index 8937dca585ee60cb4c2fcf48440ca794e1dc35d1..b40e7a463beb6b70da4f5249a65a5e5bb3fbf45d 100644 (file)
@@ -192,8 +192,7 @@ public final class TestBasic {
                 (poiFiles[0].getBytes()));
         final List<Section> sections = si.getSections();
         final Section s = sections.get(0);
-        assertTrue(org.apache.poi.hpsf.Util.equal
-            (s.getFormatID().getBytes(), SectionIDMap.SUMMARY_INFORMATION_ID));
+        assertArrayEquals(s.getFormatID().getBytes(), SectionIDMap.SUMMARY_INFORMATION_ID);
         assertNotNull(s.getProperties());
         assertEquals(17, s.getPropertyCount());
         assertEquals("Titel", s.getProperty(2));
index ffcaa3a79998fe62b3f703591f00f9ecf3fd11bb..20f7d8fae665177862042d066009758a0da5f12c 100644 (file)
@@ -216,7 +216,8 @@ public class TestWrite
         final MutablePropertySet ps = new MutablePropertySet();
         final MutableSection si = new MutableSection();
         si.setFormatID(SectionIDMap.SUMMARY_INFORMATION_ID);
-        ps.getSections().set(0, si);
+        ps.clearSections();
+        ps.addSection(si);
 
         final MutableProperty p = new MutableProperty();
         p.setID(PropertyIDMap.PID_AUTHOR);