Based on reading of the new file format docs, start to tweak how fixed length vs variable length / multi-valued properties are handled

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1593861 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Nick Burch 2014-05-11 21:16:40 +00:00
parent 1a149050ee
commit 0bcdc868f3
3 changed files with 89 additions and 45 deletions

View File

@ -90,6 +90,13 @@ public final class Chunks implements ChunkGroupWithProperties {
}
else return Collections.emptyMap();
}
public Map<MAPIProperty, PropertyValue> getRawProperties() {
if (messageProperties != null) {
return messageProperties.getRawProperties();
}
else return Collections.emptyMap();
}
public Map<MAPIProperty,List<Chunk>> getAll() {
return allChunks;
}

View File

@ -20,7 +20,7 @@ package org.apache.poi.hsmf.datatypes;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -42,9 +42,10 @@ import org.apache.poi.util.POILogFactory;
import org.apache.poi.util.POILogger;
/**
* A Chunk which holds fixed-length properties, and pointer
* to the variable length ones (which get their own chunk).
* There are two kinds of PropertiesChunks, which differ only in
* <p>A Chunk which holds (single) fixed-length properties, and pointer
* to the variable length ones / multi-valued ones (which get their
* own chunk).
* <p>There are two kinds of PropertiesChunks, which differ only in
* their headers.
*/
public abstract class PropertiesChunk extends Chunk {
@ -53,16 +54,16 @@ public abstract class PropertiesChunk extends Chunk {
/** For logging problems we spot with the file */
private POILogger logger = POILogFactory.getLogger(PropertiesChunk.class);
/**
* Holds properties, indexed by type. Properties can be multi-valued
* Holds properties, indexed by type. If a property is multi-valued,
* or variable length, it will be held via a {@link ChunkBasedPropertyValue}.
*/
private Map<MAPIProperty, List<PropertyValue>> properties =
new HashMap<MAPIProperty, List<PropertyValue>>();
private Map<MAPIProperty, PropertyValue> properties =
new HashMap<MAPIProperty, PropertyValue>();
/**
* The ChunkGroup that these properties apply to. Used when
* matching chunks to variable sized properties
* matching chunks to variable sized and multi-valued properties
*/
private ChunkGroup parentGroup;
@ -80,30 +81,52 @@ public abstract class PropertiesChunk extends Chunk {
}
/**
* Returns all the properties in the chunk
* Returns all the properties in the chunk, without
* looking up any chunk-based values
*/
public Map<MAPIProperty, List<PropertyValue>> getProperties() {
public Map<MAPIProperty, PropertyValue> getRawProperties() {
return properties;
}
/**
* Returns all values for the given property, of null if none exist
* <p>Returns all the properties in the chunk, along with their
* values.
* <p>Any chunk-based values will be looked up and returned as such
*/
public List<PropertyValue> getValues(MAPIProperty property) {
return properties.get(property);
public Map<MAPIProperty, List<PropertyValue>> getProperties() {
Map<MAPIProperty, List<PropertyValue>> props =
new HashMap<MAPIProperty, List<PropertyValue>>(properties.size());
for (MAPIProperty prop : properties.keySet()) {
props.put(prop, getValues(prop));
}
return props;
}
/**
* Returns the (first/only) value for the given property, or
* null if none exist
* Returns all values for the given property, looking up chunk based
* ones as required, of null if none exist
*/
public PropertyValue getValue(MAPIProperty property) {
List<PropertyValue> values = properties.get(property);
if (values != null && values.size() > 0) {
return values.get(0);
}
public List<PropertyValue> getValues(MAPIProperty property) {
PropertyValue val = properties.get(property);
if (val == null) {
return null;
}
if (val instanceof ChunkBasedPropertyValue) {
ChunkBasedPropertyValue cval = (ChunkBasedPropertyValue)val;
// TODO Lookup
return Collections.emptyList();
} else {
return Collections.singletonList(val);
}
}
/**
* Returns the value / pointer to the value chunk of
* the property, or null if none exists
*/
public PropertyValue getRawValue(MAPIProperty property) {
return properties.get(property);
}
/**
* Called once the parent ChunkGroup has been populated, to match
@ -118,13 +141,11 @@ public abstract class PropertiesChunk extends Chunk {
}
// Loop over our values, looking for chunk based ones
for (List<PropertyValue> vals : properties.values()) {
if (vals != null) {
for (PropertyValue val : vals) {
for (PropertyValue val : properties.values()) {
if (val instanceof ChunkBasedPropertyValue) {
ChunkBasedPropertyValue cVal = (ChunkBasedPropertyValue)val;
Chunk chunk = chunks.get(cVal.getProperty().id);
//System.err.println(cVal + " -> " + HexDump.toHex(cVal.data));
//System.err.println(cVal.getProperty() + " = " + cVal + " -> " + HexDump.toHex(cVal.data));
// TODO Make sense of the raw offset value
@ -136,8 +157,6 @@ public abstract class PropertiesChunk extends Chunk {
}
}
}
}
}
protected void readProperties(InputStream value) throws IOException {
boolean going = true;
@ -183,6 +202,10 @@ public abstract class PropertiesChunk extends Chunk {
}
}
// TODO Detect if it is multi-valued, since if it is
// then even fixed-length strings store their multiple
// values in another chunk (much as variable length ones)
// Work out how long the "data" is
// This might be the actual data, or just a pointer
// to another chunk which holds the data itself
@ -241,10 +264,10 @@ public abstract class PropertiesChunk extends Chunk {
propVal = new PropertyValue(prop, flags, data);
}
if (properties.get(prop) == null) {
properties.put(prop, new ArrayList<PropertyValue>());
if (properties.get(prop) != null) {
logger.log(POILogger.WARN, "Duplicate values found for " + prop);
}
properties.get(prop).add(propVal);
properties.put(prop, propVal);
} catch (BufferUnderrunException e) {
// Invalid property, ended short
going = false;

View File

@ -31,6 +31,7 @@ import java.util.TimeZone;
import org.apache.poi.POIDataSamples;
import org.apache.poi.POITestCase;
import org.apache.poi.hsmf.datatypes.ChunkBasedPropertyValue;
import org.apache.poi.hsmf.datatypes.Chunks;
import org.apache.poi.hsmf.datatypes.MAPIProperty;
import org.apache.poi.hsmf.datatypes.PropertyValue;
import org.apache.poi.hsmf.datatypes.PropertyValue.LongPropertyValue;
@ -86,9 +87,12 @@ public final class TestFixedSizedProperties extends POITestCase {
* Check we find properties of a variety of different types
*/
public void testPropertyValueTypes() throws Exception {
Map<MAPIProperty,List<PropertyValue>> props =
mapiMessageSucceeds.getMainChunks().getProperties();
HashSet<Class<? extends PropertyValue>> seenTypes = new HashSet<Class<? extends PropertyValue>>();
Chunks mainChunks = mapiMessageSucceeds.getMainChunks();
// Ask to have the values looked up
Map<MAPIProperty,List<PropertyValue>> props = mainChunks.getProperties();
HashSet<Class<? extends PropertyValue>> seenTypes =
new HashSet<Class<? extends PropertyValue>>();
for (List<PropertyValue> pvs : props.values()) {
for (PropertyValue pv : pvs) {
seenTypes.add(pv.getClass());
@ -97,6 +101,16 @@ public final class TestFixedSizedProperties extends POITestCase {
assertTrue(seenTypes.toString(), seenTypes.size() > 3);
assertTrue(seenTypes.toString(), seenTypes.contains(LongPropertyValue.class));
assertTrue(seenTypes.toString(), seenTypes.contains(TimePropertyValue.class));
assertFalse(seenTypes.toString(), seenTypes.contains(ChunkBasedPropertyValue.class));
// Ask for the raw values
seenTypes.clear();
for (PropertyValue pv : mainChunks.getRawProperties().values()) {
seenTypes.add(pv.getClass());
}
assertTrue(seenTypes.toString(), seenTypes.size() > 3);
assertTrue(seenTypes.toString(), seenTypes.contains(LongPropertyValue.class));
assertTrue(seenTypes.toString(), seenTypes.contains(TimePropertyValue.class));
assertTrue(seenTypes.toString(), seenTypes.contains(ChunkBasedPropertyValue.class));
}