From f85ef3c94e5a9ed4c13e3c2cf1ca103e293fcd37 Mon Sep 17 00:00:00 2001 From: Rainer Klute Date: Sat, 30 Aug 2003 09:13:53 +0000 Subject: [PATCH] HPSF writing capability added. git-svn-id: https://svn.apache.org/repos/asf/jakarta/poi/trunk@353321 13f79535-47bb-0310-9956-ffa450edef68 --- src/java/org/apache/poi/hpsf/ClassID.java | 17 +- .../poi/hpsf/DocumentSummaryInformation.java | 5 +- .../org/apache/poi/hpsf/HPSFException.java | 3 +- .../apache/poi/hpsf/HPSFRuntimeException.java | 48 +- .../poi/hpsf/MarkUnsupportedException.java | 11 +- .../org/apache/poi/hpsf/MutableProperty.java | 85 ++++ .../apache/poi/hpsf/MutablePropertySet.java | 243 ++++++++++ .../org/apache/poi/hpsf/MutableSection.java | 426 ++++++++++++++++++ .../hpsf/NoPropertySetStreamException.java | 23 +- .../poi/hpsf/NoSingleSectionException.java | 3 +- src/java/org/apache/poi/hpsf/Property.java | 108 +++-- src/java/org/apache/poi/hpsf/PropertySet.java | 50 +- .../apache/poi/hpsf/PropertySetFactory.java | 3 +- .../hpsf/ReadingNotSupportedException.java | 14 +- src/java/org/apache/poi/hpsf/Section.java | 51 ++- .../apache/poi/hpsf/SpecialPropertySet.java | 3 +- .../apache/poi/hpsf/SummaryInformation.java | 5 +- src/java/org/apache/poi/hpsf/TypeWriter.java | 11 +- .../UnexpectedPropertySetTypeException.java | 3 +- .../hpsf/UnsupportedVariantTypeException.java | 2 +- src/java/org/apache/poi/hpsf/Util.java | 87 +++- src/java/org/apache/poi/hpsf/Variant.java | 209 +++++++-- .../org/apache/poi/hpsf/VariantSupport.java | 189 +++++--- .../hpsf/WritingNotSupportedException.java | 10 +- .../poi/hpsf/wellknown/PropertyIDMap.java | 6 +- 25 files changed, 1418 insertions(+), 197 deletions(-) create mode 100644 src/java/org/apache/poi/hpsf/MutableProperty.java create mode 100644 src/java/org/apache/poi/hpsf/MutablePropertySet.java create mode 100644 src/java/org/apache/poi/hpsf/MutableSection.java diff --git a/src/java/org/apache/poi/hpsf/ClassID.java b/src/java/org/apache/poi/hpsf/ClassID.java index 898364fce..e179a6352 100644 --- a/src/java/org/apache/poi/hpsf/ClassID.java +++ b/src/java/org/apache/poi/hpsf/ClassID.java @@ -61,7 +61,8 @@ package org.apache.poi.hpsf; * order. Instead, it is a double word (4 bytes) followed by two * words (2 bytes each) followed by 8 bytes.

* - * @author Rainer Klute (klute@rainer-klute.de) + * @author Rainer Klute <klute@rainer-klute.de> * @version $Id$ * @since 2002-02-09 */ @@ -120,7 +121,7 @@ public class ClassID /** *

Gets the bytes making out the class ID. They are returned in * correct order, i.e. big-endian.

- * + * * @return the bytes making out the class ID. */ public byte[] getBytes() @@ -175,7 +176,7 @@ public class ClassID * * @param offset The offset within the dst byte array. * - * @exception ArrayStoreException if there is not enough room for the class + * @exception ArrayStoreException if there is not enough room for the class * ID 16 bytes in the byte array after the offset position. */ public void write(final byte[] dst, final int offset) @@ -228,4 +229,14 @@ public class ClassID return true; } + + + /** + * @see Object#hashCode() + */ + public int hashCode() + { + throw new UnsupportedOperationException("FIXME: Not yet implemented."); + } + } diff --git a/src/java/org/apache/poi/hpsf/DocumentSummaryInformation.java b/src/java/org/apache/poi/hpsf/DocumentSummaryInformation.java index d1b694f08..704717cf9 100644 --- a/src/java/org/apache/poi/hpsf/DocumentSummaryInformation.java +++ b/src/java/org/apache/poi/hpsf/DocumentSummaryInformation.java @@ -60,7 +60,8 @@ import org.apache.poi.hpsf.wellknown.PropertyIDMap; *

Convenience class representing a DocumentSummary Information stream in a * Microsoft Office document.

* - * @author Rainer Klute (klute@rainer-klute.de) + * @author Rainer Klute <klute@rainer-klute.de> * @author Drew Varner (Drew.Varner closeTo sc.edu) * @see SummaryInformation * @version $Id$ @@ -289,7 +290,7 @@ public class DocumentSummaryInformation extends SpecialPropertySet *

Returns true if the custom links are hampered * by excessive noise, for all applications.

* - * FIXME: Explain this some more! I (Rainer) + * FIXME (3): Explain this some more! I (Rainer) * don't understand it.

* * @return The linksDirty value diff --git a/src/java/org/apache/poi/hpsf/HPSFException.java b/src/java/org/apache/poi/hpsf/HPSFException.java index 8a9b7c86d..cb6f5648d 100644 --- a/src/java/org/apache/poi/hpsf/HPSFException.java +++ b/src/java/org/apache/poi/hpsf/HPSFException.java @@ -59,7 +59,8 @@ package org.apache.poi.hpsf; * thrown in this package. It supports a nested "reason" throwable, * i.e. an exception that caused this one to be thrown.

* - * @author Rainer Klute (klute@rainer-klute.de) + * @author Rainer Klute <klute@rainer-klute.de> * @version $Id$ * @since 2002-02-09 */ diff --git a/src/java/org/apache/poi/hpsf/HPSFRuntimeException.java b/src/java/org/apache/poi/hpsf/HPSFRuntimeException.java index d86f8e871..1fb46f4f5 100644 --- a/src/java/org/apache/poi/hpsf/HPSFRuntimeException.java +++ b/src/java/org/apache/poi/hpsf/HPSFRuntimeException.java @@ -54,12 +54,16 @@ */ package org.apache.poi.hpsf; +import java.io.PrintStream; +import java.io.PrintWriter; + /** *

This exception is the superclass of all other unchecked * exceptions thrown in this package. It supports a nested "reason" * throwable, i.e. an exception that caused this one to be thrown.

* - * @author Rainer Klute (klute@rainer-klute.de) + * @author Rainer Klute <klute@rainer-klute.de> * @version $Id$ * @since 2002-02-09 */ @@ -138,4 +142,46 @@ public class HPSFRuntimeException extends RuntimeException return reason; } + + + /** + * @see Throwable#printStackTrace() + */ + public void printStackTrace() + { + printStackTrace(System.err); + } + + + + /** + * @see Throwable#printStackTrace(java.io.PrintStream) + */ + public void printStackTrace(final PrintStream p) + { + final Throwable reason = getReason(); + super.printStackTrace(p); + if (reason != null) + { + p.println("Caused by:"); + reason.printStackTrace(p); + } + } + + + + /** + * @see Throwable#printStackTrace(java.io.PrintWriter) + */ + public void printStackTrace(final PrintWriter p) + { + final Throwable reason = getReason(); + super.printStackTrace(p); + if (reason != null) + { + p.println("Caused by:"); + reason.printStackTrace(p); + } + } + } diff --git a/src/java/org/apache/poi/hpsf/MarkUnsupportedException.java b/src/java/org/apache/poi/hpsf/MarkUnsupportedException.java index 656f3cab3..86ccb2674 100644 --- a/src/java/org/apache/poi/hpsf/MarkUnsupportedException.java +++ b/src/java/org/apache/poi/hpsf/MarkUnsupportedException.java @@ -58,7 +58,8 @@ package org.apache.poi.hpsf; *

This exception is thrown if an {@link java.io.InputStream} does * not support the {@link java.io.InputStream#mark} operation.

* - * @author Rainer Klute (klute@rainer-klute.de) + * @author Rainer Klute <klute@rainer-klute.de> * @version $Id$ * @since 2002-02-09 */ @@ -76,7 +77,7 @@ public class MarkUnsupportedException extends HPSFException /** *

Constructor

- * + * * @param msg The exception's message string */ public MarkUnsupportedException(final String msg) @@ -87,7 +88,7 @@ public class MarkUnsupportedException extends HPSFException /** *

Constructor

- * + * * @param reason This exception's underlying reason */ public MarkUnsupportedException(final Throwable reason) @@ -98,7 +99,7 @@ public class MarkUnsupportedException extends HPSFException /** *

Constructor

- * + * * @param msg The exception's message string * @param reason This exception's underlying reason */ @@ -107,4 +108,4 @@ public class MarkUnsupportedException extends HPSFException super(msg, reason); } -} \ No newline at end of file +} diff --git a/src/java/org/apache/poi/hpsf/MutableProperty.java b/src/java/org/apache/poi/hpsf/MutableProperty.java new file mode 100644 index 000000000..b5f11643e --- /dev/null +++ b/src/java/org/apache/poi/hpsf/MutableProperty.java @@ -0,0 +1,85 @@ +package org.apache.poi.hpsf; + +import java.io.IOException; +import java.io.OutputStream; + +/** + *

Adds writing capability to the {@link Property} class.

+ * + *

Please be aware that this class' functionality will be merged into the + * {@link Property} class at a later time, so the API will change.

+ * + * @author Rainer Klute <klute@rainer-klute.de> + * @since 2003-08-03 + * @version $Id$ + */ +public class MutableProperty extends Property +{ + + /** + *

Creates an empty property. It must be filled using the set method to + * be usable.

+ */ + public MutableProperty() + { } + + + + /** + *

Sets the property's ID.

+ * + * @param id the ID + */ + public void setID(final long id) + { + this.id = id; + } + + + + /** + *

Sets the property's type.

+ * + * @param type the property's type + */ + public void setType(final long type) + { + this.type = type; + } + + + + /** + *

Sets the property's value.

+ * + * @param value the property's value + */ + public void setValue(final Object value) + { + this.value = value; + } + + + + /** + *

Writes the property to an output stream.

+ * + * @param out The output stream to write to. + * @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) + throws IOException, WritingNotSupportedException + { + int length = 0; + long variantType = getType(); + length += TypeWriter.writeUIntToStream(out, variantType); + length += VariantSupport.write(out, variantType, getValue()); + return length; + } + +} diff --git a/src/java/org/apache/poi/hpsf/MutablePropertySet.java b/src/java/org/apache/poi/hpsf/MutablePropertySet.java new file mode 100644 index 000000000..dc0b14529 --- /dev/null +++ b/src/java/org/apache/poi/hpsf/MutablePropertySet.java @@ -0,0 +1,243 @@ +/* + * ==================================================================== + * The Apache Software License, Version 1.1 + * + * Copyright (c) 2003 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Apache" and "Apache Software Foundation" must + * not be used to endorse or promote products derived from this + * software without prior written permission. For written + * permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache", + * nor may "Apache" appear in their name, without prior written + * permission of the Apache Software Foundation. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + */ +package org.apache.poi.hpsf; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.LinkedList; +import java.util.ListIterator; +import org.apache.poi.util.LittleEndian; +import org.apache.poi.util.LittleEndianConsts; + + + +/** + *

Adds writing support to the {@link PropertySet} class.

+ * + *

Please be aware that this class' functionality will be merged into the + * {@link PropertySet} class at a later time, so the API will change.

+ * + * @author Rainer Klute <klute@rainer-klute.de> + * @version $Id$ + * @since 2003-02-19 + */ +public class MutablePropertySet extends PropertySet +{ + + /** + *

Constructs a MutablePropertySet instance. Its + * primary task is to initialize the immutable field with their proper + * values. It also sets fields that might change to reasonable defaults.

+ */ + 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(); + sections.add(new MutableSection()); + } + + + + /** + *

The length of the property set stream header.

+ */ + private final 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 */ + + + + /** + *

Sets the "byteOrder" property.

+ * + * @param byteOrder the byteOrder value to set + */ + public void setByteOrder(final int byteOrder) + { + this.byteOrder = byteOrder; + } + + + + /** + *

Sets the "format" property.

+ * + * @param format the format value to set + */ + public void setFormat(final int format) + { + this.format = format; + } + + + + /** + *

Sets the "osVersion" property.

+ * + * @param osVersion the osVersion value to set + */ + public void setOSVersion(final int osVersion) + { + this.osVersion = osVersion; + } + + + + /** + *

Sets the property set stream's low-level "class ID" + * field.

+ * + * @param classID The property set stream's low-level "class ID" field. + * + * @see #getClassID + */ + public void setClassID(final ClassID classID) + { + this.classID = classID; + } + + + + /** + *

Removes all sections from this property set.

+ */ + public void clearSections() + { + sections = null; + } + + + + /** + *

Adds a section to this property set.

+ * + * @param section The {@link Section} to add. It will be appended + * after any sections that are already present in the property set + * and thus become the last section. + */ + public void addSection(final Section section) + { + if (sections == null) + sections = new LinkedList(); + sections.add(section); + } + + + + /** + *

Writes the property set to an output stream.

+ * + * @param out the output stream to write the section to + * @exception IOException if an error when writing to the output stream + * occurs + * @exception WritingNotSupportedException if HPSF does not yet support + * writing a property's variant type. + */ + public void write(final OutputStream out) + throws WritingNotSupportedException, IOException + { + /* Write the number of sections in this property set stream. */ + final int nrSections = sections.size(); + int length = 0; + + /* Write the property set's header. */ + length += TypeWriter.writeToStream(out, (short) getByteOrder()); + length += TypeWriter.writeToStream(out, (short) getFormat()); + length += TypeWriter.writeToStream(out, (int) getOSVersion()); + length += TypeWriter.writeToStream(out, getClassID()); + length += TypeWriter.writeToStream(out, (int) nrSections); + int offset = OFFSET_HEADER; + + /* Write the section list, i.e. the references to the sections. Each + * entry in the section list consist of a class ID and the offset to the + * section's begin. */ + offset += nrSections * (ClassID.LENGTH + LittleEndian.INT_SIZE); + final int sectionsBegin = offset; + for (final ListIterator i = sections.listIterator(); i.hasNext();) + { + final MutableSection s = (MutableSection) i.next(); + length += TypeWriter.writeToStream(out, s.getFormatID()); + length += TypeWriter.writeUIntToStream(out, offset); + offset += s.getSize(); + } + + /* Write the sections themselves. */ + offset = sectionsBegin; + for (final ListIterator i = sections.listIterator(); i.hasNext();) + { + final MutableSection s = (MutableSection) i.next(); + offset = s.write(out, offset); + } + } + +} \ No newline at end of file diff --git a/src/java/org/apache/poi/hpsf/MutableSection.java b/src/java/org/apache/poi/hpsf/MutableSection.java new file mode 100644 index 000000000..ec2453973 --- /dev/null +++ b/src/java/org/apache/poi/hpsf/MutableSection.java @@ -0,0 +1,426 @@ +/* + * ==================================================================== + * The Apache Software License, Version 1.1 + * + * Copyright (c) 2000 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Apache" and "Apache Software Foundation" must + * not be used to endorse or promote products derived from this + * software without prior written permission. For written + * permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache", + * nor may "Apache" appear in their name, without prior written + * permission of the Apache Software Foundation. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + */ +package org.apache.poi.hpsf; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +import org.apache.poi.util.LittleEndian; +import org.apache.poi.util.LittleEndianConsts; + +/** + *

Adds writing capability to the {@link Section} class.

+ * + *

Please be aware that this class' functionality will be merged into the + * {@link Section} class at a later time, so the API will change.

+ * + * @author Rainer Klute <klute@rainer-klute.de> + * @version $Id$ + * @since 2002-02-20 + */ +public class MutableSection extends Section +{ + + /** + *

If the "dirty" flag is true, the section's size must be + * (re-)calculated before the section is written.

+ */ + private boolean dirty = true; + + + + /** + *

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}.

+ */ + private List preprops; + + + + /** + *

Creates an empty mutable section.

+ */ + public MutableSection() + { + dirty = true; + formatID = null; + offset = -1; + preprops = new LinkedList(); + } + + + + /** + *

Sets the section's format ID.

+ * + * @param formatID The section's format ID + * + * @see #setFormatID(byte[]) + * @see #getFormatID + */ + public void setFormatID(final ClassID formatID) + { + this.formatID = formatID; + } + + + + /** + *

Sets the section's format ID.

+ * + * @param formatID The section's format ID as a byte array. It components + * are in big-endian format. + * + * @see #setFormatID(ClassID) + * @see #getFormatID + */ + public void setFormatID(final byte[] formatID) + { + setFormatID(new ClassID(formatID, 0)); + } + + + + /** + *

Sets this section's properties. Any former values are overwritten.

+ * + * @param properties This section's new properties. + */ + public void setProperties(final Property[] properties) + { + preprops = new LinkedList(); + for (int i = 0; i < properties.length; i++) + preprops.add(properties[i]); + dirty = true; + } + + + + /** + *

Sets the value 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.

+ * + * @param id The property's ID + * @param value The property's value. It will be written as a Unicode + * string. + * + * @see #setProperty(int, int, Object) + * @see #getProperty + */ + public void setProperty(final int id, final String value) + { + setProperty(id, Variant.VT_LPWSTR, value); + dirty = true; + } + + + + /** + *

Sets the value and the variant type of the property with the + * specified ID. If a property with this ID is not yet present in + * the section, it will be added. An already present property with + * the specified ID will be overwritten. A default mapping will be + * used to choose the property's type.

+ * + * @param id The property's ID. + * @param variantType The property's variant type. + * @param value The property's value. + * + * @see #setProperty(int, Object) + * @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; + } + + + + /** + *

Sets a property. If a property with the same ID is not yet present in + * the section, the property will be added to the section. If there is + * already a property with the same ID present in the section, it will be + * overwritten.

+ * + * @param p The property to be added to the section + * + * @see #setProperty(int, int, Object) + * @see #setProperty(int, String) + * @see #getProperty + * @see Variant + */ + public void setProperty(final Property p) + { + final long id = p.getID(); + for (final Iterator i = preprops.iterator(); i.hasNext();) + if (((Property) i.next()).getID() == id) + { + i.remove(); + break; + } + preprops.add(p); + dirty = true; + } + + + + /** + *

Sets the value of the boolean property with the specified + * ID.

+ * + * @param id The property's ID + * @param value The property's value + * + * @see #setProperty(int, int, Object) + * @see #getProperty + * @see Variant + */ + protected void setPropertyBooleanValue(final int id, final boolean value) + { + setProperty(id, (long) Variant.VT_BOOL, new Boolean(value)); + } + + + + /** + *

Returns the section's size.

+ * + * @return the section's size. + */ + public int getSize() + { + if (dirty) + { + size = calcSize(); + dirty = false; + } + return size; + } + + + + /** + *

Calculates the section's size. It is the sum of the lengths of the + * section's header (8), the properties list (16 times the number of + * properties) and the properties themselves.

+ * + * @return the section's length in bytes. + */ + private int calcSize() + { + int length = 0; + + /* The section header. */ + length += LittleEndianConsts.INT_SIZE * 2; + + /* The length of the property list. */ + Property[] psa = getProperties(); + if (psa == null) + psa = new MutableProperty[0]; + length += psa.length * LittleEndianConsts.INT_SIZE * 3; + + /* The sum of the lengths of the properties - it is calculated by simply + * writing the properties to a temporary byte array output stream: */ + final ByteArrayOutputStream b = new ByteArrayOutputStream(); + for (int i = 0; i < psa.length; i++) + { + final MutableProperty mp = new MutableProperty(); + mp.setID(psa[i].getID()); + mp.setType(psa[i].getType()); + mp.setValue(psa[i].getValue()); + try + { + length += mp.write(b); + } + catch (WritingNotSupportedException ex) + { + /* It was not possible to write the property, not even as a + * byte array. We cannot do anything about that. Instead of the + * property we insert an empty one into the stream. */ + mp.setType(Variant.VT_EMPTY); + mp.setValue(null); + try + { + length += mp.write(b); + } + catch (Exception ex2) + { + /* Even writing an empty property went awfully wrong. + * Let's give up. */ + throw new HPSFRuntimeException(ex2); + } + } + catch (IOException ex) + { + /* Should never occur. */ + throw new HPSFRuntimeException(ex); + } + } + + return length; + } + + + + /** + *

Writes this section into an output stream.

+ * + *

Internally this is done by writing into three byte array output + * streams: one for the properties, one for the property list and one for + * the section as such. The two former are appended to the latter when they + * have received all their data.

+ * + * @param out The stream to write into + * @param offset The offset from the beginning of the property set + * stream this section begins at + * + * @return The offset of the first byte following this section in + * the property set stream. + * @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, final int offset) + throws WritingNotSupportedException, IOException + { + /* 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 to the beginning of the properties themselves. */ + position += 2 * LittleEndian.INT_SIZE + + getPropertyCount() * 2 * LittleEndian.INT_SIZE; + + /* Write the properties and the property list into their respective + * streams: */ + for (final Iterator i = preprops.iterator(); i.hasNext();) + { + final MutableProperty p = (MutableProperty) i.next(); + + /* Write the property list entry. */ + TypeWriter.writeUIntToStream(propertyListStream, p.getID()); + TypeWriter.writeUIntToStream(propertyListStream, position); + + /* Write the property and update the position to the next + * property. */ + position += p.write(propertyStream); + } + propertyStream.close(); + propertyListStream.close(); + + /* Write the section: */ + byte[] pb1 = propertyListStream.toByteArray(); + byte[] pb2 = propertyStream.toByteArray(); + TypeWriter.writeToStream(out, LittleEndian.INT_SIZE * 2 + pb1.length + + pb2.length); + TypeWriter.writeToStream(out, getPropertyCount()); + out.write(pb1); + out.write(pb2); + + return offset + position; + } + + + + /** + *

Overwrites the super class' method to cope with a redundancy: + * the property count is maintained in a separate member variable, but + * shouldn't.

+ * + * @return The number of properties in this section + */ + public int getPropertyCount() + { + return preprops.size(); + } + + + + /** + *

Returns this section's properties.

+ * + * @return this section's properties. + */ + public Property[] getProperties() + { + return (Property[]) preprops.toArray(new Property[0]); + } + +} diff --git a/src/java/org/apache/poi/hpsf/NoPropertySetStreamException.java b/src/java/org/apache/poi/hpsf/NoPropertySetStreamException.java index cfccbb733..8d6d4a71e 100644 --- a/src/java/org/apache/poi/hpsf/NoPropertySetStreamException.java +++ b/src/java/org/apache/poi/hpsf/NoPropertySetStreamException.java @@ -55,18 +55,16 @@ package org.apache.poi.hpsf; /** - *

+ *

This exception is thrown if a format error in a property set stream is + * detected or when the input data do not constitute a property set stream.

+ * + *

The constructors of this class are analogous to those of its superclass + * and are documented there.

* - * This exception is thrown if a format error in a property set stream is - * detected or when the input data do not constitute a property set stream.

- *

- * - * The constructors of this class are analogous to those of its superclass and - * documented there.

- * - *@author Rainer Klute (klute@rainer-klute.de) - *@version $Id$ - *@since 2002-02-09 + * @author Rainer Klute <klute@rainer-klute.de> + * @version $Id$ + * @since 2002-02-09 */ public class NoPropertySetStreamException extends HPSFException { @@ -80,6 +78,7 @@ public class NoPropertySetStreamException extends HPSFException } + /** *

Constructor

* @@ -91,6 +90,7 @@ public class NoPropertySetStreamException extends HPSFException } + /** *

Constructor

* @@ -102,6 +102,7 @@ public class NoPropertySetStreamException extends HPSFException } + /** *

Constructor

* diff --git a/src/java/org/apache/poi/hpsf/NoSingleSectionException.java b/src/java/org/apache/poi/hpsf/NoSingleSectionException.java index 9b22c40b0..c622ec4a7 100644 --- a/src/java/org/apache/poi/hpsf/NoSingleSectionException.java +++ b/src/java/org/apache/poi/hpsf/NoSingleSectionException.java @@ -63,7 +63,8 @@ package org.apache.poi.hpsf; *

The constructors of this class are analogous to those of its * superclass and documented there.

* - * @author Rainer Klute (klute@rainer-klute.de) + * @author Rainer Klute <klute@rainer-klute.de> * @version $Id$ * @since 2002-02-09 */ diff --git a/src/java/org/apache/poi/hpsf/Property.java b/src/java/org/apache/poi/hpsf/Property.java index 1131d4593..0a3bc97fa 100644 --- a/src/java/org/apache/poi/hpsf/Property.java +++ b/src/java/org/apache/poi/hpsf/Property.java @@ -63,8 +63,6 @@ package org.apache.poi.hpsf; import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; import java.util.Map; import org.apache.poi.util.LittleEndian; @@ -85,11 +83,13 @@ import org.apache.poi.util.LittleEndian; * value, {@link Variant#VT_FILETIME} some date and time (of a * file).

* - *

FIXME: Reading is not implemented for all - * {@link Variant} types yet. Feel free to submit error reports or - * patches for the types you need.

+ *

Please note that not all {@link Variant} types yet. This might change + * over time but largely depends on your feedback so that the POI team knows + * which variant types are really needed. So please feel free to submit error + * reports or patches for the types you need.

* - * @author Rainer Klute (klute@rainer-klute.de) + * @author Rainer Klute <klute@rainer-klute.de> * @author Drew Varner (Drew.Varner InAndAround sc.edu) * @see Section * @see Variant @@ -103,7 +103,7 @@ public class Property private static final int CP_UNICODE = 1200; /**

The property's ID.

*/ - protected int id; + protected long id; /** @@ -111,7 +111,7 @@ public class Property * * @return The ID value */ - public int getID() + public long getID() { return id; } @@ -162,7 +162,7 @@ public class Property * @param codepage The section's and thus the property's * codepage. It is needed only when reading string values. */ - public Property(final int id, final byte[] src, final long offset, + public Property(final long id, final byte[] src, final long offset, final int length, final int codepage) { this.id = id; @@ -187,7 +187,7 @@ public class Property } catch (UnsupportedVariantTypeException ex) { - logUnsupported(ex); + VariantSupport.writeUnsupportedTypeMessage(ex); value = ex.getValue(); } } @@ -281,15 +281,21 @@ public class Property * 4.

* * @return the property's size in bytes + * + * @exception WritingNotSupportedException if HPSF does not yet support the + * property's variant type. */ - protected int getSize() + protected int getSize() throws WritingNotSupportedException { - int length = LittleEndian.INT_SIZE; + int length = VariantSupport.getVariantLength(type); + if (length >= 0) + return length; /* Fixed length */ + if (length == -2) + /* Unknown length */ + throw new WritingNotSupportedException(type, null); + + /* Variable length: */ final int PADDING = 4; /* Pad to multiples of 4. */ - if (type > Integer.MAX_VALUE) - throw new HPSFRuntimeException - ("Variant type " + type + " is greater than " + - Integer.MAX_VALUE + "."); switch ((int) type) { case Variant.VT_LPSTR: @@ -304,9 +310,7 @@ public class Property case Variant.VT_EMPTY: break; default: - throw new HPSFRuntimeException - ("Writing is not yet implemented for variant type " + - type + ". Please report this problem to the POI team!"); + throw new WritingNotSupportedException(type, value); } return length; } @@ -318,27 +322,65 @@ public class Property */ public boolean equals(final Object o) { - throw new UnsupportedOperationException("FIXME: Not yet implemented."); + if (!(o instanceof Property)) + return false; + final Property p = (Property) o; + final Object pValue = p.getValue(); + if (id != p.getID() || type != p.getType()) + return false; + if (value == null && pValue == null) + return true; + if (value == null || pValue == null) + return false; + + /* It's clear now that both values are non-null. */ + final Class valueClass = value.getClass(); + final Class pValueClass = pValue.getClass(); + if (!(valueClass.isAssignableFrom(pValueClass)) && + !(pValueClass.isAssignableFrom(valueClass))) + return false; + + if (value instanceof byte[]) + return Util.equal((byte[]) value, (byte[]) pValue); + + return value.equals(pValue); } /** - *

Keeps a list of those variant types for those an "unsupported" message - * has already been issued.

+ * @see Object#hashCode() */ - protected static List unsupportedMessage; - - private static void logUnsupported(final UnsupportedVariantTypeException ex) + public int hashCode() { - if (unsupportedMessage == null) - unsupportedMessage = new LinkedList(); - Long vt = new Long(ex.getVariantType()); - if (!unsupportedMessage.contains(vt)) - { - System.err.println(ex.getMessage()); - unsupportedMessage.add(vt); - } + long hashCode = 0; + hashCode += id; + hashCode += type; + if (value != null) + hashCode += value.hashCode(); + final int returnHashCode = (int) (hashCode & 0x0ffffffffL ); + return returnHashCode; + + } + + + + /** + * @see Object#toString() + */ + public String toString() + { + final StringBuffer b = new StringBuffer(); + b.append(getClass().getName()); + b.append('['); + b.append("id: "); + b.append(getID()); + b.append(", type: "); + b.append(getType()); + b.append(", value: "); + b.append(getValue()); + b.append(']'); + return b.toString(); } } diff --git a/src/java/org/apache/poi/hpsf/PropertySet.java b/src/java/org/apache/poi/hpsf/PropertySet.java index a039ff9bc..8470b44b8 100644 --- a/src/java/org/apache/poi/hpsf/PropertySet.java +++ b/src/java/org/apache/poi/hpsf/PropertySet.java @@ -57,7 +57,6 @@ package org.apache.poi.hpsf; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; -import java.util.LinkedList; import java.util.List; import org.apache.poi.hpsf.wellknown.SectionIDMap; @@ -91,7 +90,8 @@ import org.apache.poi.util.LittleEndian; * NoSingleSectionException} if the {@link PropertySet} contains more * (or less) than exactly one {@link Section}).

* - * @author Rainer Klute (klute@rainer-klute.de) + * @author Rainer Klute <klute@rainer-klute.de> * @author Drew Varner (Drew.Varner hanginIn sc.edu) * @version $Id$ * @since 2002-02-09 @@ -397,7 +397,7 @@ public class PropertySet final int offset, final int length) { - /* FIXME: Ensure that at most "length" bytes are read. */ + /* FIXME (3): Ensure that at most "length" bytes are read. */ /* * Read the header fields of the stream. They must always be @@ -442,7 +442,7 @@ public class PropertySet */ private void init(final byte[] src, final int offset, final int length) { - /* FIXME: Ensure that at most "length" bytes are read. */ + /* FIXME (3): Ensure that at most "length" bytes are read. */ /* * Read the stream's header fields. @@ -645,6 +645,9 @@ public class PropertySet * to the specified parameter, else false.

* * @param o the object to compare this PropertySet with + * + * @return true if the objects are equal, false + * if not */ public boolean equals(final Object o) { @@ -672,4 +675,43 @@ public class PropertySet return Util.equals(getSections(), ps.getSections()); } + + + /** + * @see Object#hashCode() + */ + public int hashCode() + { + throw new UnsupportedOperationException("FIXME: Not yet implemented."); + } + + + + /** + * @see Object#toString() + */ + public String toString() + { + final StringBuffer b = new StringBuffer(); + final int sectionCount = getSectionCount(); + b.append(getClass().getName()); + b.append('['); + b.append("byteOrder: "); + b.append(getByteOrder()); + b.append(", classID: "); + b.append(getClassID()); + b.append(", format: "); + b.append(getFormat()); + b.append(", OSVersion: "); + b.append(getOSVersion()); + b.append(", sectionCount: "); + b.append(sectionCount); + b.append(", sections: ["); + final List sections = getSections(); + for (int i = 0; i < sectionCount; i++) + b.append(((Section) sections.get(0)).toString()); + b.append(']'); + b.append(']'); + return b.toString(); + } } diff --git a/src/java/org/apache/poi/hpsf/PropertySetFactory.java b/src/java/org/apache/poi/hpsf/PropertySetFactory.java index bb03da05c..39b4ac88a 100644 --- a/src/java/org/apache/poi/hpsf/PropertySetFactory.java +++ b/src/java/org/apache/poi/hpsf/PropertySetFactory.java @@ -61,7 +61,8 @@ import java.io.IOException; *

Factory class to create instances of {@link SummaryInformation}, * {@link DocumentSummaryInformation} and {@link PropertySet}.

* - * @author Rainer Klute (klute@rainer-klute.de) + * @author Rainer Klute <klute@rainer-klute.de> * @version $Id$ * @since 2002-02-09 */ diff --git a/src/java/org/apache/poi/hpsf/ReadingNotSupportedException.java b/src/java/org/apache/poi/hpsf/ReadingNotSupportedException.java index 1daf2e647..c748e2f8f 100644 --- a/src/java/org/apache/poi/hpsf/ReadingNotSupportedException.java +++ b/src/java/org/apache/poi/hpsf/ReadingNotSupportedException.java @@ -63,8 +63,11 @@ package org.apache.poi.hpsf; /** - *

This exception is thrown when trying to read a (yet) unsupported variant - * type.

+ *

This exception is thrown when HPSF tries to read a (yet) unsupported + * variant type.

+ * + * @see WritingNotSupportedException + * @see UnsupportedVariantTypeException * * @author Rainer Klute <klute@rainer-klute.de> @@ -78,10 +81,11 @@ public class ReadingNotSupportedException /** *

Constructor

* - * @param variantType - * @param value + * @param variantType The unsupported variant type. + * @param value The value. */ - public ReadingNotSupportedException(long variantType, Object value) + public ReadingNotSupportedException(final long variantType, + final Object value) { super(variantType, value); } diff --git a/src/java/org/apache/poi/hpsf/Section.java b/src/java/org/apache/poi/hpsf/Section.java index 8ec477103..31538b71b 100644 --- a/src/java/org/apache/poi/hpsf/Section.java +++ b/src/java/org/apache/poi/hpsf/Section.java @@ -67,7 +67,8 @@ import org.apache.poi.util.LittleEndian; /** *

Represents a section in a {@link PropertySet}.

* - * @author Rainer Klute (klute@rainer-klute.de) + * @author Rainer Klute <klute@rainer-klute.de> * @author Drew Varner (Drew.Varner allUpIn sc.edu) * @version $Id$ * @since 2002-02-09 @@ -493,7 +494,7 @@ public class Section /** *

Checks whether this section is equal to another object.

* - * @param o The object to cpmpare this section with + * @param o The object to compare this section with * @return true if the objects are equal, false if * not */ @@ -509,4 +510,50 @@ public class Section return Util.equals(s.getProperties(), getProperties()); } + + + /** + * @see Object#hashCode() + */ + public int hashCode() + { + long hashCode = 0; + hashCode += getFormatID().hashCode(); + final Property[] pa = getProperties(); + for (int i = 0; i < pa.length; i++) + hashCode += pa[i].hashCode(); + final int returnHashCode = (int) (hashCode & 0x0ffffffffL); + return returnHashCode; + } + + + + /** + * @see Object#toString() + */ + public String toString() + { + final StringBuffer b = new StringBuffer(); + final Property[] pa = getProperties(); + b.append(getClass().getName()); + b.append('['); + b.append("formatID: "); + b.append(getFormatID()); + b.append(", offset: "); + b.append(getOffset()); + b.append(", propertyCount: "); + b.append(getPropertyCount()); + b.append(", size: "); + b.append(getSize()); + b.append(", properties: [\n"); + for (int i = 0; i < pa.length; i++) + { + b.append(pa[i].toString()); + b.append(",\n"); + } + b.append(']'); + b.append(']'); + return b.toString(); + } + } diff --git a/src/java/org/apache/poi/hpsf/SpecialPropertySet.java b/src/java/org/apache/poi/hpsf/SpecialPropertySet.java index 027944934..75eeb0fe1 100644 --- a/src/java/org/apache/poi/hpsf/SpecialPropertySet.java +++ b/src/java/org/apache/poi/hpsf/SpecialPropertySet.java @@ -82,7 +82,8 @@ import java.util.List; * went the other way round historically: the convenience classes came * only late to my mind.

* - * @author Rainer Klute (klute@rainer-klute.de) + * @author Rainer Klute <klute@rainer-klute.de> * @version $Id$ * @since 2002-02-09 */ diff --git a/src/java/org/apache/poi/hpsf/SummaryInformation.java b/src/java/org/apache/poi/hpsf/SummaryInformation.java index be54e79b0..5384ae4ee 100644 --- a/src/java/org/apache/poi/hpsf/SummaryInformation.java +++ b/src/java/org/apache/poi/hpsf/SummaryInformation.java @@ -69,7 +69,8 @@ import org.apache.poi.hpsf.wellknown.PropertyIDMap; * href="http://msdn.microsoft.com/library/default.asp?url=/library/en-us/com/stgu_8910.asp">http://msdn.microsoft.com/library/default.asp?url=/library/en-us/com/stgu_8910.asp * for documentation from That Redmond Company.

* - * @author Rainer Klute (klute@rainer-klute.de) + * @author Rainer Klute <klute@rainer-klute.de> * @see DocumentSummaryInformation * @version $Id$ * @since 2002-02-09 @@ -297,7 +298,7 @@ public class SummaryInformation extends SpecialPropertySet * when this method is implemented. Please note that the * return type is likely to change!

* - *

FIXME / Hint to developers: Drew Varner + *

FIXME (3) / Hint to developers: Drew Varner * <Drew.Varner -at- sc.edu> said that this is an image in * WMF or Clipboard (BMP?) format. He also provided two links that * might be helpful: Class for writing little-endian data and more.

* - * @author Rainer Klute (klute@rainer-klute.de) + * @author Rainer Klute <klute@rainer-klute.de> * @version $Id$ * @since 2003-02-20 */ @@ -81,8 +81,9 @@ public class TypeWriter /** *

Writes a two-byte value (short) to an output stream.

* - * @param out The stream to write to - * @param n The value to write + * @param out The stream to write to. + * @param n The value to write. + * @return The number of bytes that have been written. * @exception IOException if an I/O error occurs */ public static int writeToStream(final OutputStream out, final short n) @@ -149,7 +150,7 @@ public class TypeWriter throws IOException { long high = n & 0xFFFFFFFF00000000L; - if (high != 0) + if (high != 0 && high != 0xFFFFFFFF00000000L) throw new IllegalPropertySetDataException ("Value " + n + " cannot be represented by 4 bytes."); return writeToStream(out, (int) n); diff --git a/src/java/org/apache/poi/hpsf/UnexpectedPropertySetTypeException.java b/src/java/org/apache/poi/hpsf/UnexpectedPropertySetTypeException.java index 0f541f989..910bd0292 100644 --- a/src/java/org/apache/poi/hpsf/UnexpectedPropertySetTypeException.java +++ b/src/java/org/apache/poi/hpsf/UnexpectedPropertySetTypeException.java @@ -62,7 +62,8 @@ package org.apache.poi.hpsf; *

The constructors of this class are analogous to those of its * superclass and documented there.

* - * @author Rainer Klute (klute@rainer-klute.de) + * @author Rainer Klute <klute@rainer-klute.de> * @version $Id$ * @since 2002-02-09 */ diff --git a/src/java/org/apache/poi/hpsf/UnsupportedVariantTypeException.java b/src/java/org/apache/poi/hpsf/UnsupportedVariantTypeException.java index 6cad52662..7d725991e 100644 --- a/src/java/org/apache/poi/hpsf/UnsupportedVariantTypeException.java +++ b/src/java/org/apache/poi/hpsf/UnsupportedVariantTypeException.java @@ -88,7 +88,7 @@ public abstract class UnsupportedVariantTypeException extends HPSFException { super("HPSF does not yet support the variant type " + variantType + " (" + Variant.getVariantName(variantType) + ", " + - HexDump.toHex((int) variantType) + "). If you want support for " + + HexDump.toHex(variantType) + "). If you want support for " + "this variant type in one of the next POI releases please " + "submit a request for enhancement (RFE) to " + "! Thank you!"); diff --git a/src/java/org/apache/poi/hpsf/Util.java b/src/java/org/apache/poi/hpsf/Util.java index b340df047..552d86651 100644 --- a/src/java/org/apache/poi/hpsf/Util.java +++ b/src/java/org/apache/poi/hpsf/Util.java @@ -78,7 +78,7 @@ public class Util *
  • if for each i with * i >= 0 and * i < a.length holds - * a[i] == b[i].

  • + * a[i] == b[i].

    * * * @@ -191,6 +191,16 @@ public class Util return new Date(ms_since_19700101); } + + + /** + *

    Converts a {@link Date} into a filetime.

    + * + * @param date The date to be converted + * @return The filetime + * + * @see #filetimeToDate + */ public static long dateToFileTime(final Date date) { long ms_since_19700101 = date.getTime(); @@ -228,6 +238,17 @@ public class Util return internalEquals(o1, o2); } + + + /** + *

    Compares to object arrays with regarding the objects' order. For + * example, [1, 2, 3] and [2, 1, 3] are equal.

    + * + * @param c1 The first object array. + * @param c2 The second object array. + * @return true if the object arrays are equal, + * false if they are not. + */ public static boolean equals(final Object[] c1, final Object[] c2) { final Object[] o1 = (Object[]) c1.clone(); @@ -252,4 +273,68 @@ public class Util return true; } + + + /** + *

    Pads a byte array with 0x00 bytes so that its length is a multiple of + * 4.

    + * + * @param ba The byte array to pad. + * @return The padded byte array. + */ + public static byte[] pad4(final byte[] ba) + { + final int PAD = 4; + final byte[] result; + int l = ba.length % PAD; + if (l == 0) + result = ba; + else + { + l = PAD - l; + result = new byte[ba.length + l]; + System.arraycopy(ba, 0, result, 0, ba.length); + } + return result; + } + + + + /** + *

    Pads a character array with 0x0000 characters so that its length is a + * multiple of 4.

    + * + * @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; + } + + + + /** + *

    Pads a string with 0x0000 characters so that its length is a + * multiple of 4.

    + * + * @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()); + } + } diff --git a/src/java/org/apache/poi/hpsf/Variant.java b/src/java/org/apache/poi/hpsf/Variant.java index 54f41255b..b12aa80da 100644 --- a/src/java/org/apache/poi/hpsf/Variant.java +++ b/src/java/org/apache/poi/hpsf/Variant.java @@ -54,6 +54,7 @@ */ package org.apache.poi.hpsf; +import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -357,79 +358,193 @@ public class Variant public static final int VT_BYREF = 0x4000; /** - *

    FIXME: Document this!

    + *

    FIXME (3): Document this!

    */ public static final int VT_RESERVED = 0x8000; /** - *

    FIXME: Document this!

    + *

    FIXME (3): Document this!

    */ public static final int VT_ILLEGAL = 0xFFFF; /** - *

    FIXME: Document this!

    + *

    FIXME (3): Document this!

    */ public static final int VT_ILLEGALMASKED = 0xFFF; /** - *

    FIXME: Document this!

    + *

    FIXME (3): Document this!

    */ public static final int VT_TYPEMASK = 0xFFF; - public static final Map m = new HashMap(); + /** + *

    Maps the numbers denoting the variant types to their corresponding + * variant type names.

    + */ + private static Map numberToName; + + private static Map numberToLength; + + /** + *

    Denotes a variant type with a length that is unknown to HPSF yet.

    + */ + public static final Integer LENGTH_UNKNOWN = new Integer(-2); + + /** + *

    Denotes a variant type with a variable length.

    + */ + public static final Integer LENGTH_VARIABLE = new Integer(-1); + + /** + *

    Denotes a variant type with a length of 0 bytes.

    + */ + public static final Integer LENGTH_0 = new Integer(0); + + /** + *

    Denotes a variant type with a length of 2 bytes.

    + */ + public static final Integer LENGTH_2 = new Integer(2); + + /** + *

    Denotes a variant type with a length of 4 bytes.

    + */ + public static final Integer LENGTH_4 = new Integer(4); + + /** + *

    Denotes a variant type with a length of 8 bytes.

    + */ + public static final Integer LENGTH_8 = new Integer(8); + + static { - m.put(new Integer(0), "VT_EMPTY"); - m.put(new Integer(1), "VT_NULL"); - m.put(new Integer(2), "VT_I2"); - m.put(new Integer(3), "VT_I4"); - m.put(new Integer(4), "VT_R4"); - m.put(new Integer(5), "VT_R8"); - m.put(new Integer(6), "VT_CY"); - m.put(new Integer(7), "VT_DATE"); - m.put(new Integer(8), "VT_BSTR"); - m.put(new Integer(9), "VT_DISPATCH"); - m.put(new Integer(10), "VT_ERROR"); - m.put(new Integer(11), "VT_BOOL"); - m.put(new Integer(12), "VT_VARIANT"); - m.put(new Integer(13), "VT_UNKNOWN"); - m.put(new Integer(14), "VT_DECIMAL"); - m.put(new Integer(16), "VT_I1"); - m.put(new Integer(17), "VT_UI1"); - m.put(new Integer(18), "VT_UI2"); - m.put(new Integer(19), "VT_UI4"); - m.put(new Integer(20), "VT_I8"); - m.put(new Integer(21), "VT_UI8"); - m.put(new Integer(22), "VT_INT"); - m.put(new Integer(23), "VT_UINT"); - m.put(new Integer(24), "VT_VOID"); - m.put(new Integer(25), "VT_HRESULT"); - m.put(new Integer(26), "VT_PTR"); - m.put(new Integer(27), "VT_SAFEARRAY"); - m.put(new Integer(28), "VT_CARRAY"); - m.put(new Integer(29), "VT_USERDEFINED"); - m.put(new Integer(30), "VT_LPSTR"); - m.put(new Integer(31), "VT_LPWSTR"); - m.put(new Integer(64), "VT_FILETIME"); - m.put(new Integer(65), "VT_BLOB"); - m.put(new Integer(66), "VT_STREAM"); - m.put(new Integer(67), "VT_STORAGE"); - m.put(new Integer(68), "VT_STREAMED_OBJECT"); - m.put(new Integer(69), "VT_STORED_OBJECT"); - m.put(new Integer(70), "VT_BLOB_OBJECT"); - m.put(new Integer(71), "VT_CF"); - m.put(new Integer(72), "VT_CLSID"); + /* Initialize the number-to-name map: */ + Map tm1 = new HashMap(); + tm1.put(new Long(0), "VT_EMPTY"); + tm1.put(new Long(1), "VT_NULL"); + tm1.put(new Long(2), "VT_I2"); + tm1.put(new Long(3), "VT_I4"); + tm1.put(new Long(4), "VT_R4"); + tm1.put(new Long(5), "VT_R8"); + tm1.put(new Long(6), "VT_CY"); + tm1.put(new Long(7), "VT_DATE"); + tm1.put(new Long(8), "VT_BSTR"); + tm1.put(new Long(9), "VT_DISPATCH"); + tm1.put(new Long(10), "VT_ERROR"); + tm1.put(new Long(11), "VT_BOOL"); + tm1.put(new Long(12), "VT_VARIANT"); + tm1.put(new Long(13), "VT_UNKNOWN"); + tm1.put(new Long(14), "VT_DECIMAL"); + tm1.put(new Long(16), "VT_I1"); + tm1.put(new Long(17), "VT_UI1"); + tm1.put(new Long(18), "VT_UI2"); + tm1.put(new Long(19), "VT_UI4"); + tm1.put(new Long(20), "VT_I8"); + tm1.put(new Long(21), "VT_UI8"); + tm1.put(new Long(22), "VT_INT"); + tm1.put(new Long(23), "VT_UINT"); + tm1.put(new Long(24), "VT_VOID"); + tm1.put(new Long(25), "VT_HRESULT"); + tm1.put(new Long(26), "VT_PTR"); + tm1.put(new Long(27), "VT_SAFEARRAY"); + tm1.put(new Long(28), "VT_CARRAY"); + tm1.put(new Long(29), "VT_USERDEFINED"); + tm1.put(new Long(30), "VT_LPSTR"); + tm1.put(new Long(31), "VT_LPWSTR"); + tm1.put(new Long(64), "VT_FILETIME"); + tm1.put(new Long(65), "VT_BLOB"); + tm1.put(new Long(66), "VT_STREAM"); + tm1.put(new Long(67), "VT_STORAGE"); + tm1.put(new Long(68), "VT_STREAMED_OBJECT"); + tm1.put(new Long(69), "VT_STORED_OBJECT"); + tm1.put(new Long(70), "VT_BLOB_OBJECT"); + tm1.put(new Long(71), "VT_CF"); + tm1.put(new Long(72), "VT_CLSID"); + Map tm2 = new HashMap(tm1.size(), 1.0F); + tm2.putAll(tm1); + numberToName = Collections.unmodifiableMap(tm2); + + /* Initialize the number-to-length map: */ + tm1.clear(); + tm1.put(new Long(0), LENGTH_0); + tm1.put(new Long(1), LENGTH_UNKNOWN); + tm1.put(new Long(2), LENGTH_2); + tm1.put(new Long(3), LENGTH_4); + tm1.put(new Long(4), LENGTH_4); + tm1.put(new Long(5), LENGTH_8); + tm1.put(new Long(6), LENGTH_UNKNOWN); + tm1.put(new Long(7), LENGTH_UNKNOWN); + tm1.put(new Long(8), LENGTH_UNKNOWN); + tm1.put(new Long(9), LENGTH_UNKNOWN); + tm1.put(new Long(10), LENGTH_UNKNOWN); + tm1.put(new Long(11), LENGTH_UNKNOWN); + tm1.put(new Long(12), LENGTH_UNKNOWN); + tm1.put(new Long(13), LENGTH_UNKNOWN); + tm1.put(new Long(14), LENGTH_UNKNOWN); + tm1.put(new Long(16), LENGTH_UNKNOWN); + tm1.put(new Long(17), LENGTH_UNKNOWN); + tm1.put(new Long(18), LENGTH_UNKNOWN); + tm1.put(new Long(19), LENGTH_UNKNOWN); + tm1.put(new Long(20), LENGTH_UNKNOWN); + tm1.put(new Long(21), LENGTH_UNKNOWN); + tm1.put(new Long(22), LENGTH_UNKNOWN); + tm1.put(new Long(23), LENGTH_UNKNOWN); + tm1.put(new Long(24), LENGTH_UNKNOWN); + tm1.put(new Long(25), LENGTH_UNKNOWN); + tm1.put(new Long(26), LENGTH_UNKNOWN); + tm1.put(new Long(27), LENGTH_UNKNOWN); + tm1.put(new Long(28), LENGTH_UNKNOWN); + tm1.put(new Long(29), LENGTH_UNKNOWN); + tm1.put(new Long(30), LENGTH_VARIABLE); + tm1.put(new Long(31), LENGTH_UNKNOWN); + tm1.put(new Long(64), LENGTH_8); + tm1.put(new Long(65), LENGTH_UNKNOWN); + tm1.put(new Long(66), LENGTH_UNKNOWN); + tm1.put(new Long(67), LENGTH_UNKNOWN); + tm1.put(new Long(68), LENGTH_UNKNOWN); + tm1.put(new Long(69), LENGTH_UNKNOWN); + tm1.put(new Long(70), LENGTH_UNKNOWN); + tm1.put(new Long(71), LENGTH_UNKNOWN); + tm1.put(new Long(72), LENGTH_UNKNOWN); + tm2 = new HashMap(tm1.size(), 1.0F); + tm2.putAll(tm1); + numberToLength = Collections.unmodifiableMap(tm2); } + /** + *

    Returns the variant type name associated with a variant type + * number.

    + * + * @param variantType The variant type number + * @return The variant type name or the string "unknown variant type" + */ public static String getVariantName(final long variantType) { - String name = (String) m.get(new Integer((int) variantType)); + final String name = (String) numberToName.get(new Long(variantType)); return name != null ? name : "unknown variant type"; } -} \ No newline at end of file + /** + *

    Returns a variant type's length.

    + * + * @param variantType The variant type number + * @return The length of the variant type's data in bytes. If the length is + * variable, i.e. the length of a string, -1 is returned. If HPSF does not + * know the length, -2 is returned. The latter usually indicates an + * unsupported variant type. + */ + public static int getVariantLength(final long variantType) + { + final Long key = new Long((int) variantType); + final Long length = (Long) numberToLength.get(key); + if (length == null) + return -2; + return length.intValue(); + } + +} diff --git a/src/java/org/apache/poi/hpsf/VariantSupport.java b/src/java/org/apache/poi/hpsf/VariantSupport.java index f67eec47c..4bb9f5ee9 100644 --- a/src/java/org/apache/poi/hpsf/VariantSupport.java +++ b/src/java/org/apache/poi/hpsf/VariantSupport.java @@ -65,6 +65,8 @@ package org.apache.poi.hpsf; import java.io.IOException; import java.io.OutputStream; import java.util.Date; +import java.util.LinkedList; +import java.util.List; import org.apache.poi.util.LittleEndian; import org.apache.poi.util.LittleEndianConsts; @@ -72,8 +74,8 @@ import org.apache.poi.util.LittleEndianConsts; /** *

    Supports reading and writing of variant data.

    * - *

    FIXME: Reading and writing must be made more uniform than - * it is now. The following items should be resolved: + *

    FIXME (3): Reading and writing should be made more + * uniform than it is now. The following items should be resolved: * *

      * @@ -87,14 +89,73 @@ import org.apache.poi.util.LittleEndianConsts; * * @author Rainer Klute <klute@rainer-klute.de> - * @since 08.08.2003 + * @since 2003-08-08 * @version $Id$ */ public class VariantSupport extends Variant { + private static boolean logUnsupportedTypes = false; + /** - *

      Reads a variant data type from a byte array.

      + *

      Specifies whether warnings about unsupported variant types are to be + * written to System.err or not.

      + * + * @param logUnsupportedTypes If true warnings will be written, + * if false they won't. + */ + public static void setLogUnsupportedTypes(final boolean logUnsupportedTypes) + { + VariantSupport.logUnsupportedTypes = logUnsupportedTypes; + } + + /** + *

      Checks whether logging of unsupported variant types warning is turned + * on or off.

      + * + * @return true if logging is turned on, else + * false. + */ + public static boolean isLogUnsupportedTypes() + { + return logUnsupportedTypes; + } + + + + /** + *

      Keeps a list of the variant types an "unsupported" message has already + * been issued for.

      + */ + protected static List unsupportedMessage; + + /** + *

      Writes a warning to System.err that a variant type is + * unsupported by HPSF. Such a warning is written only once for each variant + * type. Log messages can be turned on or off by

      + * + * @param ex The exception to log + */ + protected static void writeUnsupportedTypeMessage + (final UnsupportedVariantTypeException ex) + { + if (isLogUnsupportedTypes()) + { + if (unsupportedMessage == null) + unsupportedMessage = new LinkedList(); + Long vt = new Long(ex.getVariantType()); + if (!unsupportedMessage.contains(vt)) + { + System.err.println(ex.getMessage()); + unsupportedMessage.add(vt); + } + } + } + + + + /** + *

      Reads a variant type from a byte array.

      * * @param src The byte array * @param offset The offset in the byte array where the variant @@ -105,8 +166,8 @@ public class VariantSupport extends Variant * @return A Java object that corresponds best to the variant * field. For example, a VT_I4 is returned as a {@link Long}, a * VT_LPSTR as a {@link String}. - * @exception UnsupportedVariantTypeException if HPSF does not (yet) - * support the variant type which is to be read + * @exception ReadingNotSupportedException if a property is to be written + * who's variant type HPSF does not yet support * * @see Variant */ @@ -161,7 +222,7 @@ public class VariantSupport extends Variant * String object. The 0x00 bytes at the end must be * stripped. * - * FIXME: Reading an 8-bit string should pay attention + * FIXME (2): Reading an 8-bit string should pay attention * to the codepage. Currently the byte making out the * property's value are interpreted according to the * platform's default character set. @@ -238,51 +299,57 @@ public class VariantSupport extends Variant /** - *

      Writes a variant value to an output stream.

      + *

      Writes a variant value to an output stream. This method ensures that + * always a multiple of 4 bytes is written.

      * * @param out The stream to write the value to. * @param type The variant's type. * @param value The variant's value. * @return The number of entities that have been written. In many cases an * "entity" is a byte but this is not always the case. + * @exception IOException if an I/O exceptions occurs + * @exception WritingNotSupportedException if a property is to be written + * who's variant type HPSF does not yet support */ public static int write(final OutputStream out, final long type, - final Object value) + final Object value) throws IOException, WritingNotSupportedException { + int length = 0; switch ((int) type) { case Variant.VT_BOOL: { int trueOrFalse; - int length = 0; if (((Boolean) value).booleanValue()) trueOrFalse = 1; else trueOrFalse = 0; - length += TypeWriter.writeUIntToStream(out, trueOrFalse); - return length; + length = TypeWriter.writeUIntToStream(out, trueOrFalse); + break; } case Variant.VT_LPSTR: { - TypeWriter.writeUIntToStream + length = TypeWriter.writeUIntToStream (out, ((String) value).length() + 1); - char[] s = toPaddedCharArray((String) value); - /* FIXME: The following line forces characters to bytes. This - * is generally wrong and should only be done according to a - * codepage. Alternatively Unicode could be written (see + char[] s = Util.pad4((String) value); + /* FIXME (2): The following line forces characters to bytes. + * This is generally wrong and should only be done according to + * a codepage. Alternatively Unicode could be written (see * Variant.VT_LPWSTR). */ - byte[] b = new byte[s.length]; + byte[] b = new byte[s.length + 1]; for (int i = 0; i < s.length; i++) b[i] = (byte) s[i]; + b[b.length - 1] = 0x00; out.write(b); - return b.length; + length += b.length; + break; } case Variant.VT_LPWSTR: { final int nrOfChars = ((String) value).length() + 1; TypeWriter.writeUIntToStream(out, nrOfChars); - char[] s = toPaddedCharArray((String) value); + char[] s = Util.pad4((String) value); for (int i = 0; i < s.length; i++) { final int high = (int) ((s[i] & 0xff00) >> 8); @@ -292,79 +359,73 @@ public class VariantSupport extends Variant out.write(lowb); out.write(highb); } - return nrOfChars * 2; + length = nrOfChars * 2; + out.write(0x00); + out.write(0x00); + length += 2; + break; } case Variant.VT_CF: { final byte[] b = (byte[]) value; out.write(b); - return b.length; + length = b.length; + break; } case Variant.VT_EMPTY: { TypeWriter.writeUIntToStream(out, Variant.VT_EMPTY); - return LittleEndianConsts.INT_SIZE; + length = LittleEndianConsts.INT_SIZE; + break; } case Variant.VT_I2: { TypeWriter.writeToStream(out, ((Integer) value).shortValue()); - return LittleEndianConsts.SHORT_SIZE; + length = LittleEndianConsts.SHORT_SIZE; + break; } case Variant.VT_I4: { TypeWriter.writeToStream(out, ((Long) value).intValue()); - return LittleEndianConsts.INT_SIZE; + length = LittleEndianConsts.INT_SIZE; + break; } case Variant.VT_FILETIME: { - int length = 0; long filetime = Util.dateToFileTime((Date) value); int high = (int) ((filetime >> 32) & 0xFFFFFFFFL); int low = (int) (filetime & 0x00000000FFFFFFFFL); - length += TypeWriter.writeUIntToStream(out, 0x0000000FFFFFFFFL & low); - length += TypeWriter.writeUIntToStream(out, 0x0000000FFFFFFFFL & high); - return length; + length += TypeWriter.writeUIntToStream + (out, 0x0000000FFFFFFFFL & low); + length += TypeWriter.writeUIntToStream + (out, 0x0000000FFFFFFFFL & high); + break; } default: { - throw new WritingNotSupportedException(type, value); - } + /* The variant type is not supported yet. However, if the value + * is a byte array we can write it nevertheless. */ + if (value instanceof byte[]) + { + final byte[] b = (byte[]) value; + out.write(b); + length = b.length; + writeUnsupportedTypeMessage + (new WritingNotSupportedException(type, value)); + } + else + throw new WritingNotSupportedException(type, value); + break; + } } - } - - - /** - *

      Converts a string into a 0x00-terminated character sequence padded - * with 0x00 bytes to a multiple of 4.

      - * - * @param value The string to convert - * @return The padded character array - */ - private static char[] toPaddedCharArray(final String s) - { - final int PADDING = 4; - int dl = s.length() + 1; - final int r = dl % 4; - if (r > 0) - dl += PADDING - r; - char[] buffer = new char[dl]; - s.getChars(0, s.length(), buffer, 0); - for (int i = s.length(); i < dl; i++) - buffer[i] = (char) 0; - return buffer; - } - - - - public static int getLength(final long variantType, final int lengthInBytes) - { - switch ((int) variantType) + /* Add 0x00 character to write a multiple of four bytes: */ + while (length % 4 != 0) { - case VT_LPWSTR: - return lengthInBytes / 2; - default: - return lengthInBytes; + out.write(0); + length++; } + return length; } + } \ No newline at end of file diff --git a/src/java/org/apache/poi/hpsf/WritingNotSupportedException.java b/src/java/org/apache/poi/hpsf/WritingNotSupportedException.java index a0fe7ea30..dcfb12c83 100644 --- a/src/java/org/apache/poi/hpsf/WritingNotSupportedException.java +++ b/src/java/org/apache/poi/hpsf/WritingNotSupportedException.java @@ -65,6 +65,9 @@ package org.apache.poi.hpsf; /** *

      This exception is thrown when trying to write a (yet) unsupported variant * type.

      + * + * @see ReadingNotSupportedException + * @see UnsupportedVariantTypeException * * @author Rainer Klute <klute@rainer-klute.de> @@ -78,10 +81,11 @@ public class WritingNotSupportedException /** *

      Constructor

      * - * @param variantType - * @param value + * @param variantType The unsupported varian type. + * @param value The value. */ - public WritingNotSupportedException(long variantType, Object value) + public WritingNotSupportedException(final long variantType, + final Object value) { super(variantType, value); } diff --git a/src/java/org/apache/poi/hpsf/wellknown/PropertyIDMap.java b/src/java/org/apache/poi/hpsf/wellknown/PropertyIDMap.java index f5a907449..00f6eb38f 100644 --- a/src/java/org/apache/poi/hpsf/wellknown/PropertyIDMap.java +++ b/src/java/org/apache/poi/hpsf/wellknown/PropertyIDMap.java @@ -66,9 +66,9 @@ import java.util.HashMap; * should treat them as unmodifiable, copy them and modifiy the * copies.

      * - *

      FIXME: Make the singletons unmodifiable. However, + *

      FIXME (3): Make the singletons unmodifiable. However, * since this requires to use a {@link HashMap} delegate instead of - * extending {@link HashMap} and thus requires a lot of stupid typing. I won't + * extending {@link HashMap} and thus requires a lot of stupid typing, I won't * do that for the time being.

      * * @author Rainer Klute (klute@rainer-klute.de) @@ -141,7 +141,7 @@ public class PropertyIDMap extends HashMap * document

      */ public static final int PID_APPNAME = 18; - /**

      ID of the property that denotes... FIXME

      */ + /**

      ID of the property that denotes... FIXME (2)

      */ public static final int PID_SECURITY = 19;