diff options
19 files changed, 2128 insertions, 3473 deletions
diff --git a/src/java/org/apache/poi/hpsf/CustomProperties.java b/src/java/org/apache/poi/hpsf/CustomProperties.java index f305836fa8..c420f1d3a0 100644 --- a/src/java/org/apache/poi/hpsf/CustomProperties.java +++ b/src/java/org/apache/poi/hpsf/CustomProperties.java @@ -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<Long,CustomProperty>, that's the - * internal representation. To external calls, it should appear as - * HashMap<String,Object> mapping between Names and Custom Property Values.</p> + * While this class is roughly HashMap<Long,CustomProperty>, that's the + * internal representation. To external calls, it should appear as + * HashMap<String,Object> mapping between Names and Custom Property Values. */ @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; } } diff --git a/src/java/org/apache/poi/hpsf/CustomProperty.java b/src/java/org/apache/poi/hpsf/CustomProperty.java index a256d94d3a..83511655ab 100644 --- a/src/java/org/apache/poi/hpsf/CustomProperty.java +++ b/src/java/org/apache/poi/hpsf/CustomProperty.java @@ -18,10 +18,10 @@ 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(); } diff --git a/src/java/org/apache/poi/hpsf/DocumentSummaryInformation.java b/src/java/org/apache/poi/hpsf/DocumentSummaryInformation.java index 0f8c629cf8..f63ab8d92d 100644 --- a/src/java/org/apache/poi/hpsf/DocumentSummaryInformation.java +++ b/src/java/org/apache/poi/hpsf/DocumentSummaryInformation.java @@ -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."); } } diff --git a/src/java/org/apache/poi/hpsf/MutableProperty.java b/src/java/org/apache/poi/hpsf/MutableProperty.java index 9d77c0dd08..10d86be040 100644 --- a/src/java/org/apache/poi/hpsf/MutableProperty.java +++ b/src/java/org/apache/poi/hpsf/MutableProperty.java @@ -17,103 +17,22 @@ 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); } } diff --git a/src/java/org/apache/poi/hpsf/MutablePropertySet.java b/src/java/org/apache/poi/hpsf/MutablePropertySet.java index 5c7d386587..16978a4d6a 100644 --- a/src/java/org/apache/poi/hpsf/MutablePropertySet.java +++ b/src/java/org/apache/poi/hpsf/MutablePropertySet.java @@ -17,288 +17,21 @@ 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()); - } - } diff --git a/src/java/org/apache/poi/hpsf/MutableSection.java b/src/java/org/apache/poi/hpsf/MutableSection.java index b0d71acf0e..c5f0d6e0c8 100644 --- a/src/java/org/apache/poi/hpsf/MutableSection.java +++ b/src/java/org/apache/poi/hpsf/MutableSection.java @@ -17,683 +17,27 @@ package org.apache.poi.hpsf; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.util.Collections; -import java.util.Comparator; -import java.util.Date; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.ListIterator; -import java.util.Map; +import java.io.UnsupportedEncodingException; -import org.apache.poi.hpsf.wellknown.PropertyIDMap; -import org.apache.poi.util.CodePageUtil; -import org.apache.poi.util.LittleEndian; +import org.apache.poi.util.Removal; /** * <p>Adds writing capability to the {@link Section} class.</p> * * <p>Please be aware that this class' functionality will be merged into the * {@link Section} class at a later time, so the API will change.</p> + * + * @deprecated POI 3.16 - use Section as base class instead */ -public class MutableSection extends Section -{ - /** - * <p>If the "dirty" flag is true, the section's size must be - * (re-)calculated before the section is written.</p> - */ - private boolean dirty = true; - - - - /** - * <p>List to assemble the properties. Unfortunately a wrong - * decision has been taken when specifying the "properties" field - * as an Property[]. It should have been a {@link java.util.List}.</p> - */ - private List<Property> preprops; - - - - /** - * <p>Contains the bytes making out the section. This byte array is - * established when the section's size is calculated and can be reused - * later. It is valid only if the "dirty" flag is false.</p> - */ - private byte[] sectionBytes; - - - - /** - * <p>Creates an empty mutable section.</p> - */ - public MutableSection() - { - dirty = true; - formatID = null; - offset = -1; - preprops = new LinkedList<Property>(); - } - - - - /** - * <p>Constructs a <code>MutableSection</code> by doing a deep copy of an - * existing <code>Section</code>. All nested <code>Property</code> - * instances, will be their mutable counterparts in the new - * <code>MutableSection</code>.</p> - * - * @param s The section set to copy - */ - public MutableSection(final Section s) - { - setFormatID(s.getFormatID()); - final Property[] pa = s.getProperties(); - final MutableProperty[] mpa = new MutableProperty[pa.length]; - for (int i = 0; i < pa.length; i++) - mpa[i] = new MutableProperty(pa[i]); - setProperties(mpa); - setDictionary(s.getDictionary()); - } - - - - /** - * <p>Sets the section's format ID.</p> - * - * @param formatID The section's format ID - * - * @see #setFormatID(byte[]) - * @see Section#getFormatID - */ - public void setFormatID(final ClassID formatID) - { - this.formatID = formatID; - } - - - - /** - * <p>Sets the section's format ID.</p> - * - * @param formatID The section's format ID as a byte array. It components - * are in big-endian format. - * - * @see #setFormatID(ClassID) - * @see Section#getFormatID - */ - public void setFormatID(final byte[] formatID) - { - ClassID fid = getFormatID(); - if (fid == null) - { - fid = new ClassID(); - setFormatID(fid); - } - fid.setBytes(formatID); - } - - - - /** - * <p>Sets this section's properties. Any former values are overwritten.</p> - * - * @param properties This section's new properties. - */ - public void setProperties(final Property[] properties) - { - this.properties = properties; - preprops = new LinkedList<Property>(); - for (int i = 0; i < properties.length; i++) - preprops.add(properties[i]); - dirty = true; - } - - - - /** - * <p>Sets the string value of the property with the specified ID.</p> - * - * @param id The property's ID - * @param value The property's value. It will be written as a Unicode - * string. - * - * @see #setProperty(int, long, Object) - * @see #getProperty - */ - public void setProperty(final int id, final String value) - { - setProperty(id, Variant.VT_LPWSTR, value); - dirty = true; - } - - - - /** - * <p>Sets the int value of the property with the specified ID.</p> - * - * @param id The property's ID - * @param value The property's value. - * - * @see #setProperty(int, long, Object) - * @see #getProperty - */ - public void setProperty(final int id, final int value) - { - setProperty(id, Variant.VT_I4, Integer.valueOf(value)); - dirty = true; - } - - - - /** - * <p>Sets the long value of the property with the specified ID.</p> - * - * @param id The property's ID - * @param value The property's value. - * - * @see #setProperty(int, long, Object) - * @see #getProperty - */ - public void setProperty(final int id, final long value) - { - setProperty(id, Variant.VT_I8, Long.valueOf(value)); - dirty = true; - } - - - - /** - * <p>Sets the boolean value of the property with the specified ID.</p> - * - * @param id The property's ID - * @param value The property's value. - * - * @see #setProperty(int, long, Object) - * @see #getProperty - */ - public void setProperty(final int id, final boolean value) - { - setProperty(id, Variant.VT_BOOL, Boolean.valueOf(value)); - dirty = true; - } - - - - /** - * <p>Sets the value and the variant type of the property with the - * specified ID. If a property with this ID is not yet present in - * the section, it will be added. An already present property with - * the specified ID will be overwritten. A default mapping will be - * used to choose the property's type.</p> - * - * @param id The property's ID. - * @param variantType The property's variant type. - * @param value The property's value. - * - * @see #setProperty(int, String) - * @see #getProperty - * @see Variant - */ - public void setProperty(final int id, final long variantType, - final Object value) - { - final MutableProperty p = new MutableProperty(); - p.setID(id); - p.setType(variantType); - p.setValue(value); - setProperty(p); - dirty = true; - } - - - - /** - * <p>Sets a property.</p> - * - * @param p The property to be set. - * - * @see #setProperty(int, long, Object) - * @see #getProperty - * @see Variant - */ - public void setProperty(final Property p) - { - final long id = p.getID(); - removeProperty(id); - preprops.add(p); - dirty = true; - } - - - - /** - * <p>Removes a property.</p> - * - * @param id The ID of the property to be removed - */ - public void removeProperty(final long id) - { - for (final Iterator<Property> i = preprops.iterator(); i.hasNext();) - if (i.next().getID() == id) - { - i.remove(); - break; - } - dirty = true; - } - - - - /** - * <p>Sets the value of the boolean property with the specified - * ID.</p> - * - * @param id The property's ID - * @param value The property's value - * - * @see #setProperty(int, long, Object) - * @see #getProperty - * @see Variant - */ - protected void setPropertyBooleanValue(final int id, final boolean value) - { - setProperty(id, Variant.VT_BOOL, Boolean.valueOf(value)); - } - - - - /** - * <p>Returns the section's size.</p> - * - * @return the section's size. - */ - public int getSize() - { - if (dirty) - { - try - { - size = calcSize(); - dirty = false; - } - catch (HPSFRuntimeException ex) - { - throw ex; - } - catch (Exception ex) - { - throw new HPSFRuntimeException(ex); - } - } - return size; - } - - - - /** - * <p>Calculates the section's size. It is the sum of the lengths of the - * section's header (8), the properties list (16 times the number of - * properties) and the properties themselves.</p> - * - * @return the section's length in bytes. - * @throws WritingNotSupportedException - * @throws IOException - */ - private int calcSize() throws WritingNotSupportedException, IOException - { - final ByteArrayOutputStream out = new ByteArrayOutputStream(); - write(out); - out.close(); - /* Pad to multiple of 4 bytes so that even the Windows shell (explorer) - * shows custom properties. */ - sectionBytes = Util.pad4(out.toByteArray()); - return sectionBytes.length; - } - - - - /** - * <p>Writes this section into an output stream.</p> - * - * <p>Internally this is done by writing into three byte array output - * streams: one for the properties, one for the property list and one for - * the section as such. The two former are appended to the latter when they - * have received all their data.</p> - * - * @param out The stream to write into. - * - * @return The number of bytes written, i.e. the section's size. - * @exception IOException if an I/O error occurs - * @exception WritingNotSupportedException if HPSF does not yet support - * writing a property's variant type. - */ - public int write(final OutputStream out) - throws WritingNotSupportedException, IOException - { - /* Check whether we have already generated the bytes making out the - * section. */ - if (!dirty && sectionBytes != null) - { - out.write(sectionBytes); - return sectionBytes.length; - } - - /* The properties are written to this stream. */ - final ByteArrayOutputStream propertyStream = - new ByteArrayOutputStream(); - - /* The property list is established here. After each property that has - * been written to "propertyStream", a property list entry is written to - * "propertyListStream". */ - final ByteArrayOutputStream propertyListStream = - new ByteArrayOutputStream(); - - /* Maintain the current position in the list. */ - int position = 0; - - /* Increase the position variable by the size of the property list so - * that it points behind the property list and to the beginning of the - * properties themselves. */ - position += 2 * LittleEndian.INT_SIZE + - getPropertyCount() * 2 * LittleEndian.INT_SIZE; - - /* Writing the section's dictionary it tricky. If there is a dictionary - * (property 0) the codepage property (property 1) must be set, too. */ - int codepage = -1; - if (getProperty(PropertyIDMap.PID_DICTIONARY) != null) - { - final Object p1 = getProperty(PropertyIDMap.PID_CODEPAGE); - if (p1 != null) - { - if (!(p1 instanceof Integer)) - throw new IllegalPropertySetDataException - ("The codepage property (ID = 1) must be an " + - "Integer object."); - } - else - /* Warning: The codepage property is not set although a - * dictionary is present. In order to cope with this problem we - * add the codepage property and set it to Unicode. */ - setProperty(PropertyIDMap.PID_CODEPAGE, Variant.VT_I2, - Integer.valueOf(CodePageUtil.CP_UNICODE)); - codepage = getCodepage(); - } - - /* Sort the property list by their property IDs: */ - Collections.sort(preprops, new Comparator<Property>() - { - public int compare(final Property p1, final Property p2) - { - if (p1.getID() < p2.getID()) - return -1; - else if (p1.getID() == p2.getID()) - return 0; - else - return 1; - } - }); - - /* Write the properties and the property list into their respective - * streams: */ - for (final ListIterator<Property> i = preprops.listIterator(); i.hasNext();) - { - final MutableProperty p = (MutableProperty) i.next(); - final long id = p.getID(); - - /* Write the property list entry. */ - TypeWriter.writeUIntToStream(propertyListStream, p.getID()); - TypeWriter.writeUIntToStream(propertyListStream, position); - - /* If the property ID is not equal 0 we write the property and all - * is fine. However, if it equals 0 we have to write the section's - * dictionary which has an implicit type only and an explicit - * value. */ - if (id != 0) - /* Write the property and update the position to the next - * property. */ - position += p.write(propertyStream, getCodepage()); - else - { - if (codepage == -1) - throw new IllegalPropertySetDataException - ("Codepage (property 1) is undefined."); - position += writeDictionary(propertyStream, dictionary, - codepage); - } - } - propertyStream.close(); - propertyListStream.close(); - - /* Write the section: */ - byte[] pb1 = propertyListStream.toByteArray(); - byte[] pb2 = propertyStream.toByteArray(); - - /* Write the section's length: */ - TypeWriter.writeToStream(out, LittleEndian.INT_SIZE * 2 + - pb1.length + pb2.length); - - /* Write the section's number of properties: */ - TypeWriter.writeToStream(out, getPropertyCount()); - - /* Write the property list: */ - out.write(pb1); - - /* Write the properties: */ - out.write(pb2); - - int streamLength = LittleEndian.INT_SIZE * 2 + pb1.length + pb2.length; - return streamLength; - } - +@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); } } diff --git a/src/java/org/apache/poi/hpsf/NoFormatIDException.java b/src/java/org/apache/poi/hpsf/NoFormatIDException.java index f1a09438e1..51f76c3486 100644 --- a/src/java/org/apache/poi/hpsf/NoFormatIDException.java +++ b/src/java/org/apache/poi/hpsf/NoFormatIDException.java @@ -18,53 +18,47 @@ package org.apache.poi.hpsf; /** - * <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); } diff --git a/src/java/org/apache/poi/hpsf/Property.java b/src/java/org/apache/poi/hpsf/Property.java index 14dbee0276..b9841dbcc9 100644 --- a/src/java/org/apache/poi/hpsf/Property.java +++ b/src/java/org/apache/poi/hpsf/Property.java @@ -17,8 +17,11 @@ package org.apache.poi.hpsf; +import java.io.IOException; +import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.nio.charset.Charset; +import java.util.Arrays; import java.util.LinkedHashMap; import java.util.Map; @@ -29,104 +32,74 @@ import org.apache.poi.util.POILogFactory; import org.apache.poi.util.POILogger; /** - * <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; + } + } diff --git a/src/java/org/apache/poi/hpsf/PropertySet.java b/src/java/org/apache/poi/hpsf/PropertySet.java index 9c9b1876a0..30a636ffce 100644 --- a/src/java/org/apache/poi/hpsf/PropertySet.java +++ b/src/java/org/apache/poi/hpsf/PropertySet.java @@ -17,246 +17,195 @@ package org.apache.poi.hpsf; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; import java.io.UnsupportedEncodingException; -import java.util.ArrayList; +import java.nio.charset.Charset; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedList; import java.util.List; +import org.apache.poi.EmptyFileException; +import org.apache.poi.hpsf.wellknown.PropertyIDMap; import org.apache.poi.hpsf.wellknown.SectionIDMap; +import org.apache.poi.poifs.filesystem.DirectoryEntry; +import org.apache.poi.poifs.filesystem.Entry; import org.apache.poi.util.IOUtils; import org.apache.poi.util.LittleEndian; +import org.apache.poi.util.LittleEndianConsts; /** - * <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); + } } diff --git a/src/java/org/apache/poi/hpsf/PropertySetFactory.java b/src/java/org/apache/poi/hpsf/PropertySetFactory.java index 4d4b4e6386..db0a73d135 100644 --- a/src/java/org/apache/poi/hpsf/PropertySetFactory.java +++ b/src/java/org/apache/poi/hpsf/PropertySetFactory.java @@ -22,24 +22,22 @@ import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; -import org.apache.poi.hpsf.wellknown.SectionIDMap; import org.apache.poi.poifs.filesystem.DirectoryEntry; import org.apache.poi.poifs.filesystem.DocumentEntry; import org.apache.poi.poifs.filesystem.DocumentInputStream; /** - * <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 diff --git a/src/java/org/apache/poi/hpsf/Section.java b/src/java/org/apache/poi/hpsf/Section.java index c54fe51ab2..a27e9485cd 100644 --- a/src/java/org/apache/poi/hpsf/Section.java +++ b/src/java/org/apache/poi/hpsf/Section.java @@ -17,127 +17,96 @@ package org.apache.poi.hpsf; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; import java.io.UnsupportedEncodingException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; +import java.util.Date; import java.util.Map; +import java.util.TreeMap; +import org.apache.commons.collections4.bidimap.TreeBidiMap; import org.apache.poi.hpsf.wellknown.PropertyIDMap; import org.apache.poi.hpsf.wellknown.SectionIDMap; +import org.apache.poi.util.CodePageUtil; import org.apache.poi.util.LittleEndian; /** - * <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; } - } diff --git a/src/java/org/apache/poi/hpsf/SpecialPropertySet.java b/src/java/org/apache/poi/hpsf/SpecialPropertySet.java index aa8f8051d2..843d681ebc 100644 --- a/src/java/org/apache/poi/hpsf/SpecialPropertySet.java +++ b/src/java/org/apache/poi/hpsf/SpecialPropertySet.java @@ -17,397 +17,24 @@ 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(); - } - } diff --git a/src/java/org/apache/poi/hpsf/SummaryInformation.java b/src/java/org/apache/poi/hpsf/SummaryInformation.java index 5c260187c0..856f566e75 100644 --- a/src/java/org/apache/poi/hpsf/SummaryInformation.java +++ b/src/java/org/apache/poi/hpsf/SummaryInformation.java @@ -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); } } diff --git a/src/java/org/apache/poi/hpsf/Util.java b/src/java/org/apache/poi/hpsf/Util.java index 5d75cb986f..d8ab7315db 100644 --- a/src/java/org/apache/poi/hpsf/Util.java +++ b/src/java/org/apache/poi/hpsf/Util.java @@ -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> >= 0 and - * <var>i</var> < <var>a.length</var> holds - * <var>a</var>[<var>i</var>] == <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> diff --git a/src/java/org/apache/poi/hpsf/VariantSupport.java b/src/java/org/apache/poi/hpsf/VariantSupport.java index b4816eb912..61fc30d883 100644 --- a/src/java/org/apache/poi/hpsf/VariantSupport.java +++ b/src/java/org/apache/poi/hpsf/VariantSupport.java @@ -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++; } diff --git a/src/java/org/apache/poi/hpsf/extractor/HPSFPropertiesExtractor.java b/src/java/org/apache/poi/hpsf/extractor/HPSFPropertiesExtractor.java index fc526ee619..36c3f3a9cb 100644 --- a/src/java/org/apache/poi/hpsf/extractor/HPSFPropertiesExtractor.java +++ b/src/java/org/apache/poi/hpsf/extractor/HPSFPropertiesExtractor.java @@ -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); diff --git a/src/java/org/apache/poi/util/IOUtils.java b/src/java/org/apache/poi/util/IOUtils.java index 2f61fd1d73..929ade06c1 100644 --- a/src/java/org/apache/poi/util/IOUtils.java +++ b/src/java/org/apache/poi/util/IOUtils.java @@ -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 >0 but <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. */ diff --git a/src/testcases/org/apache/poi/hpsf/basic/TestBasic.java b/src/testcases/org/apache/poi/hpsf/basic/TestBasic.java index 8937dca585..b40e7a463b 100644 --- a/src/testcases/org/apache/poi/hpsf/basic/TestBasic.java +++ b/src/testcases/org/apache/poi/hpsf/basic/TestBasic.java @@ -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)); diff --git a/src/testcases/org/apache/poi/hpsf/basic/TestWrite.java b/src/testcases/org/apache/poi/hpsf/basic/TestWrite.java index ffcaa3a799..20f7d8fae6 100644 --- a/src/testcases/org/apache/poi/hpsf/basic/TestWrite.java +++ b/src/testcases/org/apache/poi/hpsf/basic/TestWrite.java @@ -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); |