#60331 - Remove deprecated classes - deprecate Mutable* property classes

sonarcube fix - make protected attributes private

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1771640 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Andreas Beeker 2016-11-27 20:19:18 +00:00
parent 8968b6d6b6
commit 6ea58e94bc
19 changed files with 2198 additions and 3543 deletions

View File

@ -19,132 +19,112 @@ package org.apache.poi.hpsf;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import org.apache.commons.collections4.bidimap.TreeBidiMap;
import org.apache.poi.hpsf.wellknown.PropertyIDMap;
/**
* <p>Maintains the instances of {@link CustomProperty} that belong to a
* Maintains the instances of {@link CustomProperty} that belong to a
* {@link DocumentSummaryInformation}. The class maintains the names of the
* custom properties in a dictionary. It implements the {@link Map} interface
* and by this provides a simplified view on custom properties: A property's
* name is the key that maps to a typed value. This implementation hides
* property IDs from the developer and regards the property names as keys to
* typed values.</p>
* typed values.<p>
*
* <p>While this class provides a simple API to custom properties, it ignores
* While this class provides a simple API to custom properties, it ignores
* the fact that not names, but IDs are the real keys to properties. Under the
* hood this class maintains a 1:1 relationship between IDs and names. Therefore
* you should not use this class to process property sets with several IDs
* mapping to the same name or with properties without a name: the result will
* contain only a subset of the original properties. If you really need to deal
* such property sets, use HPSF's low-level access methods.</p>
* such property sets, use HPSF's low-level access methods.<p>
*
* <p>An application can call the {@link #isPure} method to check whether a
* An application can call the {@link #isPure} method to check whether a
* property set parsed by {@link CustomProperties} is still pure (i.e.
* unmodified) or whether one or more properties have been dropped.</p>
* unmodified) or whether one or more properties have been dropped.<p>
*
* <p>This class is not thread-safe; concurrent access to instances of this
* class must be synchronized.</p>
* This class is not thread-safe; concurrent access to instances of this
* class must be synchronized.<p>
*
* <p>While this class is roughly HashMap&lt;Long,CustomProperty&gt;, that's the
* internal representation. To external calls, it should appear as
* HashMap&lt;String,Object&gt; mapping between Names and Custom Property Values.</p>
* While this class is roughly HashMap&lt;Long,CustomProperty&gt;, that's the
* internal representation. To external calls, it should appear as
* HashMap&lt;String,Object&gt; mapping between Names and Custom Property Values.
*/
@SuppressWarnings("serial")
public class CustomProperties extends HashMap<Object,CustomProperty>
{
public class CustomProperties extends HashMap<Long,CustomProperty> {
/**
* <p>Maps property IDs to property names.</p>
* Maps property IDs to property names and vice versa.
*/
private final Map<Long,String> dictionaryIDToName = new HashMap<Long,String>();
private final TreeBidiMap<Long,String> dictionary = new TreeBidiMap<Long,String>();
/**
* <p>Maps property names to property IDs.</p>
*/
private final Map<String,Long> dictionaryNameToID = new HashMap<String,Long>();
/**
* <p>Tells whether this object is pure or not.</p>
* Tells whether this object is pure or not.
*/
private boolean isPure = true;
/**
* <p>Puts a {@link CustomProperty} into this map. It is assumed that the
* Puts a {@link CustomProperty} into this map. It is assumed that the
* {@link CustomProperty} already has a valid ID. Otherwise use
* {@link #put(CustomProperty)}.</p>
* {@link #put(CustomProperty)}.
*
* @param name the property name
* @param cp the property
*
* @return the previous property stored under this name
*/
public CustomProperty put(final String name, final CustomProperty cp)
{
if (name == null)
{
public CustomProperty put(final String name, final CustomProperty cp) {
if (name == null) {
/* Ignoring a property without a name. */
isPure = false;
return null;
}
if (!(name.equals(cp.getName())))
if (!name.equals(cp.getName())) {
throw new IllegalArgumentException("Parameter \"name\" (" + name +
") and custom property's name (" + cp.getName() +
") do not match.");
}
/* Register name and ID in the dictionary. Mapping in both directions is possible. If there is already a */
final Long idKey = Long.valueOf(cp.getID());
final Long oldID = dictionaryNameToID.get(name);
dictionaryIDToName.remove(oldID);
dictionaryNameToID.put(name, idKey);
dictionaryIDToName.put(idKey, name);
super.remove(dictionary.getKey(name));
dictionary.put(cp.getID(), name);
/* Put the custom property into this map. */
final CustomProperty oldCp = super.remove(oldID);
super.put(idKey, cp);
return oldCp;
return super.put(cp.getID(), cp);
}
/**
* <p>Puts a {@link CustomProperty} that has not yet a valid ID into this
* map. The method will allocate a suitable ID for the custom property:</p>
* Puts a {@link CustomProperty} that has not yet a valid ID into this
* map. The method will allocate a suitable ID for the custom property:
*
* <ul>
* <li>If there is already a property with the same name, take the ID
* of that property.
*
* <li><p>If there is already a property with the same name, take the ID
* of that property.</p></li>
*
* <li><p>Otherwise find the highest ID and use its value plus one.</p></li>
*
* <li>Otherwise find the highest ID and use its value plus one.
* </ul>
*
* @param customProperty
* @return If the was already a property with the same name, the
* @return If there was already a property with the same name, the old property
* @throws ClassCastException
*/
private Object put(final CustomProperty customProperty) throws ClassCastException
{
private Object put(final CustomProperty customProperty) throws ClassCastException {
final String name = customProperty.getName();
/* Check whether a property with this name is in the map already. */
final Long oldId = dictionaryNameToID.get(name);
if (oldId != null)
customProperty.setID(oldId.longValue());
else
{
long max = 1;
for (Long long1 : dictionaryIDToName.keySet()) {
final long id = long1.longValue();
if (id > max)
max = id;
}
customProperty.setID(max + 1);
final Long oldId = (name == null) ? null : dictionary.getKey(name);
if (oldId != null) {
customProperty.setID(oldId);
} else {
long lastKey = (dictionary.isEmpty()) ? 0 : dictionary.lastKey();
customProperty.setID(Math.max(lastKey,PropertyIDMap.PID_MAX) + 1);
}
return this.put(name, customProperty);
}
@ -152,123 +132,92 @@ public class CustomProperties extends HashMap<Object,CustomProperty>
/**
* <p>Removes a custom property.</p>
* Removes a custom property.
* @param name The name of the custom property to remove
* @return The removed property or <code>null</code> if the specified property was not found.
* @return The removed property or {@code null} if the specified property was not found.
*
* @see java.util.HashSet#remove(java.lang.Object)
*/
public Object remove(final String name)
{
final Long id = dictionaryNameToID.get(name);
if (id == null)
return null;
dictionaryIDToName.remove(id);
dictionaryNameToID.remove(name);
public Object remove(final String name) {
final Long id = dictionary.removeValue(name);
return super.remove(id);
}
/**
* <p>Adds a named string property.</p>
* Adds a named string property.
*
* @param name The property's name.
* @param value The property's value.
* @return the property that was stored under the specified name before, or
* <code>null</code> if there was no such property before.
* {@code null} if there was no such property before.
*/
public Object put(final String name, final String value)
{
final MutableProperty p = new MutableProperty();
p.setID(-1);
p.setType(Variant.VT_LPWSTR);
p.setValue(value);
final CustomProperty cp = new CustomProperty(p, name);
return put(cp);
public Object put(final String name, final String value) {
final Property p = new Property(-1, Variant.VT_LPWSTR, value);
return put(new CustomProperty(p, name));
}
/**
* <p>Adds a named long property.</p>
* Adds a named long property.
*
* @param name The property's name.
* @param value The property's value.
* @return the property that was stored under the specified name before, or
* <code>null</code> if there was no such property before.
* {@code null} if there was no such property before.
*/
public Object put(final String name, final Long value)
{
final MutableProperty p = new MutableProperty();
p.setID(-1);
p.setType(Variant.VT_I8);
p.setValue(value);
final CustomProperty cp = new CustomProperty(p, name);
return put(cp);
public Object put(final String name, final Long value) {
final Property p = new Property(-1, Variant.VT_I8, value);
return put(new CustomProperty(p, name));
}
/**
* <p>Adds a named double property.</p>
* Adds a named double property.
*
* @param name The property's name.
* @param value The property's value.
* @return the property that was stored under the specified name before, or
* <code>null</code> if there was no such property before.
* {@code null} if there was no such property before.
*/
public Object put(final String name, final Double value)
{
final MutableProperty p = new MutableProperty();
p.setID(-1);
p.setType(Variant.VT_R8);
p.setValue(value);
final CustomProperty cp = new CustomProperty(p, name);
return put(cp);
public Object put(final String name, final Double value) {
final Property p = new Property(-1, Variant.VT_R8, value);
return put(new CustomProperty(p, name));
}
/**
* <p>Adds a named integer property.</p>
* Adds a named integer property.
*
* @param name The property's name.
* @param value The property's value.
* @return the property that was stored under the specified name before, or
* <code>null</code> if there was no such property before.
* {@code null} if there was no such property before.
*/
public Object put(final String name, final Integer value)
{
final MutableProperty p = new MutableProperty();
p.setID(-1);
p.setType(Variant.VT_I4);
p.setValue(value);
final CustomProperty cp = new CustomProperty(p, name);
return put(cp);
public Object put(final String name, final Integer value) {
final Property p = new Property(-1, Variant.VT_I4, value);
return put(new CustomProperty(p, name));
}
/**
* <p>Adds a named boolean property.</p>
* Adds a named boolean property.
*
* @param name The property's name.
* @param value The property's value.
* @return the property that was stored under the specified name before, or
* <code>null</code> if there was no such property before.
* {@code null} if there was no such property before.
*/
public Object put(final String name, final Boolean value)
{
final MutableProperty p = new MutableProperty();
p.setID(-1);
p.setType(Variant.VT_BOOL);
p.setValue(value);
final CustomProperty cp = new CustomProperty(p, name);
return put(cp);
public Object put(final String name, final Boolean value) {
final Property p = new Property(-1, Variant.VT_BOOL, value);
return put(new CustomProperty(p, name));
}
/**
* <p>Gets a named value from the custom properties.</p>
* Gets a named value from the custom properties.
*
* @param name the name of the value to get
* @return the value or <code>null</code> if a value with the specified
* @return the value or {@code null} if a value with the specified
* name is not found in the custom properties.
*/
public Object get(final String name)
{
final Long id = dictionaryNameToID.get(name);
public Object get(final String name) {
final Long id = dictionary.getKey(name);
final CustomProperty cp = super.get(id);
return cp != null ? cp.getValue() : null;
}
@ -276,21 +225,16 @@ public class CustomProperties extends HashMap<Object,CustomProperty>
/**
* <p>Adds a named date property.</p>
* Adds a named date property.
*
* @param name The property's name.
* @param value The property's value.
* @return the property that was stored under the specified name before, or
* <code>null</code> if there was no such property before.
* {@code null} if there was no such property before.
*/
public Object put(final String name, final Date value)
{
final MutableProperty p = new MutableProperty();
p.setID(-1);
p.setType(Variant.VT_FILETIME);
p.setValue(value);
final CustomProperty cp = new CustomProperty(p, name);
return put(cp);
public Object put(final String name, final Date value) {
final Property p = new Property(-1, Variant.VT_FILETIME, value);
return put(new CustomProperty(p, name));
}
/**
@ -302,7 +246,7 @@ public class CustomProperties extends HashMap<Object,CustomProperty>
@Override
@SuppressWarnings({ "rawtypes", "unchecked" })
public Set keySet() {
return dictionaryNameToID.keySet();
return dictionary.values();
}
/**
@ -311,7 +255,7 @@ public class CustomProperties extends HashMap<Object,CustomProperty>
* @return a set of all the names of our custom properties
*/
public Set<String> nameSet() {
return dictionaryNameToID.keySet();
return dictionary.values();
}
/**
@ -320,21 +264,17 @@ public class CustomProperties extends HashMap<Object,CustomProperty>
* @return a set of all the IDs of our custom properties
*/
public Set<String> idSet() {
return dictionaryNameToID.keySet();
return dictionary.values();
}
/**
* <p>Sets the codepage.</p>
* Sets the codepage.
*
* @param codepage the codepage
*/
public void setCodepage(final int codepage)
{
final MutableProperty p = new MutableProperty();
p.setID(PropertyIDMap.PID_CODEPAGE);
p.setType(Variant.VT_I2);
p.setValue(Integer.valueOf(codepage));
public void setCodepage(final int codepage) {
Property p = new Property(PropertyIDMap.PID_CODEPAGE, Variant.VT_I2, codepage);
put(new CustomProperty(p));
}
@ -346,84 +286,65 @@ public class CustomProperties extends HashMap<Object,CustomProperty>
*
* @return the dictionary.
*/
Map<Long,String> getDictionary()
{
return dictionaryIDToName;
Map<Long,String> getDictionary() {
return dictionary;
}
/**
* Checks against both String Name and Long ID
*/
@Override
public boolean containsKey(Object key) {
if(key instanceof Long) {
return super.containsKey(key);
}
if(key instanceof String) {
return super.containsKey(dictionaryNameToID.get(key));
}
return false;
}
@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);
} else {
for(CustomProperty cp : super.values()) {
/**
* 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;
}
}

View File

@ -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();
}

File diff suppressed because it is too large Load Diff

View File

@ -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
{
@Removal(version="3.18")
public class MutableProperty extends Property {
public MutableProperty() {}
/**
* <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;
length += TypeWriter.writeUIntToStream(out, variantType);
length += VariantSupport.write(out, variantType, getValue(), codepage);
return length;
public MutableProperty(final Property p) {
super(p);
}
}

View File

@ -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>
*
* <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.
*
* @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());
@Removal(version="3.18")
public class MutablePropertySet extends PropertySet {
public MutablePropertySet() {}
public MutablePropertySet(final PropertySet ps) {
super(ps);
}
/**
* <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;
}
/**
* <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());
}
}

View File

@ -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;
@Removal(version="3.18")
public class MutableSection extends Section {
public MutableSection() {}
/**
* <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>();
public MutableSection(final Section s) {
super(s);
}
/**
* <p>Constructs a <code>MutableSection</code> by doing a deep copy of an
* existing <code>Section</code>. All nested <code>Property</code>
* instances, will be their mutable counterparts in the new
* <code>MutableSection</code>.</p>
*
* @param s The section set to copy
*/
public MutableSection(final Section s)
{
setFormatID(s.getFormatID());
final Property[] pa = s.getProperties();
final MutableProperty[] mpa = new MutableProperty[pa.length];
for (int i = 0; i < pa.length; i++)
mpa[i] = new MutableProperty(pa[i]);
setProperties(mpa);
setDictionary(s.getDictionary());
}
/**
* <p>Sets the section's format ID.</p>
*
* @param formatID The section's format ID
*
* @see #setFormatID(byte[])
* @see Section#getFormatID
*/
public void setFormatID(final ClassID formatID)
{
this.formatID = formatID;
}
/**
* <p>Sets the section's format ID.</p>
*
* @param formatID The section's format ID as a byte array. It components
* are in big-endian format.
*
* @see #setFormatID(ClassID)
* @see Section#getFormatID
*/
public void setFormatID(final byte[] formatID)
{
ClassID fid = getFormatID();
if (fid == null)
{
fid = new ClassID();
setFormatID(fid);
}
fid.setBytes(formatID);
}
/**
* <p>Sets this section's properties. Any former values are overwritten.</p>
*
* @param properties This section's new properties.
*/
public void setProperties(final Property[] properties)
{
this.properties = properties;
preprops = new LinkedList<Property>();
for (int i = 0; i < properties.length; i++)
preprops.add(properties[i]);
dirty = true;
}
/**
* <p>Sets the string value of the property with the specified ID.</p>
*
* @param id The property's ID
* @param value The property's value. It will be written as a Unicode
* string.
*
* @see #setProperty(int, long, Object)
* @see #getProperty
*/
public void setProperty(final int id, final String value)
{
setProperty(id, Variant.VT_LPWSTR, value);
dirty = true;
}
/**
* <p>Sets the int value of the property with the specified ID.</p>
*
* @param id The property's ID
* @param value The property's value.
*
* @see #setProperty(int, long, Object)
* @see #getProperty
*/
public void setProperty(final int id, final int value)
{
setProperty(id, Variant.VT_I4, Integer.valueOf(value));
dirty = true;
}
/**
* <p>Sets the long value of the property with the specified ID.</p>
*
* @param id The property's ID
* @param value The property's value.
*
* @see #setProperty(int, long, Object)
* @see #getProperty
*/
public void setProperty(final int id, final long value)
{
setProperty(id, Variant.VT_I8, Long.valueOf(value));
dirty = true;
}
/**
* <p>Sets the boolean value of the property with the specified ID.</p>
*
* @param id The property's ID
* @param value The property's value.
*
* @see #setProperty(int, long, Object)
* @see #getProperty
*/
public void setProperty(final int id, final boolean value)
{
setProperty(id, Variant.VT_BOOL, Boolean.valueOf(value));
dirty = true;
}
/**
* <p>Sets the value and the variant type of the property with the
* specified ID. If a property with this ID is not yet present in
* the section, it will be added. An already present property with
* the specified ID will be overwritten. A default mapping will be
* used to choose the property's type.</p>
*
* @param id The property's ID.
* @param variantType The property's variant type.
* @param value The property's value.
*
* @see #setProperty(int, String)
* @see #getProperty
* @see Variant
*/
public void setProperty(final int id, final long variantType,
final Object value)
{
final MutableProperty p = new MutableProperty();
p.setID(id);
p.setType(variantType);
p.setValue(value);
setProperty(p);
dirty = true;
}
/**
* <p>Sets a property.</p>
*
* @param p The property to be set.
*
* @see #setProperty(int, long, Object)
* @see #getProperty
* @see Variant
*/
public void setProperty(final Property p)
{
final long id = p.getID();
removeProperty(id);
preprops.add(p);
dirty = true;
}
/**
* <p>Removes a property.</p>
*
* @param id The ID of the property to be removed
*/
public void removeProperty(final long id)
{
for (final Iterator<Property> i = preprops.iterator(); i.hasNext();)
if (i.next().getID() == id)
{
i.remove();
break;
}
dirty = true;
}
/**
* <p>Sets the value of the boolean property with the specified
* ID.</p>
*
* @param id The property's ID
* @param value The property's value
*
* @see #setProperty(int, long, Object)
* @see #getProperty
* @see Variant
*/
protected void setPropertyBooleanValue(final int id, final boolean value)
{
setProperty(id, Variant.VT_BOOL, Boolean.valueOf(value));
}
/**
* <p>Returns the section's size.</p>
*
* @return the section's size.
*/
public int getSize()
{
if (dirty)
{
try
{
size = calcSize();
dirty = false;
}
catch (HPSFRuntimeException ex)
{
throw ex;
}
catch (Exception ex)
{
throw new HPSFRuntimeException(ex);
}
}
return size;
}
/**
* <p>Calculates the section's size. It is the sum of the lengths of the
* section's header (8), the properties list (16 times the number of
* properties) and the properties themselves.</p>
*
* @return the section's length in bytes.
* @throws WritingNotSupportedException
* @throws IOException
*/
private int calcSize() throws WritingNotSupportedException, IOException
{
final ByteArrayOutputStream out = new ByteArrayOutputStream();
write(out);
out.close();
/* Pad to multiple of 4 bytes so that even the Windows shell (explorer)
* shows custom properties. */
sectionBytes = Util.pad4(out.toByteArray());
return sectionBytes.length;
}
/**
* <p>Writes this section into an output stream.</p>
*
* <p>Internally this is done by writing into three byte array output
* streams: one for the properties, one for the property list and one for
* the section as such. The two former are appended to the latter when they
* have received all their data.</p>
*
* @param out The stream to write into.
*
* @return The number of bytes written, i.e. the section's size.
* @exception IOException if an I/O error occurs
* @exception WritingNotSupportedException if HPSF does not yet support
* writing a property's variant type.
*/
public int write(final OutputStream out)
throws WritingNotSupportedException, IOException
{
/* Check whether we have already generated the bytes making out the
* section. */
if (!dirty && sectionBytes != null)
{
out.write(sectionBytes);
return sectionBytes.length;
}
/* The properties are written to this stream. */
final ByteArrayOutputStream propertyStream =
new ByteArrayOutputStream();
/* The property list is established here. After each property that has
* been written to "propertyStream", a property list entry is written to
* "propertyListStream". */
final ByteArrayOutputStream propertyListStream =
new ByteArrayOutputStream();
/* Maintain the current position in the list. */
int position = 0;
/* Increase the position variable by the size of the property list so
* that it points behind the property list and to the beginning of the
* properties themselves. */
position += 2 * LittleEndian.INT_SIZE +
getPropertyCount() * 2 * LittleEndian.INT_SIZE;
/* Writing the section's dictionary it tricky. If there is a dictionary
* (property 0) the codepage property (property 1) must be set, too. */
int codepage = -1;
if (getProperty(PropertyIDMap.PID_DICTIONARY) != null)
{
final Object p1 = getProperty(PropertyIDMap.PID_CODEPAGE);
if (p1 != null)
{
if (!(p1 instanceof Integer))
throw new IllegalPropertySetDataException
("The codepage property (ID = 1) must be an " +
"Integer object.");
}
else
/* Warning: The codepage property is not set although a
* dictionary is present. In order to cope with this problem we
* add the codepage property and set it to Unicode. */
setProperty(PropertyIDMap.PID_CODEPAGE, Variant.VT_I2,
Integer.valueOf(CodePageUtil.CP_UNICODE));
codepage = getCodepage();
}
/* Sort the property list by their property IDs: */
Collections.sort(preprops, new Comparator<Property>()
{
public int compare(final Property p1, final Property p2)
{
if (p1.getID() < p2.getID())
return -1;
else if (p1.getID() == p2.getID())
return 0;
else
return 1;
}
});
/* Write the properties and the property list into their respective
* streams: */
for (final ListIterator<Property> i = preprops.listIterator(); i.hasNext();)
{
final MutableProperty p = (MutableProperty) i.next();
final long id = p.getID();
/* Write the property list entry. */
TypeWriter.writeUIntToStream(propertyListStream, p.getID());
TypeWriter.writeUIntToStream(propertyListStream, position);
/* If the property ID is not equal 0 we write the property and all
* is fine. However, if it equals 0 we have to write the section's
* dictionary which has an implicit type only and an explicit
* value. */
if (id != 0)
/* Write the property and update the position to the next
* property. */
position += p.write(propertyStream, getCodepage());
else
{
if (codepage == -1)
throw new IllegalPropertySetDataException
("Codepage (property 1) is undefined.");
position += writeDictionary(propertyStream, dictionary,
codepage);
}
}
propertyStream.close();
propertyListStream.close();
/* Write the section: */
byte[] pb1 = propertyListStream.toByteArray();
byte[] pb2 = propertyStream.toByteArray();
/* Write the section's length: */
TypeWriter.writeToStream(out, LittleEndian.INT_SIZE * 2 +
pb1.length + pb2.length);
/* Write the section's number of properties: */
TypeWriter.writeToStream(out, getPropertyCount());
/* Write the property list: */
out.write(pb1);
/* Write the properties: */
out.write(pb2);
int streamLength = LittleEndian.INT_SIZE * 2 + pb1.length + pb2.length;
return streamLength;
}
/**
* <p>Writes the section's dictionary.</p>
*
* @param out The output stream to write to.
* @param dictionary The dictionary.
* @param codepage The codepage to be used to write the dictionary items.
* @return The number of bytes written
* @exception IOException if an I/O exception occurs.
*/
private static int writeDictionary(final OutputStream out,
final Map<Long,String> dictionary, final int codepage)
throws IOException
{
int length = TypeWriter.writeUIntToStream(out, dictionary.size());
for (Map.Entry<Long,String> ls : dictionary.entrySet()) {
final Long key = ls.getKey();
final String value = ls.getValue();
if (codepage == CodePageUtil.CP_UNICODE)
{
/* Write the dictionary item in Unicode. */
int sLength = value.length() + 1;
if ((sLength & 1) == 1) {
sLength++;
}
length += TypeWriter.writeUIntToStream(out, key.longValue());
length += TypeWriter.writeUIntToStream(out, sLength);
final byte[] ca = CodePageUtil.getBytesInCodePage(value, codepage);
for (int j = 2; j < ca.length; j += 2)
{
out.write(ca[j+1]);
out.write(ca[j]);
length += 2;
}
sLength -= value.length();
while (sLength > 0)
{
out.write(0x00);
out.write(0x00);
length += 2;
sLength--;
}
}
else
{
/* Write the dictionary item in another codepage than
* Unicode. */
length += TypeWriter.writeUIntToStream(out, key.longValue());
length += TypeWriter.writeUIntToStream(out, value.length() + 1);
final byte[] ba = CodePageUtil.getBytesInCodePage(value, codepage);
for (int j = 0; j < ba.length; j++)
{
out.write(ba[j]);
length++;
}
out.write(0x00);
length++;
}
}
return length;
}
/**
* <p>Overwrites the super class' method to cope with a redundancy:
* the property count is maintained in a separate member variable, but
* shouldn't.</p>
*
* @return The number of properties in this section
*/
public int getPropertyCount()
{
return preprops.size();
}
/**
* <p>Gets this section's properties.</p>
*
* @return this section's properties.
*/
public Property[] getProperties()
{
properties = preprops.toArray(new Property[0]);
return properties;
}
/**
* <p>Gets a property.</p>
*
* @param id The ID of the property to get
* @return The property or <code>null</code> if there is no such property
*/
public Object getProperty(final long id)
{
/* Calling getProperties() ensures that properties and preprops are in
* sync.</p> */
getProperties();
return super.getProperty(id);
}
/**
* <p>Sets the section's dictionary. All keys in the dictionary must be
* {@link java.lang.Long} instances, all values must be
* {@link java.lang.String}s. This method overwrites the properties with IDs
* 0 and 1 since they are reserved for the dictionary and the dictionary's
* codepage. Setting these properties explicitly might have surprising
* effects. An application should never do this but always use this
* method.</p>
*
* @param dictionary The dictionary
*
* @exception IllegalPropertySetDataException if the dictionary's key and
* value types are not correct.
*
* @see Section#getDictionary()
*/
public void setDictionary(final Map<Long,String> dictionary)
throws IllegalPropertySetDataException
{
if (dictionary != null)
{
this.dictionary = dictionary;
/* Set the dictionary property (ID 0). Please note that the second
* parameter in the method call below is unused because dictionaries
* don't have a type. */
setProperty(PropertyIDMap.PID_DICTIONARY, -1, dictionary);
/* If the codepage property (ID 1) for the strings (keys and
* values) used in the dictionary is not yet defined, set it to
* Unicode. */
final Integer codepage =
(Integer) getProperty(PropertyIDMap.PID_CODEPAGE);
if (codepage == null)
setProperty(PropertyIDMap.PID_CODEPAGE, Variant.VT_I2,
Integer.valueOf(CodePageUtil.CP_UNICODE));
}
else
/* Setting the dictionary to null means to remove property 0.
* However, it does not mean to remove property 1 (codepage). */
removeProperty(PropertyIDMap.PID_DICTIONARY);
}
/**
* <p>Sets a property.</p>
*
* @param id The property ID.
* @param value The property's value. The value's class must be one of those
* supported by HPSF.
*/
public void setProperty(final int id, final Object value)
{
if (value instanceof String)
setProperty(id, (String) value);
else if (value instanceof Long)
setProperty(id, ((Long) value).longValue());
else if (value instanceof Integer)
setProperty(id, ((Integer) value).intValue());
else if (value instanceof Short)
setProperty(id, ((Short) value).intValue());
else if (value instanceof Boolean)
setProperty(id, ((Boolean) value).booleanValue());
else if (value instanceof Date)
setProperty(id, Variant.VT_FILETIME, value);
else
throw new HPSFRuntimeException(
"HPSF does not support properties of type " +
value.getClass().getName() + ".");
}
/**
* <p>Removes all properties from the section including 0 (dictionary) and
* 1 (codepage).</p>
*/
public void clear()
{
final Property[] properties = getProperties();
for (int i = 0; i < properties.length; i++)
{
final Property p = properties[i];
removeProperty(p.getID());
}
}
/**
* <p>Sets the codepage.</p>
*
* @param codepage the codepage
*/
public void setCodepage(final int codepage)
{
setProperty(PropertyIDMap.PID_CODEPAGE, Variant.VT_I2,
Integer.valueOf(codepage));
public MutableSection(final byte[] src, final int offset) throws UnsupportedEncodingException {
super(src,offset);
}
}

View File

@ -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);
}

View File

@ -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;
/**
* <p>Returns the property's type.</p>
*
* @return The type value
*/
public long getType()
{
return type;
}
/** <p>The property's value.</p> */
/** The property's value. */
protected Object value;
/**
* <p>Returns the property's value.</p>
*
* @return The property's value
* Creates an empty property. It must be filled using the set method to be usable.
*/
public Object getValue()
{
return value;
public Property() {
}
/**
* <p>Creates a property.</p>
* Creates a {@code Property} as a copy of an existing {@code Property}.
*
* @param p The property to copy.
*/
public Property(Property p) {
this(p.id, p.type, p.value);
}
/**
* Creates a property.
*
* @param id the property's ID.
* @param type the property's type, see {@link Variant}.
* @param value the property's value. Only certain types are allowed, see
* {@link Variant}.
*/
public Property(final long id, final long type, final Object value)
{
public Property(final long id, final long type, final Object value) {
this.id = id;
this.type = type;
this.value = value;
}
/**
* <p>Creates a {@link Property} instance by reading its bytes
* from the property set stream.</p>
* Creates a {@link Property} instance by reading its bytes
* from the property set stream.
*
* @param id The property's ID.
* @param src The bytes the property set stream consists of.
@ -138,18 +111,15 @@ public class Property
* @exception UnsupportedEncodingException if the specified codepage is not
* supported.
*/
public Property(final long id, final byte[] src, final long offset,
final int length, final int codepage)
throws UnsupportedEncodingException
{
public Property(final long id, final byte[] src, final long offset, final int length, final int codepage)
throws UnsupportedEncodingException {
this.id = id;
/*
* ID 0 is a special case since it specifies a dictionary of
* property IDs and property names.
*/
if (id == 0)
{
if (id == 0) {
value = readDictionary(src, offset, length, codepage);
return;
}
@ -158,12 +128,9 @@ public class Property
type = LittleEndian.getUInt(src, o);
o += LittleEndian.INT_SIZE;
try
{
try {
value = VariantSupport.read(src, o, length, (int) type, codepage);
}
catch (UnsupportedVariantTypeException ex)
{
} catch (UnsupportedVariantTypeException ex) {
VariantSupport.writeUnsupportedTypeMessage(ex);
value = ex.getValue();
}
@ -172,19 +139,68 @@ public class Property
/**
* <p>Creates an empty property. It must be filled using the set method to
* be usable.</p>
* Returns the property's ID.
*
* @return The ID value
*/
protected Property()
{ }
public long getID() {
return id;
}
/**
* <p>Reads a dictionary.</p>
* Sets the property's ID.
*
* @param id the ID
*/
public void setID(final long id) {
this.id = id;
}
/**
* Returns the property's type.
*
* @return The type value
*/
public long getType() {
return type;
}
/**
* Sets the property's type.
*
* @param type the property's type
*/
public void setType(final long type) {
this.type = type;
}
/**
* Returns the property's value.
*
* @return The property's value
*/
public Object getValue() {
return value;
}
/**
* Sets the property's value.
*
* @param value the property's value
*/
public void setValue(final Object value) {
this.value = value;
}
/**
* Reads a dictionary.
*
* @param src The byte array containing the bytes making out the dictionary.
* @param offset At this offset within <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)
sLength++;
o += (sLength + sLength);
}
else
if (codepage == CodePageUtil.CP_UNICODE) {
if (sLength % 2 == 1) {
sLength++;
}
o += (sLength + sLength);
} 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;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -22,24 +22,22 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import org.apache.poi.hpsf.wellknown.SectionIDMap;
import org.apache.poi.poifs.filesystem.DirectoryEntry;
import org.apache.poi.poifs.filesystem.DocumentEntry;
import org.apache.poi.poifs.filesystem.DocumentInputStream;
/**
* <p>Factory class to create instances of {@link SummaryInformation},
* {@link DocumentSummaryInformation} and {@link PropertySet}.</p>
* Factory class to create instances of {@link SummaryInformation},
* {@link DocumentSummaryInformation} and {@link PropertySet}.
*/
public class PropertySetFactory
{
public class PropertySetFactory {
/**
* <p>Creates the most specific {@link PropertySet} from an entry
* Creates the most specific {@link PropertySet} from an entry
* in the specified POIFS Directory. This is preferrably a {@link
* DocumentSummaryInformation} or a {@link SummaryInformation}. If
* the specified entry does not contain a property set stream, an
* exception is thrown. If no entry is found with the given name,
* an exception is thrown.</p>
* an exception is thrown.
*
* @param dir The directory to find the PropertySet in
* @param name The name of the entry containing the PropertySet
@ -52,55 +50,53 @@ public class PropertySetFactory
* supported.
*/
public static PropertySet create(final DirectoryEntry dir, final String name)
throws FileNotFoundException, NoPropertySetStreamException,
IOException, UnsupportedEncodingException
{
throws FileNotFoundException, NoPropertySetStreamException, IOException, UnsupportedEncodingException {
InputStream inp = null;
try {
DocumentEntry entry = (DocumentEntry)dir.getEntry(name);
inp = new DocumentInputStream(entry);
try {
return create(inp);
} catch (MarkUnsupportedException e) { return null; }
} catch (MarkUnsupportedException e) {
return null;
}
} finally {
if (inp != null) inp.close();
if (inp != null) {
inp.close();
}
}
}
/**
* <p>Creates the most specific {@link PropertySet} from an {@link
* Creates the most specific {@link PropertySet} from an {@link
* InputStream}. This is preferrably a {@link
* DocumentSummaryInformation} or a {@link SummaryInformation}. If
* the specified {@link InputStream} does not contain a property
* set stream, an exception is thrown and the {@link InputStream}
* is repositioned at its beginning.</p>
* is repositioned at its beginning.
*
* @param stream Contains the property set stream's data.
* @return The created {@link PropertySet}.
* @throws NoPropertySetStreamException if the stream does not
* contain a property set.
* @throws MarkUnsupportedException if the stream does not support
* the <code>mark</code> operation.
* the {@code mark} operation.
* @throws IOException if some I/O problem occurs.
* @exception UnsupportedEncodingException if the specified codepage is not
* supported.
*/
public static PropertySet create(final InputStream stream)
throws NoPropertySetStreamException, MarkUnsupportedException,
UnsupportedEncodingException, IOException
{
throws NoPropertySetStreamException, MarkUnsupportedException, UnsupportedEncodingException, IOException {
final PropertySet ps = new PropertySet(stream);
try
{
if (ps.isSummaryInformation())
try {
if (ps.isSummaryInformation()) {
return new SummaryInformation(ps);
else if (ps.isDocumentSummaryInformation())
} else if (ps.isDocumentSummaryInformation()) {
return new DocumentSummaryInformation(ps);
else
} else {
return ps;
}
catch (UnexpectedPropertySetTypeException ex)
{
}
} catch (UnexpectedPropertySetTypeException ex) {
/* This exception will never be throws because we already checked
* explicitly for this case above. */
throw new IllegalStateException(ex);
@ -108,44 +104,20 @@ public class PropertySetFactory
}
/**
* <p>Creates a new summary information.</p>
* Creates a new summary information.
*
* @return the new summary information.
*/
public static SummaryInformation newSummaryInformation()
{
final MutablePropertySet ps = new MutablePropertySet();
final MutableSection s = (MutableSection) ps.getFirstSection();
s.setFormatID(SectionIDMap.SUMMARY_INFORMATION_ID);
try
{
return new SummaryInformation(ps);
}
catch (UnexpectedPropertySetTypeException ex)
{
/* This should never happen. */
throw new HPSFRuntimeException(ex);
}
public static SummaryInformation newSummaryInformation() {
return new SummaryInformation();
}
/**
* <p>Creates a new document summary information.</p>
* Creates a new document summary information.
*
* @return the new document summary information.
*/
public static DocumentSummaryInformation newDocumentSummaryInformation()
{
final MutablePropertySet ps = new MutablePropertySet();
final MutableSection s = (MutableSection) ps.getFirstSection();
s.setFormatID(SectionIDMap.DOCUMENT_SUMMARY_INFORMATION_ID[0]);
try
{
return new DocumentSummaryInformation(ps);
}
catch (UnexpectedPropertySetTypeException ex)
{
/* This should never happen. */
throw new HPSFRuntimeException(ex);
}
public static DocumentSummaryInformation newDocumentSummaryInformation() {
return new DocumentSummaryInformation();
}
}

File diff suppressed because it is too large Load Diff

View File

@ -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);
@Removal(version="3.18")
public class SpecialPropertySet extends MutablePropertySet {
public SpecialPropertySet() {
}
/**
* <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;
public SpecialPropertySet(final PropertySet ps) throws UnexpectedPropertySetTypeException {
super(ps);
}
/**
* @see PropertySet#getByteOrder
*/
@Override
public int getByteOrder()
{
return delegate.getByteOrder();
}
/**
* @see PropertySet#getFormat
*/
@Override
public int getFormat()
{
return delegate.getFormat();
}
/**
* @see PropertySet#getOSVersion
*/
@Override
public int getOSVersion()
{
return delegate.getOSVersion();
}
/**
* @see PropertySet#getClassID
*/
@Override
public ClassID getClassID()
{
return delegate.getClassID();
}
/**
* @see PropertySet#getSectionCount
*/
@Override
public int getSectionCount()
{
return delegate.getSectionCount();
}
/**
* @see PropertySet#getSections
*/
@Override
public 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);
}
/**
* @see org.apache.poi.hpsf.PropertySet#getProperties()
*/
@Override
public Property[] getProperties() throws NoSingleSectionException
{
return delegate.getProperties();
}
/**
* @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();
}
}

View File

@ -20,18 +20,18 @@ package org.apache.poi.hpsf;
import java.util.Date;
import org.apache.poi.hpsf.wellknown.PropertyIDMap;
import org.apache.poi.hpsf.wellknown.SectionIDMap;
/**
* <p>Convenience class representing a Summary Information stream in a
* Microsoft Office document.</p>
* Convenience class representing a Summary Information stream in a
* Microsoft Office document.
*
* @see DocumentSummaryInformation
*/
public final class SummaryInformation extends SpecialPropertySet {
/**
* <p>The document name a summary information stream usually has in a POIFS
* filesystem.</p>
* The document name a summary information stream usually has in a POIFS filesystem.
*/
public static final String DEFAULT_STREAM_NAME = "\005SummaryInformation";
@ -39,324 +39,291 @@ public final class SummaryInformation extends SpecialPropertySet {
return PropertyIDMap.getSummaryInformationProperties();
}
/**
* <p>Creates a {@link SummaryInformation} from a given {@link
* PropertySet}.</p>
* Creates a {@link SummaryInformation} from a given {@link
* PropertySet}.
*
* @param ps A property set which should be created from a summary
* information stream.
* @throws UnexpectedPropertySetTypeException if <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() {
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 {@code ps} does not
* contain a summary information stream.
*/
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);
}
}

View File

@ -20,110 +20,17 @@ package org.apache.poi.hpsf;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Collection;
import java.util.Date;
import org.apache.poi.util.Internal;
import org.apache.poi.util.SuppressForbidden;
/**
* <p>Provides various static utility methods.</p>
*/
@Internal
public class Util
{
/**
* <p>Checks whether two byte arrays <var>a</var> and <var>b</var>
* are equal. They are equal</p>
*
* <ul>
*
* <li><p>if they have the same length and</p></li>
*
* <li><p>if for each <var>i</var> with
* <var>i</var>&nbsp;&gt;=&nbsp;0 and
* <var>i</var>&nbsp;&lt;&nbsp;<var>a.length</var> holds
* <var>a</var>[<var>i</var>]&nbsp;== <var>b</var>[<var>i</var>].</p></li>
*
* </ul>
*
* @param a The first byte array
* @param b The first byte array
* @return <code>true</code> if the byte arrays are equal, else
* <code>false</code>
*/
public static boolean equal(final byte[] a, final byte[] b)
{
if (a.length != b.length)
return false;
for (int i = 0; i < a.length; i++)
if (a[i] != b[i])
return false;
return true;
}
/**
* <p>Copies a part of a byte array into another byte array.</p>
*
* @param src The source byte array.
* @param srcOffset Offset in the source byte array.
* @param length The number of bytes to copy.
* @param dst The destination byte array.
* @param dstOffset Offset in the destination byte array.
*/
public static void copy(final byte[] src, final int srcOffset,
final int length, final byte[] dst,
final int dstOffset)
{
for (int i = 0; i < length; i++)
dst[dstOffset + i] = src[srcOffset + i];
}
/**
* <p>Concatenates the contents of several byte arrays into a
* single one.</p>
*
* @param byteArrays The byte arrays to be concatened.
* @return A new byte array containing the concatenated byte
* arrays.
*/
public static byte[] cat(final byte[][] byteArrays)
{
int capacity = 0;
for (int i = 0; i < byteArrays.length; i++)
capacity += byteArrays[i].length;
final byte[] result = new byte[capacity];
int r = 0;
for (int i = 0; i < byteArrays.length; i++)
for (int j = 0; j < byteArrays[i].length; j++)
result[r++] = byteArrays[i][j];
return result;
}
/**
* <p>Copies bytes from a source byte array into a new byte
* array.</p>
*
* @param src Copy from this byte array.
* @param offset Start copying here.
* @param length Copy this many bytes.
* @return The new byte array. Its length is number of copied bytes.
*/
public static byte[] copy(final byte[] src, final int offset,
final int length)
{
final byte[] result = new byte[length];
copy(src, offset, length, result, 0);
return result;
}
/**
* <p>The difference between the Windows epoch (1601-01-01
* 00:00:00) and the Unix epoch (1970-01-01 00:00:00) in
@ -189,36 +96,6 @@ public class Util
}
/**
* <p>Checks whether two collections are equal. Two collections
* C<sub>1</sub> and C<sub>2</sub> are equal, if the following conditions
* are true:</p>
*
* <ul>
*
* <li><p>For each c<sub>1<em>i</em></sub> (element of C<sub>1</sub>) there
* is a c<sub>2<em>j</em></sub> (element of C<sub>2</sub>), and
* c<sub>1<em>i</em></sub> equals c<sub>2<em>j</em></sub>.</p></li>
*
* <li><p>For each c<sub>2<em>i</em></sub> (element of C<sub>2</sub>) there
* is a c<sub>1<em>j</em></sub> (element of C<sub>1</sub>) and
* c<sub>2<em>i</em></sub> equals c<sub>1<em>j</em></sub>.</p></li>
*
* </ul>
*
* @param c1 the first collection
* @param c2 the second collection
* @return <code>true</code> if the collections are equal, else
* <code>false</code>.
*/
public static boolean equals(Collection<?> c1, Collection<?> c2)
{
Object[] o1 = c1.toArray();
Object[] o2 = c2.toArray();
return internalEquals(o1, o2);
}
/**
* <p>Compares to object arrays with regarding the objects' order. For
@ -231,24 +108,17 @@ public class Util
*/
public static boolean equals(Object[] c1, Object[] c2)
{
final Object[] o1 = c1.clone();
final Object[] o2 = c2.clone();
return internalEquals(o1, o2);
}
private static boolean internalEquals(Object[] o1, Object[] o2)
{
for (int i1 = 0; i1 < o1.length; i1++)
for (int i1 = 0; i1 < c1.length; i1++)
{
final Object obj1 = o1[i1];
final Object obj1 = c1[i1];
boolean matchFound = false;
for (int i2 = 0; !matchFound && i2 < o1.length; i2++)
for (int i2 = 0; !matchFound && i2 < c1.length; i2++)
{
final Object obj2 = o2[i2];
final Object obj2 = c2[i2];
if (obj1.equals(obj2))
{
matchFound = true;
o2[i2] = null;
c2[i2] = null;
}
}
if (!matchFound)
@ -257,8 +127,6 @@ public class Util
return true;
}
/**
* <p>Pads a byte array with 0x00 bytes so that its length is a multiple of
* 4.</p>
@ -283,46 +151,6 @@ public class Util
}
/**
* <p>Pads a character array with 0x0000 characters so that its length is a
* multiple of 4.</p>
*
* @param ca The character array to pad.
* @return The padded character array.
*/
public static char[] pad4(final char[] ca)
{
final int PAD = 4;
final char[] result;
int l = ca.length % PAD;
if (l == 0)
result = ca;
else
{
l = PAD - l;
result = new char[ca.length + l];
System.arraycopy(ca, 0, result, 0, ca.length);
}
return result;
}
/**
* <p>Pads a string with 0x0000 characters so that its length is a
* multiple of 4.</p>
*
* @param s The string to pad.
* @return The padded string as a character array.
*/
public static char[] pad4(final String s)
{
return pad4(s.toCharArray());
}
/**
* <p>Returns a textual representation of a {@link Throwable}, including a
* stacktrace.</p>

View File

@ -27,70 +27,77 @@ import java.util.List;
import org.apache.poi.util.CodePageUtil;
import org.apache.poi.util.POILogFactory;
import org.apache.poi.util.POILogger;
import org.apache.poi.util.Removal;
/**
* <p>Supports reading and writing of variant data.</p>
* Supports reading and writing of variant data.<p>
*
* <p><strong>FIXME (3):</strong> Reading and writing should be made more
* uniform than it is now. The following items should be resolved:
* <strong>FIXME (3):</strong> Reading and writing should be made more
* uniform than it is now. The following items should be resolved:<p>
*
* <ul>
*
* <li><p>Reading requires a length parameter that is 4 byte greater than the
* actual data, because the variant type field is included. </p></li>
* <li>Reading requires a length parameter that is 4 byte greater than the
* actual data, because the variant type field is included.
*
* <li><p>Reading reads from a byte array while writing writes to an byte array
* output stream.</p></li>
* <li>Reading reads from a byte array while writing writes to an byte array
* output stream.
*
* </ul>
*/
public class VariantSupport extends Variant
{
private static final POILogger logger = POILogFactory.getLogger(VariantSupport.class);
public class VariantSupport extends Variant {
/**
* HPSF is able to read these {@link Variant} types.
*/
public static final int[] SUPPORTED_TYPES = { Variant.VT_EMPTY,
Variant.VT_I2, Variant.VT_I4, Variant.VT_I8, Variant.VT_R8,
Variant.VT_FILETIME, Variant.VT_LPSTR, Variant.VT_LPWSTR,
Variant.VT_CF, Variant.VT_BOOL };
private static final POILogger logger = POILogFactory.getLogger(VariantSupport.class);
private static boolean logUnsupportedTypes = false;
/**
* <p>Specifies whether warnings about unsupported variant types are to be
* written to <code>System.err</code> or not.</p>
*
* @param logUnsupportedTypes If <code>true</code> warnings will be written,
* if <code>false</code> they won't.
* Keeps a list of the variant types an "unsupported" message has already
* been issued for.
*/
public static void setLogUnsupportedTypes(final boolean logUnsupportedTypes)
{
protected static 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} warnings will be written,
* if {@code false} they won't.
*/
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++;
}

View File

@ -27,6 +27,7 @@ import org.apache.poi.hpsf.CustomProperties;
import org.apache.poi.hpsf.DocumentSummaryInformation;
import org.apache.poi.hpsf.HPSFPropertiesOnlyDocument;
import org.apache.poi.hpsf.Property;
import org.apache.poi.hpsf.PropertySet;
import org.apache.poi.hpsf.SpecialPropertySet;
import org.apache.poi.hpsf.SummaryInformation;
import org.apache.poi.hpsf.wellknown.PropertyIDMap;
@ -67,7 +68,7 @@ public class HPSFPropertiesExtractor extends POIOLE2TextExtractor {
CustomProperties cps = dsi == null ? null : dsi.getCustomProperties();
if (cps != null) {
for (String key : cps.nameSet()) {
String val = HelperPropertySet.getPropertyValueText(cps.get(key));
String val = getPropertyValueText(cps.get(key));
text.append(key).append(" = ").append(val).append("\n");
}
}
@ -86,7 +87,7 @@ public class HPSFPropertiesExtractor extends POIOLE2TextExtractor {
return getPropertiesText(si);
}
private static String getPropertiesText(SpecialPropertySet ps) {
private static String getPropertiesText(PropertySet ps) {
if (ps == null) {
// Not defined, oh well
return "";
@ -98,12 +99,12 @@ public class HPSFPropertiesExtractor extends POIOLE2TextExtractor {
Property[] props = ps.getProperties();
for (Property prop : props) {
String type = Long.toString(prop.getID());
Object typeObj = idMap.get(prop.getID());
Object typeObj = (idMap == null) ? null : idMap.get(prop.getID());
if (typeObj != null) {
type = typeObj.toString();
}
String val = HelperPropertySet.getPropertyValueText(prop.getValue());
String val = getPropertyValueText(prop.getValue());
text.append(type).append(" = ").append(val).append("\n");
}
@ -125,18 +126,12 @@ public class HPSFPropertiesExtractor extends POIOLE2TextExtractor {
throw new IllegalStateException("You already have the Metadata Text Extractor, not recursing!");
}
private static abstract class HelperPropertySet extends SpecialPropertySet {
public HelperPropertySet() {
super(null);
}
public static String getPropertyValueText(Object val) {
if (val == null) {
return "(not set)";
}
return SpecialPropertySet.getPropertyStringValue(val);
}
private static String getPropertyValueText(Object val) {
return (val == null)
? "(not set)"
: PropertySet.getPropertyStringValue(val);
}
@Override
public boolean equals(Object o) {
return super.equals(o);

View File

@ -45,26 +45,42 @@ public final class IOUtils {
* @throws EmptyFileException if the stream is empty
*/
public static byte[] peekFirst8Bytes(InputStream stream) throws IOException, EmptyFileException {
// We want to peek at the first 8 bytes
stream.mark(8);
return peekFirstNBytes(stream, 8);
}
byte[] header = new byte[8];
int read = IOUtils.readFully(stream, header);
/**
* Peeks at the first N bytes of the stream. Returns those bytes, but
* with the stream unaffected. Requires a stream that supports mark/reset,
* or a PushbackInputStream. If the stream has &gt;0 but &lt;N bytes,
* remaining bytes will be zero.
* @throws EmptyFileException if the stream is empty
*/
public static byte[] peekFirstNBytes(InputStream stream, int limit) throws IOException, EmptyFileException {
stream.mark(limit);
ByteArrayOutputStream bos = new ByteArrayOutputStream(limit);
copy(new BoundedInputStream(stream, limit), bos);
if (read < 1)
int readBytes = bos.size();
if (readBytes == 0) {
throw new EmptyFileException();
// Wind back those 8 bytes
}
if (readBytes < limit) {
bos.write(new byte[limit-readBytes]);
}
byte peekedBytes[] = bos.toByteArray();
if(stream instanceof PushbackInputStream) {
PushbackInputStream pin = (PushbackInputStream)stream;
pin.unread(header, 0, read);
pin.unread(peekedBytes, 0, readBytes);
} else {
stream.reset();
}
return header;
return peekedBytes;
}
/**
* Reads all the data from the input stream, and returns the bytes read.
*/

View File

@ -192,8 +192,7 @@ public final class TestBasic {
(poiFiles[0].getBytes()));
final List<Section> sections = si.getSections();
final Section s = sections.get(0);
assertTrue(org.apache.poi.hpsf.Util.equal
(s.getFormatID().getBytes(), SectionIDMap.SUMMARY_INFORMATION_ID));
assertArrayEquals(s.getFormatID().getBytes(), SectionIDMap.SUMMARY_INFORMATION_ID);
assertNotNull(s.getProperties());
assertEquals(17, s.getPropertyCount());
assertEquals("Titel", s.getProperty(2));

View File

@ -216,7 +216,8 @@ public class TestWrite
final MutablePropertySet ps = new MutablePropertySet();
final MutableSection si = new MutableSection();
si.setFormatID(SectionIDMap.SUMMARY_INFORMATION_ID);
ps.getSections().set(0, si);
ps.clearSections();
ps.addSection(si);
final MutableProperty p = new MutableProperty();
p.setID(PropertyIDMap.PID_AUTHOR);