+ /**
+ * <p>Creates a <code>MutableProperty</code> as a copy of an existing
+ * <code>Property</code>.</p>
+ *
+ * @param p The property to copy.
+ */
+ public MutableProperty(final Property p)
+ {
+ setID(p.getID());
+ setType(p.getType());
+ setValue(p.getValue());
+ }
+
+
/**
* <p>Sets the property's ID.</p>
*
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.util.Iterator;
import java.util.LinkedList;
import java.util.ListIterator;
+ /**
+ * <p>Constructs a <code>MutablePropertySet</code> by doing a deep copy of
+ * an existing <code>PropertySet</code>. All nested elements, i.e.
+ * <code>Section</code>s and <code>Property</code> instances, will be their
+ * mutable counterparts in the new <code>MutablePropertySet</code>.</p>
+ *
+ * @param ps The property set to copy
+ */
+ public MutablePropertySet(final PropertySet ps)
+ {
+ byteOrder = ps.getByteOrder();
+ format = ps.getFormat();
+ osVersion = ps.getOSVersion();
+ classID = new ClassID(ps.getClassID().getBytes(), 0);
+ clearSections();
+ for (final Iterator i = ps.getSections().iterator(); i.hasNext();)
+ {
+ final MutableSection s = new MutableSection((Section) (i.next()));
+ addSection(s);
+ }
+ }
+
+
+
/**
* <p>The length of the property set stream header.</p>
*/
public void clearSections()
{
sections = null;
+ sectionCount = 0;
}
if (sections == null)
sections = new LinkedList();
sections.add(section);
+ sectionCount = sections.size();
}
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. */
+ * entry in the section list consist of the section's class ID and the
+ * section's offset relative to the beginning of the stream. */
offset += nrSections * (ClassID.LENGTH + LittleEndian.INT_SIZE);
final int sectionsBegin = offset;
for (final ListIterator i = sections.listIterator(); i.hasNext();)
{
final MutableSection s = (MutableSection) i.next();
+ final ClassID formatID = s.getFormatID();
+ if (formatID == null)
+ throw new NoFormatIDException();
length += TypeWriter.writeToStream(out, s.getFormatID());
length += TypeWriter.writeUIntToStream(out, offset);
offset += s.getSize();
for (final ListIterator i = sections.listIterator(); i.hasNext();)
{
final MutableSection s = (MutableSection) i.next();
- offset = s.write(out, offset);
+ offset += s.write(out);
}
}
* <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"><klute@rainer-klute.de></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>
+ /**
+ * <p>Contains the bytes making out the section. This byte array is
+ * established when the section's size is calculated and can be reused
+ * later. It is valid only if the "dirty" flag is false.</p>
+ */
+ private byte[] sectionBytes;
+
+
+
/**
* <p>Creates an empty mutable section.</p>
*/
+ /**
+ * <p>Constructs a <code>MutableSection</code> by doing a deep copy of an
+ * existing <code>Section</code>. All nested <code>Property</code>
+ * instances, will be their mutable counterparts in the new
+ * <code>MutableSection</code>.</p>
+ *
+ * @param s The section set to copy
+ */
+ public MutableSection(final Section s)
+ {
+ setFormatID(s.getFormatID());
+ final Property[] pa = s.getProperties();
+ final MutableProperty[] mpa = new MutableProperty[pa.length];
+ for (int i = 0; i < pa.length; i++)
+ mpa[i] = new MutableProperty(pa[i]);
+ setProperties(mpa);
+ }
+
+
+
/**
* <p>Sets the section's format ID.</p>
*
*/
public void setProperties(final Property[] properties)
{
+ this.properties = properties;
preprops = new LinkedList();
for (int i = 0; i < properties.length; i++)
preprops.add(properties[i]);
dirty = true;
+ propertyCount = properties.length;
}
* @param value The property's value. It will be written as a Unicode
* string.
*
- * @see #setProperty(int, int, Object)
+ * @see #setProperty(int, long, Object)
* @see #getProperty
*/
public void setProperty(final int id, final String value)
* @param variantType The property's variant type.
* @param value The property's value.
*
- * @see #setProperty(int, Object)
+ * @see #setProperty(int, String)
* @see #getProperty
* @see Variant
*/
*
* @param p The property to be added to the section
*
- * @see #setProperty(int, int, Object)
+ * @see #setProperty(int, long, Object)
* @see #setProperty(int, String)
* @see #getProperty
* @see Variant
}
preprops.add(p);
dirty = true;
+ propertyCount = preprops.size();
}
* @param id The property's ID
* @param value The property's value
*
- * @see #setProperty(int, int, Object)
+ * @see #setProperty(int, long, Object)
* @see #getProperty
* @see Variant
*/
{
if (dirty)
{
- size = calcSize();
- dirty = false;
+ try
+ {
+ size = calcSize();
+ dirty = false;
+ }
+ catch (Exception ex)
+ {
+ throw new HPSFRuntimeException(ex);
+ }
}
return size;
}
*
* @return the section's length in bytes.
*/
- private int calcSize()
+ private int calcSize() throws WritingNotSupportedException, IOException
{
- 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;
+ final ByteArrayOutputStream out = new ByteArrayOutputStream();
+ write(out);
+ out.close();
+ sectionBytes = out.toByteArray();
+ return sectionBytes.length;
}
* 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
+ * @param out The stream to write into.
*
- * @return The offset of the first byte following this section in
- * the property set stream.
+ * @return The number of bytes written, i.e. the section's size.
* @exception IOException if an I/O error occurs
* @exception WritingNotSupportedException if HPSF does not yet support
* writing a property's variant type.
*/
- public int write(final OutputStream out, final int offset)
+ public int write(final OutputStream out)
throws WritingNotSupportedException, IOException
{
+ /* Check whether we have already generated the bytes making out the
+ * section. */
+ if (!dirty && sectionBytes != null)
+ {
+ out.write(sectionBytes);
+ return sectionBytes.length;
+ }
+
/* The properties are written to this stream. */
final ByteArrayOutputStream propertyStream =
new ByteArrayOutputStream();
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. */
+ * that it points behind the property list and to the beginning of the
+ * properties themselves. */
position += 2 * LittleEndian.INT_SIZE +
getPropertyCount() * 2 * LittleEndian.INT_SIZE;
/* Write the section: */
byte[] pb1 = propertyListStream.toByteArray();
byte[] pb2 = propertyStream.toByteArray();
- TypeWriter.writeToStream(out, LittleEndian.INT_SIZE * 2 + pb1.length +
- pb2.length);
+
+ /* Write the section's length: */
+ TypeWriter.writeToStream(out, LittleEndian.INT_SIZE * 2 +
+ pb1.length + pb2.length);
+
+ /* Write the section's number of properties: */
TypeWriter.writeToStream(out, getPropertyCount());
+
+ /* Write the property list: */
out.write(pb1);
+
+ /* Write the properties: */
out.write(pb2);
- return offset + position;
+ int streamLength = LittleEndian.INT_SIZE * 2 + pb1.length + pb2.length;
+ return streamLength;
}
/**
* <p>The number of sections in this {@link PropertySet}.</p>
+ *
+ * <p>FIXME (2): Get rid of this! The number of sections is implicitly
+ * available.</p>
*/
protected int sectionCount;
* Summary Information stream has 2. Everything else is a rare
* exception and is no longer fostered by Microsoft.
*/
- sections = new ArrayList(2);
+ sections = new ArrayList(sectionCount);
/*
* Loop over the section descriptor array. Each descriptor
/**
+ * FIXME (2): Get rid of this! The property count is implicitly available as
+ * the length of the "properties" array.
+ *
* @see #getPropertyCount
*/
- private int propertyCount;
+ protected int propertyCount;
/**
/**
* @see #getProperties
*/
- private Property[] properties;
+ protected Property[] properties;
/**
return properties;
}
- /**
- * <p>Sets this section's properties.</p>
- *
- * @param properties This section's new properties.
- */
- protected void setProperties(final Property[] properties)
- {
- this.properties = properties;
- }
-
/**
*
* @return The property's value
*/
- public Object getProperty(final int id)
+ public Object getProperty(final long id)
{
wasNull = false;
for (int i = 0; i < properties.length; i++)
*
* @return The property's value
*/
- protected int getPropertyIntValue(final int id)
+ protected int getPropertyIntValue(final long id)
{
- final Integer i = (Integer) getProperty(id);
+ final Long i = (Long) getProperty(id);
if (i != null)
return i.intValue();
else
*/
package org.apache.poi.hpsf;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
import java.util.Collection;
import java.util.Date;
return pad4(s.toCharArray());
}
+
+
+ /**
+ * <p>Returns a textual representation of a {@link Throwable}, including a
+ * stacktrace.</p>
+ *
+ * @param t The {@link Throwable}
+ *
+ * @return a string containing the output of a call to
+ * <code>t.printStacktrace()</code>.
+ */
+ public static String toString(final Throwable t)
+ {
+ final StringWriter sw = new StringWriter();
+ final PrintWriter pw = new PrintWriter(sw);
+ t.printStackTrace(pw);
+ pw.close();
+ try
+ {
+ sw.close();
+ return sw.toString();
+ }
+ catch (IOException e)
+ {
+ final StringBuffer b = new StringBuffer(t.getMessage());
+ b.append("\n");
+ b.append("Could not create a stacktrace. Reason: ");
+ b.append(e.getMessage());
+ return b.toString();
+ }
+ }
+
}
* <var>id</var>, or <code>null</code> if there was no mapping for
* key.
*/
- public Object put(final int id, final String idString)
+ public Object put(final long id, final String idString)
{
- return put(new Integer(id), idString);
+ return put(new Long(id), idString);
}
}
catch (Throwable t)
{
- final String s = Util.toString(t);
+ final String s = org.apache.poi.hpsf.Util.toString(t);
fail(s);
}
}
import org.apache.poi.hpsf.MutableProperty;
import org.apache.poi.hpsf.MutablePropertySet;
import org.apache.poi.hpsf.MutableSection;
+import org.apache.poi.hpsf.NoFormatIDException;
import org.apache.poi.hpsf.Property;
import org.apache.poi.hpsf.PropertySet;
import org.apache.poi.hpsf.PropertySetFactory;
import org.apache.poi.hpsf.UnsupportedVariantTypeException;
import org.apache.poi.hpsf.Variant;
import org.apache.poi.hpsf.VariantSupport;
+import org.apache.poi.hpsf.WritingNotSupportedException;
import org.apache.poi.hpsf.wellknown.PropertyIDMap;
import org.apache.poi.hpsf.wellknown.SectionIDMap;
import org.apache.poi.poifs.eventfilesystem.POIFSReader;
+ /**
+ * <p>Writes an empty property set to a POIFS and reads it back
+ * in.</p>
+ *
+ * @exception IOException if an I/O exception occurs
+ * @exception UnsupportedVariantTypeException if HPSF does not yet support
+ * a variant type to be written
+ */
+ public void testNoFormatID()
+ throws IOException, UnsupportedVariantTypeException
+ {
+ final File dataDir =
+ new File(System.getProperty("HPSF.testdata.path"));
+ final File filename = new File(dataDir, POI_FS);
+ filename.deleteOnExit();
+
+ /* Create a mutable property set with a section that does not have the
+ * formatID set: */
+ final OutputStream out = new FileOutputStream(filename);
+ final POIFSFileSystem poiFs = new POIFSFileSystem();
+ final MutablePropertySet ps = new MutablePropertySet();
+ ps.clearSections();
+ ps.addSection(new MutableSection());
+
+ /* Write it to a POIFS and the latter to disk: */
+ try
+ {
+ final ByteArrayOutputStream psStream = new ByteArrayOutputStream();
+ ps.write(psStream);
+ psStream.close();
+ final byte[] streamData = psStream.toByteArray();
+ poiFs.createDocument(new ByteArrayInputStream(streamData),
+ SummaryInformation.DEFAULT_STREAM_NAME);
+ poiFs.writeFilesystem(out);
+ out.close();
+ Assert.fail("Should have thrown a NoFormatIDException.");
+ }
+ catch (Exception ex)
+ {
+ Assert.assertTrue(ex instanceof NoFormatIDException);
+ }
+ finally
+ {
+ out.close();
+ }
+ }
+
+
+
/**
* <p>Writes an empty property set to a POIFS and reads it back
* in.</p>
/**
- * <p>Writes a simple property set with a SummaryInformation and a
- * DocumentSummaryInformation stream to a POIFS and reads it back in.</p>
+ * <p>Writes a simple property set with a SummaryInformation section to a
+ * POIFS and reads it back in.</p>
*
* @exception IOException if an I/O exception occurs
* @exception UnsupportedVariantTypeException if HPSF does not yet support
filename.deleteOnExit();
final OutputStream out = new FileOutputStream(filename);
final POIFSFileSystem poiFs = new POIFSFileSystem();
-
+
final MutablePropertySet ps = new MutablePropertySet();
final MutableSection si = new MutableSection();
si.setFormatID(SectionIDMap.SUMMARY_INFORMATION_ID);
ps.getSections().set(0, si);
-
+
final MutableProperty p = new MutableProperty();
p.setID(PropertyIDMap.PID_AUTHOR);
p.setType(Variant.VT_LPWSTR);
p.setValue(AUTHOR);
si.setProperty(p);
si.setProperty(PropertyIDMap.PID_TITLE, Variant.VT_LPSTR, TITLE);
-
+
poiFs.createDocument(ps.toInputStream(),
SummaryInformation.DEFAULT_STREAM_NAME);
poiFs.writeFilesystem(out);
out.close();
-
+
/* Read the POIFS: */
final PropertySet[] psa = new PropertySet[1];
final POIFSReader r = new POIFSReader();
throw new RuntimeException(ex.toString());
}
}
-
+
},
SummaryInformation.DEFAULT_STREAM_NAME);
r.read(new FileInputStream(filename));
+ /**
+ * <p>Writes a simple property set with two sections to a POIFS and reads it
+ * back in.</p>
+ *
+ * @exception IOException if an I/O exception occurs
+ * @exception WritingNotSupportedException if HPSF does not yet support
+ * a variant type to be written
+ */
+ public void testWriteTwoSections()
+ throws WritingNotSupportedException, IOException
+ {
+ final String STREAM_NAME = "PropertySetStream";
+ final String SECTION1 = "Section 1";
+ final String SECTION2 = "Section 2";
+
+ final File dataDir =
+ new File(System.getProperty("HPSF.testdata.path"));
+ final File filename = new File(dataDir, POI_FS);
+ filename.deleteOnExit();
+ final OutputStream out = new FileOutputStream(filename);
+
+ final POIFSFileSystem poiFs = new POIFSFileSystem();
+ final MutablePropertySet ps = new MutablePropertySet();
+ ps.clearSections();
+
+ final byte[] formatID =
+ new byte[]{0, 1, 2, 3, 4, 5, 6, 7,
+ 8, 9, 10, 11, 12, 13, 14, 15};
+ final MutableSection s1 = new MutableSection();
+ s1.setFormatID(formatID);
+ s1.setProperty(2, SECTION1);
+ ps.addSection(s1);
+
+ final MutableSection s2 = new MutableSection();
+ s2.setFormatID(formatID);
+ s2.setProperty(2, SECTION2);
+ ps.addSection(s2);
+
+ poiFs.createDocument(ps.toInputStream(), STREAM_NAME);
+ poiFs.writeFilesystem(out);
+ out.close();
+
+ /* Read the POIFS: */
+ final PropertySet[] psa = new PropertySet[1];
+ final POIFSReader r = new POIFSReader();
+ r.registerListener(new POIFSReaderListener()
+ {
+ public void processPOIFSReaderEvent
+ (final POIFSReaderEvent event)
+ {
+ try
+ {
+ psa[0] = PropertySetFactory.create(event.getStream());
+ }
+ catch (Exception ex)
+ {
+ ex.printStackTrace();
+ throw new RuntimeException(ex);
+ }
+ }
+ },
+ STREAM_NAME);
+ r.read(new FileInputStream(filename));
+ Assert.assertNotNull(psa[0]);
+ Section s = (Section) (psa[0].getSections().get(0));
+ Object p = s.getProperty(2);
+ Assert.assertEquals(SECTION1, p);
+ s = (Section) (psa[0].getSections().get(1));
+ p = s.getProperty(2);
+ Assert.assertEquals(SECTION2, p);
+ }
+
+
+
static class MyPOIFSReaderListener implements POIFSReaderListener
{
public void processPOIFSReaderEvent(final POIFSReaderEvent event)
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
-import java.io.PrintWriter;
-import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
- /**
- * <p>Returns a textual representation of a {@link Throwable}, including a
- * stacktrace.</p>
- *
- * @param t The {@link Throwable}
- *
- * @return a string containing the output of a call to
- * <code>t.printStacktrace()</code>.
- */
- public static String toString(final Throwable t)
- {
- final StringWriter sw = new StringWriter();
- final PrintWriter pw = new PrintWriter(sw);
- t.printStackTrace(pw);
- pw.close();
- try
- {
- sw.close();
- return sw.toString();
- }
- catch (IOException e)
- {
- final StringBuffer b = new StringBuffer(t.getMessage());
- b.append("\n");
- b.append("Could not create a stacktrace. Reason: ");
- b.append(e.getMessage());
- return b.toString();
- }
- }
-
}