HPSF writing capability added.

git-svn-id: https://svn.apache.org/repos/asf/jakarta/poi/trunk@353321 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Rainer Klute 2003-08-30 09:13:53 +00:00
parent aeaa6a0856
commit f85ef3c94e
25 changed files with 1418 additions and 197 deletions

View File

@ -61,7 +61,8 @@ package org.apache.poi.hpsf;
* order. Instead, it is a double word (4 bytes) followed by two * order. Instead, it is a double word (4 bytes) followed by two
* words (2 bytes each) followed by 8 bytes.</p> * words (2 bytes each) followed by 8 bytes.</p>
* *
* @author Rainer Klute (klute@rainer-klute.de) * @author Rainer Klute <a
* href="mailto:klute@rainer-klute.de">&lt;klute@rainer-klute.de&gt;</a>
* @version $Id$ * @version $Id$
* @since 2002-02-09 * @since 2002-02-09
*/ */
@ -228,4 +229,14 @@ public class ClassID
return true; return true;
} }
/**
* @see Object#hashCode()
*/
public int hashCode()
{
throw new UnsupportedOperationException("FIXME: Not yet implemented.");
}
} }

View File

@ -60,7 +60,8 @@ import org.apache.poi.hpsf.wellknown.PropertyIDMap;
* <p>Convenience class representing a DocumentSummary Information stream in a * <p>Convenience class representing a DocumentSummary Information stream in a
* Microsoft Office document.</p> * Microsoft Office document.</p>
* *
* @author Rainer Klute (klute@rainer-klute.de) * @author Rainer Klute <a
* href="mailto:klute@rainer-klute.de">&lt;klute@rainer-klute.de&gt;</a>
* @author Drew Varner (Drew.Varner closeTo sc.edu) * @author Drew Varner (Drew.Varner closeTo sc.edu)
* @see SummaryInformation * @see SummaryInformation
* @version $Id$ * @version $Id$
@ -289,7 +290,7 @@ public class DocumentSummaryInformation extends SpecialPropertySet
* <p>Returns <code>true</code> if the custom links are hampered * <p>Returns <code>true</code> if the custom links are hampered
* by excessive noise, for all applications.</p> <p> * by excessive noise, for all applications.</p> <p>
* *
* <strong>FIXME:</strong> Explain this some more! I (Rainer) * <strong>FIXME (3):</strong> Explain this some more! I (Rainer)
* don't understand it.</p> * don't understand it.</p>
* *
* @return The linksDirty value * @return The linksDirty value

View File

@ -59,7 +59,8 @@ package org.apache.poi.hpsf;
* thrown in this package. It supports a nested "reason" throwable, * thrown in this package. It supports a nested "reason" throwable,
* i.e. an exception that caused this one to be thrown.</p> * i.e. an exception that caused this one to be thrown.</p>
* *
* @author Rainer Klute (klute@rainer-klute.de) * @author Rainer Klute <a
* href="mailto:klute@rainer-klute.de">&lt;klute@rainer-klute.de&gt;</a>
* @version $Id$ * @version $Id$
* @since 2002-02-09 * @since 2002-02-09
*/ */

View File

@ -54,12 +54,16 @@
*/ */
package org.apache.poi.hpsf; package org.apache.poi.hpsf;
import java.io.PrintStream;
import java.io.PrintWriter;
/** /**
* <p>This exception is the superclass of all other unchecked * <p>This exception is the superclass of all other unchecked
* exceptions thrown in this package. It supports a nested "reason" * exceptions thrown in this package. It supports a nested "reason"
* throwable, i.e. an exception that caused this one to be thrown.</p> * throwable, i.e. an exception that caused this one to be thrown.</p>
* *
* @author Rainer Klute (klute@rainer-klute.de) * @author Rainer Klute <a
* href="mailto:klute@rainer-klute.de">&lt;klute@rainer-klute.de&gt;</a>
* @version $Id$ * @version $Id$
* @since 2002-02-09 * @since 2002-02-09
*/ */
@ -138,4 +142,46 @@ public class HPSFRuntimeException extends RuntimeException
return reason; 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);
}
}
} }

View File

@ -58,7 +58,8 @@ package org.apache.poi.hpsf;
* <p>This exception is thrown if an {@link java.io.InputStream} does * <p>This exception is thrown if an {@link java.io.InputStream} does
* not support the {@link java.io.InputStream#mark} operation.</p> * not support the {@link java.io.InputStream#mark} operation.</p>
* *
* @author Rainer Klute (klute@rainer-klute.de) * @author Rainer Klute <a
* href="mailto:klute@rainer-klute.de">&lt;klute@rainer-klute.de&gt;</a>
* @version $Id$ * @version $Id$
* @since 2002-02-09 * @since 2002-02-09
*/ */

View File

@ -0,0 +1,85 @@
package org.apache.poi.hpsf;
import java.io.IOException;
import java.io.OutputStream;
/**
* <p>Adds writing capability to the {@link Property} class.</p>
*
* <p>Please be aware that this class' functionality will be merged into the
* {@link Property} class at a later time, so the API will change.</p>
*
* @author Rainer Klute <a
* href="mailto:klute@rainer-klute.de">&lt;klute@rainer-klute.de&gt;</a>
* @since 2003-08-03
* @version $Id$
*/
public class MutableProperty extends Property
{
/**
* <p>Creates an empty property. It must be filled using the set method to
* be usable.</p>
*/
public MutableProperty()
{ }
/**
* <p>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.
* @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;
}
}

View File

@ -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
* <http://www.apache.org/>.
*/
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;
/**
* <p>Adds writing support to the {@link PropertySet} class.</p>
*
* <p>Please be aware that this class' functionality will be merged into the
* {@link PropertySet} class at a later time, so the API will change.</p>
*
* @author Rainer Klute <a
* href="mailto:klute@rainer-klute.de">&lt;klute@rainer-klute.de&gt;</a>
* @version $Id$
* @since 2003-02-19
*/
public class MutablePropertySet extends PropertySet
{
/**
* <p>Constructs a <code>MutablePropertySet</code> instance. Its
* primary task is to initialize the immutable field with their proper
* values. It also sets fields that might change to reasonable defaults.</p>
*/
public MutablePropertySet()
{
/* Initialize the "byteOrder" field. */
byteOrder = LittleEndian.getUShort(BYTE_ORDER_ASSERTION);
/* Initialize the "format" field. */
format = LittleEndian.getUShort(FORMAT_ASSERTION);
/* Initialize "osVersion" field as if the property has been created on
* a Win32 platform, whether this is the case or not. */
osVersion = (OS_WIN32 << 16) | 0x0A04;
/* Initailize the "classID" field. */
classID = new ClassID();
/* Initialize the sections. Since property set must have at least
* one section it is added right here. */
sections = new LinkedList();
sections.add(new MutableSection());
}
/**
* <p>The length of the property set stream header.</p>
*/
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 */
/**
* <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 #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();
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();
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);
}
}
}

View File

@ -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
* <http://www.apache.org/>.
*/
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;
/**
* <p>Adds writing capability to the {@link Section} class.</p>
*
* <p>Please be aware that this class' functionality will be merged into the
* {@link Section} class at a later time, so the API will change.</p>
*
* @author Rainer Klute <a
* href="mailto:klute@rainer-klute.de">&lt;klute@rainer-klute.de&gt;</a>
* @version $Id$
* @since 2002-02-20
*/
public class MutableSection extends Section
{
/**
* <p>If the "dirty" flag is true, the section's size must be
* (re-)calculated before the section is written.</p>
*/
private boolean dirty = true;
/**
* <p>List to assemble the properties. Unfortunately a wrong
* decision has been taken when specifying the "properties" field
* as an Property[]. It should have been a {@link java.util.List}.</p>
*/
private List preprops;
/**
* <p>Creates an empty mutable section.</p>
*/
public MutableSection()
{
dirty = true;
formatID = null;
offset = -1;
preprops = new LinkedList();
}
/**
* <p>Sets the section's format ID.</p>
*
* @param formatID The section's format ID
*
* @see #setFormatID(byte[])
* @see #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 #getFormatID
*/
public void setFormatID(final byte[] formatID)
{
setFormatID(new ClassID(formatID, 0));
}
/**
* <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)
{
preprops = new LinkedList();
for (int i = 0; i < properties.length; i++)
preprops.add(properties[i]);
dirty = true;
}
/**
* <p>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.</p>
*
* @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;
}
/**
* <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, 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;
}
/**
* <p>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.</p>
*
* @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;
}
/**
* <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, int, Object)
* @see #getProperty
* @see Variant
*/
protected void setPropertyBooleanValue(final int id, final boolean value)
{
setProperty(id, (long) Variant.VT_BOOL, new Boolean(value));
}
/**
* <p>Returns the section's size.</p>
*
* @return the section's size.
*/
public int getSize()
{
if (dirty)
{
size = calcSize();
dirty = false;
}
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.
*/
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;
}
/**
* <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
* @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;
}
/**
* <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>Returns this section's properties.</p>
*
* @return this section's properties.
*/
public Property[] getProperties()
{
return (Property[]) preprops.toArray(new Property[0]);
}
}

View File

@ -55,18 +55,16 @@
package org.apache.poi.hpsf; package org.apache.poi.hpsf;
/** /**
* <p> * <p>This exception is thrown if a format error in a property set stream is
*
* 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.</p> * detected or when the input data do not constitute a property set stream.</p>
* <p>
* *
* The constructors of this class are analogous to those of its superclass and * <p>The constructors of this class are analogous to those of its superclass
* documented there.</p> * and are documented there.</p>
* *
*@author Rainer Klute (klute@rainer-klute.de) * @author Rainer Klute <a
*@version $Id$ * href="mailto:klute@rainer-klute.de">&lt;klute@rainer-klute.de&gt;</a>
*@since 2002-02-09 * @version $Id$
* @since 2002-02-09
*/ */
public class NoPropertySetStreamException extends HPSFException public class NoPropertySetStreamException extends HPSFException
{ {
@ -80,6 +78,7 @@ public class NoPropertySetStreamException extends HPSFException
} }
/** /**
* <p>Constructor</p> * <p>Constructor</p>
* *
@ -91,6 +90,7 @@ public class NoPropertySetStreamException extends HPSFException
} }
/** /**
* <p>Constructor</p> * <p>Constructor</p>
* *
@ -102,6 +102,7 @@ public class NoPropertySetStreamException extends HPSFException
} }
/** /**
* <p>Constructor</p> * <p>Constructor</p>
* *

View File

@ -63,7 +63,8 @@ package org.apache.poi.hpsf;
* <p>The constructors of this class are analogous to those of its * <p>The constructors of this class are analogous to those of its
* superclass and documented there.</p> * superclass and documented there.</p>
* *
* @author Rainer Klute (klute@rainer-klute.de) * @author Rainer Klute <a
* href="mailto:klute@rainer-klute.de">&lt;klute@rainer-klute.de&gt;</a>
* @version $Id$ * @version $Id$
* @since 2002-02-09 * @since 2002-02-09
*/ */

View File

@ -63,8 +63,6 @@
package org.apache.poi.hpsf; package org.apache.poi.hpsf;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map; import java.util.Map;
import org.apache.poi.util.LittleEndian; 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 * value, {@link Variant#VT_FILETIME} some date and time (of a
* file).</p> * file).</p>
* *
* <p><strong>FIXME:</strong> Reading is not implemented for all * <p>Please note that not all {@link Variant} types yet. This might change
* {@link Variant} types yet. Feel free to submit error reports or * over time but largely depends on your feedback so that the POI team knows
* patches for the types you need.</p> * which variant types are really needed. So please feel free to submit error
* reports or patches for the types you need.</p>
* *
* @author Rainer Klute (klute@rainer-klute.de) * @author Rainer Klute <a
* href="mailto:klute@rainer-klute.de">&lt;klute@rainer-klute.de&gt;</a>
* @author Drew Varner (Drew.Varner InAndAround sc.edu) * @author Drew Varner (Drew.Varner InAndAround sc.edu)
* @see Section * @see Section
* @see Variant * @see Variant
@ -103,7 +103,7 @@ public class Property
private static final int CP_UNICODE = 1200; private static final int CP_UNICODE = 1200;
/** <p>The property's ID.</p> */ /** <p>The property's ID.</p> */
protected int id; protected long id;
/** /**
@ -111,7 +111,7 @@ public class Property
* *
* @return The ID value * @return The ID value
*/ */
public int getID() public long getID()
{ {
return id; return id;
} }
@ -162,7 +162,7 @@ public class Property
* @param codepage The section's and thus the property's * @param codepage The section's and thus the property's
* codepage. It is needed only when reading string values. * 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) final int length, final int codepage)
{ {
this.id = id; this.id = id;
@ -187,7 +187,7 @@ public class Property
} }
catch (UnsupportedVariantTypeException ex) catch (UnsupportedVariantTypeException ex)
{ {
logUnsupported(ex); VariantSupport.writeUnsupportedTypeMessage(ex);
value = ex.getValue(); value = ex.getValue();
} }
} }
@ -281,15 +281,21 @@ public class Property
* 4.</p> * 4.</p>
* *
* @return the property's size in bytes * @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. */ 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) switch ((int) type)
{ {
case Variant.VT_LPSTR: case Variant.VT_LPSTR:
@ -304,9 +310,7 @@ public class Property
case Variant.VT_EMPTY: case Variant.VT_EMPTY:
break; break;
default: default:
throw new HPSFRuntimeException throw new WritingNotSupportedException(type, value);
("Writing is not yet implemented for variant type " +
type + ". Please report this problem to the POI team!");
} }
return length; return length;
} }
@ -318,27 +322,65 @@ public class Property
*/ */
public boolean equals(final Object o) 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);
} }
/** /**
* <p>Keeps a list of those variant types for those an "unsupported" message * @see Object#hashCode()
* has already been issued.</p>
*/ */
protected static List unsupportedMessage; public int hashCode()
{
long hashCode = 0;
hashCode += id;
hashCode += type;
if (value != null)
hashCode += value.hashCode();
final int returnHashCode = (int) (hashCode & 0x0ffffffffL );
return returnHashCode;
private static void logUnsupported(final UnsupportedVariantTypeException ex)
{
if (unsupportedMessage == null)
unsupportedMessage = new LinkedList();
Long vt = new Long(ex.getVariantType());
if (!unsupportedMessage.contains(vt))
{
System.err.println(ex.getMessage());
unsupportedMessage.add(vt);
} }
/**
* @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();
} }
} }

View File

@ -57,7 +57,6 @@ package org.apache.poi.hpsf;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import org.apache.poi.hpsf.wellknown.SectionIDMap; import org.apache.poi.hpsf.wellknown.SectionIDMap;
@ -91,7 +90,8 @@ import org.apache.poi.util.LittleEndian;
* NoSingleSectionException} if the {@link PropertySet} contains more * NoSingleSectionException} if the {@link PropertySet} contains more
* (or less) than exactly one {@link Section}).</p> * (or less) than exactly one {@link Section}).</p>
* *
* @author Rainer Klute (klute@rainer-klute.de) * @author Rainer Klute <a
* href="mailto:klute@rainer-klute.de">&lt;klute@rainer-klute.de&gt;</a>
* @author Drew Varner (Drew.Varner hanginIn sc.edu) * @author Drew Varner (Drew.Varner hanginIn sc.edu)
* @version $Id$ * @version $Id$
* @since 2002-02-09 * @since 2002-02-09
@ -397,7 +397,7 @@ public class PropertySet
final int offset, final int offset,
final int length) 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 * 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) 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. * Read the stream's header fields.
@ -645,6 +645,9 @@ public class PropertySet
* to the specified parameter, else <code>false</code>.</p> * to the specified parameter, else <code>false</code>.</p>
* *
* @param o the object to compare this <code>PropertySet</code> with * @param o the object to compare this <code>PropertySet</code> with
*
* @return <code>true</code> if the objects are equal, <code>false</code>
* if not
*/ */
public boolean equals(final Object o) public boolean equals(final Object o)
{ {
@ -672,4 +675,43 @@ public class PropertySet
return Util.equals(getSections(), ps.getSections()); 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();
}
} }

View File

@ -61,7 +61,8 @@ import java.io.IOException;
* <p>Factory class to create instances of {@link SummaryInformation}, * <p>Factory class to create instances of {@link SummaryInformation},
* {@link DocumentSummaryInformation} and {@link PropertySet}.</p> * {@link DocumentSummaryInformation} and {@link PropertySet}.</p>
* *
* @author Rainer Klute (klute@rainer-klute.de) * @author Rainer Klute <a
* href="mailto:klute@rainer-klute.de">&lt;klute@rainer-klute.de&gt;</a>
* @version $Id$ * @version $Id$
* @since 2002-02-09 * @since 2002-02-09
*/ */

View File

@ -63,8 +63,11 @@
package org.apache.poi.hpsf; package org.apache.poi.hpsf;
/** /**
* <p>This exception is thrown when trying to read a (yet) unsupported variant * <p>This exception is thrown when HPSF tries to read a (yet) unsupported
* type.</p> * variant type.</p>
*
* @see WritingNotSupportedException
* @see UnsupportedVariantTypeException
* *
* @author Rainer Klute <a * @author Rainer Klute <a
* href="mailto:klute@rainer-klute.de">&lt;klute@rainer-klute.de&gt;</a> * href="mailto:klute@rainer-klute.de">&lt;klute@rainer-klute.de&gt;</a>
@ -78,10 +81,11 @@ public class ReadingNotSupportedException
/** /**
* <p>Constructor</p> * <p>Constructor</p>
* *
* @param variantType * @param variantType The unsupported variant type.
* @param value * @param value The value.
*/ */
public ReadingNotSupportedException(long variantType, Object value) public ReadingNotSupportedException(final long variantType,
final Object value)
{ {
super(variantType, value); super(variantType, value);
} }

View File

@ -67,7 +67,8 @@ import org.apache.poi.util.LittleEndian;
/** /**
* <p>Represents a section in a {@link PropertySet}.</p> * <p>Represents a section in a {@link PropertySet}.</p>
* *
* @author Rainer Klute (klute@rainer-klute.de) * @author Rainer Klute <a
* href="mailto:klute@rainer-klute.de">&lt;klute@rainer-klute.de&gt;</a>
* @author Drew Varner (Drew.Varner allUpIn sc.edu) * @author Drew Varner (Drew.Varner allUpIn sc.edu)
* @version $Id$ * @version $Id$
* @since 2002-02-09 * @since 2002-02-09
@ -493,7 +494,7 @@ public class Section
/** /**
* <p>Checks whether this section is equal to another object.</p> * <p>Checks whether this section is equal to another object.</p>
* *
* @param o The object to cpmpare this section with * @param o The object to compare this section with
* @return <code>true</code> if the objects are equal, <code>false</code> if * @return <code>true</code> if the objects are equal, <code>false</code> if
* not * not
*/ */
@ -509,4 +510,50 @@ public class Section
return Util.equals(s.getProperties(), getProperties()); 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();
}
} }

View File

@ -82,7 +82,8 @@ import java.util.List;
* went the other way round historically: the convenience classes came * went the other way round historically: the convenience classes came
* only late to my mind.</p> * only late to my mind.</p>
* *
* @author Rainer Klute (klute@rainer-klute.de) * @author Rainer Klute <a
* href="mailto:klute@rainer-klute.de">&lt;klute@rainer-klute.de&gt;</a>
* @version $Id$ * @version $Id$
* @since 2002-02-09 * @since 2002-02-09
*/ */

View File

@ -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</a> * 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</a>
* for documentation from That Redmond Company.</p> * for documentation from That Redmond Company.</p>
* *
* @author Rainer Klute (klute@rainer-klute.de) * @author Rainer Klute <a
* href="mailto:klute@rainer-klute.de">&lt;klute@rainer-klute.de&gt;</a>
* @see DocumentSummaryInformation * @see DocumentSummaryInformation
* @version $Id$ * @version $Id$
* @since 2002-02-09 * @since 2002-02-09
@ -297,7 +298,7 @@ public class SummaryInformation extends SpecialPropertySet
* <strong>when this method is implemented. Please note that the * <strong>when this method is implemented. Please note that the
* return type is likely to change!</strong></p> * return type is likely to change!</strong></p>
* *
* <p><strong>FIXME / Hint to developers:</strong> Drew Varner * <p><strong>FIXME (3) / Hint to developers:</strong> Drew Varner
* &lt;Drew.Varner -at- sc.edu&gt; said that this is an image in * &lt;Drew.Varner -at- sc.edu&gt; said that this is an image in
* WMF or Clipboard (BMP?) format. He also provided two links that * WMF or Clipboard (BMP?) format. He also provided two links that
* might be helpful: <a * might be helpful: <a

View File

@ -66,12 +66,12 @@ import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import org.apache.poi.util.LittleEndian; import org.apache.poi.util.LittleEndian;
import org.apache.poi.util.LittleEndianConsts;
/** /**
* <p>Class for writing little-endian data and more.</p> * <p>Class for writing little-endian data and more.</p>
* *
* @author Rainer Klute (klute@rainer-klute.de) * @author Rainer Klute <a
* href="mailto:klute@rainer-klute.de">&lt;klute@rainer-klute.de&gt;</a>
* @version $Id$ * @version $Id$
* @since 2003-02-20 * @since 2003-02-20
*/ */
@ -81,8 +81,9 @@ public class TypeWriter
/** /**
* <p>Writes a two-byte value (short) to an output stream.</p> * <p>Writes a two-byte value (short) to an output stream.</p>
* *
* @param out The stream to write to * @param out The stream to write to.
* @param n The value to write * @param n The value to write.
* @return The number of bytes that have been written.
* @exception IOException if an I/O error occurs * @exception IOException if an I/O error occurs
*/ */
public static int writeToStream(final OutputStream out, final short n) public static int writeToStream(final OutputStream out, final short n)
@ -149,7 +150,7 @@ public class TypeWriter
throws IOException throws IOException
{ {
long high = n & 0xFFFFFFFF00000000L; long high = n & 0xFFFFFFFF00000000L;
if (high != 0) if (high != 0 && high != 0xFFFFFFFF00000000L)
throw new IllegalPropertySetDataException throw new IllegalPropertySetDataException
("Value " + n + " cannot be represented by 4 bytes."); ("Value " + n + " cannot be represented by 4 bytes.");
return writeToStream(out, (int) n); return writeToStream(out, (int) n);

View File

@ -62,7 +62,8 @@ package org.apache.poi.hpsf;
* <p>The constructors of this class are analogous to those of its * <p>The constructors of this class are analogous to those of its
* superclass and documented there.</p> * superclass and documented there.</p>
* *
* @author Rainer Klute (klute@rainer-klute.de) * @author Rainer Klute <a
* href="mailto:klute@rainer-klute.de">&lt;klute@rainer-klute.de&gt;</a>
* @version $Id$ * @version $Id$
* @since 2002-02-09 * @since 2002-02-09
*/ */

View File

@ -88,7 +88,7 @@ public abstract class UnsupportedVariantTypeException extends HPSFException
{ {
super("HPSF does not yet support the variant type " + variantType + super("HPSF does not yet support the variant type " + variantType +
" (" + Variant.getVariantName(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 " + "this variant type in one of the next POI releases please " +
"submit a request for enhancement (RFE) to " + "submit a request for enhancement (RFE) to " +
"<http://nagoya.apache.org/bugzilla/>! Thank you!"); "<http://nagoya.apache.org/bugzilla/>! Thank you!");

View File

@ -78,7 +78,7 @@ public class Util
* <li><p>if for each <var>i</var> with * <li><p>if for each <var>i</var> with
* <var>i</var>&nbsp;&gt;=&nbsp;0 and * <var>i</var>&nbsp;&gt;=&nbsp;0 and
* <var>i</var>&nbsp;&lt;&nbsp;<var>a.length</var> holds * <var>i</var>&nbsp;&lt;&nbsp;<var>a.length</var> holds
* <var>a</var>[<var>i</var>]&nbsp;==&nbsp;<var>b</var>[<var>i</var>].</p></li> * <var>a</var>[<var>i</var>]&nbsp;== <var>b</var>[<var>i</var>].</p></li>
* *
* </ul> * </ul>
* *
@ -191,6 +191,16 @@ public class Util
return new Date(ms_since_19700101); return new Date(ms_since_19700101);
} }
/**
* <p>Converts a {@link Date} into a filetime.</p>
*
* @param date The date to be converted
* @return The filetime
*
* @see #filetimeToDate
*/
public static long dateToFileTime(final Date date) public static long dateToFileTime(final Date date)
{ {
long ms_since_19700101 = date.getTime(); long ms_since_19700101 = date.getTime();
@ -228,6 +238,17 @@ public class Util
return internalEquals(o1, o2); return internalEquals(o1, o2);
} }
/**
* <p>Compares to object arrays with regarding the objects' order. For
* example, [1, 2, 3] and [2, 1, 3] are equal.</p>
*
* @param c1 The first object array.
* @param c2 The second object array.
* @return <code>true</code> if the object arrays are equal,
* <code>false</code> if they are not.
*/
public static boolean equals(final Object[] c1, final Object[] c2) public static boolean equals(final Object[] c1, final Object[] c2)
{ {
final Object[] o1 = (Object[]) c1.clone(); final Object[] o1 = (Object[]) c1.clone();
@ -252,4 +273,68 @@ public class Util
return true; return true;
} }
/**
* <p>Pads a byte array with 0x00 bytes so that its length is a multiple of
* 4.</p>
*
* @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;
}
/**
* <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());
}
} }

View File

@ -54,6 +54,7 @@
*/ */
package org.apache.poi.hpsf; package org.apache.poi.hpsf;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@ -357,79 +358,193 @@ public class Variant
public static final int VT_BYREF = 0x4000; public static final int VT_BYREF = 0x4000;
/** /**
* <p>FIXME: Document this!</p> * <p>FIXME (3): Document this!</p>
*/ */
public static final int VT_RESERVED = 0x8000; public static final int VT_RESERVED = 0x8000;
/** /**
* <p>FIXME: Document this!</p> * <p>FIXME (3): Document this!</p>
*/ */
public static final int VT_ILLEGAL = 0xFFFF; public static final int VT_ILLEGAL = 0xFFFF;
/** /**
* <p>FIXME: Document this!</p> * <p>FIXME (3): Document this!</p>
*/ */
public static final int VT_ILLEGALMASKED = 0xFFF; public static final int VT_ILLEGALMASKED = 0xFFF;
/** /**
* <p>FIXME: Document this!</p> * <p>FIXME (3): Document this!</p>
*/ */
public static final int VT_TYPEMASK = 0xFFF; public static final int VT_TYPEMASK = 0xFFF;
public static final Map m = new HashMap(); /**
* <p>Maps the numbers denoting the variant types to their corresponding
* variant type names.</p>
*/
private static Map numberToName;
private static Map numberToLength;
/**
* <p>Denotes a variant type with a length that is unknown to HPSF yet.</p>
*/
public static final Integer LENGTH_UNKNOWN = new Integer(-2);
/**
* <p>Denotes a variant type with a variable length.</p>
*/
public static final Integer LENGTH_VARIABLE = new Integer(-1);
/**
* <p>Denotes a variant type with a length of 0 bytes.</p>
*/
public static final Integer LENGTH_0 = new Integer(0);
/**
* <p>Denotes a variant type with a length of 2 bytes.</p>
*/
public static final Integer LENGTH_2 = new Integer(2);
/**
* <p>Denotes a variant type with a length of 4 bytes.</p>
*/
public static final Integer LENGTH_4 = new Integer(4);
/**
* <p>Denotes a variant type with a length of 8 bytes.</p>
*/
public static final Integer LENGTH_8 = new Integer(8);
static static
{ {
m.put(new Integer(0), "VT_EMPTY"); /* Initialize the number-to-name map: */
m.put(new Integer(1), "VT_NULL"); Map tm1 = new HashMap();
m.put(new Integer(2), "VT_I2"); tm1.put(new Long(0), "VT_EMPTY");
m.put(new Integer(3), "VT_I4"); tm1.put(new Long(1), "VT_NULL");
m.put(new Integer(4), "VT_R4"); tm1.put(new Long(2), "VT_I2");
m.put(new Integer(5), "VT_R8"); tm1.put(new Long(3), "VT_I4");
m.put(new Integer(6), "VT_CY"); tm1.put(new Long(4), "VT_R4");
m.put(new Integer(7), "VT_DATE"); tm1.put(new Long(5), "VT_R8");
m.put(new Integer(8), "VT_BSTR"); tm1.put(new Long(6), "VT_CY");
m.put(new Integer(9), "VT_DISPATCH"); tm1.put(new Long(7), "VT_DATE");
m.put(new Integer(10), "VT_ERROR"); tm1.put(new Long(8), "VT_BSTR");
m.put(new Integer(11), "VT_BOOL"); tm1.put(new Long(9), "VT_DISPATCH");
m.put(new Integer(12), "VT_VARIANT"); tm1.put(new Long(10), "VT_ERROR");
m.put(new Integer(13), "VT_UNKNOWN"); tm1.put(new Long(11), "VT_BOOL");
m.put(new Integer(14), "VT_DECIMAL"); tm1.put(new Long(12), "VT_VARIANT");
m.put(new Integer(16), "VT_I1"); tm1.put(new Long(13), "VT_UNKNOWN");
m.put(new Integer(17), "VT_UI1"); tm1.put(new Long(14), "VT_DECIMAL");
m.put(new Integer(18), "VT_UI2"); tm1.put(new Long(16), "VT_I1");
m.put(new Integer(19), "VT_UI4"); tm1.put(new Long(17), "VT_UI1");
m.put(new Integer(20), "VT_I8"); tm1.put(new Long(18), "VT_UI2");
m.put(new Integer(21), "VT_UI8"); tm1.put(new Long(19), "VT_UI4");
m.put(new Integer(22), "VT_INT"); tm1.put(new Long(20), "VT_I8");
m.put(new Integer(23), "VT_UINT"); tm1.put(new Long(21), "VT_UI8");
m.put(new Integer(24), "VT_VOID"); tm1.put(new Long(22), "VT_INT");
m.put(new Integer(25), "VT_HRESULT"); tm1.put(new Long(23), "VT_UINT");
m.put(new Integer(26), "VT_PTR"); tm1.put(new Long(24), "VT_VOID");
m.put(new Integer(27), "VT_SAFEARRAY"); tm1.put(new Long(25), "VT_HRESULT");
m.put(new Integer(28), "VT_CARRAY"); tm1.put(new Long(26), "VT_PTR");
m.put(new Integer(29), "VT_USERDEFINED"); tm1.put(new Long(27), "VT_SAFEARRAY");
m.put(new Integer(30), "VT_LPSTR"); tm1.put(new Long(28), "VT_CARRAY");
m.put(new Integer(31), "VT_LPWSTR"); tm1.put(new Long(29), "VT_USERDEFINED");
m.put(new Integer(64), "VT_FILETIME"); tm1.put(new Long(30), "VT_LPSTR");
m.put(new Integer(65), "VT_BLOB"); tm1.put(new Long(31), "VT_LPWSTR");
m.put(new Integer(66), "VT_STREAM"); tm1.put(new Long(64), "VT_FILETIME");
m.put(new Integer(67), "VT_STORAGE"); tm1.put(new Long(65), "VT_BLOB");
m.put(new Integer(68), "VT_STREAMED_OBJECT"); tm1.put(new Long(66), "VT_STREAM");
m.put(new Integer(69), "VT_STORED_OBJECT"); tm1.put(new Long(67), "VT_STORAGE");
m.put(new Integer(70), "VT_BLOB_OBJECT"); tm1.put(new Long(68), "VT_STREAMED_OBJECT");
m.put(new Integer(71), "VT_CF"); tm1.put(new Long(69), "VT_STORED_OBJECT");
m.put(new Integer(72), "VT_CLSID"); 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);
} }
/**
* <p>Returns the variant type name associated with a variant type
* number.</p>
*
* @param variantType The variant type number
* @return The variant type name or the string "unknown variant type"
*/
public static String getVariantName(final long variantType) 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"; return name != null ? name : "unknown variant type";
} }
/**
* <p>Returns a variant type's length.</p>
*
* @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();
}
} }

View File

@ -65,6 +65,8 @@ package org.apache.poi.hpsf;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.Date; import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import org.apache.poi.util.LittleEndian; import org.apache.poi.util.LittleEndian;
import org.apache.poi.util.LittleEndianConsts; import org.apache.poi.util.LittleEndianConsts;
@ -72,8 +74,8 @@ import org.apache.poi.util.LittleEndianConsts;
/** /**
* <p>Supports reading and writing of variant data.</p> * <p>Supports reading and writing of variant data.</p>
* *
* <p><strong>FIXME:</strong> Reading and writing must be made more uniform than * <p><strong>FIXME (3):</strong> Reading and writing should be made more
* it is now. The following items should be resolved: * uniform than it is now. The following items should be resolved:
* *
* <ul> * <ul>
* *
@ -87,14 +89,73 @@ import org.apache.poi.util.LittleEndianConsts;
* *
* @author Rainer Klute <a * @author Rainer Klute <a
* href="mailto:klute@rainer-klute.de">&lt;klute@rainer-klute.de&gt;</a> * href="mailto:klute@rainer-klute.de">&lt;klute@rainer-klute.de&gt;</a>
* @since 08.08.2003 * @since 2003-08-08
* @version $Id$ * @version $Id$
*/ */
public class VariantSupport extends Variant public class VariantSupport extends Variant
{ {
private static boolean logUnsupportedTypes = false;
/** /**
* <p>Reads a variant data type from a byte array.</p> * <p>Specifies whether warnings about unsupported variant types are to be
* written to <code>System.err</code> or not.</p>
*
* @param logUnsupportedTypes If <code>true</code> warnings will be written,
* if <code>false</code> they won't.
*/
public static void setLogUnsupportedTypes(final boolean logUnsupportedTypes)
{
VariantSupport.logUnsupportedTypes = logUnsupportedTypes;
}
/**
* <p>Checks whether logging of unsupported variant types warning is turned
* on or off.</p>
*
* @return <code>true</code> if logging is turned on, else
* <code>false</code>.
*/
public static boolean isLogUnsupportedTypes()
{
return logUnsupportedTypes;
}
/**
* <p>Keeps a list of the variant types an "unsupported" message has already
* been issued for.</p>
*/
protected static List 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
* type. Log messages can be turned on or off by </p>
*
* @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);
}
}
}
/**
* <p>Reads a variant type from a byte array.</p>
* *
* @param src The byte array * @param src The byte array
* @param offset The offset in the byte array where the variant * @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 * @return A Java object that corresponds best to the variant
* field. For example, a VT_I4 is returned as a {@link Long}, a * field. For example, a VT_I4 is returned as a {@link Long}, a
* VT_LPSTR as a {@link String}. * VT_LPSTR as a {@link String}.
* @exception UnsupportedVariantTypeException if HPSF does not (yet) * @exception ReadingNotSupportedException if a property is to be written
* support the variant type which is to be read * who's variant type HPSF does not yet support
* *
* @see Variant * @see Variant
*/ */
@ -161,7 +222,7 @@ public class VariantSupport extends Variant
* String object. The 0x00 bytes at the end must be * String object. The 0x00 bytes at the end must be
* stripped. * 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 * to the codepage. Currently the byte making out the
* property's value are interpreted according to the * property's value are interpreted according to the
* platform's default character set. * platform's default character set.
@ -238,51 +299,57 @@ public class VariantSupport extends Variant
/** /**
* <p>Writes a variant value to an output stream.</p> * <p>Writes a variant value to an output stream. This method ensures that
* always a multiple of 4 bytes is written.</p>
* *
* @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.
* @param value The variant's value. * @param value The variant's value.
* @return The number of entities that have been written. In many cases an * @return The number of entities that have been written. In many cases an
* "entity" is a byte but this is not always the case. * "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, public static int write(final OutputStream out, final long type,
final Object value) final Object value)
throws IOException, WritingNotSupportedException throws IOException, WritingNotSupportedException
{ {
int length = 0;
switch ((int) type) switch ((int) type)
{ {
case Variant.VT_BOOL: case Variant.VT_BOOL:
{ {
int trueOrFalse; int trueOrFalse;
int length = 0;
if (((Boolean) value).booleanValue()) if (((Boolean) value).booleanValue())
trueOrFalse = 1; trueOrFalse = 1;
else else
trueOrFalse = 0; trueOrFalse = 0;
length += TypeWriter.writeUIntToStream(out, trueOrFalse); length = TypeWriter.writeUIntToStream(out, trueOrFalse);
return length; break;
} }
case Variant.VT_LPSTR: case Variant.VT_LPSTR:
{ {
TypeWriter.writeUIntToStream length = TypeWriter.writeUIntToStream
(out, ((String) value).length() + 1); (out, ((String) value).length() + 1);
char[] s = toPaddedCharArray((String) value); char[] s = Util.pad4((String) value);
/* FIXME: The following line forces characters to bytes. This /* FIXME (2): The following line forces characters to bytes.
* is generally wrong and should only be done according to a * This is generally wrong and should only be done according to
* codepage. Alternatively Unicode could be written (see * a codepage. Alternatively Unicode could be written (see
* Variant.VT_LPWSTR). */ * Variant.VT_LPWSTR). */
byte[] b = new byte[s.length]; byte[] b = new byte[s.length + 1];
for (int i = 0; i < s.length; i++) for (int i = 0; i < s.length; i++)
b[i] = (byte) s[i]; b[i] = (byte) s[i];
b[b.length - 1] = 0x00;
out.write(b); out.write(b);
return b.length; length += b.length;
break;
} }
case Variant.VT_LPWSTR: case Variant.VT_LPWSTR:
{ {
final int nrOfChars = ((String) value).length() + 1; final int nrOfChars = ((String) value).length() + 1;
TypeWriter.writeUIntToStream(out, nrOfChars); TypeWriter.writeUIntToStream(out, nrOfChars);
char[] s = toPaddedCharArray((String) value); char[] s = Util.pad4((String) value);
for (int i = 0; i < s.length; i++) for (int i = 0; i < s.length; i++)
{ {
final int high = (int) ((s[i] & 0xff00) >> 8); final int high = (int) ((s[i] & 0xff00) >> 8);
@ -292,79 +359,73 @@ public class VariantSupport extends Variant
out.write(lowb); out.write(lowb);
out.write(highb); out.write(highb);
} }
return nrOfChars * 2; length = nrOfChars * 2;
out.write(0x00);
out.write(0x00);
length += 2;
break;
} }
case Variant.VT_CF: case Variant.VT_CF:
{ {
final byte[] b = (byte[]) value; final byte[] b = (byte[]) value;
out.write(b); out.write(b);
return b.length; length = b.length;
break;
} }
case Variant.VT_EMPTY: case Variant.VT_EMPTY:
{ {
TypeWriter.writeUIntToStream(out, Variant.VT_EMPTY); TypeWriter.writeUIntToStream(out, Variant.VT_EMPTY);
return LittleEndianConsts.INT_SIZE; length = LittleEndianConsts.INT_SIZE;
break;
} }
case Variant.VT_I2: case Variant.VT_I2:
{ {
TypeWriter.writeToStream(out, ((Integer) value).shortValue()); TypeWriter.writeToStream(out, ((Integer) value).shortValue());
return LittleEndianConsts.SHORT_SIZE; length = LittleEndianConsts.SHORT_SIZE;
break;
} }
case Variant.VT_I4: case Variant.VT_I4:
{ {
TypeWriter.writeToStream(out, ((Long) value).intValue()); TypeWriter.writeToStream(out, ((Long) value).intValue());
return LittleEndianConsts.INT_SIZE; length = LittleEndianConsts.INT_SIZE;
break;
} }
case Variant.VT_FILETIME: case Variant.VT_FILETIME:
{ {
int length = 0;
long filetime = Util.dateToFileTime((Date) value); long filetime = Util.dateToFileTime((Date) value);
int high = (int) ((filetime >> 32) & 0xFFFFFFFFL); int high = (int) ((filetime >> 32) & 0xFFFFFFFFL);
int low = (int) (filetime & 0x00000000FFFFFFFFL); int low = (int) (filetime & 0x00000000FFFFFFFFL);
length += TypeWriter.writeUIntToStream(out, 0x0000000FFFFFFFFL & low); length += TypeWriter.writeUIntToStream
length += TypeWriter.writeUIntToStream(out, 0x0000000FFFFFFFFL & high); (out, 0x0000000FFFFFFFFL & low);
length += TypeWriter.writeUIntToStream
(out, 0x0000000FFFFFFFFL & high);
break;
}
default:
{
/* The variant type is not supported yet. However, if the value
* is a byte array we can write it nevertheless. */
if (value instanceof byte[])
{
final byte[] b = (byte[]) value;
out.write(b);
length = b.length;
writeUnsupportedTypeMessage
(new WritingNotSupportedException(type, value));
}
else
throw new WritingNotSupportedException(type, value);
break;
}
}
/* Add 0x00 character to write a multiple of four bytes: */
while (length % 4 != 0)
{
out.write(0);
length++;
}
return length; return length;
} }
default:
{
throw new WritingNotSupportedException(type, value);
}
}
}
/**
* <p>Converts a string into a 0x00-terminated character sequence padded
* with 0x00 bytes to a multiple of 4.</p>
*
* @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)
{
case VT_LPWSTR:
return lengthInBytes / 2;
default:
return lengthInBytes;
}
}
} }

View File

@ -66,6 +66,9 @@ package org.apache.poi.hpsf;
* <p>This exception is thrown when trying to write a (yet) unsupported variant * <p>This exception is thrown when trying to write a (yet) unsupported variant
* type.</p> * type.</p>
* *
* @see ReadingNotSupportedException
* @see UnsupportedVariantTypeException
*
* @author Rainer Klute <a * @author Rainer Klute <a
* href="mailto:klute@rainer-klute.de">&lt;klute@rainer-klute.de&gt;</a> * href="mailto:klute@rainer-klute.de">&lt;klute@rainer-klute.de&gt;</a>
* @since 2003-08-08 * @since 2003-08-08
@ -78,10 +81,11 @@ public class WritingNotSupportedException
/** /**
* <p>Constructor</p> * <p>Constructor</p>
* *
* @param variantType * @param variantType The unsupported varian type.
* @param value * @param value The value.
*/ */
public WritingNotSupportedException(long variantType, Object value) public WritingNotSupportedException(final long variantType,
final Object value)
{ {
super(variantType, value); super(variantType, value);
} }

View File

@ -66,9 +66,9 @@ import java.util.HashMap;
* should treat them as unmodifiable, copy them and modifiy the * should treat them as unmodifiable, copy them and modifiy the
* copies.</p> * copies.</p>
* *
* <p><strong>FIXME:</strong> Make the singletons unmodifiable. However, * <p><strong>FIXME (3):</strong> Make the singletons unmodifiable. However,
* since this requires to use a {@link HashMap} delegate instead of * 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.</p> * do that for the time being.</p>
* *
* @author Rainer Klute (klute@rainer-klute.de) * @author Rainer Klute (klute@rainer-klute.de)
@ -141,7 +141,7 @@ public class PropertyIDMap extends HashMap
* document</p> */ * document</p> */
public static final int PID_APPNAME = 18; public static final int PID_APPNAME = 18;
/** <p>ID of the property that denotes... FIXME</p> */ /** <p>ID of the property that denotes... FIXME (2)</p> */
public static final int PID_SECURITY = 19; public static final int PID_SECURITY = 19;