Switch HMEF attributes to a factory scheme for creation, then add subtypes for cleaner code. Also adds a few more tests

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1076310 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Nick Burch 2011-03-02 17:52:12 +00:00
parent 20f6e280a5
commit 145de166a1
11 changed files with 408 additions and 54 deletions

View File

@ -20,8 +20,11 @@ package org.apache.poi.hmef;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.apache.poi.hmef.attribute.TNEFAttribute;
import org.apache.poi.hmef.attribute.MAPIAttribute; import org.apache.poi.hmef.attribute.MAPIAttribute;
import org.apache.poi.hmef.attribute.TNEFAttribute;
import org.apache.poi.hmef.attribute.TNEFMAPIAttribute;
import org.apache.poi.hmef.attribute.TNEFProperty;
import org.apache.poi.hsmf.datatypes.MAPIProperty;
/** /**
@ -33,21 +36,59 @@ public final class Attachment {
private final List<TNEFAttribute> attributes = new ArrayList<TNEFAttribute>(); private final List<TNEFAttribute> attributes = new ArrayList<TNEFAttribute>();
private final List<MAPIAttribute> mapiAttributes = new ArrayList<MAPIAttribute>(); private final List<MAPIAttribute> mapiAttributes = new ArrayList<MAPIAttribute>();
protected void addAttribute(TNEFAttribute attr) { protected void addAttribute(TNEFAttribute attr) {
attributes.add(attr); attributes.add(attr);
if(attr instanceof TNEFMAPIAttribute) {
TNEFMAPIAttribute tnefMAPI = (TNEFMAPIAttribute)attr;
mapiAttributes.addAll( tnefMAPI.getMAPIAttributes() );
}
} }
protected void addAttribute(MAPIAttribute attr) { /**
mapiAttributes.add(attr); * Return the attachment attribute with the given ID,
* or null if there isn't one.
*/
public TNEFAttribute getMessageAttribute(TNEFProperty id) {
for(TNEFAttribute attr : attributes) {
if(attr.getProperty() == id) {
return attr;
}
}
return null;
} }
/**
* Return the attachment MAPI Attribute with the given ID,
* or null if there isn't one.
*/
public MAPIAttribute getMessageMAPIAttribute(MAPIProperty id) {
for(MAPIAttribute attr : mapiAttributes) {
if(attr.getProperty() == id) {
return attr;
}
}
return null;
}
/**
* Returns all HMEF/TNEF attributes of the attachment,
* such as filename, icon and contents
*/
public List<TNEFAttribute> getAttributes() { public List<TNEFAttribute> getAttributes() {
return attributes; return attributes;
} }
/**
* Returns all MAPI attributes of the attachment,
* such as extension, encoding, size and position
*/
public List<MAPIAttribute> getMAPIAttributes() { public List<MAPIAttribute> getMAPIAttributes() {
return mapiAttributes; return mapiAttributes;
} }
public String getFilename() {
TNEFAttribute attr = null;
return null;
}
} }

View File

@ -23,7 +23,10 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.apache.poi.hmef.attribute.MAPIAttribute; import org.apache.poi.hmef.attribute.MAPIAttribute;
import org.apache.poi.hmef.attribute.MAPIRtfAttribute;
import org.apache.poi.hmef.attribute.MAPIStringAttribute;
import org.apache.poi.hmef.attribute.TNEFAttribute; import org.apache.poi.hmef.attribute.TNEFAttribute;
import org.apache.poi.hmef.attribute.TNEFMAPIAttribute;
import org.apache.poi.hmef.attribute.TNEFProperty; import org.apache.poi.hmef.attribute.TNEFProperty;
import org.apache.poi.hsmf.datatypes.MAPIProperty; import org.apache.poi.hsmf.datatypes.MAPIProperty;
import org.apache.poi.util.LittleEndian; import org.apache.poi.util.LittleEndian;
@ -59,24 +62,6 @@ public final class HMEFMessage {
// Now begin processing the contents // Now begin processing the contents
process(inp, 0); process(inp, 0);
// Finally expand out the MAPI Attributes
for(TNEFAttribute attr : messageAttributes) {
if(attr.getProperty() == TNEFProperty.ID_MAPIPROPERTIES) {
mapiAttributes.addAll(
MAPIAttribute.create(attr)
);
}
}
for(Attachment attachment : attachments) {
for(TNEFAttribute attr : attachment.getAttributes()) {
if(attr.getProperty()== TNEFProperty.ID_MAPIPROPERTIES) {
attachment.getMAPIAttributes().addAll(
MAPIAttribute.create(attr)
);
}
}
}
} }
private void process(InputStream inp, int lastLevel) throws IOException { private void process(InputStream inp, int lastLevel) throws IOException {
@ -87,11 +72,16 @@ public final class HMEFMessage {
} }
// Build the attribute // Build the attribute
TNEFAttribute attr = new TNEFAttribute(inp); TNEFAttribute attr = TNEFAttribute.create(inp);
// Decide what to attach it to, based on the levels and IDs // Decide what to attach it to, based on the levels and IDs
if(level == TNEFProperty.LEVEL_MESSAGE) { if(level == TNEFProperty.LEVEL_MESSAGE) {
messageAttributes.add(attr); messageAttributes.add(attr);
if(attr instanceof TNEFMAPIAttribute) {
TNEFMAPIAttribute tnefMAPI = (TNEFMAPIAttribute)attr;
mapiAttributes.addAll( tnefMAPI.getMAPIAttributes() );
}
} else if(level == TNEFProperty.LEVEL_ATTACHMENT) { } else if(level == TNEFProperty.LEVEL_ATTACHMENT) {
// Previous attachment or a new one? // Previous attachment or a new one?
if(attachments.size() == 0 || attr.getProperty() == TNEFProperty.ID_ATTACHRENDERDATA) { if(attachments.size() == 0 || attr.getProperty() == TNEFProperty.ID_ATTACHRENDERDATA) {
@ -99,7 +89,8 @@ public final class HMEFMessage {
} }
// Save the attribute for it // Save the attribute for it
attachments.get(attachments.size()-1).addAttribute(attr); Attachment attach = attachments.get(attachments.size()-1);
attach.addAttribute(attr);
} else { } else {
throw new IllegalStateException("Unhandled level " + level); throw new IllegalStateException("Unhandled level " + level);
} }
@ -158,4 +149,40 @@ public final class HMEFMessage {
} }
return null; return null;
} }
/**
* Return the string value of the mapi property, or null
* if it isn't set
*/
private String getString(MAPIProperty id) {
MAPIAttribute attr = getMessageMAPIAttribute(id);
if(id == null) {
return null;
}
if(attr instanceof MAPIStringAttribute) {
return ((MAPIStringAttribute)attr).getDataString();
}
if(attr instanceof MAPIRtfAttribute) {
return ((MAPIRtfAttribute)attr).getDataString();
}
System.err.println("Warning, no string property found: " + attr.toString());
return null;
}
/**
* Returns the Message Subject, or null if the mapi property
* for this isn't set
*/
public String getSubject() {
return getString(MAPIProperty.CONVERSATION_TOPIC);
}
/**
* Returns the Message Body, as RTF, or null if the mapi property
* for this isn't set
*/
public String getBody() {
return getString(MAPIProperty.RTF_COMPRESSED);
}
} }

View File

@ -82,7 +82,14 @@ public class MAPIAttribute {
* the list of MAPI Attributes contained within it * the list of MAPI Attributes contained within it
*/ */
public static List<MAPIAttribute> create(TNEFAttribute parent) throws IOException { public static List<MAPIAttribute> create(TNEFAttribute parent) throws IOException {
if(parent.getProperty() != TNEFProperty.ID_MAPIPROPERTIES) { if(parent.getProperty() == TNEFProperty.ID_MAPIPROPERTIES) {
// Regular MAPI Properties, normally on the message
}
else if(parent.getProperty() == TNEFProperty.ID_ATTACHMENT) {
// MAPI Properties for an attachment
}
else {
// Something else, oh dear...
throw new IllegalArgumentException( throw new IllegalArgumentException(
"Can only create from a MAPIProperty attribute, " + "Can only create from a MAPIProperty attribute, " +
"instead received a " + parent.getProperty() + " one" "instead received a " + parent.getProperty() + " one"

View File

@ -32,19 +32,18 @@ import org.apache.poi.util.LittleEndian;
* Note - the types and IDs differ from standard Outlook/MAPI * Note - the types and IDs differ from standard Outlook/MAPI
* ones, so we can't just re-use the HSMF ones. * ones, so we can't just re-use the HSMF ones.
*/ */
public final class TNEFAttribute { public class TNEFAttribute {
private final TNEFProperty property; private final TNEFProperty property;
private final int type; private final int type;
private final byte[] data; private final byte[] data;
private final int checksum; private final int checksum;
/** /**
* Constructs a single new attribute from * Constructs a single new attribute from the id, type,
* the contents of the stream * and the contents of the stream
*/ */
public TNEFAttribute(InputStream inp) throws IOException { protected TNEFAttribute(int id, int type, InputStream inp) throws IOException {
int id = LittleEndian.readUShort(inp); this.type = type;
this.type = LittleEndian.readUShort(inp);
int length = LittleEndian.readInt(inp); int length = LittleEndian.readInt(inp);
property = TNEFProperty.getBest(id, type); property = TNEFProperty.getBest(id, type);
@ -52,9 +51,26 @@ public final class TNEFAttribute {
IOUtils.readFully(inp, data); IOUtils.readFully(inp, data);
checksum = LittleEndian.readUShort(inp); checksum = LittleEndian.readUShort(inp);
}
// TODO Handle the MapiProperties attribute in /**
// a different way, as we need to recurse into it * Creates a new TNEF Attribute by reading data from
* the stream within a {@link HMEFMessage}
*/
public static TNEFAttribute create(InputStream inp) throws IOException {
int id = LittleEndian.readUShort(inp);
int type = LittleEndian.readUShort(inp);
// Create as appropriate
if(id == TNEFProperty.ID_MAPIPROPERTIES.id ||
id == TNEFProperty.ID_ATTACHMENT.id) {
return new TNEFMAPIAttribute(id, type, inp);
}
if(type == TNEFProperty.TYPE_STRING ||
type == TNEFProperty.TYPE_TEXT) {
return new TNEFStringAttribute(id, type, inp);
}
return new TNEFAttribute(id, type, inp);
} }
public TNEFProperty getProperty() { public TNEFProperty getProperty() {
@ -70,7 +86,7 @@ public final class TNEFAttribute {
} }
public String toString() { public String toString() {
return "Attachment " + property.toString() + ", type=" + type + return "Attribute " + property.toString() + ", type=" + type +
", data length=" + data.length; ", data length=" + data.length;
} }
} }

View File

@ -0,0 +1,52 @@
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */
package org.apache.poi.hmef.attribute;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import org.apache.poi.hmef.Attachment;
import org.apache.poi.hmef.HMEFMessage;
/**
* A TNEF Attribute holding MAPI Attributes, which applies to a
* {@link HMEFMessage} or one of its {@link Attachment}s.
*/
public final class TNEFMAPIAttribute extends TNEFAttribute {
private final List<MAPIAttribute> attributes;
/**
* Constructs a single new mapi containing attribute from the
* id, type, and the contents of the stream
*/
protected TNEFMAPIAttribute(int id, int type, InputStream inp) throws IOException {
super(id, type, inp);
attributes = MAPIAttribute.create(this);
}
public List<MAPIAttribute> getMAPIAttributes() {
return attributes;
}
public String toString() {
return "Attribute " + getProperty().toString() + ", type=" + getType() +
", " + attributes.size() + " MAPI Attributes";
}
}

View File

@ -0,0 +1,55 @@
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */
package org.apache.poi.hmef.attribute;
import java.io.IOException;
import java.io.InputStream;
import org.apache.poi.hmef.Attachment;
import org.apache.poi.hmef.HMEFMessage;
import org.apache.poi.util.StringUtil;
/**
* An String attribute which applies to a {@link HMEFMessage}
* or one of its {@link Attachment}s.
*/
public final class TNEFStringAttribute extends TNEFAttribute {
/**
* Constructs a single new string attribute from the id, type,
* and the contents of the stream
*/
protected TNEFStringAttribute(int id, int type, InputStream inp) throws IOException {
super(id, type, inp);
}
public String getString() {
byte[] data = getData();
// TODO Verify if these are the right way around
if(getType() == TNEFProperty.TYPE_TEXT) {
return StringUtil.getFromUnicodeLE(data);
}
return StringUtil.getFromCompressedUnicode(
data, 0, data.length
);
}
public String toString() {
return "Attribute " + getProperty().toString() + ", type=" + getType() +
", data=" + getString();
}
}

View File

@ -79,6 +79,7 @@ public final class HMEFDumper {
private void dump() throws IOException { private void dump() throws IOException {
int level; int level;
int attachments = 0;
while(true) { while(true) {
// Fetch the level // Fetch the level
@ -88,7 +89,16 @@ public final class HMEFDumper {
} }
// Build the attribute // Build the attribute
TNEFAttribute attr = new TNEFAttribute(inp); TNEFAttribute attr = TNEFAttribute.create(inp);
// Is it a new attachment?
if(level == TNEFProperty.LEVEL_ATTACHMENT &&
attr.getProperty() == TNEFProperty.ID_ATTACHRENDERDATA) {
attachments++;
System.out.println();
System.out.println("Attachment # " + attachments);
System.out.println();
}
// Print the attribute into // Print the attribute into
System.out.println( System.out.println(
@ -125,7 +135,8 @@ public final class HMEFDumper {
} }
System.out.println(); System.out.println();
if(attr.getProperty() == TNEFProperty.ID_MAPIPROPERTIES) { if(attr.getProperty() == TNEFProperty.ID_MAPIPROPERTIES ||
attr.getProperty() == TNEFProperty.ID_ATTACHMENT) {
List<MAPIAttribute> attrs = MAPIAttribute.create(attr); List<MAPIAttribute> attrs = MAPIAttribute.create(attr);
for(MAPIAttribute ma : attrs) { for(MAPIAttribute ma : attrs) {
System.out.println(indent + indent + ma); System.out.println(indent + indent + ma);

View File

@ -0,0 +1,60 @@
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */
package org.apache.poi.hmef;
import junit.framework.TestCase;
import org.apache.poi.POIDataSamples;
public final class TestAttachments extends TestCase {
private static final POIDataSamples _samples = POIDataSamples.getHMEFInstance();
/**
* Check the file is as we expect
*/
public void testCounts() throws Exception {
HMEFMessage msg = new HMEFMessage(
_samples.openResourceAsStream("quick-winmail.dat")
);
// Should have 5 attachments
assertEquals(5, msg.getAttachments().size());
}
/**
* Check some basic bits about the attachments
*/
public void testBasicAttachments() throws Exception {
// TODO
}
/**
* Query the attachments in detail, and check we see
* the right values for key things
*/
public void testAttachmentDetails() throws Exception {
// TODO
}
/**
* Ensure the attachment contents come back as they should do
*/
public void testAttachmentContents() throws Exception {
// TODO
}
}

View File

@ -20,8 +20,8 @@ package org.apache.poi.hmef;
import junit.framework.TestCase; import junit.framework.TestCase;
import org.apache.poi.POIDataSamples; import org.apache.poi.POIDataSamples;
import org.apache.poi.hmef.attribute.TNEFAttribute;
import org.apache.poi.hmef.attribute.TNEFProperty; import org.apache.poi.hmef.attribute.TNEFProperty;
import org.apache.poi.util.LittleEndian;
public final class TestHMEFMessage extends TestCase { public final class TestHMEFMessage extends TestCase {
private static final POIDataSamples _samples = POIDataSamples.getHMEFInstance(); private static final POIDataSamples _samples = POIDataSamples.getHMEFInstance();
@ -56,13 +56,9 @@ public final class TestHMEFMessage extends TestCase {
int mapiAttrCount = attach.getMAPIAttributes().size(); int mapiAttrCount = attach.getMAPIAttributes().size();
assertEquals(6, attrCount); assertEquals(6, attrCount);
// TODO assertTrue("Should be 20-25 mapi attributes, found " + mapiAttrCount, mapiAttrCount >= 20);
// assertTrue("Should be 3-4 attributes, found " + mapiAttrCount, mapiAttrCount >= 20); assertTrue("Should be 20-25 mapi attributes, found " + mapiAttrCount, mapiAttrCount <= 25);
// assertTrue("Should be 3-4 attributes, found " + mapiAttrCount, mapiAttrCount <= 25);
} }
// TODO
} }
public void testBasicMessageAttributes() throws Exception { public void testBasicMessageAttributes() throws Exception {
@ -88,18 +84,22 @@ public final class TestHMEFMessage extends TestCase {
assertNull(msg.getMessageAttribute(TNEFProperty.ID_ATTACHDATA)); assertNull(msg.getMessageAttribute(TNEFProperty.ID_ATTACHDATA));
// Now check the details of one or two // Now check the details of one or two
// TODO assertEquals(
0x010000,
LittleEndian.getInt( msg.getMessageAttribute(TNEFProperty.ID_TNEFVERSION).getData() )
);
assertEquals(
"IPM.Microsoft Mail.Note\0",
new String(msg.getMessageAttribute(TNEFProperty.ID_MESSAGECLASS).getData(), "ASCII")
);
} }
public void testBasicMessageMAPIAttributes() throws Exception { public void testBasicMessageMAPIAttributes() throws Exception {
// TODO HMEFMessage msg = new HMEFMessage(
} _samples.openResourceAsStream("quick-winmail.dat")
);
public void testBasicAttachments() throws Exception { assertEquals("This is a test message", msg.getSubject());
// TODO assertEquals("{\\rtf1", msg.getBody().substring(0, 6));
}
public void testMessageAttributeDetails() throws Exception {
// TODO
} }
} }

View File

@ -0,0 +1,42 @@
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */
package org.apache.poi.hmef.attribute;
import junit.framework.TestCase;
import org.apache.poi.POIDataSamples;
import org.apache.poi.hmef.HMEFMessage;
public final class TestMAPIAttributes extends TestCase {
private static final POIDataSamples _samples = POIDataSamples.getHMEFInstance();
public void testOpen() throws Exception {
HMEFMessage msg = new HMEFMessage(
_samples.openResourceAsStream("quick-winmail.dat")
);
assertNotNull(msg);
}
// Test basics
// Test counts
// Check untyped
// Check typed
// Check common
}

View File

@ -0,0 +1,43 @@
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */
package org.apache.poi.hmef.attribute;
import junit.framework.TestCase;
import org.apache.poi.POIDataSamples;
import org.apache.poi.hmef.HMEFMessage;
public final class TestTNEFAttributes extends TestCase {
private static final POIDataSamples _samples = POIDataSamples.getHMEFInstance();
public void testOpen() throws Exception {
HMEFMessage msg = new HMEFMessage(
_samples.openResourceAsStream("quick-winmail.dat")
);
assertNotNull(msg);
}
// Test counts
// Test basics
// Test string
// Test a bit of mapi
}