Writing: API enhanced, bugs fixed, performance improvements, code simplifications, two new testcases, more int to long promotions.

git-svn-id: https://svn.apache.org/repos/asf/jakarta/poi/trunk@353341 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Rainer Klute 2003-09-04 20:15:24 +00:00
parent 495dfa3eb7
commit 69581f5f0d
10 changed files with 305 additions and 131 deletions

View File

@ -26,6 +26,20 @@ public class MutableProperty extends Property
/**
* <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>
*

View File

@ -59,6 +59,7 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.ListIterator;
@ -109,6 +110,30 @@ public class MutablePropertySet extends PropertySet
/**
* <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>
*/
@ -178,6 +203,7 @@ public class MutablePropertySet extends PropertySet
public void clearSections()
{
sections = null;
sectionCount = 0;
}
@ -194,6 +220,7 @@ public class MutablePropertySet extends PropertySet
if (sections == null)
sections = new LinkedList();
sections.add(section);
sectionCount = sections.size();
}
@ -223,13 +250,16 @@ public class MutablePropertySet extends PropertySet
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();
@ -240,7 +270,7 @@ public class MutablePropertySet extends PropertySet
for (final ListIterator i = sections.listIterator(); i.hasNext();)
{
final MutableSection s = (MutableSection) i.next();
offset = s.write(out, offset);
offset += s.write(out);
}
}

View File

@ -70,14 +70,11 @@ import org.apache.poi.util.LittleEndianConsts;
* <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>
@ -95,6 +92,15 @@ public class MutableSection extends Section
/**
* <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>
*/
@ -108,6 +114,26 @@ public class MutableSection extends Section
/**
* <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>
*
@ -146,10 +172,12 @@ public class MutableSection extends Section
*/
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;
}
@ -164,7 +192,7 @@ public class MutableSection extends Section
* @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)
@ -186,7 +214,7 @@ public class MutableSection extends Section
* @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
*/
@ -211,7 +239,7 @@ public class MutableSection extends Section
*
* @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
@ -227,6 +255,7 @@ public class MutableSection extends Section
}
preprops.add(p);
dirty = true;
propertyCount = preprops.size();
}
@ -238,7 +267,7 @@ public class MutableSection extends Section
* @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
*/
@ -258,8 +287,15 @@ public class MutableSection extends Section
{
if (dirty)
{
size = calcSize();
dirty = false;
try
{
size = calcSize();
dirty = false;
}
catch (Exception ex)
{
throw new HPSFRuntimeException(ex);
}
}
return size;
}
@ -273,58 +309,13 @@ public class MutableSection extends Section
*
* @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;
}
@ -337,19 +328,24 @@ public class MutableSection extends Section
* 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();
@ -364,7 +360,8 @@ public class MutableSection extends Section
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;
@ -388,13 +385,22 @@ public class MutableSection extends Section
/* 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;
}

View File

@ -209,6 +209,9 @@ public class PropertySet
/**
* <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;
@ -474,7 +477,7 @@ public class PropertySet
* 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

View File

@ -142,9 +142,12 @@ public class Section
/**
* 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;
/**
@ -162,7 +165,7 @@ public class Section
/**
* @see #getProperties
*/
private Property[] properties;
protected Property[] properties;
/**
@ -175,16 +178,6 @@ public class Section
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;
}
/**
@ -383,7 +376,7 @@ public class Section
*
* @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++)
@ -406,9 +399,9 @@ public class Section
*
* @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

View File

@ -54,6 +54,9 @@
*/
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;
@ -337,4 +340,36 @@ public class Util
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();
}
}
}

View File

@ -290,9 +290,9 @@ public class PropertyIDMap extends HashMap
* <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);
}

View File

@ -272,7 +272,7 @@ public class TestBasic extends TestCase
}
catch (Throwable t)
{
final String s = Util.toString(t);
final String s = org.apache.poi.hpsf.Util.toString(t);
fail(s);
}
}

View File

@ -75,6 +75,7 @@ import org.apache.poi.hpsf.HPSFRuntimeException;
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;
@ -83,6 +84,7 @@ import org.apache.poi.hpsf.SummaryInformation;
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;
@ -138,6 +140,55 @@ public class TestWrite extends TestCase
/**
* <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>
@ -180,8 +231,8 @@ public class TestWrite extends TestCase
/**
* <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
@ -198,24 +249,24 @@ public class TestWrite extends TestCase
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();
@ -234,7 +285,7 @@ public class TestWrite extends TestCase
throw new RuntimeException(ex.toString());
}
}
},
SummaryInformation.DEFAULT_STREAM_NAME);
r.read(new FileInputStream(filename));
@ -248,6 +299,80 @@ public class TestWrite extends TestCase
/**
* <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)

View File

@ -62,8 +62,6 @@ import java.io.FileNotFoundException;
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;
@ -301,34 +299,4 @@ public class Util
/**
* <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();
}
}
}