#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.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import org.apache.commons.collections4.bidimap.TreeBidiMap;
import org.apache.poi.hpsf.wellknown.PropertyIDMap; 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 * {@link DocumentSummaryInformation}. The class maintains the names of the
* custom properties in a dictionary. It implements the {@link Map} interface * custom properties in a dictionary. It implements the {@link Map} interface
* and by this provides a simplified view on custom properties: A property's * 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 * 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 * 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 * 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 * 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 * 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 * 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 * 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. * 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 * This class is not thread-safe; concurrent access to instances of this
* class must be synchronized.</p> * class must be synchronized.<p>
* *
* <p>While this class is roughly HashMap&lt;Long,CustomProperty&gt;, that's the * While this class is roughly HashMap&lt;Long,CustomProperty&gt;, that's the
* internal representation. To external calls, it should appear as * internal representation. To external calls, it should appear as
* HashMap&lt;String,Object&gt; mapping between Names and Custom Property Values.</p> * HashMap&lt;String,Object&gt; mapping between Names and Custom Property Values.
*/ */
@SuppressWarnings("serial") @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> * Tells whether this object is pure or not.
*/
private final Map<String,Long> dictionaryNameToID = new HashMap<String,Long>();
/**
* <p>Tells whether this object is pure or not.</p>
*/ */
private boolean isPure = true; 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 CustomProperty} already has a valid ID. Otherwise use
* {@link #put(CustomProperty)}.</p> * {@link #put(CustomProperty)}.
* *
* @param name the property name * @param name the property name
* @param cp the property * @param cp the property
* *
* @return the previous property stored under this name * @return the previous property stored under this name
*/ */
public CustomProperty put(final String name, final CustomProperty cp) public CustomProperty put(final String name, final CustomProperty cp) {
{ if (name == null) {
if (name == null)
{
/* Ignoring a property without a name. */ /* Ignoring a property without a name. */
isPure = false; isPure = false;
return null; return null;
} }
if (!(name.equals(cp.getName())))
if (!name.equals(cp.getName())) {
throw new IllegalArgumentException("Parameter \"name\" (" + name + throw new IllegalArgumentException("Parameter \"name\" (" + name +
") and custom property's name (" + cp.getName() + ") and custom property's name (" + cp.getName() +
") do not match."); ") do not match.");
}
/* Register name and ID in the dictionary. Mapping in both directions is possible. If there is already a */ /* 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()); super.remove(dictionary.getKey(name));
final Long oldID = dictionaryNameToID.get(name); dictionary.put(cp.getID(), name);
dictionaryIDToName.remove(oldID);
dictionaryNameToID.put(name, idKey);
dictionaryIDToName.put(idKey, name);
/* Put the custom property into this map. */ /* Put the custom property into this map. */
final CustomProperty oldCp = super.remove(oldID); return super.put(cp.getID(), cp);
super.put(idKey, cp);
return oldCp;
} }
/** /**
* <p>Puts a {@link CustomProperty} that has not yet a valid ID into this * 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> * map. The method will allocate a suitable ID for the custom property:
* *
* <ul> * <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 * <li>Otherwise find the highest ID and use its value plus one.
* of that property.</p></li>
*
* <li><p>Otherwise find the highest ID and use its value plus one.</p></li>
*
* </ul> * </ul>
* *
* @param customProperty * @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 * @throws ClassCastException
*/ */
private Object put(final CustomProperty customProperty) throws ClassCastException private Object put(final CustomProperty customProperty) throws ClassCastException {
{
final String name = customProperty.getName(); final String name = customProperty.getName();
/* Check whether a property with this name is in the map already. */ /* Check whether a property with this name is in the map already. */
final Long oldId = dictionaryNameToID.get(name); final Long oldId = (name == null) ? null : dictionary.getKey(name);
if (oldId != null) if (oldId != null) {
customProperty.setID(oldId.longValue()); customProperty.setID(oldId);
else } else {
{ long lastKey = (dictionary.isEmpty()) ? 0 : dictionary.lastKey();
long max = 1; customProperty.setID(Math.max(lastKey,PropertyIDMap.PID_MAX) + 1);
for (Long long1 : dictionaryIDToName.keySet()) {
final long id = long1.longValue();
if (id > max)
max = id;
}
customProperty.setID(max + 1);
} }
return this.put(name, customProperty); 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 * @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) * @see java.util.HashSet#remove(java.lang.Object)
*/ */
public Object remove(final String name) public Object remove(final String name) {
{ final Long id = dictionary.removeValue(name);
final Long id = dictionaryNameToID.get(name);
if (id == null)
return null;
dictionaryIDToName.remove(id);
dictionaryNameToID.remove(name);
return super.remove(id); return super.remove(id);
} }
/** /**
* <p>Adds a named string property.</p> * Adds a named string property.
* *
* @param name The property's name. * @param name The property's name.
* @param value The property's value. * @param value The property's value.
* @return the property that was stored under the specified name before, or * @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) public Object put(final String name, final String value) {
{ final Property p = new Property(-1, Variant.VT_LPWSTR, value);
final MutableProperty p = new MutableProperty(); return put(new CustomProperty(p, name));
p.setID(-1);
p.setType(Variant.VT_LPWSTR);
p.setValue(value);
final CustomProperty cp = new CustomProperty(p, name);
return put(cp);
} }
/** /**
* <p>Adds a named long property.</p> * Adds a named long property.
* *
* @param name The property's name. * @param name The property's name.
* @param value The property's value. * @param value The property's value.
* @return the property that was stored under the specified name before, or * @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) public Object put(final String name, final Long value) {
{ final Property p = new Property(-1, Variant.VT_I8, value);
final MutableProperty p = new MutableProperty(); return put(new CustomProperty(p, name));
p.setID(-1);
p.setType(Variant.VT_I8);
p.setValue(value);
final CustomProperty cp = new CustomProperty(p, name);
return put(cp);
} }
/** /**
* <p>Adds a named double property.</p> * Adds a named double property.
* *
* @param name The property's name. * @param name The property's name.
* @param value The property's value. * @param value The property's value.
* @return the property that was stored under the specified name before, or * @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) public Object put(final String name, final Double value) {
{ final Property p = new Property(-1, Variant.VT_R8, value);
final MutableProperty p = new MutableProperty(); return put(new CustomProperty(p, name));
p.setID(-1);
p.setType(Variant.VT_R8);
p.setValue(value);
final CustomProperty cp = new CustomProperty(p, name);
return put(cp);
} }
/** /**
* <p>Adds a named integer property.</p> * Adds a named integer property.
* *
* @param name The property's name. * @param name The property's name.
* @param value The property's value. * @param value The property's value.
* @return the property that was stored under the specified name before, or * @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) public Object put(final String name, final Integer value) {
{ final Property p = new Property(-1, Variant.VT_I4, value);
final MutableProperty p = new MutableProperty(); return put(new CustomProperty(p, name));
p.setID(-1);
p.setType(Variant.VT_I4);
p.setValue(value);
final CustomProperty cp = new CustomProperty(p, name);
return put(cp);
} }
/** /**
* <p>Adds a named boolean property.</p> * Adds a named boolean property.
* *
* @param name The property's name. * @param name The property's name.
* @param value The property's value. * @param value The property's value.
* @return the property that was stored under the specified name before, or * @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) public Object put(final String name, final Boolean value) {
{ final Property p = new Property(-1, Variant.VT_BOOL, value);
final MutableProperty p = new MutableProperty(); return put(new CustomProperty(p, name));
p.setID(-1);
p.setType(Variant.VT_BOOL);
p.setValue(value);
final CustomProperty cp = new CustomProperty(p, name);
return put(cp);
} }
/** /**
* <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 * @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. * name is not found in the custom properties.
*/ */
public Object get(final String name) public Object get(final String name) {
{ final Long id = dictionary.getKey(name);
final Long id = dictionaryNameToID.get(name);
final CustomProperty cp = super.get(id); final CustomProperty cp = super.get(id);
return cp != null ? cp.getValue() : null; 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 name The property's name.
* @param value The property's value. * @param value The property's value.
* @return the property that was stored under the specified name before, or * @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) public Object put(final String name, final Date value) {
{ final Property p = new Property(-1, Variant.VT_FILETIME, value);
final MutableProperty p = new MutableProperty(); return put(new CustomProperty(p, name));
p.setID(-1);
p.setType(Variant.VT_FILETIME);
p.setValue(value);
final CustomProperty cp = new CustomProperty(p, name);
return put(cp);
} }
/** /**
@ -302,7 +246,7 @@ public class CustomProperties extends HashMap<Object,CustomProperty>
@Override @Override
@SuppressWarnings({ "rawtypes", "unchecked" }) @SuppressWarnings({ "rawtypes", "unchecked" })
public Set keySet() { 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 * @return a set of all the names of our custom properties
*/ */
public Set<String> nameSet() { 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 * @return a set of all the IDs of our custom properties
*/ */
public Set<String> idSet() { public Set<String> idSet() {
return dictionaryNameToID.keySet(); return dictionary.values();
} }
/** /**
* <p>Sets the codepage.</p> * Sets the codepage.
* *
* @param codepage the codepage * @param codepage the codepage
*/ */
public void setCodepage(final int codepage) public void setCodepage(final int codepage) {
{ Property p = new Property(PropertyIDMap.PID_CODEPAGE, Variant.VT_I2, codepage);
final MutableProperty p = new MutableProperty();
p.setID(PropertyIDMap.PID_CODEPAGE);
p.setType(Variant.VT_I2);
p.setValue(Integer.valueOf(codepage));
put(new CustomProperty(p)); put(new CustomProperty(p));
} }
@ -346,84 +286,65 @@ public class CustomProperties extends HashMap<Object,CustomProperty>
* *
* @return the dictionary. * @return the dictionary.
*/ */
Map<Long,String> getDictionary() Map<Long,String> getDictionary() {
{ return dictionary;
return dictionaryIDToName;
} }
/** /**
* Checks against both String Name and Long ID * Checks against both String Name and Long ID
*/ */
@Override @Override
public boolean containsKey(Object key) { public boolean containsKey(Object key) {
if(key instanceof Long) { return ((key instanceof Long && dictionary.containsKey(key)) || dictionary.containsValue(key));
return super.containsKey(key); }
}
if(key instanceof String) {
return super.containsKey(dictionaryNameToID.get(key));
}
return false;
}
/** /**
* Checks against both the property, and its values. * Checks against both the property, and its values.
*/ */
@Override @Override
public boolean containsValue(Object value) { public boolean containsValue(Object value) {
if(value instanceof CustomProperty) { if(value instanceof CustomProperty) {
return super.containsValue(value); return super.containsValue(value);
} else { }
for(CustomProperty cp : super.values()) {
for(CustomProperty cp : super.values()) {
if(cp.getValue() == value) { if(cp.getValue() == value) {
return true; return true;
} }
} }
}
return false;
}
return false;
}
/**
/** * Gets the codepage.
* <p>Gets the codepage.</p>
* *
* @return the codepage or -1 if the codepage is undefined. * @return the codepage or -1 if the codepage is undefined.
*/ */
public int getCodepage() public int getCodepage() {
{ CustomProperty cp = get(PropertyIDMap.PID_CODEPAGE);
int codepage = -1; return (cp == null) ? -1 : (Integer)cp.getValue();
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;
} }
/** /**
* <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 * 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 * @return {@code true} if the {@link CustomProperties} is pure, else
* <code>false</code>. * {@code false}.
*/ */
public boolean isPure() public boolean isPure() {
{
return 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 * @param isPure the purity
*/ */
public void setPure(final boolean isPure) public void setPure(final boolean isPure) {
{
this.isPure = isPure; this.isPure = isPure;
} }
} }

View File

@ -18,10 +18,10 @@
package org.apache.poi.hpsf; 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 * information stream. The difference to normal properties is that custom
* properties have an optional name. If the name is not <code>null</code> it * properties have an optional name. If the name is not {@code null} it
* will be maintained in the section's dictionary.</p> * will be maintained in the section's dictionary.
*/ */
public class CustomProperty extends MutableProperty public class CustomProperty extends MutableProperty
{ {
@ -29,80 +29,75 @@ public class CustomProperty extends MutableProperty
private String name; private String name;
/** /**
* <p>Creates an empty {@link CustomProperty}. The set methods must be * Creates an empty {@link CustomProperty}. The set methods must be
* called to make it usable.</p> * called to make it usable.
*/ */
public CustomProperty() public CustomProperty() {
{
this.name = null; this.name = null;
} }
/** /**
* <p>Creates a {@link CustomProperty} without a name by copying the * Creates a {@link CustomProperty} without a name by copying the
* underlying {@link Property}' attributes.</p> * underlying {@link Property}' attributes.
* *
* @param property the property to copy * @param property the property to copy
*/ */
public CustomProperty(final Property property) public CustomProperty(final Property property) {
{
this(property, null); 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 * @param property This property's attributes are copied to the new custom
* property. * property.
* @param name The new custom property's name. * @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); super(property);
this.name = name; this.name = name;
} }
/** /**
* <p>Gets the property's name.</p> * Gets the property's name.
* *
* @return the property's name. * @return the property's name.
*/ */
public String getName() public String getName() {
{
return name; return name;
} }
/** /**
* <p>Sets the property's name.</p> * Sets the property's name.
* *
* @param name The name to set. * @param name The name to set.
*/ */
public void setName(final String name) public void setName(final String name) {
{
this.name = name; this.name = name;
} }
/** /**
* <p>Compares two custom properties for equality. The method returns * Compares two custom properties for equality. The method returns
* <code>true</code> if all attributes of the two custom properties are * {@code true} if all attributes of the two custom properties are
* equal.</p> * equal.
* *
* @param o The custom property to compare with. * @param o The custom property to compare with.
* @return <code>true</code> if both custom properties are equal, else * @return {@code true} if both custom properties are equal, else
* <code>false</code>. * {@code false}.
* *
* @see java.util.AbstractSet#equals(java.lang.Object) * @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 CustomProperty c = (CustomProperty) o;
final String name1 = c.getName(); final String name1 = c.getName();
final String name2 = this.getName(); final String name2 = this.getName();
boolean equalNames = true; boolean equalNames = true;
if (name1 == null) if (name1 == null) {
equalNames = name2 == null; equalNames = name2 == null;
else } else {
equalNames = name1.equals(name2); equalNames = name1.equals(name2);
}
return equalNames && c.getID() == this.getID() return equalNames && c.getID() == this.getID()
&& c.getType() == this.getType() && c.getType() == this.getType()
&& c.getValue().equals(this.getValue()); && c.getValue().equals(this.getValue());
@ -112,8 +107,7 @@ public class CustomProperty extends MutableProperty
* @see java.util.AbstractSet#hashCode() * @see java.util.AbstractSet#hashCode()
*/ */
@Override @Override
public int hashCode() public int hashCode() {
{
return (int) this.getID(); 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; package org.apache.poi.hpsf;
import java.io.IOException; import org.apache.poi.util.Removal;
import java.io.OutputStream;
import org.apache.poi.util.CodePageUtil;
/** /**
* <p>Adds writing capability to the {@link Property} class.</p> * <p>Adds writing capability to the {@link Property} class.</p>
* *
* <p>Please be aware that this class' functionality will be merged into the * <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> * {@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() {}
/** public MutableProperty(final Property p) {
* <p>Creates an empty property. It must be filled using the set method to super(p);
* 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;
} }
} }

View File

@ -17,288 +17,21 @@
package org.apache.poi.hpsf; package org.apache.poi.hpsf;
import java.io.ByteArrayInputStream; import org.apache.poi.util.Removal;
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;
/** /**
* <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 * 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> * {@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 @Removal(version="3.18")
{ public class MutablePropertySet extends PropertySet {
public MutablePropertySet() {}
/**
* <p>Constructs a <code>MutablePropertySet</code> instance. Its public MutablePropertySet(final PropertySet ps) {
* primary task is to initialize the immutable field with their proper super(ps);
* values. It also sets fields that might change to reasonable defaults.</p>
*/
public MutablePropertySet()
{
/* Initialize the "byteOrder" field. */
byteOrder = LittleEndian.getUShort(BYTE_ORDER_ASSERTION);
/* Initialize the "format" field. */
format = LittleEndian.getUShort(FORMAT_ASSERTION);
/* Initialize "osVersion" field as if the property has been created on
* a Win32 platform, whether this is the case or not. */
osVersion = (OS_WIN32 << 16) | 0x0A04;
/* Initailize the "classID" field. */
classID = new ClassID();
/* Initialize the sections. Since property set must have at least
* one section it is added right here. */
sections = new LinkedList<Section>();
sections.add(new MutableSection());
} }
/**
* <p>Constructs a <code>MutablePropertySet</code> by doing a deep copy of
* an existing <code>PropertySet</code>. All nested elements, i.e.
* <code>Section</code>s and <code>Property</code> instances, will be their
* mutable counterparts in the new <code>MutablePropertySet</code>.</p>
*
* @param ps The property set to copy
*/
public MutablePropertySet(final PropertySet ps)
{
byteOrder = ps.getByteOrder();
format = ps.getFormat();
osVersion = ps.getOSVersion();
setClassID(ps.getClassID());
clearSections();
if (sections == null)
sections = new LinkedList<Section>();
for (final Section section : ps.getSections())
{
final MutableSection s = new MutableSection(section);
addSection(s);
}
}
/**
* <p>The length of the property set stream header.</p>
*/
private final static int OFFSET_HEADER =
BYTE_ORDER_ASSERTION.length + /* Byte order */
FORMAT_ASSERTION.length + /* Format */
LittleEndianConsts.INT_SIZE + /* OS version */
ClassID.LENGTH + /* Class ID */
LittleEndianConsts.INT_SIZE; /* Section count */
/**
* <p>Sets the "byteOrder" property.</p>
*
* @param byteOrder the byteOrder value to set
*/
public void setByteOrder(final int byteOrder)
{
this.byteOrder = byteOrder;
}
/**
* <p>Sets the "format" property.</p>
*
* @param format the format value to set
*/
public void setFormat(final int format)
{
this.format = format;
}
/**
* <p>Sets the "osVersion" property.</p>
*
* @param osVersion the osVersion value to set
*/
public void setOSVersion(final int osVersion)
{
this.osVersion = osVersion;
}
/**
* <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; package org.apache.poi.hpsf;
import java.io.ByteArrayOutputStream; import java.io.UnsupportedEncodingException;
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 org.apache.poi.hpsf.wellknown.PropertyIDMap; import org.apache.poi.util.Removal;
import org.apache.poi.util.CodePageUtil;
import org.apache.poi.util.LittleEndian;
/** /**
* <p>Adds writing capability to the {@link Section} class.</p> * <p>Adds writing capability to the {@link Section} class.</p>
* *
* <p>Please be aware that this class' functionality will be merged into the * <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> * {@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 @Removal(version="3.18")
{ public class MutableSection extends Section {
/** public MutableSection() {}
* <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;
public MutableSection(final Section s) {
super(s);
/**
* <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 byte[] src, final int offset) throws UnsupportedEncodingException {
super(src,offset);
/**
* <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));
} }
} }

View File

@ -18,53 +18,47 @@
package org.apache.poi.hpsf; package org.apache.poi.hpsf;
/** /**
* <p>This exception is thrown if a {@link MutablePropertySet} is to be written * This exception is thrown if a {@link PropertySet} is to be written
* but does not have a formatID set (see {@link * but does not have a formatID set (see {@link Section#setFormatID(ClassID)} or
* MutableSection#setFormatID(ClassID)} or * {@link org.apache.poi.hpsf.Section#setFormatID(byte[])}.
* {@link org.apache.poi.hpsf.MutableSection#setFormatID(byte[])}.
*/ */
public class NoFormatIDException extends HPSFRuntimeException public class NoFormatIDException extends HPSFRuntimeException {
{
/** /**
* <p>Constructor</p> * Constructor
*/ */
public NoFormatIDException() public NoFormatIDException() {
{
super(); super();
} }
/** /**
* <p>Constructor</p> * Constructor
* *
* @param msg The exception's message string * @param msg The exception's message string
*/ */
public NoFormatIDException(final String msg) public NoFormatIDException(final String msg) {
{
super(msg); super(msg);
} }
/** /**
* <p>Constructor</p> * Constructor
* *
* @param reason This exception's underlying reason * @param reason This exception's underlying reason
*/ */
public NoFormatIDException(final Throwable reason) public NoFormatIDException(final Throwable reason) {
{
super(reason); super(reason);
} }
/** /**
* <p>Constructor</p> * Constructor
* *
* @param msg The exception's message string * @param msg The exception's message string
* @param reason This exception's underlying reason * @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); super(msg, reason);
} }

View File

@ -17,8 +17,11 @@
package org.apache.poi.hpsf; package org.apache.poi.hpsf;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
@ -29,104 +32,74 @@ import org.apache.poi.util.POILogFactory;
import org.apache.poi.util.POILogger; 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 * 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 * The property's {@code type} determines how its
* <strong>value </strong> is interpreted. For example, if the type is * {@code value} is interpreted. For example, if the type is
* {@link Variant#VT_LPSTR} (byte string), the value consists of a * {@link Variant#VT_LPSTR} (byte string), the value consists of a
* DWord telling how many bytes the string contains. The bytes follow * DWord telling how many bytes the string contains. The bytes follow
* immediately, including any null bytes that terminate the * immediately, including any null bytes that terminate the
* string. The type {@link Variant#VT_I4} denotes a four-byte integer * string. The type {@link Variant#VT_I4} denotes a four-byte integer
* value, {@link Variant#VT_FILETIME} some date and time (of a * value, {@link Variant#VT_FILETIME} some date and time (of a file).<p>
* 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 * 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 * which variant types are really needed. So please feel free to submit error
* reports or patches for the types you need.</p> * reports or patches for the types you need.
*
* <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>.
* *
* @see Section * @see Section
* @see Variant * @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> */ /** The property's ID. */
protected long id; private long id;
/** The property's type. */
private long type;
/** /** The property's value. */
* <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> */
protected Object value; protected Object value;
/** /**
* <p>Returns the property's value.</p> * Creates an empty property. It must be filled using the set method to be usable.
*
* @return The property's value
*/ */
public Object getValue() public Property() {
{
return value;
} }
/** /**
* <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 id the property's ID.
* @param type the property's type, see {@link Variant}. * @param type the property's type, see {@link Variant}.
* @param value the property's value. Only certain types are allowed, see * @param value the property's value. Only certain types are allowed, see
* {@link Variant}. * {@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.id = id;
this.type = type; this.type = type;
this.value = value; this.value = value;
} }
/** /**
* <p>Creates a {@link Property} instance by reading its bytes * Creates a {@link Property} instance by reading its bytes
* from the property set stream.</p> * from the property set stream.
* *
* @param id The property's ID. * @param id The property's ID.
* @param src The bytes the property set stream consists of. * @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 * @exception UnsupportedEncodingException if the specified codepage is not
* supported. * supported.
*/ */
public Property(final long id, final byte[] src, final long offset, public Property(final long id, final byte[] src, final long offset, final int length, final int codepage)
final int length, final int codepage) throws UnsupportedEncodingException {
throws UnsupportedEncodingException
{
this.id = id; this.id = id;
/* /*
* ID 0 is a special case since it specifies a dictionary of * ID 0 is a special case since it specifies a dictionary of
* property IDs and property names. * property IDs and property names.
*/ */
if (id == 0) if (id == 0) {
{
value = readDictionary(src, offset, length, codepage); value = readDictionary(src, offset, length, codepage);
return; return;
} }
@ -158,12 +128,9 @@ public class Property
type = LittleEndian.getUInt(src, o); type = LittleEndian.getUInt(src, o);
o += LittleEndian.INT_SIZE; o += LittleEndian.INT_SIZE;
try try {
{
value = VariantSupport.read(src, o, length, (int) type, codepage); value = VariantSupport.read(src, o, length, (int) type, codepage);
} } catch (UnsupportedVariantTypeException ex) {
catch (UnsupportedVariantTypeException ex)
{
VariantSupport.writeUnsupportedTypeMessage(ex); VariantSupport.writeUnsupportedTypeMessage(ex);
value = ex.getValue(); value = ex.getValue();
} }
@ -172,19 +139,68 @@ public class Property
/** /**
* <p>Creates an empty property. It must be filled using the set method to * Returns the property's ID.
* be usable.</p> *
* @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 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. * starts.
* @param length The dictionary contains at most this many bytes. * @param length The dictionary contains at most this many bytes.
* @param codepage The codepage of the string values. * @param codepage The codepage of the string values.
@ -192,15 +208,14 @@ public class Property
* @throws UnsupportedEncodingException if the dictionary's codepage is not * @throws UnsupportedEncodingException if the dictionary's codepage is not
* (yet) supported. * (yet) supported.
*/ */
protected Map<?, ?> readDictionary(final byte[] src, final long offset, protected Map<?, ?> readDictionary(final byte[] src, final long offset, final int length, final int codepage)
final int length, final int codepage) throws UnsupportedEncodingException {
throws UnsupportedEncodingException
{
/* Check whether "offset" points into the "src" array". */ /* Check whether "offset" points into the "src" array". */
if (offset < 0 || offset > src.length) if (offset < 0 || offset > src.length) {
throw new HPSFRuntimeException throw new HPSFRuntimeException
("Illegal offset " + offset + " while HPSF stream contains " + ("Illegal offset " + offset + " while HPSF stream contains " +
length + " bytes."); length + " bytes.");
}
int o = (int) offset; int o = (int) offset;
/* /*
@ -209,13 +224,10 @@ public class Property
final long nrEntries = LittleEndian.getUInt(src, o); final long nrEntries = LittleEndian.getUInt(src, o);
o += LittleEndian.INT_SIZE; o += LittleEndian.INT_SIZE;
final Map<Object, Object> m = new LinkedHashMap<Object, Object>( final Map<Object, Object> m = new LinkedHashMap<Object, Object>((int) nrEntries, (float) 1.0 );
(int) nrEntries, (float) 1.0 );
try try {
{ for (int i = 0; i < nrEntries; i++) {
for (int i = 0; i < nrEntries; i++)
{
/* The key. */ /* The key. */
final Long id = Long.valueOf(LittleEndian.getUInt(src, o)); final Long id = Long.valueOf(LittleEndian.getUInt(src, o));
o += LittleEndian.INT_SIZE; o += LittleEndian.INT_SIZE;
@ -230,17 +242,13 @@ public class Property
/* Read the string. */ /* Read the string. */
final StringBuffer b = new StringBuffer(); final StringBuffer b = new StringBuffer();
switch (codepage) switch (codepage) {
{
case -1: case -1:
{
/* Without a codepage the length is equal to the number of /* Without a codepage the length is equal to the number of
* bytes. */ * bytes. */
b.append(new String(src, o, (int) sLength, Charset.forName("ASCII"))); b.append(new String(src, o, (int) sLength, Charset.forName("ASCII")));
break; break;
}
case CodePageUtil.CP_UNICODE: case CodePageUtil.CP_UNICODE:
{
/* The length is the number of characters, i.e. the number /* The length is the number of characters, i.e. the number
* of bytes is twice the number of the characters. */ * of bytes is twice the number of the characters. */
final int nrBytes = (int) (sLength * 2); final int nrBytes = (int) (sLength * 2);
@ -250,36 +258,30 @@ public class Property
h[i2] = src[o + i2 + 1]; h[i2] = src[o + i2 + 1];
h[i2 + 1] = src[o + i2]; h[i2 + 1] = src[o + i2];
} }
b.append(new String(h, 0, nrBytes, b.append(new String(h, 0, nrBytes, CodePageUtil.codepageToEncoding(codepage)));
CodePageUtil.codepageToEncoding(codepage)));
break; break;
}
default: default:
{
/* For encodings other than Unicode the length is the number /* For encodings other than Unicode the length is the number
* of bytes. */ * of bytes. */
b.append(new String(src, o, (int) sLength, b.append(new String(src, o, (int) sLength, CodePageUtil.codepageToEncoding(codepage)));
VariantSupport.codepageToEncoding(codepage)));
break; break;
}
} }
/* Strip 0x00 characters from the end of the string: */ /* 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); 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; o += sLength;
}
m.put(id, b.toString()); m.put(id, b.toString());
} }
} } catch (RuntimeException ex) {
catch (RuntimeException ex)
{
final POILogger l = POILogFactory.getLogger(getClass()); final POILogger l = POILogFactory.getLogger(getClass());
l.log(POILogger.WARN, l.log(POILogger.WARN,
"The property set's dictionary contains bogus data. " "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 * Returns the property's size in bytes. This is always a multiple of 4.
* 4.</p>
* *
* @return the property's size in bytes * @return the property's size in bytes
* *
@ -303,18 +304,18 @@ public class Property
protected int getSize() throws WritingNotSupportedException protected int getSize() throws WritingNotSupportedException
{ {
int length = VariantSupport.getVariantLength(type); int length = VariantSupport.getVariantLength(type);
if (length >= 0) if (length >= 0) {
return length; /* Fixed length */ return length; /* Fixed length */
if (length == -2) }
if (length == -2) {
/* Unknown length */ /* Unknown length */
throw new WritingNotSupportedException(type, null); throw new WritingNotSupportedException(type, null);
}
/* Variable length: */ /* Variable length: */
final int PADDING = 4; /* Pad to multiples of 4. */ final int PADDING = 4; /* Pad to multiples of 4. */
switch ((int) type) switch ((int) type) {
{ case Variant.VT_LPSTR: {
case Variant.VT_LPSTR:
{
int l = ((String) value).length() + 1; int l = ((String) value).length() + 1;
int r = l % PADDING; int r = l % PADDING;
if (r > 0) 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 * 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 * 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) * @see Object#equals(java.lang.Object)
*/ */
public boolean equals(final Object o) public boolean equals(final Object o) {
{
if (!(o instanceof Property)) { if (!(o instanceof Property)) {
return false; return false;
} }
final Property p = (Property) o; final Property p = (Property) o;
final Object pValue = p.getValue(); final Object pValue = p.getValue();
final long pId = p.getID(); 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; return false;
if (value == null && pValue == null) }
if (value == null && pValue == null) {
return true; return true;
if (value == null || pValue == null) }
if (value == null || pValue == null) {
return false; return false;
}
/* It's clear now that both values are non-null. */ /* It's clear now that both values are non-null. */
final Class<?> valueClass = value.getClass(); final Class<?> valueClass = value.getClass();
final Class<?> pValueClass = pValue.getClass(); final Class<?> pValueClass = pValue.getClass();
if (!(valueClass.isAssignableFrom(pValueClass)) && if (!(valueClass.isAssignableFrom(pValueClass)) &&
!(pValueClass.isAssignableFrom(valueClass))) !(pValueClass.isAssignableFrom(valueClass))) {
return false; return false;
}
if (value instanceof byte[]) if (value instanceof byte[]) {
return Util.equal((byte[]) value, (byte[]) pValue); return Arrays.equals((byte[]) value, (byte[]) pValue);
}
return value.equals(pValue); return value.equals(pValue);
} }
private boolean typesAreEqual(final long t1, final long t2) private boolean typesAreEqual(final long t1, final long t2) {
{ return (t1 == t2 ||
if (t1 == t2 ||
(t1 == Variant.VT_LPSTR && t2 == Variant.VT_LPWSTR) || (t1 == Variant.VT_LPSTR && t2 == Variant.VT_LPWSTR) ||
(t2 == Variant.VT_LPSTR && t1 == Variant.VT_LPWSTR)) { (t2 == Variant.VT_LPSTR && t1 == Variant.VT_LPWSTR));
return true;
}
return false;
} }
@ -385,15 +388,14 @@ public class Property
/** /**
* @see Object#hashCode() * @see Object#hashCode()
*/ */
public int hashCode() public int hashCode() {
{
long hashCode = 0; long hashCode = 0;
hashCode += id; hashCode += id;
hashCode += type; hashCode += type;
if (value != null) if (value != null) {
hashCode += value.hashCode(); 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() * @see Object#toString()
*/ */
public String toString() public String toString() {
{
final StringBuffer b = new StringBuffer(); final StringBuffer b = new StringBuffer();
b.append(getClass().getName()); b.append(getClass().getName());
b.append('['); b.append('[');
@ -413,14 +414,12 @@ public class Property
b.append(getType()); b.append(getType());
final Object value = getValue(); final Object value = getValue();
b.append(", value: "); b.append(", value: ");
if (value instanceof String) if (value instanceof String) {
{
b.append(value.toString()); b.append(value.toString());
final String s = (String) value; final String s = (String) value;
final int l = s.length(); final int l = s.length();
final byte[] bytes = new byte[l * 2]; 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 char c = s.charAt(i);
final byte high = (byte) ((c & 0x00ff00) >> 8); final byte high = (byte) ((c & 0x00ff00) >> 8);
final byte low = (byte) ((c & 0x0000ff) >> 0); final byte low = (byte) ((c & 0x0000ff) >> 0);
@ -433,21 +432,43 @@ public class Property
b.append(hex); b.append(hex);
} }
b.append("]"); b.append("]");
} } else if (value instanceof byte[]) {
else if (value instanceof byte[])
{
byte[] bytes = (byte[])value; byte[] bytes = (byte[])value;
if(bytes.length > 0) { if(bytes.length > 0) {
String hex = HexDump.dump(bytes, 0L, 0); String hex = HexDump.dump(bytes, 0L, 0);
b.append(hex); b.append(hex);
} }
} } else {
else
{
b.append(value.toString()); b.append(value.toString());
} }
b.append(']'); b.append(']');
return b.toString(); 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.InputStream;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import org.apache.poi.hpsf.wellknown.SectionIDMap;
import org.apache.poi.poifs.filesystem.DirectoryEntry; import org.apache.poi.poifs.filesystem.DirectoryEntry;
import org.apache.poi.poifs.filesystem.DocumentEntry; import org.apache.poi.poifs.filesystem.DocumentEntry;
import org.apache.poi.poifs.filesystem.DocumentInputStream; import org.apache.poi.poifs.filesystem.DocumentInputStream;
/** /**
* <p>Factory class to create instances of {@link SummaryInformation}, * Factory class to create instances of {@link SummaryInformation},
* {@link DocumentSummaryInformation} and {@link PropertySet}.</p> * {@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 * in the specified POIFS Directory. This is preferrably a {@link
* DocumentSummaryInformation} or a {@link SummaryInformation}. If * DocumentSummaryInformation} or a {@link SummaryInformation}. If
* the specified entry does not contain a property set stream, an * the specified entry does not contain a property set stream, an
* exception is thrown. If no entry is found with the given name, * 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 dir The directory to find the PropertySet in
* @param name The name of the entry containing the PropertySet * @param name The name of the entry containing the PropertySet
@ -52,55 +50,53 @@ public class PropertySetFactory
* supported. * supported.
*/ */
public static PropertySet create(final DirectoryEntry dir, final String name) public static PropertySet create(final DirectoryEntry dir, final String name)
throws FileNotFoundException, NoPropertySetStreamException, throws FileNotFoundException, NoPropertySetStreamException, IOException, UnsupportedEncodingException {
IOException, UnsupportedEncodingException
{
InputStream inp = null; InputStream inp = null;
try { try {
DocumentEntry entry = (DocumentEntry)dir.getEntry(name); DocumentEntry entry = (DocumentEntry)dir.getEntry(name);
inp = new DocumentInputStream(entry); inp = new DocumentInputStream(entry);
try { try {
return create(inp); return create(inp);
} catch (MarkUnsupportedException e) { return null; } } catch (MarkUnsupportedException e) {
return null;
}
} finally { } 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 * InputStream}. This is preferrably a {@link
* DocumentSummaryInformation} or a {@link SummaryInformation}. If * DocumentSummaryInformation} or a {@link SummaryInformation}. If
* the specified {@link InputStream} does not contain a property * the specified {@link InputStream} does not contain a property
* set stream, an exception is thrown and the {@link InputStream} * 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. * @param stream Contains the property set stream's data.
* @return The created {@link PropertySet}. * @return The created {@link PropertySet}.
* @throws NoPropertySetStreamException if the stream does not * @throws NoPropertySetStreamException if the stream does not
* contain a property set. * contain a property set.
* @throws MarkUnsupportedException if the stream does not support * @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. * @throws IOException if some I/O problem occurs.
* @exception UnsupportedEncodingException if the specified codepage is not * @exception UnsupportedEncodingException if the specified codepage is not
* supported. * supported.
*/ */
public static PropertySet create(final InputStream stream) public static PropertySet create(final InputStream stream)
throws NoPropertySetStreamException, MarkUnsupportedException, throws NoPropertySetStreamException, MarkUnsupportedException, UnsupportedEncodingException, IOException {
UnsupportedEncodingException, IOException
{
final PropertySet ps = new PropertySet(stream); final PropertySet ps = new PropertySet(stream);
try try {
{ if (ps.isSummaryInformation()) {
if (ps.isSummaryInformation())
return new SummaryInformation(ps); return new SummaryInformation(ps);
else if (ps.isDocumentSummaryInformation()) } else if (ps.isDocumentSummaryInformation()) {
return new DocumentSummaryInformation(ps); return new DocumentSummaryInformation(ps);
else } else {
return ps; return ps;
} }
catch (UnexpectedPropertySetTypeException ex) } catch (UnexpectedPropertySetTypeException ex) {
{
/* This exception will never be throws because we already checked /* This exception will never be throws because we already checked
* explicitly for this case above. */ * explicitly for this case above. */
throw new IllegalStateException(ex); 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. * @return the new summary information.
*/ */
public static SummaryInformation newSummaryInformation() public static SummaryInformation newSummaryInformation() {
{ return new SummaryInformation();
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);
}
} }
/** /**
* <p>Creates a new document summary information.</p> * Creates a new document summary information.
* *
* @return the new document summary information. * @return the new document summary information.
*/ */
public static DocumentSummaryInformation newDocumentSummaryInformation() public static DocumentSummaryInformation newDocumentSummaryInformation() {
{ return new DocumentSummaryInformation();
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);
}
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -17,397 +17,24 @@
package org.apache.poi.hpsf; package org.apache.poi.hpsf;
import java.io.IOException; import org.apache.poi.util.Removal;
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;
/** /**
* <p>Abstract superclass for the convenience classes {@link * Interface for the convenience classes {@link SummaryInformation}
* SummaryInformation} and {@link DocumentSummaryInformation}.</p> * and {@link DocumentSummaryInformation}.<p>
* *
* <p>The motivation behind this class is quite nasty if you look * This used to be an abstract class to support late loading
* behind the scenes, but it serves the application programmer well by * of the SummaryInformation classes, as their concrete instance can
* providing him with the easy-to-use {@link SummaryInformation} and * only be determined after the PropertySet has been loaded.
* {@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>
* *
* <p>A cleaner implementation would have been like this: The {@link * @deprecated POI 3.16 - use PropertySet as base class instead
* 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>
*/ */
public abstract class SpecialPropertySet extends MutablePropertySet @Removal(version="3.18")
{ public class SpecialPropertySet extends MutablePropertySet {
/** public SpecialPropertySet() {
* 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);
} }
public SpecialPropertySet(final PropertySet ps) throws UnexpectedPropertySetTypeException {
super(ps);
/**
* <p>Creates a <code>SpecialPropertySet</code>.
*
* @param ps The mutable property set to be encapsulated by the
* <code>SpecialPropertySet</code>
*/
public SpecialPropertySet(final MutablePropertySet ps)
{
delegate = ps;
} }
/**
* @see PropertySet#getByteOrder
*/
@Override
public int getByteOrder()
{
return delegate.getByteOrder();
}
/**
* @see PropertySet#getFormat
*/
@Override
public int getFormat()
{
return delegate.getFormat();
}
/**
* @see PropertySet#getOSVersion
*/
@Override
public int getOSVersion()
{
return delegate.getOSVersion();
}
/**
* @see PropertySet#getClassID
*/
@Override
public ClassID getClassID()
{
return delegate.getClassID();
}
/**
* @see PropertySet#getSectionCount
*/
@Override
public int getSectionCount()
{
return delegate.getSectionCount();
}
/**
* @see PropertySet#getSections
*/
@Override
public List<Section> getSections()
{
return delegate.getSections();
}
/**
* @see PropertySet#isSummaryInformation
*/
@Override
public boolean isSummaryInformation()
{
return delegate.isSummaryInformation();
}
/**
* @see PropertySet#isDocumentSummaryInformation
*/
@Override
public boolean isDocumentSummaryInformation()
{
return delegate.isDocumentSummaryInformation();
}
/**
* @see PropertySet#getSingleSection
*/
@Override
public Section getFirstSection()
{
return delegate.getFirstSection();
}
/**
* @see org.apache.poi.hpsf.MutablePropertySet#addSection(org.apache.poi.hpsf.Section)
*/
@Override
public void addSection(final Section section)
{
delegate.addSection(section);
}
/**
* @see org.apache.poi.hpsf.MutablePropertySet#clearSections()
*/
@Override
public void clearSections()
{
delegate.clearSections();
}
/**
* @see org.apache.poi.hpsf.MutablePropertySet#setByteOrder(int)
*/
@Override
public void setByteOrder(final int byteOrder)
{
delegate.setByteOrder(byteOrder);
}
/**
* @see org.apache.poi.hpsf.MutablePropertySet#setClassID(org.apache.poi.hpsf.ClassID)
*/
@Override
public void setClassID(final ClassID classID)
{
delegate.setClassID(classID);
}
/**
* @see org.apache.poi.hpsf.MutablePropertySet#setFormat(int)
*/
@Override
public void setFormat(final int format)
{
delegate.setFormat(format);
}
/**
* @see org.apache.poi.hpsf.MutablePropertySet#setOSVersion(int)
*/
@Override
public void setOSVersion(final int osVersion)
{
delegate.setOSVersion(osVersion);
}
/**
* @see org.apache.poi.hpsf.MutablePropertySet#toInputStream()
*/
@Override
public InputStream toInputStream() throws IOException, WritingNotSupportedException
{
return delegate.toInputStream();
}
/**
* @see org.apache.poi.hpsf.MutablePropertySet#write(org.apache.poi.poifs.filesystem.DirectoryEntry, java.lang.String)
*/
@Override
public void write(final DirectoryEntry dir, final String name) throws WritingNotSupportedException, IOException
{
delegate.write(dir, name);
}
/**
* @see org.apache.poi.hpsf.MutablePropertySet#write(java.io.OutputStream)
*/
@Override
public void write(final OutputStream out) throws WritingNotSupportedException, IOException
{
delegate.write(out);
}
/**
* @see org.apache.poi.hpsf.PropertySet#equals(java.lang.Object)
*/
@Override
public boolean equals(final Object o)
{
return delegate.equals(o);
}
/**
* @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 java.util.Date;
import org.apache.poi.hpsf.wellknown.PropertyIDMap; import org.apache.poi.hpsf.wellknown.PropertyIDMap;
import org.apache.poi.hpsf.wellknown.SectionIDMap;
/** /**
* <p>Convenience class representing a Summary Information stream in a * Convenience class representing a Summary Information stream in a
* Microsoft Office document.</p> * Microsoft Office document.
* *
* @see DocumentSummaryInformation * @see DocumentSummaryInformation
*/ */
public final class SummaryInformation extends SpecialPropertySet { public final class SummaryInformation extends SpecialPropertySet {
/** /**
* <p>The document name a summary information stream usually has in a POIFS * The document name a summary information stream usually has in a POIFS filesystem.
* filesystem.</p>
*/ */
public static final String DEFAULT_STREAM_NAME = "\005SummaryInformation"; public static final String DEFAULT_STREAM_NAME = "\005SummaryInformation";
@ -39,324 +39,291 @@ public final class SummaryInformation extends SpecialPropertySet {
return PropertyIDMap.getSummaryInformationProperties(); return PropertyIDMap.getSummaryInformationProperties();
} }
/** /**
* <p>Creates a {@link SummaryInformation} from a given {@link * Creates a {@link SummaryInformation} from a given {@link
* PropertySet}.</p> * PropertySet}.
* *
* @param ps A property set which should be created from a summary * @param ps A property set which should be created from a summary
* information stream. * information stream.
* @throws UnexpectedPropertySetTypeException if <var>ps</var> does not * @throws UnexpectedPropertySetTypeException if {@code ps} does not
* contain a summary information stream. * contain a summary information stream.
*/ */
public SummaryInformation(final PropertySet ps) public SummaryInformation() {
throws UnexpectedPropertySetTypeException 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); super(ps);
if (!isSummaryInformation()) if (!isSummaryInformation()) {
throw new UnexpectedPropertySetTypeException("Not a " throw new UnexpectedPropertySetTypeException("Not a " + getClass().getName());
+ getClass().getName()); }
} }
/** /**
* <p>Returns the title (or <code>null</code>).</p> * @return The title or {@code null}
*
* @return The title or <code>null</code>
*/ */
public String getTitle() public String getTitle() {
{
return getPropertyStringValue(PropertyIDMap.PID_TITLE); return getPropertyStringValue(PropertyIDMap.PID_TITLE);
} }
/** /**
* <p>Sets the title.</p> * Sets the title.
* *
* @param title The title to set. * @param title The title to set.
*/ */
public void setTitle(final String title) public void setTitle(final String title) {
{ set1stProperty(PropertyIDMap.PID_TITLE, title);
final MutableSection s = (MutableSection) getFirstSection();
s.setProperty(PropertyIDMap.PID_TITLE, title);
} }
/** /**
* <p>Removes the title.</p> * Removes the title.
*/ */
public void removeTitle() public void removeTitle() {
{ remove1stProperty(PropertyIDMap.PID_TITLE);
final MutableSection s = (MutableSection) getFirstSection();
s.removeProperty(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); return getPropertyStringValue(PropertyIDMap.PID_SUBJECT);
} }
/** /**
* <p>Sets the subject.</p> * Sets the subject.
* *
* @param subject The subject to set. * @param subject The subject to set.
*/ */
public void setSubject(final String subject) public void setSubject(final String subject) {
{ set1stProperty(PropertyIDMap.PID_SUBJECT, subject);
final MutableSection s = (MutableSection) getFirstSection();
s.setProperty(PropertyIDMap.PID_SUBJECT, subject);
} }
/** /**
* <p>Removes the subject.</p> * Removes the subject.
*/ */
public void removeSubject() public void removeSubject() {
{ remove1stProperty(PropertyIDMap.PID_SUBJECT);
final MutableSection s = (MutableSection) getFirstSection();
s.removeProperty(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); return getPropertyStringValue(PropertyIDMap.PID_AUTHOR);
} }
/** /**
* <p>Sets the author.</p> * Sets the author.
* *
* @param author The author to set. * @param author The author to set.
*/ */
public void setAuthor(final String author) public void setAuthor(final String author) {
{ set1stProperty(PropertyIDMap.PID_AUTHOR, author);
final MutableSection s = (MutableSection) getFirstSection();
s.setProperty(PropertyIDMap.PID_AUTHOR, author);
} }
/** /**
* <p>Removes the author.</p> * Removes the author.
*/ */
public void removeAuthor() public void removeAuthor() {
{ remove1stProperty(PropertyIDMap.PID_AUTHOR);
final MutableSection s = (MutableSection) getFirstSection();
s.removeProperty(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); return getPropertyStringValue(PropertyIDMap.PID_KEYWORDS);
} }
/** /**
* <p>Sets the keywords.</p> * Sets the keywords.
* *
* @param keywords The keywords to set. * @param keywords The keywords to set.
*/ */
public void setKeywords(final String keywords) public void setKeywords(final String keywords) {
{ set1stProperty(PropertyIDMap.PID_KEYWORDS, keywords);
final MutableSection s = (MutableSection) getFirstSection();
s.setProperty(PropertyIDMap.PID_KEYWORDS, keywords);
} }
/** /**
* <p>Removes the keywords.</p> * Removes the keywords.
*/ */
public void removeKeywords() public void removeKeywords() {
{ remove1stProperty(PropertyIDMap.PID_KEYWORDS);
final MutableSection s = (MutableSection) getFirstSection();
s.removeProperty(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); return getPropertyStringValue(PropertyIDMap.PID_COMMENTS);
} }
/** /**
* <p>Sets the comments.</p> * Sets the comments.
* *
* @param comments The comments to set. * @param comments The comments to set.
*/ */
public void setComments(final String comments) public void setComments(final String comments) {
{ set1stProperty(PropertyIDMap.PID_COMMENTS, comments);
final MutableSection s = (MutableSection) getFirstSection();
s.setProperty(PropertyIDMap.PID_COMMENTS, comments);
} }
/** /**
* <p>Removes the comments.</p> * Removes the comments.
*/ */
public void removeComments() public void removeComments() {
{ remove1stProperty(PropertyIDMap.PID_COMMENTS);
final MutableSection s = (MutableSection) getFirstSection();
s.removeProperty(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); return getPropertyStringValue(PropertyIDMap.PID_TEMPLATE);
} }
/** /**
* <p>Sets the template.</p> * Sets the template.
* *
* @param template The template to set. * @param template The template to set.
*/ */
public void setTemplate(final String template) public void setTemplate(final String template) {
{ set1stProperty(PropertyIDMap.PID_TEMPLATE, template);
final MutableSection s = (MutableSection) getFirstSection();
s.setProperty(PropertyIDMap.PID_TEMPLATE, template);
} }
/** /**
* <p>Removes the template.</p> * Removes the template.
*/ */
public void removeTemplate() public void removeTemplate() {
{ remove1stProperty(PropertyIDMap.PID_TEMPLATE);
final MutableSection s = (MutableSection) getFirstSection();
s.removeProperty(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); return getPropertyStringValue(PropertyIDMap.PID_LASTAUTHOR);
} }
/** /**
* <p>Sets the last author.</p> * Sets the last author.
* *
* @param lastAuthor The last author to set. * @param lastAuthor The last author to set.
*/ */
public void setLastAuthor(final String lastAuthor) public void setLastAuthor(final String lastAuthor) {
{ set1stProperty(PropertyIDMap.PID_LASTAUTHOR, lastAuthor);
final MutableSection s = (MutableSection) getFirstSection();
s.setProperty(PropertyIDMap.PID_LASTAUTHOR, lastAuthor);
} }
/** /**
* <p>Removes the last author.</p> * Removes the last author.
*/ */
public void removeLastAuthor() public void removeLastAuthor() {
{ remove1stProperty(PropertyIDMap.PID_LASTAUTHOR);
final MutableSection s = (MutableSection) getFirstSection();
s.removeProperty(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); return getPropertyStringValue(PropertyIDMap.PID_REVNUMBER);
} }
/** /**
* <p>Sets the revision number.</p> * Sets the revision number.
* *
* @param revNumber The revision number to set. * @param revNumber The revision number to set.
*/ */
public void setRevNumber(final String revNumber) public void setRevNumber(final String revNumber) {
{ set1stProperty(PropertyIDMap.PID_REVNUMBER, revNumber);
final MutableSection s = (MutableSection) getFirstSection();
s.setProperty(PropertyIDMap.PID_REVNUMBER, revNumber);
} }
/** /**
* <p>Removes the revision number.</p> * Removes the revision number.
*/ */
public void removeRevNumber() public void removeRevNumber() {
{ remove1stProperty(PropertyIDMap.PID_REVNUMBER);
final MutableSection s = (MutableSection) getFirstSection();
s.removeProperty(PropertyIDMap.PID_REVNUMBER);
} }
/** /**
* <p>Returns the total time spent in editing the document (or * Returns the total time spent in editing the document (or
* <code>0</code>).</p> * {@code 0}).
* *
* @return The total time spent in editing the document or 0 if the {@link * @return The total time spent in editing the document or 0 if the {@link
* SummaryInformation} does not contain this information. * SummaryInformation} does not contain this information.
*/ */
public long getEditTime() public long getEditTime() {
{
final Date d = (Date) getProperty(PropertyIDMap.PID_EDITTIME); final Date d = (Date) getProperty(PropertyIDMap.PID_EDITTIME);
if (d == null) { if (d == null) {
return 0; 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. * @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 Date d = Util.filetimeToDate(time);
final MutableSection s = (MutableSection) getFirstSection(); getFirstSection().setProperty(PropertyIDMap.PID_EDITTIME, Variant.VT_FILETIME, d);
s.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() public void removeEditTime() {
{ remove1stProperty(PropertyIDMap.PID_EDITTIME);
final MutableSection s = (MutableSection) getFirstSection();
s.removeProperty(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); return (Date) getProperty(PropertyIDMap.PID_LASTPRINTED);
} }
/** /**
* <p>Sets the lastPrinted.</p> * Sets the lastPrinted.
* *
* @param lastPrinted The lastPrinted to set. * @param lastPrinted The lastPrinted to set.
*/ */
public void setLastPrinted(final Date lastPrinted) public void setLastPrinted(final Date lastPrinted) {
{ getFirstSection().setProperty(PropertyIDMap.PID_LASTPRINTED, Variant.VT_FILETIME, lastPrinted);
final MutableSection s = (MutableSection) getFirstSection();
s.setProperty(PropertyIDMap.PID_LASTPRINTED, Variant.VT_FILETIME,
lastPrinted);
} }
/** /**
* <p>Removes the lastPrinted.</p> * Removes the lastPrinted.
*/ */
public void removeLastPrinted() public void removeLastPrinted() {
{ remove1stProperty(PropertyIDMap.PID_LASTPRINTED);
final MutableSection s = (MutableSection) getFirstSection();
s.removeProperty(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); return (Date) getProperty(PropertyIDMap.PID_CREATE_DTM);
} }
/** /**
* <p>Sets the creation time.</p> * Sets the creation time.
* *
* @param createDateTime The creation time to set. * @param createDateTime The creation time to set.
*/ */
public void setCreateDateTime(final Date createDateTime) public void setCreateDateTime(final Date createDateTime) {
{ getFirstSection().setProperty(PropertyIDMap.PID_CREATE_DTM, Variant.VT_FILETIME, createDateTime);
final MutableSection s = (MutableSection) getFirstSection();
s.setProperty(PropertyIDMap.PID_CREATE_DTM, Variant.VT_FILETIME,
createDateTime);
} }
/** /**
* <p>Removes the creation time.</p> * Removes the creation time.
*/ */
public void removeCreateDateTime() public void removeCreateDateTime() {
{ remove1stProperty(PropertyIDMap.PID_CREATE_DTM);
final MutableSection s = (MutableSection) getFirstSection();
s.removeProperty(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); 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. * @param time The time to set.
*/ */
public void setLastSaveDateTime(final Date time) public void setLastSaveDateTime(final Date time) {
{ final Section s = getFirstSection();
final MutableSection s = (MutableSection) getFirstSection();
s s
.setProperty(PropertyIDMap.PID_LASTSAVE_DTM, .setProperty(PropertyIDMap.PID_LASTSAVE_DTM,
Variant.VT_FILETIME, time); 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() public void removeLastSaveDateTime() {
{ remove1stProperty(PropertyIDMap.PID_LASTSAVE_DTM);
final MutableSection s = (MutableSection) getFirstSection();
s.removeProperty(PropertyIDMap.PID_LASTSAVE_DTM);
} }
/** /**
* <p>Returns the page count or 0 if the {@link SummaryInformation} does * Returns the page count or 0 if the {@link SummaryInformation} does
* not contain a page count.</p> * not contain a page count.
* *
* @return The page count or 0 if the {@link SummaryInformation} does not * @return The page count or 0 if the {@link SummaryInformation} does not
* contain a page count. * contain a page count.
*/ */
public int getPageCount() public int getPageCount() {
{
return getPropertyIntValue(PropertyIDMap.PID_PAGECOUNT); return getPropertyIntValue(PropertyIDMap.PID_PAGECOUNT);
} }
/** /**
* <p>Sets the page count.</p> * Sets the page count.
* *
* @param pageCount The page count to set. * @param pageCount The page count to set.
*/ */
public void setPageCount(final int pageCount) public void setPageCount(final int pageCount) {
{ set1stProperty(PropertyIDMap.PID_PAGECOUNT, pageCount);
final MutableSection s = (MutableSection) getFirstSection();
s.setProperty(PropertyIDMap.PID_PAGECOUNT, pageCount);
} }
/** /**
* <p>Removes the page count.</p> * Removes the page count.
*/ */
public void removePageCount() public void removePageCount() {
{ remove1stProperty(PropertyIDMap.PID_PAGECOUNT);
final MutableSection s = (MutableSection) getFirstSection();
s.removeProperty(PropertyIDMap.PID_PAGECOUNT);
} }
/** /**
* <p>Returns the word count or 0 if the {@link SummaryInformation} does * Returns the word count or 0 if the {@link SummaryInformation} does
* not contain a word count.</p> * 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); return getPropertyIntValue(PropertyIDMap.PID_WORDCOUNT);
} }
/** /**
* <p>Sets the word count.</p> * Sets the word count.
* *
* @param wordCount The word count to set. * @param wordCount The word count to set.
*/ */
public void setWordCount(final int wordCount) public void setWordCount(final int wordCount) {
{ set1stProperty(PropertyIDMap.PID_WORDCOUNT, wordCount);
final MutableSection s = (MutableSection) getFirstSection();
s.setProperty(PropertyIDMap.PID_WORDCOUNT, wordCount);
} }
/** /**
* <p>Removes the word count.</p> * Removes the word count.
*/ */
public void removeWordCount() public void removeWordCount() {
{ remove1stProperty(PropertyIDMap.PID_WORDCOUNT);
final MutableSection s = (MutableSection) getFirstSection();
s.removeProperty(PropertyIDMap.PID_WORDCOUNT);
} }
/** /**
* <p>Returns the character count or 0 if the {@link SummaryInformation} * Returns the character count or 0 if the {@link SummaryInformation}
* does not contain a char count.</p> * 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); return getPropertyIntValue(PropertyIDMap.PID_CHARCOUNT);
} }
/** /**
* <p>Sets the character count.</p> * Sets the character count.
* *
* @param charCount The character count to set. * @param charCount The character count to set.
*/ */
public void setCharCount(final int charCount) public void setCharCount(final int charCount) {
{ set1stProperty(PropertyIDMap.PID_CHARCOUNT, charCount);
final MutableSection s = (MutableSection) getFirstSection();
s.setProperty(PropertyIDMap.PID_CHARCOUNT, charCount);
} }
/** /**
* <p>Removes the character count.</p> * Removes the character count.
*/ */
public void removeCharCount() public void removeCharCount() {
{ remove1stProperty(PropertyIDMap.PID_CHARCOUNT);
final MutableSection s = (MutableSection) getFirstSection();
s.removeProperty(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 * 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 * {@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); 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 * 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(); byte[] data = getThumbnail();
if (data == null) return null; if (data == null) return null;
return new Thumbnail(data); 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. * @param thumbnail The thumbnail to set.
*/ */
public void setThumbnail(final byte[] thumbnail) public void setThumbnail(final byte[] thumbnail) {
{ getFirstSection().setProperty(PropertyIDMap.PID_THUMBNAIL, /* FIXME: */ Variant.VT_LPSTR, thumbnail);
final MutableSection s = (MutableSection) getFirstSection();
s.setProperty(PropertyIDMap.PID_THUMBNAIL, /* FIXME: */
Variant.VT_LPSTR, thumbnail);
} }
/** /**
* <p>Removes the thumbnail.</p> * Removes the thumbnail.
*/ */
public void removeThumbnail() public void removeThumbnail() {
{ remove1stProperty(PropertyIDMap.PID_THUMBNAIL);
final MutableSection s = (MutableSection) getFirstSection();
s.removeProperty(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); return getPropertyStringValue(PropertyIDMap.PID_APPNAME);
} }
/** /**
* <p>Sets the application name.</p> * Sets the application name.
* *
* @param applicationName The application name to set. * @param applicationName The application name to set.
*/ */
public void setApplicationName(final String applicationName) public void setApplicationName(final String applicationName) {
{ set1stProperty(PropertyIDMap.PID_APPNAME, applicationName);
final MutableSection s = (MutableSection) getFirstSection();
s.setProperty(PropertyIDMap.PID_APPNAME, applicationName);
} }
/** /**
* <p>Removes the application name.</p> * Removes the application name.
*/ */
public void removeApplicationName() public void removeApplicationName() {
{ remove1stProperty(PropertyIDMap.PID_APPNAME);
final MutableSection s = (MutableSection) getFirstSection();
s.removeProperty(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> * <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 * security field or if there is no security on the document. Use
* {@link PropertySet#wasNull()} to distinguish between the two * {@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> * </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); return getPropertyIntValue(PropertyIDMap.PID_SECURITY);
} }
/** /**
* <p>Sets the security code.</p> * Sets the security code.
* *
* @param security The security code to set. * @param security The security code to set.
*/ */
public void setSecurity(final int security) public void setSecurity(final int security) {
{ set1stProperty(PropertyIDMap.PID_SECURITY, security);
final MutableSection s = (MutableSection) getFirstSection();
s.setProperty(PropertyIDMap.PID_SECURITY, security);
} }
/** /**
* <p>Removes the security code.</p> * Removes the security code.
*/ */
public void removeSecurity() public void removeSecurity() {
{ remove1stProperty(PropertyIDMap.PID_SECURITY);
final MutableSection s = (MutableSection) getFirstSection();
s.removeProperty(PropertyIDMap.PID_SECURITY);
} }
} }

View File

@ -20,110 +20,17 @@ package org.apache.poi.hpsf;
import java.io.IOException; import java.io.IOException;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.io.StringWriter; import java.io.StringWriter;
import java.util.Collection;
import java.util.Date; import java.util.Date;
import org.apache.poi.util.Internal;
import org.apache.poi.util.SuppressForbidden; import org.apache.poi.util.SuppressForbidden;
/** /**
* <p>Provides various static utility methods.</p> * <p>Provides various static utility methods.</p>
*/ */
@Internal
public class Util 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 * <p>The difference between the Windows epoch (1601-01-01
* 00:00:00) and the Unix epoch (1970-01-01 00:00:00) in * 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 * <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) public static boolean equals(Object[] c1, Object[] c2)
{ {
final Object[] o1 = c1.clone(); for (int i1 = 0; i1 < c1.length; i1++)
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++)
{ {
final Object obj1 = o1[i1]; final Object obj1 = c1[i1];
boolean matchFound = false; 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)) if (obj1.equals(obj2))
{ {
matchFound = true; matchFound = true;
o2[i2] = null; c2[i2] = null;
} }
} }
if (!matchFound) if (!matchFound)
@ -257,8 +127,6 @@ public class Util
return true; return true;
} }
/** /**
* <p>Pads a byte array with 0x00 bytes so that its length is a multiple of * <p>Pads a byte array with 0x00 bytes so that its length is a multiple of
* 4.</p> * 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 * <p>Returns a textual representation of a {@link Throwable}, including a
* stacktrace.</p> * stacktrace.</p>

View File

@ -27,70 +27,77 @@ import java.util.List;
import org.apache.poi.util.CodePageUtil; import org.apache.poi.util.CodePageUtil;
import org.apache.poi.util.POILogFactory; import org.apache.poi.util.POILogFactory;
import org.apache.poi.util.POILogger; 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 * <strong>FIXME (3):</strong> Reading and writing should be made more
* uniform than it is now. The following items should be resolved: * uniform than it is now. The following items should be resolved:<p>
* *
* <ul> * <ul>
* *
* <li><p>Reading requires a length parameter that is 4 byte greater than the * <li>Reading requires a length parameter that is 4 byte greater than the
* actual data, because the variant type field is included. </p></li> * actual data, because the variant type field is included.
* *
* <li><p>Reading reads from a byte array while writing writes to an byte array * <li>Reading reads from a byte array while writing writes to an byte array
* output stream.</p></li> * output stream.
* *
* </ul> * </ul>
*/ */
public class VariantSupport extends Variant public class VariantSupport extends Variant {
{ /**
private static final POILogger logger = POILogFactory.getLogger(VariantSupport.class); * 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; private static boolean logUnsupportedTypes = false;
/** /**
* <p>Specifies whether warnings about unsupported variant types are to be * Keeps a list of the variant types an "unsupported" message has already
* written to <code>System.err</code> or not.</p> * been issued for.
*
* @param logUnsupportedTypes If <code>true</code> warnings will be written,
* if <code>false</code> they won't.
*/ */
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; VariantSupport.logUnsupportedTypes = logUnsupportedTypes;
} }
/** /**
* <p>Checks whether logging of unsupported variant types warning is turned * Checks whether logging of unsupported variant types warning is turned
* on or off.</p> * on or off.
* *
* @return <code>true</code> if logging is turned on, else * @return {@code true} if logging is turned on, else
* <code>false</code>. * {@code false}.
*/ */
public static boolean isLogUnsupportedTypes() public static boolean isLogUnsupportedTypes() {
{
return logUnsupportedTypes; return logUnsupportedTypes;
} }
/** /**
* <p>Keeps a list of the variant types an "unsupported" message has already * Writes a warning to {@code System.err} that a variant type is
* been issued for.</p>
*/
protected static List<Long> unsupportedMessage;
/**
* <p>Writes a warning to <code>System.err</code> that a variant type is
* unsupported by HPSF. Such a warning is written only once for each variant * 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 * @param ex The exception to log
*/ */
protected static void writeUnsupportedTypeMessage protected static void writeUnsupportedTypeMessage
(final UnsupportedVariantTypeException ex) (final UnsupportedVariantTypeException ex) {
{
if (isLogUnsupportedTypes()) if (isLogUnsupportedTypes())
{ {
if (unsupportedMessage == null) 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} * types should be implemented included in the {@link #SUPPORTED_TYPES}
* array.</p> * array.
* *
* @see Variant * @see Variant
* @param variantType the variant type to check * @param variantType the variant type to check
* @return <code>true</code> if HPFS supports this type, else * @return {@code true} if HPFS supports this type, else
* <code>false</code> * {@code false}
*/ */
public boolean isSupportedType(final int variantType) public boolean isSupportedType(final int variantType) {
{
for (int i = 0; i < SUPPORTED_TYPES.length; i++) for (int i = 0; i < SUPPORTED_TYPES.length; i++)
if (variantType == SUPPORTED_TYPES[i]) if (variantType == SUPPORTED_TYPES[i])
return true; 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 src The byte array
* @param offset The offset in the byte array where the variant starts * @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, public static Object read( final byte[] src, final int offset,
final int length, final long type, final int codepage ) final int length, final long type, final int codepage )
throws ReadingNotSupportedException, UnsupportedEncodingException throws ReadingNotSupportedException, UnsupportedEncodingException {
{ TypedPropertyValue typedPropertyValue = new TypedPropertyValue( (int) type, null );
TypedPropertyValue typedPropertyValue = new TypedPropertyValue(
(int) type, null );
int unpadded; int unpadded;
try try {
{
unpadded = typedPropertyValue.readValue( src, offset ); unpadded = typedPropertyValue.readValue( src, offset );
} } catch ( UnsupportedOperationException exc ) {
catch ( UnsupportedOperationException exc )
{
int propLength = Math.min( length, src.length - offset ); int propLength = Math.min( length, src.length - offset );
final byte[] v = new byte[propLength]; final byte[] v = new byte[propLength];
System.arraycopy( src, offset, v, 0, propLength ); System.arraycopy( src, offset, v, 0, propLength );
throw new ReadingNotSupportedException( type, v ); throw new ReadingNotSupportedException( type, v );
} }
switch ( (int) type ) switch ( (int) type ) {
{
case Variant.VT_EMPTY:
case Variant.VT_I4:
case Variant.VT_I8:
case Variant.VT_R8:
/* /*
* we have more property types that can be converted into Java * we have more property types that can be converted into Java
* objects, but current API need to be preserved, and it returns * objects, but current API need to be preserved, and it returns
* other types as byte arrays. In future major versions it shall be * other types as byte arrays. In future major versions it shall be
* changed -- sergey * 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 * also for backward-compatibility with prev. versions of POI
* --sergey * --sergey
*/ */
return Integer.valueOf( ( (Short) typedPropertyValue.getValue() ) case Variant.VT_I2:
.intValue() ); return ( (Short) typedPropertyValue.getValue() ).intValue();
}
case Variant.VT_FILETIME: case Variant.VT_FILETIME:
{ Filetime filetime = (Filetime) typedPropertyValue.getValue();
Filetime filetime = (Filetime) typedPropertyValue.getValue(); return Util.filetimeToDate( (int) filetime.getHigh(), (int) filetime.getLow() );
return Util.filetimeToDate( (int) filetime.getHigh(),
(int) filetime.getLow() ); case Variant.VT_LPSTR:
} CodePageString cpString = (CodePageString) typedPropertyValue.getValue();
case Variant.VT_LPSTR: return cpString.getJavaValue( codepage );
{
CodePageString string = (CodePageString) typedPropertyValue case Variant.VT_LPWSTR:
.getValue(); UnicodeString uniString = (UnicodeString) typedPropertyValue.getValue();
return string.getJavaValue( codepage ); return uniString.toJavaString();
}
case Variant.VT_LPWSTR:
{
UnicodeString string = (UnicodeString) typedPropertyValue
.getValue();
return string.toJavaString();
}
case Variant.VT_CF:
{
// if(l1 < 0) { // if(l1 < 0) {
/** /**
* YK: reading the ClipboardData packet (VT_CF) is not quite * 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 * 45583 clearly show that this approach does not always work. The
* workaround below attempts to gracefully handle such cases instead * workaround below attempts to gracefully handle such cases instead
* of throwing exceptions. * of throwing exceptions.
* *
* August 20, 2009 * August 20, 2009
*/ */
// l1 = LittleEndian.getInt(src, o1); o1 += LittleEndian.INT_SIZE; // 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); // System.arraycopy(src, o1, v, 0, v.length);
// value = v; // value = v;
// break; // break;
ClipboardData clipboardData = (ClipboardData) typedPropertyValue case Variant.VT_CF:
.getValue(); ClipboardData clipboardData = (ClipboardData) typedPropertyValue.getValue();
return clipboardData.toByteArray(); return clipboardData.toByteArray();
}
case Variant.VT_BOOL: case Variant.VT_BOOL:
{ VariantBool bool = (VariantBool) typedPropertyValue.getValue();
VariantBool bool = (VariantBool) typedPropertyValue.getValue(); return bool.getValue();
return Boolean.valueOf( bool.getValue() );
}
default:
{
/* /*
* it is not very good, but what can do without breaking current * it is not very good, but what can do without breaking current
* API? --sergey * API? --sergey
*/ */
final byte[] v = new byte[unpadded]; default:
System.arraycopy( src, offset, v, 0, unpadded ); final byte[] v = new byte[unpadded];
throw new ReadingNotSupportedException( type, v ); System.arraycopy( src, offset, v, 0, unpadded );
} throw new ReadingNotSupportedException( type, v );
} }
} }
/** /**
* <p>Turns a codepage number into the equivalent character encoding's * Turns a codepage number into the equivalent character encoding's
* name.</p> * name.
* *
* @param codepage The codepage number * @param codepage The codepage number
* *
@ -269,7 +245,10 @@ public class VariantSupport extends Variant
* *
* @exception UnsupportedEncodingException if the specified codepage is * @exception UnsupportedEncodingException if the specified codepage is
* less than zero. * less than zero.
*
* @deprecated POI 3.16 - use {@link CodePageUtil#codepageToEncoding(int)}
*/ */
@Removal(version="3.18")
public static String codepageToEncoding(final int codepage) public static String codepageToEncoding(final int codepage)
throws UnsupportedEncodingException throws UnsupportedEncodingException
{ {
@ -278,13 +257,13 @@ public class VariantSupport extends Variant
/** /**
* <p>Writes a variant value to an output stream. This method ensures that * Writes a variant value to an output stream. This method ensures that
* always a multiple of 4 bytes is written.</p> * 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} * <strong>must</strong> always be written as {@link Variant#VT_LPWSTR}
* strings, not as {@link Variant#VT_LPSTR} strings. This method ensure this * 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 out The stream to write the value to.
* @param type The variant's type. * @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, public static int write(final OutputStream out, final long type,
final Object value, final int codepage) final Object value, final int codepage)
throws IOException, WritingNotSupportedException throws IOException, WritingNotSupportedException {
{
int length = 0; int length = 0;
switch ((int) type) switch ((int) type) {
{
case Variant.VT_BOOL: case Variant.VT_BOOL:
{ if ( ( (Boolean) value ).booleanValue() ) {
if ( ( (Boolean) value ).booleanValue() )
{
out.write( 0xff ); out.write( 0xff );
out.write( 0xff ); out.write( 0xff );
} } else {
else
{
out.write( 0x00 ); out.write( 0x00 );
out.write( 0x00 ); out.write( 0x00 );
} }
length += 2; length += 2;
break; break;
}
case Variant.VT_LPSTR: case Variant.VT_LPSTR:
{ CodePageString codePageString = new CodePageString( (String) value, codepage );
CodePageString codePageString = new CodePageString( (String) value,
codepage );
length += codePageString.write( out ); length += codePageString.write( out );
break; break;
}
case Variant.VT_LPWSTR: case Variant.VT_LPWSTR:
{
final int nrOfChars = ( (String) value ).length() + 1; final int nrOfChars = ( (String) value ).length() + 1;
length += TypeWriter.writeUIntToStream( out, nrOfChars ); length += TypeWriter.writeUIntToStream( out, nrOfChars );
char[] s = ( (String) value ).toCharArray(); for ( char s : ( (String) value ).toCharArray() ) {
for ( int i = 0; i < s.length; i++ ) final int high = ( ( s & 0x0000ff00 ) >> 8 );
{ final int low = ( s & 0x000000ff );
final int high = ( ( s[i] & 0x0000ff00 ) >> 8 );
final int low = ( s[i] & 0x000000ff );
final byte highb = (byte) high; final byte highb = (byte) high;
final byte lowb = (byte) low; final byte lowb = (byte) low;
out.write( lowb ); out.write( lowb );
@ -345,79 +313,63 @@ public class VariantSupport extends Variant
out.write( 0x00 ); out.write( 0x00 );
length += 2; length += 2;
break; break;
}
case Variant.VT_CF: case Variant.VT_CF:
{ final byte[] cf = (byte[]) value;
final byte[] b = (byte[]) value; out.write(cf);
out.write(b); length = cf.length;
length = b.length;
break; break;
}
case Variant.VT_EMPTY: case Variant.VT_EMPTY:
{
length += TypeWriter.writeUIntToStream( out, Variant.VT_EMPTY ); length += TypeWriter.writeUIntToStream( out, Variant.VT_EMPTY );
break; break;
}
case Variant.VT_I2: case Variant.VT_I2:
{ length += TypeWriter.writeToStream( out, ( (Integer) value ).shortValue() );
length += TypeWriter.writeToStream( out,
( (Integer) value ).shortValue() );
break; break;
}
case Variant.VT_I4: case Variant.VT_I4:
{ if (!(value instanceof Integer)) {
if (!(value instanceof Integer))
{
throw new ClassCastException("Could not cast an object to " throw new ClassCastException("Could not cast an object to "
+ Integer.class.toString() + ": " + Integer.class.toString() + ": "
+ value.getClass().toString() + ", " + value.getClass().toString() + ", "
+ value.toString()); + value.toString());
} }
length += TypeWriter.writeToStream(out, length += TypeWriter.writeToStream(out, ((Integer) value).intValue());
((Integer) value).intValue());
break; break;
}
case Variant.VT_I8: case Variant.VT_I8:
{
length += TypeWriter.writeToStream(out, ((Long) value).longValue()); length += TypeWriter.writeToStream(out, ((Long) value).longValue());
break; break;
}
case Variant.VT_R8: case Variant.VT_R8:
{ length += TypeWriter.writeToStream(out, ((Double) value).doubleValue());
length += TypeWriter.writeToStream(out,
((Double) value).doubleValue());
break; break;
}
case Variant.VT_FILETIME: case Variant.VT_FILETIME:
{
long filetime = Util.dateToFileTime((Date) value); long filetime = Util.dateToFileTime((Date) value);
int high = (int) ((filetime >> 32) & 0x00000000FFFFFFFFL); int high = (int) ((filetime >> 32) & 0x00000000FFFFFFFFL);
int low = (int) (filetime & 0x00000000FFFFFFFFL); int low = (int) (filetime & 0x00000000FFFFFFFFL);
Filetime filetimeValue = new Filetime( low, high); Filetime filetimeValue = new Filetime( low, high);
length += filetimeValue.write( out ); length += filetimeValue.write( out );
break; break;
}
default: default:
{
/* The variant type is not supported yet. However, if the value /* The variant type is not supported yet. However, if the value
* is a byte array we can write it nevertheless. */ * is a byte array we can write it nevertheless. */
if (value instanceof byte[]) if (value instanceof byte[]) {
{
final byte[] b = (byte[]) value; final byte[] b = (byte[]) value;
out.write(b); out.write(b);
length = b.length; length = b.length;
writeUnsupportedTypeMessage writeUnsupportedTypeMessage(new WritingNotSupportedException(type, value));
(new WritingNotSupportedException(type, value)); } else {
}
else
throw new WritingNotSupportedException(type, value); throw new WritingNotSupportedException(type, value);
}
break; break;
}
} }
/* pad values to 4-bytes */ /* pad values to 4-bytes */
while ( ( length & 0x3 ) != 0 ) while ( ( length & 0x3 ) != 0 ) {
{
out.write( 0x00 ); out.write( 0x00 );
length++; length++;
} }

View File

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

View File

@ -45,26 +45,42 @@ public final class IOUtils {
* @throws EmptyFileException if the stream is empty * @throws EmptyFileException if the stream is empty
*/ */
public static byte[] peekFirst8Bytes(InputStream stream) throws IOException, EmptyFileException { public static byte[] peekFirst8Bytes(InputStream stream) throws IOException, EmptyFileException {
// We want to peek at the first 8 bytes return peekFirstNBytes(stream, 8);
stream.mark(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(); 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) { if(stream instanceof PushbackInputStream) {
PushbackInputStream pin = (PushbackInputStream)stream; PushbackInputStream pin = (PushbackInputStream)stream;
pin.unread(header, 0, read); pin.unread(peekedBytes, 0, readBytes);
} else { } else {
stream.reset(); stream.reset();
} }
return header; return peekedBytes;
} }
/** /**
* Reads all the data from the input stream, and returns the bytes read. * 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())); (poiFiles[0].getBytes()));
final List<Section> sections = si.getSections(); final List<Section> sections = si.getSections();
final Section s = sections.get(0); final Section s = sections.get(0);
assertTrue(org.apache.poi.hpsf.Util.equal assertArrayEquals(s.getFormatID().getBytes(), SectionIDMap.SUMMARY_INFORMATION_ID);
(s.getFormatID().getBytes(), SectionIDMap.SUMMARY_INFORMATION_ID));
assertNotNull(s.getProperties()); assertNotNull(s.getProperties());
assertEquals(17, s.getPropertyCount()); assertEquals(17, s.getPropertyCount());
assertEquals("Titel", s.getProperty(2)); assertEquals("Titel", s.getProperty(2));

View File

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