Extended new builder classes with methods to create copies of message elements and to parse message from content stream

git-svn-id: https://svn.apache.org/repos/asf/james/mime4j/trunk@1611278 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/dom/src/main/java/org/apache/james/mime4j/message/AbstractEntityBuilder.java b/dom/src/main/java/org/apache/james/mime4j/message/AbstractEntityBuilder.java
index 7fb9fb2..434a47f 100644
--- a/dom/src/main/java/org/apache/james/mime4j/message/AbstractEntityBuilder.java
+++ b/dom/src/main/java/org/apache/james/mime4j/message/AbstractEntityBuilder.java
@@ -19,8 +19,6 @@
 
 package org.apache.james.mime4j.message;
 
-import java.io.IOException;
-import java.nio.charset.Charset;
 import java.util.Collections;
 import java.util.Date;
 import java.util.HashMap;
@@ -39,7 +37,6 @@
 import org.apache.james.mime4j.dom.field.FieldName;
 import org.apache.james.mime4j.dom.field.ParsedField;
 import org.apache.james.mime4j.field.Fields;
-import org.apache.james.mime4j.io.InputStreams;
 import org.apache.james.mime4j.stream.Field;
 import org.apache.james.mime4j.stream.NameValuePair;
 import org.apache.james.mime4j.util.MimeUtil;
@@ -51,8 +48,6 @@
 
     private Body body;
 
-    private BodyFactory bodyFactory;
-
     AbstractEntityBuilder() {
         this.fields = new LinkedList<Field>();
         this.fieldMap = new HashMap<String, List<Field>>();
@@ -189,6 +184,15 @@
         return this;
     }
 
+    /**
+     * Clears all fields.
+     */
+    public AbstractEntityBuilder clearFields() {
+        fields.clear();
+        fieldMap.clear();
+        return this;
+    }
+
     @SuppressWarnings("unchecked")
     <F extends ParsedField> F obtainField(String fieldName) {
         return (F) getField(fieldName);
@@ -454,59 +458,6 @@
     }
 
     /**
-     * Sets {@link org.apache.james.mime4j.message.BodyFactory} that will be
-     * used to generate message body.
-     *
-     * @param bodyFactory body factory.
-     */
-    public AbstractEntityBuilder use(final BodyFactory bodyFactory) {
-        this.bodyFactory = bodyFactory;
-        return this;
-    }
-
-    /**
-     * Sets text of this message with the charset.
-     *
-     * @param text
-     *            the text.
-     * @param charset
-     *            the charset of the text.
-     */
-    public AbstractEntityBuilder setBody(String text, Charset charset) throws IOException {
-        return setBody(text, null, charset);
-    }
-
-    /**
-     * Sets text of this message with the given MIME subtype and charset.
-     *
-     * @param text
-     *            the text.
-     * @param charset
-     *            the charset of the text.
-     * @param subtype
-     *            the text subtype (e.g. &quot;plain&quot;, &quot;html&quot; or
-     *            &quot;xml&quot;).
-     */
-    public AbstractEntityBuilder setBody(String text, String subtype, Charset charset) throws IOException {
-        if (subtype != null) {
-            if (charset != null) {
-                setField(Fields.contentType("text/" + subtype, new NameValuePair("charset", charset.name())));
-            } else {
-                setField(Fields.contentType("text/" + subtype));
-            }
-        }
-        TextBody textBody;
-        if (bodyFactory != null) {
-            textBody = bodyFactory.textBody(
-                    InputStreams.create(text, charset),
-                    charset != null ? charset.name() : null);
-        } else {
-            textBody = BasicBodyFactory.INSTANCE.textBody(text, charset);
-        }
-        return setBody(textBody);
-    }
-
-    /**
      * Returns message body.
      *
      * @return the message body.
diff --git a/dom/src/main/java/org/apache/james/mime4j/message/BodyPartBuilder.java b/dom/src/main/java/org/apache/james/mime4j/message/BodyPartBuilder.java
index 4b76cb3..066efe3 100644
--- a/dom/src/main/java/org/apache/james/mime4j/message/BodyPartBuilder.java
+++ b/dom/src/main/java/org/apache/james/mime4j/message/BodyPartBuilder.java
@@ -23,8 +23,11 @@
 import java.nio.charset.Charset;
 import java.util.Date;
 
+import org.apache.james.mime4j.dom.BinaryBody;
 import org.apache.james.mime4j.dom.Body;
-import org.apache.james.mime4j.dom.Header;
+import org.apache.james.mime4j.dom.TextBody;
+import org.apache.james.mime4j.field.Fields;
+import org.apache.james.mime4j.io.InputStreams;
 import org.apache.james.mime4j.stream.Field;
 import org.apache.james.mime4j.stream.NameValuePair;
 
@@ -33,10 +36,23 @@
  */
 public class BodyPartBuilder extends AbstractEntityBuilder {
 
+    private BodyFactory bodyFactory;
+
     public static BodyPartBuilder create() {
         return new BodyPartBuilder();
     }
 
+    /**
+     * Sets {@link org.apache.james.mime4j.message.BodyFactory} that will be
+     * used to generate message body.
+     *
+     * @param bodyFactory body factory.
+     */
+    public BodyPartBuilder use(BodyFactory bodyFactory) {
+        this.bodyFactory = bodyFactory;
+        return this;
+    }
+
     @Override
     public BodyPartBuilder setField(Field field) {
         super.setField(field);
@@ -102,22 +118,68 @@
         return this;
     }
 
-    @Override
-    public BodyPartBuilder use(final BodyFactory bodyFactory) {
-        super.use(bodyFactory);
-        return this;
-    }
-
-    @Override
+    /**
+     * Sets text of this message with the charset.
+     *
+     * @param text
+     *            the text.
+     * @param charset
+     *            the charset of the text.
+     */
     public BodyPartBuilder setBody(String text, Charset charset) throws IOException {
-        super.setBody(text, charset);
-        return this;
+        return setBody(text, null, charset);
     }
 
-    @Override
+    /**
+     * Sets text of this message with the given MIME subtype and charset.
+     *
+     * @param text
+     *            the text.
+     * @param charset
+     *            the charset of the text.
+     * @param subtype
+     *            the text subtype (e.g. &quot;plain&quot;, &quot;html&quot; or
+     *            &quot;xml&quot;).
+     */
     public BodyPartBuilder setBody(String text, String subtype, Charset charset) throws IOException {
-        super.setBody(text, subtype, charset);
-        return this;
+        if (subtype != null) {
+            if (charset != null) {
+                setField(Fields.contentType("text/" + subtype, new NameValuePair("charset", charset.name())));
+            } else {
+                setField(Fields.contentType("text/" + subtype));
+            }
+        }
+        TextBody textBody;
+        if (bodyFactory != null) {
+            textBody = bodyFactory.textBody(
+                    InputStreams.create(text, charset),
+                    charset != null ? charset.name() : null);
+        } else {
+            textBody = BasicBodyFactory.INSTANCE.textBody(text, charset);
+        }
+        return setBody(textBody);
+    }
+
+    /**
+     * Sets binary content of this message with the given MIME type.
+     *
+     * @param body
+     *            the body.
+     * @param mimeType
+     *            the MIME media type of the specified body
+     *            (&quot;type/subtype&quot;).
+     */
+    public BodyPartBuilder setBody(byte[] bin, String mimeType) throws IOException {
+        if (mimeType != null) {
+            setField(Fields.contentType(mimeType));
+        }
+        BinaryBody binBody;
+        if (bodyFactory != null) {
+            binBody = bodyFactory.binaryBody(InputStreams.create(bin));
+        } else {
+            binBody = BasicBodyFactory.INSTANCE.binaryBody(bin);
+        }
+        return setBody(binBody);
     }
 
     public BodyPart build() {
diff --git a/dom/src/main/java/org/apache/james/mime4j/message/MessageBuilder.java b/dom/src/main/java/org/apache/james/mime4j/message/MessageBuilder.java
index 9a332af..1f163cb 100644
--- a/dom/src/main/java/org/apache/james/mime4j/message/MessageBuilder.java
+++ b/dom/src/main/java/org/apache/james/mime4j/message/MessageBuilder.java
@@ -20,6 +20,7 @@
 package org.apache.james.mime4j.message;
 
 import java.io.IOException;
+import java.io.InputStream;
 import java.nio.charset.Charset;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -29,9 +30,17 @@
 import java.util.List;
 import java.util.TimeZone;
 
+import org.apache.james.mime4j.MimeException;
+import org.apache.james.mime4j.MimeIOException;
+import org.apache.james.mime4j.codec.DecodeMonitor;
+import org.apache.james.mime4j.dom.BinaryBody;
 import org.apache.james.mime4j.dom.Body;
+import org.apache.james.mime4j.dom.FieldParser;
 import org.apache.james.mime4j.dom.Header;
 import org.apache.james.mime4j.dom.Message;
+import org.apache.james.mime4j.dom.Multipart;
+import org.apache.james.mime4j.dom.SingleBody;
+import org.apache.james.mime4j.dom.TextBody;
 import org.apache.james.mime4j.dom.address.Address;
 import org.apache.james.mime4j.dom.address.AddressList;
 import org.apache.james.mime4j.dom.address.Mailbox;
@@ -43,9 +52,15 @@
 import org.apache.james.mime4j.dom.field.MailboxListField;
 import org.apache.james.mime4j.dom.field.ParseException;
 import org.apache.james.mime4j.dom.field.UnstructuredField;
+import org.apache.james.mime4j.field.DefaultFieldParser;
 import org.apache.james.mime4j.field.Fields;
+import org.apache.james.mime4j.field.LenientFieldParser;
 import org.apache.james.mime4j.field.address.AddressBuilder;
+import org.apache.james.mime4j.io.InputStreams;
+import org.apache.james.mime4j.parser.MimeStreamParser;
+import org.apache.james.mime4j.stream.BodyDescriptorBuilder;
 import org.apache.james.mime4j.stream.Field;
+import org.apache.james.mime4j.stream.MimeConfig;
 import org.apache.james.mime4j.stream.NameValuePair;
 
 /**
@@ -53,10 +68,112 @@
  */
 public class MessageBuilder extends AbstractEntityBuilder {
 
+    private MimeConfig config;
+    private DecodeMonitor monitor;
+    private BodyDescriptorBuilder bodyDescBuilder;
+    private FieldParser<?> fieldParser;
+    private BodyFactory bodyFactory;
+    private boolean flatMode;
+    private boolean rawContent;
+
     public static MessageBuilder create() {
         return new MessageBuilder();
     }
 
+    public static MessageBuilder createCopy(Message other) {
+        return new MessageBuilder().copy(other);
+    }
+
+    public static MessageBuilder read(final InputStream is) throws IOException {
+        return new MessageBuilder().parse(is);
+    }
+
+    /**
+     * Sets MIME configuration.
+     *
+     * @param config the configuration.
+     */
+    public MessageBuilder use(MimeConfig config) {
+        this.config = config;
+        return this;
+    }
+
+    /**
+     * Sets {@link org.apache.james.mime4j.codec.DecodeMonitor} that will be
+     * used to handle malformed data when executing {@link #parse(java.io.InputStream)}.
+     *
+     * @param monitor the decoder monitor.
+     */
+    public MessageBuilder use(DecodeMonitor monitor) {
+        this.monitor = monitor;
+        return this;
+    }
+
+    /**
+     * Sets {@link org.apache.james.mime4j.stream.BodyDescriptorBuilder} that will be
+     * used to generate body descriptors when executing {@link #parse(java.io.InputStream)}.
+     *
+     * @param bodyDescBuilder the body descriptor builder.
+     */
+    public MessageBuilder use(BodyDescriptorBuilder bodyDescBuilder) {
+        this.bodyDescBuilder = bodyDescBuilder;
+        return this;
+    }
+
+    /**
+     * Sets {@link org.apache.james.mime4j.dom.FieldParser} that will be
+     * used to generate parse message fields when executing {@link #parse(java.io.InputStream)}.
+     *
+     * @param fieldParser the field parser.
+     */
+    public MessageBuilder use(FieldParser<?> fieldParser) {
+        this.fieldParser = fieldParser;
+        return this;
+    }
+
+    /**
+     * Sets {@link org.apache.james.mime4j.message.BodyFactory} that will be
+     * used to generate message body.
+     *
+     * @param bodyFactory the body factory.
+     */
+    public MessageBuilder use(BodyFactory bodyFactory) {
+        this.bodyFactory = bodyFactory;
+        return this;
+    }
+
+    /**
+     * Enables flat parsing mode for {@link #parse(java.io.InputStream)} operation.
+     */
+    public MessageBuilder enableFlatMode() {
+        this.flatMode = true;
+        return this;
+    }
+
+    /**
+     * Disables flat parsing mode for {@link #parse(java.io.InputStream)} operation.
+     */
+    public MessageBuilder disableFlatMode() {
+        this.flatMode = false;
+        return this;
+    }
+
+    /**
+     * Enables automatic content decoding for {@link #parse(java.io.InputStream)} operation.
+     */
+    public MessageBuilder enableContentDecoding() {
+        this.rawContent = false;
+        return this;
+    }
+
+    /**
+     * Enables disable content decoding for {@link #parse(java.io.InputStream)} operation.
+     */
+    public MessageBuilder disableContentDecoding() {
+        this.rawContent = true;
+        return this;
+    }
+
     @Override
     public MessageBuilder setField(Field field) {
         super.setField(field);
@@ -76,6 +193,12 @@
     }
 
     @Override
+    public AbstractEntityBuilder clearFields() {
+        super.clearFields();
+        return this;
+    }
+
+    @Override
     public MessageBuilder setContentTransferEncoding(String contentTransferEncoding) {
         super.setContentTransferEncoding(contentTransferEncoding);
         return this;
@@ -118,22 +241,68 @@
         return this;
     }
 
-    @Override
-    public MessageBuilder use(final BodyFactory bodyFactory) {
-        super.use(bodyFactory);
-        return this;
-    }
-
-    @Override
+    /**
+     * Sets text of this message with the charset.
+     *
+     * @param text
+     *            the text.
+     * @param charset
+     *            the charset of the text.
+     */
     public MessageBuilder setBody(String text, Charset charset) throws IOException {
-        super.setBody(text, charset);
-        return this;
+        return setBody(text, null, charset);
     }
 
-    @Override
+    /**
+     * Sets text of this message with the given MIME subtype and charset.
+     *
+     * @param text
+     *            the text.
+     * @param charset
+     *            the charset of the text.
+     * @param subtype
+     *            the text subtype (e.g. &quot;plain&quot;, &quot;html&quot; or
+     *            &quot;xml&quot;).
+     */
     public MessageBuilder setBody(String text, String subtype, Charset charset) throws IOException {
-        super.setBody(text, subtype, charset);
-        return this;
+        if (subtype != null) {
+            if (charset != null) {
+                setField(Fields.contentType("text/" + subtype, new NameValuePair("charset", charset.name())));
+            } else {
+                setField(Fields.contentType("text/" + subtype));
+            }
+        }
+        TextBody textBody;
+        if (bodyFactory != null) {
+            textBody = bodyFactory.textBody(
+                    InputStreams.create(text, charset),
+                    charset != null ? charset.name() : null);
+        } else {
+            textBody = BasicBodyFactory.INSTANCE.textBody(text, charset);
+        }
+        return setBody(textBody);
+    }
+
+    /**
+     * Sets binary content of this message with the given MIME type.
+     *
+     * @param body
+     *            the body.
+     * @param mimeType
+     *            the MIME media type of the specified body
+     *            (&quot;type/subtype&quot;).
+     */
+    public MessageBuilder setBody(byte[] bin, String mimeType) throws IOException {
+        if (mimeType != null) {
+            setField(Fields.contentType(mimeType));
+        }
+        BinaryBody binBody;
+        if (bodyFactory != null) {
+            binBody = bodyFactory.binaryBody(InputStreams.create(bin));
+        } else {
+            binBody = BasicBodyFactory.INSTANCE.binaryBody(bin);
+        }
+        return setBody(binBody);
     }
 
     /**
@@ -676,9 +845,65 @@
         return this;
     }
 
+    public MessageBuilder copy(Message other) {
+        if (other == null) {
+            return this;
+        }
+        clearFields();
+        final Header otherHeader = other.getHeader();
+        if (otherHeader != null) {
+            final List<Field> otherFields = otherHeader.getFields();
+            for (Field field: otherFields) {
+                addField(field);
+            }
+        }
+        Body body = null;
+        Body otherBody = other.getBody();
+        if (otherBody instanceof Message) {
+            body = MessageBuilder.createCopy((Message) otherBody).build();
+        } else if (otherBody instanceof Multipart) {
+            body = MultipartBuilder.createCopy((Multipart) otherBody).build();
+        } else if (otherBody instanceof SingleBody) {
+            body = ((SingleBody) otherBody).copy();
+        }
+        setBody(body);
+        return this;
+    }
+
+    public MessageBuilder parse(final InputStream is) throws IOException {
+        MimeConfig currentConfig = config != null ? config : MimeConfig.DEFAULT;
+        boolean strict = currentConfig.isStrictParsing();
+        DecodeMonitor currentMonitor = monitor != null ? monitor : strict ? DecodeMonitor.STRICT : DecodeMonitor.SILENT;
+        BodyDescriptorBuilder currentBodyDescBuilder = bodyDescBuilder != null ? bodyDescBuilder :
+                new DefaultBodyDescriptorBuilder(null, fieldParser != null ? fieldParser :
+                        strict ? DefaultFieldParser.getParser() : LenientFieldParser.getParser(), currentMonitor);
+        BodyFactory currentBodyFactory = bodyFactory != null ? bodyFactory : new BasicBodyFactory();
+        MimeStreamParser parser = new MimeStreamParser(currentConfig, currentMonitor, currentBodyDescBuilder);
+
+        Message message = new MessageImpl();
+        parser.setContentHandler(new EntityBuilder(message, currentBodyFactory));
+        parser.setContentDecoding(!rawContent);
+        if (flatMode) {
+            parser.setFlat();
+        }
+        try {
+            parser.parse(is);
+        } catch (MimeException e) {
+            throw new MimeIOException(e);
+        }
+        clearFields();
+        final List<Field> fields = message.getHeader().getFields();
+        for (Field field: fields) {
+            addField(field);
+        }
+        setBody(message.getBody());
+        return this;
+    }
+
     public Message build() {
         MessageImpl message = new MessageImpl();
-        Header header = message.getHeader();
+        HeaderImpl header = new HeaderImpl();
+        message.setHeader(header);
         if (!containsField(FieldName.MIME_VERSION)) {
             header.setField(Fields.version("1.0"));
         }
diff --git a/dom/src/main/java/org/apache/james/mime4j/message/MultipartBuilder.java b/dom/src/main/java/org/apache/james/mime4j/message/MultipartBuilder.java
index b2d956f..226ad93 100644
--- a/dom/src/main/java/org/apache/james/mime4j/message/MultipartBuilder.java
+++ b/dom/src/main/java/org/apache/james/mime4j/message/MultipartBuilder.java
@@ -19,12 +19,22 @@
 
 package org.apache.james.mime4j.message;
 
+import java.io.IOException;
+import java.nio.charset.Charset;
 import java.util.Collections;
 import java.util.LinkedList;
 import java.util.List;
 
+import org.apache.james.mime4j.Charsets;
+import org.apache.james.mime4j.dom.Body;
 import org.apache.james.mime4j.dom.Entity;
+import org.apache.james.mime4j.dom.Header;
+import org.apache.james.mime4j.dom.Message;
 import org.apache.james.mime4j.dom.Multipart;
+import org.apache.james.mime4j.dom.SingleBody;
+import org.apache.james.mime4j.dom.TextBody;
+import org.apache.james.mime4j.io.InputStreams;
+import org.apache.james.mime4j.stream.Field;
 
 /**
  * {@link org.apache.james.mime4j.dom.Multipart} builder.
@@ -36,10 +46,16 @@
     private String preamble;
     private String epilogue;
 
+    private BodyFactory bodyFactory;
+
     public static MultipartBuilder create(String subType) {
         return new MultipartBuilder().setSubType(subType);
     }
 
+    public static MultipartBuilder createCopy(Multipart other) {
+        return new MultipartBuilder().copy(other);
+    }
+
     public static MultipartBuilder create() {
         return new MultipartBuilder();
     }
@@ -48,6 +64,11 @@
         this.bodyParts = new LinkedList<Entity>();
     }
 
+    public MultipartBuilder use(final BodyFactory bodyFactory) {
+        this.bodyFactory = bodyFactory;
+        return this;
+    }
+
     /**
      * Gets the multipart sub-type. E.g. <code>alternative</code> (the
      * default) or <code>parallel</code>. See RFC 2045 for common sub-types
@@ -194,6 +215,53 @@
         return this;
     }
 
+    public MultipartBuilder addTextPart(String text, Charset charset) throws IOException {
+        Charset cs = charset != null ? charset : Charsets.ISO_8859_1;
+        TextBody body = bodyFactory != null ? bodyFactory.textBody(
+                InputStreams.create(text, cs), cs.name()) : BasicBodyFactory.INSTANCE.textBody(text, cs);
+        BodyPart bodyPart = new BodyPart();
+        bodyPart.setText(body);
+        bodyPart.setContentTransferEncoding("quoted-printable");
+
+        return addBodyPart(bodyPart);
+    }
+
+    public MultipartBuilder copy(Multipart other) {
+        if (other == null) {
+            return this;
+        }
+        subType = other.getSubType();
+        bodyParts.clear();
+        final List<Entity> otherParts = other.getBodyParts();
+        for (Entity otherPart: otherParts) {
+            BodyPart bodyPart = new BodyPart();
+            Header otherHeader = otherPart.getHeader();
+            if (otherHeader != null) {
+                HeaderImpl header = new HeaderImpl();
+                for (Field otherField : otherHeader.getFields()) {
+                    header.addField(otherField);
+                }
+                bodyPart.setHeader(header);
+            }
+            final Body otherBody = otherPart.getBody();
+            if (otherBody != null) {
+                Body body = null;
+                if (otherBody instanceof Message) {
+                    body = MessageBuilder.createCopy((Message) otherBody).build();
+                } else if (otherBody instanceof Multipart) {
+                    body = MultipartBuilder.createCopy((Multipart) otherBody).build();
+                } else if (otherBody instanceof SingleBody) {
+                    body = ((SingleBody) otherBody).copy();
+                }
+                bodyPart.setBody(body);
+            }
+            bodyParts.add(bodyPart);
+        }
+        preamble = other.getPreamble();
+        epilogue = other.getEpilogue();
+        return this;
+    }
+
     public Multipart build() {
         MultipartImpl multipart = new MultipartImpl(subType);
         for (Entity part : bodyParts) {
diff --git a/dom/src/main/java/org/apache/james/mime4j/message/SingleBodyBuilder.java b/dom/src/main/java/org/apache/james/mime4j/message/SingleBodyBuilder.java
index 12a43b3..eed1a72 100644
--- a/dom/src/main/java/org/apache/james/mime4j/message/SingleBodyBuilder.java
+++ b/dom/src/main/java/org/apache/james/mime4j/message/SingleBodyBuilder.java
@@ -22,13 +22,17 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.Reader;
+import java.io.UnsupportedEncodingException;
 import java.nio.charset.Charset;
+import java.nio.charset.IllegalCharsetNameException;
+import java.nio.charset.UnsupportedCharsetException;
 
 import org.apache.james.mime4j.Charsets;
 import org.apache.james.mime4j.dom.BinaryBody;
 import org.apache.james.mime4j.dom.SingleBody;
 import org.apache.james.mime4j.dom.TextBody;
 import org.apache.james.mime4j.io.InputStreams;
+import org.apache.james.mime4j.util.CharsetUtil;
 import org.apache.james.mime4j.util.ContentUtil;
 
 /**
@@ -40,12 +44,16 @@
         return new SingleBodyBuilder();
     }
 
-    private BodyFactory bodyFactory;
+    public static SingleBodyBuilder createCopy(final SingleBody other) throws IOException {
+        return new SingleBodyBuilder().copy(other);
+    }
 
     private String text;
     private byte[] bin;
     private Charset charset;
 
+    private BodyFactory bodyFactory;
+
     SingleBodyBuilder() {
         super();
     }
@@ -84,6 +92,26 @@
         return this;
     }
 
+    public SingleBodyBuilder copy(final SingleBody other) throws IOException {
+        if (other == null) {
+            return this;
+        }
+        if (other instanceof TextBody) {
+            String charsetName = ((TextBody) other).getMimeCharset();
+            if (charsetName != null) {
+                try {
+                    this.charset = Charset.forName(charsetName);
+                } catch (IllegalCharsetNameException ex) {
+                    throw new UnsupportedEncodingException(charsetName);
+                } catch (UnsupportedCharsetException ex) {
+                    throw new UnsupportedEncodingException(charsetName);
+                }
+            }
+        }
+        this.bin = ContentUtil.buffer(other.getInputStream());
+        return this;
+    }
+
     public TextBody buildText() throws IOException {
         Charset cs = this.charset != null ? this.charset : Charsets.DEFAULT_CHARSET;
         if (this.bodyFactory != null) {
diff --git a/examples/src/main/java/org/apache/james/mime4j/samples/dom/MultipartMessage.java b/examples/src/main/java/org/apache/james/mime4j/samples/dom/MultipartMessage.java
index 44b64d3..298074b 100644
--- a/examples/src/main/java/org/apache/james/mime4j/samples/dom/MultipartMessage.java
+++ b/examples/src/main/java/org/apache/james/mime4j/samples/dom/MultipartMessage.java
@@ -61,21 +61,23 @@
                 .generateMessageId(InetAddress.getLocalHost().getCanonicalHostName())
         // 3) set a multipart body
                 .setBody(MultipartBuilder.create("mixed")
-                        // a multipart may have a preamble
+                        .use(bodyFactory)
+                                // a multipart may have a preamble
                         .setPreamble("This is a multi-part message in MIME format.")
-                        // first part is text/plain
+                                // first part is text/plain
                         .addBodyPart(BodyPartBuilder.create()
                                 .use(bodyFactory)
                                 .setBody("Why so serious?", Charsets.UTF_8)
                                 .setContentTransferEncoding("quoted-printable")
                                 .build())
-                        // second part is image/png (image is created on the fly)
+                                // second part is image/png (image is created on the fly)
                         .addBodyPart(BodyPartBuilder.create()
+                                .use(bodyFactory)
                                 .setBody(createImageBody(bodyFactory, renderSampleImage()))
                                 .setContentType("image/png")
                                 .setContentTransferEncoding("base64")
-                        // Specify a filename in the Content-Disposition header (implicitly sets
-                        // the disposition type to "attachment")
+                                        // Specify a filename in the Content-Disposition header (implicitly sets
+                                        // the disposition type to "attachment")
                                 .setContentDisposition("attachment", "smiley.png")
                                 .build())
                         .build())
diff --git a/examples/src/main/java/org/apache/james/mime4j/samples/transform/TransformMessage.java b/examples/src/main/java/org/apache/james/mime4j/samples/transform/TransformMessage.java
index 607d121..996aeaf 100644
--- a/examples/src/main/java/org/apache/james/mime4j/samples/transform/TransformMessage.java
+++ b/examples/src/main/java/org/apache/james/mime4j/samples/transform/TransformMessage.java
@@ -19,26 +19,21 @@
 
 package org.apache.james.mime4j.samples.transform;
 
-import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.util.Date;
 import java.util.Random;
 
-import org.apache.james.mime4j.dom.Body;
+import org.apache.james.mime4j.Charsets;
+import org.apache.james.mime4j.codec.DecodeMonitor;
 import org.apache.james.mime4j.dom.Entity;
 import org.apache.james.mime4j.dom.Message;
-import org.apache.james.mime4j.dom.MessageBuilder;
 import org.apache.james.mime4j.dom.MessageWriter;
 import org.apache.james.mime4j.dom.Multipart;
-import org.apache.james.mime4j.dom.TextBody;
-import org.apache.james.mime4j.dom.field.ParseException;
-import org.apache.james.mime4j.field.address.AddressBuilder;
 import org.apache.james.mime4j.message.BodyPart;
-import org.apache.james.mime4j.message.MessageImpl;
-import org.apache.james.mime4j.message.DefaultMessageBuilder;
+import org.apache.james.mime4j.message.BodyPartBuilder;
 import org.apache.james.mime4j.message.DefaultMessageWriter;
-import org.apache.james.mime4j.message.MultipartImpl;
-import org.apache.james.mime4j.storage.DefaultStorageProvider;
+import org.apache.james.mime4j.message.MessageBuilder;
+import org.apache.james.mime4j.message.MultipartBuilder;
 import org.apache.james.mime4j.storage.StorageBodyFactory;
 import org.apache.james.mime4j.storage.StorageProvider;
 import org.apache.james.mime4j.storage.TempFileStorageProvider;
@@ -56,15 +51,68 @@
         // Explicitly set a strategy for storing body parts. Usually not
         // necessary; for most applications the default setting is appropriate.
         StorageProvider storageProvider = new TempFileStorageProvider();
-        DefaultStorageProvider.setInstance(storageProvider);
+        StorageBodyFactory bodyFactory = new StorageBodyFactory(storageProvider, DecodeMonitor.SILENT);
 
         // Create a template message. It would be possible to load a message
         // from an input stream but for this example a message object is created
         // from scratch for demonstration purposes.
-        Message template = createTemplate();
+        Message template = MessageBuilder.create()
+                .setBody(MultipartBuilder.create("mixed")
+                        .addBodyPart(BodyPartBuilder.create()
+                                .use(bodyFactory)
+                                .setBody("This is the first part of the template..", Charsets.UTF_8)
+                                .setContentTransferEncoding("quoted-printable")
+                                .build())
+                        .addBodyPart(BodyPartBuilder.create()
+                                .use(bodyFactory)
+                                .setBody(createRandomBinary(200), "application/octet-stream")
+                                .setContentTransferEncoding("base64")
+                                .build())
+                        .addBodyPart(BodyPartBuilder.create()
+                                .use(bodyFactory)
+                                .setBody(createRandomBinary(300), "application/octet-stream")
+                                .setContentTransferEncoding("base64")
+                                .build())
+                        .build())
+                .setSubject("Template message")
+                .build();
 
         // Create a new message by transforming the template.
-        Message transformed = transform(template);
+        // Create a copy of the template. The copy can be modified without
+        // affecting the original.
+        final MessageBuilder messageBuilder = MessageBuilder.createCopy(template);
+        // In this example we know we have a multipart message. Use
+        // Message#isMultipart() if uncertain.
+        Multipart multipart = (Multipart) messageBuilder.getBody();
+
+        // Insert a new text/plain body part after every body part of the
+        // template.
+        final int count = multipart.getCount();
+        for (int i = 0; i < count; i++) {
+            String text = "Text inserted after part " + (i + 1);
+            BodyPart bodyPart = BodyPartBuilder.create()
+                    .use(bodyFactory)
+                    .setBody(text, Charsets.UTF_8)
+                    .setContentTransferEncoding("quoted-printable")
+                    .build();
+            multipart.addBodyPart(bodyPart, 2 * i + 1);
+        }
+
+        // For no particular reason remove the second binary body part (now
+        // at index four).
+        Entity removed = multipart.removeBodyPart(4);
+
+        // The removed body part no longer has a parent entity it belongs to so
+        // it should be disposed of.
+        removed.dispose();
+
+        // Set some headers on the transformed message
+        messageBuilder.generateMessageId(HOSTNAME);
+        messageBuilder.setSubject("Transformed message");
+        messageBuilder.setDate(new Date());
+        messageBuilder.setFrom("John Doe <jdoe@machine.example>");
+
+        Message transformed = messageBuilder.build();
 
         MessageWriter writer = new DefaultMessageWriter();
 
@@ -89,99 +137,11 @@
         // messages and body parts have been disposed of properly.
     }
 
-    /**
-     * Copies the given message and makes some arbitrary changes to the copy.
-     * @throws ParseException on bad arguments
-     */
-    private static Message transform(Message original) throws IOException, ParseException {
-        // Create a copy of the template. The copy can be modified without
-        // affecting the original.
-        MessageBuilder builder = new DefaultMessageBuilder();
-        Message message = builder.newMessage(original);
-
-        // In this example we know we have a multipart message. Use
-        // Message#isMultipart() if uncertain.
-        Multipart multipart = (Multipart) message.getBody();
-
-        // Insert a new text/plain body part after every body part of the
-        // template.
-        final int count = multipart.getCount();
-        for (int i = 0; i < count; i++) {
-            String text = "Text inserted after part " + (i + 1);
-            BodyPart bodyPart = createTextPart(text);
-            multipart.addBodyPart(bodyPart, 2 * i + 1);
-        }
-
-        // For no particular reason remove the second binary body part (now
-        // at index four).
-        Entity removed = multipart.removeBodyPart(4);
-
-        // The removed body part no longer has a parent entity it belongs to so
-        // it should be disposed of.
-        removed.dispose();
-
-        // Set some headers on the transformed message
-        message.createMessageId(HOSTNAME);
-        message.setSubject("Transformed message");
-        message.setDate(new Date());
-        message.setFrom(AddressBuilder.DEFAULT.parseMailbox("John Doe <jdoe@machine.example>"));
-
-        return message;
-    }
-
-    /**
-     * Creates a multipart/mixed message that consists of three parts (one text,
-     * two binary).
-     */
-    private static Message createTemplate() throws IOException {
-        Multipart multipart = new MultipartImpl("mixed");
-
-        BodyPart part1 = createTextPart("This is the first part of the template..");
-        multipart.addBodyPart(part1);
-
-        BodyPart part2 = createRandomBinaryPart(200);
-        multipart.addBodyPart(part2);
-
-        BodyPart part3 = createRandomBinaryPart(300);
-        multipart.addBodyPart(part3);
-
-        MessageImpl message = new MessageImpl();
-        message.setMultipart(multipart);
-
-        message.setSubject("Template message");
-
-        return message;
-    }
-
-    /**
-     * Creates a text part from the specified string.
-     */
-    private static BodyPart createTextPart(String text) {
-        TextBody body = new StorageBodyFactory().textBody(text, "UTF-8");
-
-        BodyPart bodyPart = new BodyPart();
-        bodyPart.setText(body);
-        bodyPart.setContentTransferEncoding("quoted-printable");
-
-        return bodyPart;
-    }
-
-    /**
-     * Creates a binary part with random content.
-     */
-    private static BodyPart createRandomBinaryPart(int numberOfBytes)
+    private static byte[] createRandomBinary(int numberOfBytes)
             throws IOException {
         byte[] data = new byte[numberOfBytes];
         new Random().nextBytes(data);
-
-        Body body = new StorageBodyFactory()
-                .binaryBody(new ByteArrayInputStream(data));
-
-        BodyPart bodyPart = new BodyPart();
-        bodyPart.setBody(body, "application/octet-stream");
-        bodyPart.setContentTransferEncoding("base64");
-
-        return bodyPart;
+        return data;
     }
 
 }