MIME4J-262 deprecated all MessageBuilder and replace it by Message.Builder
Project: http://git-wip-us.apache.org/repos/asf/james-mime4j/repo Commit: http://git-wip-us.apache.org/repos/asf/james-mime4j/commit/5c583037 Tree: http://git-wip-us.apache.org/repos/asf/james-mime4j/tree/5c583037 Diff: http://git-wip-us.apache.org/repos/asf/james-mime4j/diff/5c583037 Branch: refs/heads/master Commit: 5c5830375afc12b71055a73ce76e5895f0ab846e Parents: c14019c Author: Luc DUZAN <[email protected]> Authored: Wed May 24 15:09:21 2017 +0200 Committer: benwa <[email protected]> Committed: Thu May 25 10:11:54 2017 +0700 ---------------------------------------------------------------------- RELEASE_NOTES.txt | 20 +- .../org/apache/james/mime4j/dom/Message.java | 913 ++++++++++++++++ .../mime4j/internal/AbstractEntityBuilder.java | 576 ++++++++++ .../internal/ParserStreamContentHandler.java | 188 ++++ .../mime4j/message/AbstractEntityBuilder.java | 576 ---------- .../james/mime4j/message/BodyPartBuilder.java | 1 + .../mime4j/message/DefaultMessageBuilder.java | 1 + .../james/mime4j/message/MessageBuilder.java | 1006 +++++------------- .../message/ParserStreamContentHandler.java | 182 ---- .../apache/james/mime4j/dom/MessageTest.java | 435 ++++++++ .../message/AbstractEntityBuilderTest.java | 1 + .../mime4j/message/MessageBuilderTest.java | 155 +-- 12 files changed, 2409 insertions(+), 1645 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/james-mime4j/blob/5c583037/RELEASE_NOTES.txt ---------------------------------------------------------------------- diff --git a/RELEASE_NOTES.txt b/RELEASE_NOTES.txt index 165e541..c99eddb 100644 --- a/RELEASE_NOTES.txt +++ b/RELEASE_NOTES.txt @@ -1,21 +1,11 @@ Release 0.9.0 ------------------- -Fix MIME4J-262 without breaking compatibility with old code by deprecating: - * MessageBuilder#MessageBuilder() - * MessageBuilder#create() - * MessageBuilder#createCopy(Message) - * MessageBuilder#read(InputStream) - -and adding: - * MessageBuilder#of() - * MessageBuilder#of(Message) - * MessageBuilder#of(InputStream) - -MessageBuilder constructed by of methods produce message for which the getDate method return null if and only if the -message do not have a "Date" header. Whereas MessageBuilder constructed by deprecated method produce message for which -the getDate method never return null: if the message has no "Date" header the returned value is "new Date()" result when -the message has been instantiated +Fix MIME4J-262 without breaking compatibility with old code by deprecating MessageBuilder and adding Message.Builder + +Message.Builder produce message for which the getDate method return null if and only if the message do not have a "Date" +header. Whereas MessageBuilder will produce message for which the getDate method never return null: if the message has +no "Date" header the returned value is "new Date()" result when the message has been instantiated Release 0.7.2 ------------------- http://git-wip-us.apache.org/repos/asf/james-mime4j/blob/5c583037/dom/src/main/java/org/apache/james/mime4j/dom/Message.java ---------------------------------------------------------------------- diff --git a/dom/src/main/java/org/apache/james/mime4j/dom/Message.java b/dom/src/main/java/org/apache/james/mime4j/dom/Message.java index 17132e4..6084dd1 100644 --- a/dom/src/main/java/org/apache/james/mime4j/dom/Message.java +++ b/dom/src/main/java/org/apache/james/mime4j/dom/Message.java @@ -24,6 +24,45 @@ import java.util.Date; import org.apache.james.mime4j.dom.address.AddressList; import org.apache.james.mime4j.dom.address.Mailbox; import org.apache.james.mime4j.dom.address.MailboxList; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +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.address.Address; +import org.apache.james.mime4j.dom.field.AddressListField; +import org.apache.james.mime4j.dom.field.DateTimeField; +import org.apache.james.mime4j.dom.field.FieldName; +import org.apache.james.mime4j.dom.field.MailboxField; +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.DefaultAddressParser; +import org.apache.james.mime4j.io.InputStreams; +import org.apache.james.mime4j.internal.AbstractEntityBuilder; +import org.apache.james.mime4j.message.BasicBodyFactory; +import org.apache.james.mime4j.message.BodyFactory; +import org.apache.james.mime4j.message.DefaultBodyDescriptorBuilder; +import org.apache.james.mime4j.message.HeaderImpl; +import org.apache.james.mime4j.message.MessageImpl; +import org.apache.james.mime4j.message.MultipartBuilder; +import org.apache.james.mime4j.internal.ParserStreamContentHandler; +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; /** * An MIME message (as defined in RFC 2045). @@ -108,4 +147,878 @@ public interface Message extends Entity, Body { */ AddressList getReplyTo(); + class Builder extends AbstractEntityBuilder { + private MimeConfig config; + private DecodeMonitor monitor; + private BodyDescriptorBuilder bodyDescBuilder; + private FieldParser<?> fieldParser; + private BodyFactory bodyFactory; + private boolean flatMode; + private boolean rawContent; + + private Builder() { + super(); + } + + public static Builder of() { + return new Builder(); + } + + public static Builder of(Message other) { + return new Builder().copy(other); + } + + public static Builder of(final InputStream is) throws IOException { + return new Builder().parse(is); + } + + /** + * Sets MIME configuration. + * + * @param config the configuration. + */ + public Builder 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 Builder 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 Builder 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 Builder 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 Builder use(BodyFactory bodyFactory) { + this.bodyFactory = bodyFactory; + return this; + } + + /** + * Enables flat parsing mode for {@link #parse(java.io.InputStream)} operation. + */ + public Builder enableFlatMode() { + this.flatMode = true; + return this; + } + + /** + * Disables flat parsing mode for {@link #parse(java.io.InputStream)} operation. + */ + public Builder disableFlatMode() { + this.flatMode = false; + return this; + } + + /** + * Enables automatic content decoding for {@link #parse(java.io.InputStream)} operation. + */ + public Builder enableContentDecoding() { + this.rawContent = false; + return this; + } + + /** + * Enables disable content decoding for {@link #parse(java.io.InputStream)} operation. + */ + public Builder disableContentDecoding() { + this.rawContent = true; + return this; + } + + public Builder 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 = Builder.of((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; + } + + @Override + public Builder setField(Field field) { + super.setField(field); + return this; + } + + @Override + public Builder addField(Field field) { + super.addField(field); + return this; + } + + @Override + public Builder removeFields(String name) { + super.removeFields(name); + return this; + } + + @Override + public Builder clearFields() { + super.clearFields(); + return this; + } + + @Override + public Builder setContentTransferEncoding(String contentTransferEncoding) { + super.setContentTransferEncoding(contentTransferEncoding); + return this; + } + + @Override + public Builder setContentType(String mimeType, NameValuePair... parameters) { + super.setContentType(mimeType, parameters); + return this; + } + + @Override + public Builder setContentDisposition(String dispositionType) { + super.setContentDisposition(dispositionType); + return this; + } + + @Override + public Builder setContentDisposition(String dispositionType, String filename) { + super.setContentDisposition(dispositionType, filename); + return this; + } + + @Override + public Builder setContentDisposition(String dispositionType, String filename, long size) { + super.setContentDisposition(dispositionType, filename, size); + return this; + } + + @Override + public Builder setContentDisposition(String dispositionType, String filename, long size, + Date creationDate, Date modificationDate, Date readDate) { + super.setContentDisposition(dispositionType, filename, size, creationDate, modificationDate, readDate); + return this; + } + + @Override + public Builder setBody(Body body) { + super.setBody(body); + return this; + } + + @Override + public Builder setBody(TextBody textBody) { + super.setBody(textBody); + return this; + } + + @Override + public Builder setBody(BinaryBody binaryBody) { + super.setBody(binaryBody); + return this; + } + + @Override + public Builder setBody(Multipart multipart) { + super.setBody(multipart); + return this; + } + + @Override + public Builder setBody(Message message) { + super.setBody(message); + return this; + } + + /** + * Sets text of this message with the charset. + * + * @param text + * the text. + * @param charset + * the charset of the text. + */ + public Builder 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. "plain", "html" or + * "xml"). + */ + public Builder setBody(String text, String subtype, Charset charset) throws IOException { + String mimeType = "text/" + (subtype != null ? subtype : "plain"); + if (charset != null) { + setField(Fields.contentType(mimeType, new NameValuePair("charset", charset.name()))); + } else { + setField(Fields.contentType(mimeType)); + } + Body 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 bin + * the body. + * @param mimeType + * the MIME media type of the specified body + * ("type/subtype"). + */ + public Builder setBody(byte[] bin, String mimeType) throws IOException { + setField(Fields.contentType(mimeType != null ? mimeType : "application/octet-stream")); + Body binBody; + if (bodyFactory != null) { + binBody = bodyFactory.binaryBody(InputStreams.create(bin)); + } else { + binBody = BasicBodyFactory.INSTANCE.binaryBody(bin); + } + return setBody(binBody); + } + + /** + * Returns the value of the <i>Message-ID</i> header field of this message + * or <code>null</code> if it is not present. + * + * @return the identifier of this message. + */ + public String getMessageId() { + Field field = obtainField(FieldName.MESSAGE_ID); + return field != null ? field.getBody() : null; + } + + /** + * Generates and sets message ID for this message. + * + * @param hostname + * host name to be included in the identifier or + * <code>null</code> if no host name should be included. + */ + public Builder generateMessageId(String hostname) { + if (hostname == null) { + removeFields(FieldName.MESSAGE_ID); + } else { + setField(Fields.generateMessageId(hostname)); + } + return this; + } + + /** + * Sets message ID for this message. + * + * @param messageId + * the message ID. + */ + public Builder setMessageId(String messageId) { + if (messageId == null) { + removeFields(FieldName.MESSAGE_ID); + } else { + setField(Fields.messageId(messageId)); + } + return this; + } + + /** + * Returns the (decoded) value of the <i>Subject</i> header field of this + * message or <code>null</code> if it is not present. + * + * @return the subject of this message. + */ + public String getSubject() { + UnstructuredField field = obtainField(FieldName.SUBJECT); + return field != null ? field.getValue() : null; + } + + /** + * Sets <i>Subject</i> header field for this message. The specified + * string may contain non-ASCII characters, in which case it gets encoded as + * an 'encoded-word' automatically. + * + * @param subject + * subject to set or <code>null</code> to remove the subject + * header field. + */ + public Builder setSubject(String subject) { + if (subject == null) { + removeFields(FieldName.SUBJECT); + } else { + setField(Fields.subject(subject)); + } + return this; + } + + /** + * Returns the value of the <i>Date</i> header field of this message as + * <code>Date</code> object or <code>null</code> if it is not present. + * + * @return the date of this message. + */ + public Date getDate() { + DateTimeField field = obtainField(FieldName.DATE); + return field != null ? field.getDate() : null; + } + + /** + * Sets <i>Date</i> header field for this message. This method uses the + * default <code>TimeZone</code> of this host to encode the specified + * <code>Date</code> object into a string. + * + * @param date + * date to set or <code>null</code> to remove the date header + * field. + */ + public Builder setDate(Date date) { + return setDate(date, null); + } + + /** + * Sets <i>Date</i> header field for this message. The specified + * <code>TimeZone</code> is used to encode the specified <code>Date</code> + * object into a string. + * + * @param date + * date to set or <code>null</code> to remove the date header + * field. + * @param zone + * a time zone. + */ + public Builder setDate(Date date, TimeZone zone) { + if (date == null) { + removeFields(FieldName.DATE); + } else { + setField(Fields.date(FieldName.DATE, date, zone)); + } + return this; + } + + /** + * Returns the value of the <i>Sender</i> header field of this message as + * <code>Mailbox</code> object or <code>null</code> if it is not + * present. + * + * @return the sender of this message. + */ + public Mailbox getSender() { + return getMailbox(FieldName.SENDER); + } + + /** + * Sets <i>Sender</i> header field of this message to the specified + * mailbox address. + * + * @param sender + * address to set or <code>null</code> to remove the header + * field. + */ + public Builder setSender(Mailbox sender) { + return setMailbox(FieldName.SENDER, sender); + } + + /** + * Sets <i>Sender</i> header field of this message to the specified + * mailbox address. + * + * @param sender + * address to set or <code>null</code> to remove the header + * field. + */ + public Builder setSender(String sender) throws ParseException { + return setMailbox(FieldName.SENDER, sender); + } + + /** + * Returns the value of the <i>From</i> header field of this message as + * <code>MailboxList</code> object or <code>null</code> if it is not + * present. + * + * @return value of the from field of this message. + */ + public MailboxList getFrom() { + return getMailboxList(FieldName.FROM); + } + + /** + * Sets <i>From</i> header field of this message to the specified + * mailbox address. + * + * @param from + * address to set or <code>null</code> to remove the header + * field. + */ + public Builder setFrom(Mailbox from) { + return setMailboxList(FieldName.FROM, from); + } + + /** + * Sets <i>From</i> header field of this message to the specified + * mailbox address. + * + * @param from + * address to set or <code>null</code> to remove the header + * field. + */ + public Builder setFrom(String from) throws ParseException { + return setMailboxList(FieldName.FROM, from); + } + + /** + * Sets <i>From</i> header field of this message to the specified + * mailbox addresses. + * + * @param from + * addresses to set or <code>null</code> or no arguments to + * remove the header field. + */ + public Builder setFrom(Mailbox... from) { + return setMailboxList(FieldName.FROM, from); + } + + /** + * Sets <i>From</i> header field of this message to the specified + * mailbox addresses. + * + * @param from + * addresses to set or <code>null</code> or no arguments to + * remove the header field. + */ + public Builder setFrom(String... from) throws ParseException { + return setMailboxList(FieldName.FROM, from); + } + + /** + * Sets <i>From</i> header field of this message to the specified + * mailbox addresses. + * + * @param from + * addresses to set or <code>null</code> or an empty collection + * to remove the header field. + */ + public Builder setFrom(Collection<Mailbox> from) { + return setMailboxList(FieldName.FROM, from); + } + + /** + * Returns the value of the <i>To</i> header field of this message as + * <code>AddressList</code> object or <code>null</code> if it is not + * present. + * + * @return value of the to field of this message. + */ + public AddressList getTo() { + return getAddressList(FieldName.TO); + } + + /** + * Sets <i>To</i> header field of this message to the specified + * address. + * + * @param to + * address to set or <code>null</code> to remove the header + * field. + */ + public Builder setTo(Address to) { + return setAddressList(FieldName.TO, to); + } + + /** + * Sets <i>To</i> header field of this message to the specified + * address. + * + * @param to + * address to set or <code>null</code> to remove the header + * field. + */ + public Builder setTo(String to) throws ParseException { + return setAddressList(FieldName.TO, to); + } + + /** + * Sets <i>To</i> header field of this message to the specified + * addresses. + * + * @param to + * addresses to set or <code>null</code> or no arguments to + * remove the header field. + */ + public Builder setTo(Address... to) { + return setAddressList(FieldName.TO, to); + } + + /** + * Sets <i>To</i> header field of this message to the specified + * addresses. + * + * @param to + * addresses to set or <code>null</code> or no arguments to + * remove the header field. + */ + public Builder setTo(String... to) throws ParseException { + return setAddressList(FieldName.TO, to); + } + + /** + * Sets <i>To</i> header field of this message to the specified + * addresses. + * + * @param to + * addresses to set or <code>null</code> or an empty collection + * to remove the header field. + */ + public Builder setTo(Collection<? extends Address> to) { + return setAddressList(FieldName.TO, to); + } + + /** + * Returns the value of the <i>Cc</i> header field of this message as + * <code>AddressList</code> object or <code>null</code> if it is not + * present. + * + * @return value of the cc field of this message. + */ + public AddressList getCc() { + return getAddressList(FieldName.CC); + } + + /** + * Sets <i>Cc</i> header field of this message to the specified + * address. + * + * @param cc + * address to set or <code>null</code> to remove the header + * field. + */ + public Builder setCc(Address cc) { + return setAddressList(FieldName.CC, cc); + } + + /** + * Sets <i>Cc</i> header field of this message to the specified + * addresses. + * + * @param cc + * addresses to set or <code>null</code> or no arguments to + * remove the header field. + */ + public Builder setCc(Address... cc) { + return setAddressList(FieldName.CC, cc); + } + + /** + * Sets <i>Cc</i> header field of this message to the specified + * addresses. + * + * @param cc + * addresses to set or <code>null</code> or an empty collection + * to remove the header field. + */ + public Builder setCc(Collection<? extends Address> cc) { + return setAddressList(FieldName.CC, cc); + } + + /** + * Returns the value of the <i>Bcc</i> header field of this message as + * <code>AddressList</code> object or <code>null</code> if it is not + * present. + * + * @return value of the bcc field of this message. + */ + public AddressList getBcc() { + return getAddressList(FieldName.BCC); + } + + /** + * Sets <i>Bcc</i> header field of this message to the specified + * address. + * + * @param bcc + * address to set or <code>null</code> to remove the header + * field. + */ + public Builder setBcc(Address bcc) { + return setAddressList(FieldName.BCC, bcc); + } + + /** + * Sets <i>Bcc</i> header field of this message to the specified + * addresses. + * + * @param bcc + * addresses to set or <code>null</code> or no arguments to + * remove the header field. + */ + public Builder setBcc(Address... bcc) { + return setAddressList(FieldName.BCC, bcc); + } + + /** + * Sets <i>Bcc</i> header field of this message to the specified + * addresses. + * + * @param bcc + * addresses to set or <code>null</code> or an empty collection + * to remove the header field. + */ + public Builder setBcc(Collection<? extends Address> bcc) { + return setAddressList(FieldName.BCC, bcc); + } + + /** + * Returns the value of the <i>Reply-To</i> header field of this message as + * <code>AddressList</code> object or <code>null</code> if it is not + * present. + * + * @return value of the reply to field of this message. + */ + public AddressList getReplyTo() { + return getAddressList(FieldName.REPLY_TO); + } + + /** + * Sets <i>Reply-To</i> header field of this message to the specified + * address. + * + * @param replyTo + * address to set or <code>null</code> to remove the header + * field. + */ + public Builder setReplyTo(Address replyTo) { + return setAddressList(FieldName.REPLY_TO, replyTo); + } + + /** + * Sets <i>Reply-To</i> header field of this message to the specified + * addresses. + * + * @param replyTo + * addresses to set or <code>null</code> or no arguments to + * remove the header field. + */ + public Builder setReplyTo(Address... replyTo) { + return setAddressList(FieldName.REPLY_TO, replyTo); + } + + /** + * Sets <i>Reply-To</i> header field of this message to the specified + * addresses. + * + * @param replyTo + * addresses to set or <code>null</code> or an empty collection + * to remove the header field. + */ + public Builder setReplyTo(Collection<? extends Address> replyTo) { + return setAddressList(FieldName.REPLY_TO, replyTo); + } + + public Builder 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(!strict); + MimeStreamParser parser = new MimeStreamParser(currentConfig, currentMonitor, currentBodyDescBuilder); + + Message message = new MessageImpl(); + parser.setContentHandler(new ParserStreamContentHandler(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(); + HeaderImpl header = new HeaderImpl(); + message.setHeader(header); + if (!containsField(FieldName.MIME_VERSION)) { + header.setField(Fields.version("1.0")); + } + for (Field field : getFields()) { + header.addField(field); + } + + message.setBody(getBody()); + + return message; + } + + private Mailbox getMailbox(String fieldName) { + MailboxField field = obtainField(fieldName); + return field != null ? field.getMailbox() : null; + } + + private Builder setMailbox(String fieldName, Mailbox mailbox) { + if (mailbox == null) { + removeFields(fieldName); + } else { + setField(Fields.mailbox(fieldName, mailbox)); + } + return this; + } + + private Builder setMailbox(String fieldName, String mailbox) throws ParseException { + if (mailbox == null) { + removeFields(fieldName); + } else { + setField(Fields.mailbox(fieldName, DefaultAddressParser.DEFAULT.parseMailbox(mailbox))); + } + return this; + } + + private MailboxList getMailboxList(String fieldName) { + MailboxListField field = obtainField(fieldName); + return field != null ? field.getMailboxList() : null; + } + + private Builder setMailboxList(String fieldName, Mailbox mailbox) { + return setMailboxList(fieldName, mailbox == null ? null : Collections.singleton(mailbox)); + } + + private Builder setMailboxList(String fieldName, String mailbox) throws ParseException { + return setMailboxList(fieldName, mailbox == null ? null : DefaultAddressParser.DEFAULT.parseMailbox(mailbox)); + } + + private Builder setMailboxList(String fieldName, Mailbox... mailboxes) { + return setMailboxList(fieldName, mailboxes == null ? null : Arrays.asList(mailboxes)); + } + + private List<Mailbox> parseMailboxes(String... mailboxes) throws ParseException { + if (mailboxes == null || mailboxes.length == 0) { + return null; + } else { + List<Mailbox> list = new ArrayList<Mailbox>(); + for (String mailbox: mailboxes) { + list.add(DefaultAddressParser.DEFAULT.parseMailbox(mailbox)); + } + return list; + } + } + + private Builder setMailboxList(String fieldName, String... mailboxes) throws ParseException { + return setMailboxList(fieldName, parseMailboxes(mailboxes)); + } + + private Builder setMailboxList(String fieldName, Collection<Mailbox> mailboxes) { + if (mailboxes == null || mailboxes.isEmpty()) { + removeFields(fieldName); + } else { + setField(Fields.mailboxList(fieldName, mailboxes)); + } + return this; + } + + private AddressList getAddressList(String fieldName) { + AddressListField field = obtainField(fieldName); + return field != null? field.getAddressList() : null; + } + + private Builder setAddressList(String fieldName, Address address) { + return setAddressList(fieldName, address == null ? null : Collections.singleton(address)); + } + + private Builder setAddressList(String fieldName, String address) throws ParseException { + return setAddressList(fieldName, address == null ? null : DefaultAddressParser.DEFAULT.parseMailbox(address)); + } + + private Builder setAddressList(String fieldName, Address... addresses) { + return setAddressList(fieldName, addresses == null ? null : Arrays.asList(addresses)); + } + + private List<Address> parseAddresses(String... addresses) throws ParseException { + if (addresses == null || addresses.length == 0) { + return null; + } else { + List<Address> list = new ArrayList<Address>(); + for (String address: addresses) { + list.add(DefaultAddressParser.DEFAULT.parseAddress(address)); + } + return list; + } + } + + private Builder setAddressList(String fieldName, String... addresses) throws ParseException { + return setAddressList(fieldName, parseAddresses(addresses)); + } + + private Builder setAddressList(String fieldName, Collection<? extends Address> addresses) { + if (addresses == null || addresses.isEmpty()) { + removeFields(fieldName); + } else { + setField(Fields.addressList(fieldName, addresses)); + } + return this; + } + } } http://git-wip-us.apache.org/repos/asf/james-mime4j/blob/5c583037/dom/src/main/java/org/apache/james/mime4j/internal/AbstractEntityBuilder.java ---------------------------------------------------------------------- diff --git a/dom/src/main/java/org/apache/james/mime4j/internal/AbstractEntityBuilder.java b/dom/src/main/java/org/apache/james/mime4j/internal/AbstractEntityBuilder.java new file mode 100644 index 0000000..011a2d7 --- /dev/null +++ b/dom/src/main/java/org/apache/james/mime4j/internal/AbstractEntityBuilder.java @@ -0,0 +1,576 @@ +/**************************************************************** + * 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.james.mime4j.internal; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import org.apache.james.mime4j.dom.BinaryBody; +import org.apache.james.mime4j.dom.Body; +import org.apache.james.mime4j.dom.Message; +import org.apache.james.mime4j.dom.Multipart; +import org.apache.james.mime4j.dom.TextBody; +import org.apache.james.mime4j.dom.field.ContentDispositionField; +import org.apache.james.mime4j.dom.field.ContentTransferEncodingField; +import org.apache.james.mime4j.dom.field.ContentTypeField; +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.stream.Field; +import org.apache.james.mime4j.stream.NameValuePair; +import org.apache.james.mime4j.util.MimeUtil; + +public abstract class AbstractEntityBuilder { + + private final List<Field> fields; + private final Map<String, List<Field>> fieldMap; + + private Body body; + + public AbstractEntityBuilder() { + this.fields = new LinkedList<Field>(); + this.fieldMap = new HashMap<String, List<Field>>(); + } + + /** + * Adds a field to the end of the list of fields. + * + * @param field the field to add. + */ + public AbstractEntityBuilder addField(Field field) { + List<Field> values = fieldMap.get(field.getName().toLowerCase(Locale.US)); + if (values == null) { + values = new LinkedList<Field>(); + fieldMap.put(field.getName().toLowerCase(Locale.US), values); + } + values.add(field); + fields.add(field); + return this; + } + + /** + * Gets the fields of this header. The returned list will not be + * modifiable. + * + * @return the list of <code>Field</code> objects. + */ + public List<Field> getFields() { + return Collections.unmodifiableList(fields); + } + + /** + * Gets a <code>Field</code> given a field name. If there are multiple + * such fields defined in this header the first one will be returned. + * + * @param name the field name (e.g. From, Subject). + * @return the field or <code>null</code> if none found. + */ + public Field getField(String name) { + List<Field> l = fieldMap.get(name.toLowerCase(Locale.US)); + if (l != null && !l.isEmpty()) { + return l.get(0); + } + return null; + } + + /** + * Gets a <code>Field</code> given a field name and of the given type. + * If there are multiple such fields defined in this header the first + * one will be returned. + * + * @param name the field name (e.g. From, Subject). + * @param clazz the field class. + * @return the field or <code>null</code> if none found. + */ + public <F extends Field> F getField(final String name, final Class<F> clazz) { + List<Field> l = fieldMap.get(name.toLowerCase(Locale.US)); + for (int i = 0; i < l.size(); i++) { + Field field = l.get(i); + if (clazz.isInstance(field)) { + return clazz.cast(field); + } + } + return null; + } + + /** + * Returns <code>true<code/> if there is at least one explicitly + * set field with the given name. + * + * @param name the field name (e.g. From, Subject). + * @return <code>true<code/> if there is at least one explicitly + * set field with the given name, <code>false<code/> otherwise. + */ + public boolean containsField(String name) { + List<Field> l = fieldMap.get(name.toLowerCase(Locale.US)); + return l != null && !l.isEmpty(); + } + + /** + * Gets all <code>Field</code>s having the specified field name. + * + * @param name the field name (e.g. From, Subject). + * @return the list of fields. + */ + public List<Field> getFields(final String name) { + final String lowerCaseName = name.toLowerCase(Locale.US); + final List<Field> l = fieldMap.get(lowerCaseName); + final List<Field> results; + if (l == null || l.isEmpty()) { + results = Collections.emptyList(); + } else { + results = Collections.unmodifiableList(l); + } + return results; + } + + /** + * Gets all <code>Field</code>s having the specified field name + * and of the given type. + * + * @param name the field name (e.g. From, Subject). + * @param clazz the field class. + * @return the list of fields. + */ + public <F extends Field> List<F> getFields(final String name, final Class<F> clazz) { + final String lowerCaseName = name.toLowerCase(Locale.US); + final List<Field> l = fieldMap.get(lowerCaseName); + if (l == null) { + return Collections.emptyList(); + } + final List<F> results = new ArrayList<F>(); + for (int i = 0; i < l.size(); i++) { + Field field = l.get(i); + if (clazz.isInstance(field)) { + results.add(clazz.cast(field)); + } + } + return results; + } + + /** + * Removes all <code>Field</code>s having the specified field name. + * + * @param name + * the field name (e.g. From, Subject). + */ + public AbstractEntityBuilder removeFields(String name) { + final String lowerCaseName = name.toLowerCase(Locale.US); + List<Field> removed = fieldMap.remove(lowerCaseName); + if (removed == null || removed.isEmpty()) { + return this; + } + for (Iterator<Field> iterator = fields.iterator(); iterator.hasNext();) { + Field field = iterator.next(); + if (field.getName().equalsIgnoreCase(name)) { + iterator.remove(); + } + } + return this; + } + + /** + * Sets or replaces a field. This method is useful for header fields such as + * Subject or Message-ID that should not occur more than once in a message. + * + * If this builder does not already contain a header field of + * the same name as the given field then it is added to the end of the list + * of fields (same behavior as {@link #addField(org.apache.james.mime4j.stream.Field)}). Otherwise the + * first occurrence of a field with the same name is replaced by the given + * field and all further occurrences are removed. + * + * @param field the field to set. + */ + public AbstractEntityBuilder setField(Field field) { + final String lowerCaseName = field.getName().toLowerCase(Locale.US); + List<Field> l = fieldMap.get(lowerCaseName); + if (l == null || l.isEmpty()) { + addField(field); + return this; + } + + l.clear(); + l.add(field); + + int firstOccurrence = -1; + int index = 0; + for (Iterator<Field> iterator = fields.iterator(); iterator.hasNext(); index++) { + Field f = iterator.next(); + if (f.getName().equalsIgnoreCase(field.getName())) { + iterator.remove(); + if (firstOccurrence == -1) { + firstOccurrence = index; + } + } + } + fields.add(firstOccurrence, field); + return this; + } + + /** + * Clears all fields. + */ + public AbstractEntityBuilder clearFields() { + fields.clear(); + fieldMap.clear(); + return this; + } + + @SuppressWarnings("unchecked") + public <F extends ParsedField> F obtainField(String fieldName) { + return (F) getField(fieldName); + } + + /** + * Returns MIME type of this message. + * + * @return the MIME type or <code>null</code> if no MIME + * type has been set. + */ + public String getMimeType() { + ContentTypeField field = obtainField(FieldName.CONTENT_TYPE); + return field != null ? field.getMimeType() : null; + } + + /** + * Returns MIME character set encoding of this message. + * + * @return the MIME character set encoding or <code>null</code> if no charset + * type has been set. + */ + public String getCharset() { + ContentTypeField field = obtainField(FieldName.CONTENT_TYPE); + return field != null ? field.getCharset() : null; + } + + /** + * Sets transfer encoding of this message. + * + * @param mimeType MIME type of this message + * the MIME type to use. + * @param parameters content type parameters to use. + */ + public AbstractEntityBuilder setContentType(String mimeType, NameValuePair... parameters) { + if (mimeType == null) { + removeFields(FieldName.CONTENT_TYPE); + } else { + setField(Fields.contentType(mimeType, parameters)); + } + return this; + } + + /** + * Returns transfer encoding of this message. + * + * @return the transfer encoding. + */ + public String getContentTransferEncoding() { + ContentTransferEncodingField field = obtainField(FieldName.CONTENT_TRANSFER_ENCODING); + return field != null ? field.getEncoding() : null; + } + + /** + * Sets transfer encoding of this message. + * + * @param contentTransferEncoding + * transfer encoding to use. + */ + public AbstractEntityBuilder setContentTransferEncoding(String contentTransferEncoding) { + if (contentTransferEncoding == null) { + removeFields(FieldName.CONTENT_TRANSFER_ENCODING); + } else { + setField(Fields.contentTransferEncoding(contentTransferEncoding)); + } + return this; + } + + /** + * Return disposition type of this message. + * + * @return the disposition type or <code>null</code> if no disposition + * type has been set. + */ + public String getDispositionType() { + ContentDispositionField field = obtainField(FieldName.CONTENT_DISPOSITION); + return field != null ? field.getDispositionType() : null; + } + + /** + * Sets content disposition of this message to the + * specified disposition type. No filename, size or date parameters + * are included in the content disposition. + * + * @param dispositionType + * disposition type value (usually <code>inline</code> or + * <code>attachment</code>). + */ + public AbstractEntityBuilder setContentDisposition(String dispositionType) { + if (dispositionType == null) { + removeFields(FieldName.CONTENT_DISPOSITION); + } else { + setField(Fields.contentDisposition(dispositionType)); + } + return this; + } + + /** + * Sets content disposition of this message to the + * specified disposition type and filename. No size or date parameters are + * included in the content disposition. + * + * @param dispositionType + * disposition type value (usually <code>inline</code> or + * <code>attachment</code>). + * @param filename + * filename parameter value or <code>null</code> if the + * parameter should not be included. + */ + public AbstractEntityBuilder setContentDisposition(String dispositionType, String filename) { + if (dispositionType == null) { + removeFields(FieldName.CONTENT_DISPOSITION); + } else { + setField(Fields.contentDisposition(dispositionType, filename)); + } + return this; + } + + /** + * Sets content disposition of this message to the + * specified values. No date parameters are included in the content + * disposition. + * + * @param dispositionType + * disposition type value (usually <code>inline</code> or + * <code>attachment</code>). + * @param filename + * filename parameter value or <code>null</code> if the + * parameter should not be included. + * @param size + * size parameter value or <code>-1</code> if the parameter + * should not be included. + */ + public AbstractEntityBuilder setContentDisposition(String dispositionType, String filename, + long size) { + if (dispositionType == null) { + removeFields(FieldName.CONTENT_DISPOSITION); + } else { + setField(Fields.contentDisposition(dispositionType, filename, size)); + } + return this; + } + + /** + * Sets content disposition of this message to the + * specified values. + * + * @param dispositionType + * disposition type value (usually <code>inline</code> or + * <code>attachment</code>). + * @param filename + * filename parameter value or <code>null</code> if the + * parameter should not be included. + * @param size + * size parameter value or <code>-1</code> if the parameter + * should not be included. + * @param creationDate + * creation-date parameter value or <code>null</code> if the + * parameter should not be included. + * @param modificationDate + * modification-date parameter value or <code>null</code> if + * the parameter should not be included. + * @param readDate + * read-date parameter value or <code>null</code> if the + * parameter should not be included. + */ + public AbstractEntityBuilder setContentDisposition(String dispositionType, String filename, + long size, Date creationDate, Date modificationDate, Date readDate) { + if (dispositionType == null) { + removeFields(FieldName.CONTENT_DISPOSITION); + } else { + setField(Fields.contentDisposition(dispositionType, filename, size, + creationDate, modificationDate, readDate)); + } + return this; + } + + /** + * Returns filename of the content disposition of this message. + * + * @return the filename parameter of the content disposition or + * <code>null</code> if the filename has not been set. + */ + public String getFilename() { + ContentDispositionField field = obtainField(FieldName.CONTENT_DISPOSITION); + return field != null ? field.getFilename() : null; + } + + /** + * Returns size of the content disposition of this message. + * + * @return the size parameter of the content disposition or + * <code>-1</code> if the filename has not been set. + */ + public long getSize() { + ContentDispositionField field = obtainField(FieldName.CONTENT_DISPOSITION); + return field != null ? field.getSize() : -1; + } + + /** + * Returns creation date of the content disposition of this message. + * + * @return the creation date parameter of the content disposition or + * <code>null</code> if the filename has not been set. + */ + public Date getCreationDate() { + ContentDispositionField field = obtainField(FieldName.CONTENT_DISPOSITION); + return field != null ? field.getCreationDate() : null; + } + + /** + * Returns modification date of the content disposition of this message. + * + * @return the modification date parameter of the content disposition or + * <code>null</code> if the filename has not been set. + */ + public Date getModificationDate() { + ContentDispositionField field = obtainField(FieldName.CONTENT_DISPOSITION); + return field != null ? field.getModificationDate() : null; + } + + /** + * Returns read date of the content disposition of this message. + * + * @return the read date parameter of the content disposition or + * <code>null</code> if the filename has not been set. + */ + public Date getReadDate() { + ContentDispositionField field = obtainField(FieldName.CONTENT_DISPOSITION); + return field != null ? field.getReadDate() : null; + } + + /** + * Sets body of this message. Also sets the content type based on properties of + * the given {@link org.apache.james.mime4j.dom.Body}. + * + * @param body + * the body. + */ + public AbstractEntityBuilder setBody(Body body) { + this.body = body; + if (body == null) { + removeFields(FieldName.CONTENT_TYPE); + } + return this; + } + + /** + * Sets body of this message. Also sets the content type based on properties of + * the given {@link org.apache.james.mime4j.dom.Body}. + * + * @param body + * the body. + */ + public AbstractEntityBuilder setBody(TextBody textBody) { + this.body = textBody; + if (textBody != null) { + String mimeCharset = textBody.getMimeCharset(); + if ("us-ascii".equalsIgnoreCase(mimeCharset)) { + mimeCharset = null; + } + if (mimeCharset != null) { + setField(Fields.contentType("text/plain", new NameValuePair("charset", mimeCharset))); + } else { + setField(Fields.contentType("text/plain")); + } + } else { + removeFields(FieldName.CONTENT_TYPE); + } + return this; + } + + /** + * Sets binaryBody of this message. Also sets the content type based on properties of + * the given {@link org.apache.james.mime4j.dom.Body}. + * + * @param binaryBody + * the binaryBody. + */ + public AbstractEntityBuilder setBody(BinaryBody binaryBody) { + this.body = binaryBody; + if (binaryBody != null) { + setField(Fields.contentType("application/octet-stream")); + } else { + removeFields(FieldName.CONTENT_TYPE); + } + return this; + } + + /** + * Sets body of this message. Also sets the content type based on properties of + * the given {@link org.apache.james.mime4j.dom.Body}. + * + * @param body + * the body. + */ + public AbstractEntityBuilder setBody(Message message) { + this.body = message; + if (message != null) { + setField(Fields.contentType("message/rfc822")); + } else { + removeFields(FieldName.CONTENT_TYPE); + } + return this; + } + + /** + * Sets body of this message. Also sets the content type based on properties of + * the given {@link org.apache.james.mime4j.dom.Body}. + * + * @param body + * the body. + */ + public AbstractEntityBuilder setBody(Multipart multipart) { + this.body = multipart; + if (multipart != null) { + setField(Fields.contentType("multipart/" + multipart.getSubType(), + new NameValuePair("boundary", MimeUtil.createUniqueBoundary()))); + } else { + removeFields(FieldName.CONTENT_TYPE); + } + return this; + } + + /** + * Returns message body. + * + * @return the message body. + */ + public Body getBody() { + return body; + } + +} http://git-wip-us.apache.org/repos/asf/james-mime4j/blob/5c583037/dom/src/main/java/org/apache/james/mime4j/internal/ParserStreamContentHandler.java ---------------------------------------------------------------------- diff --git a/dom/src/main/java/org/apache/james/mime4j/internal/ParserStreamContentHandler.java b/dom/src/main/java/org/apache/james/mime4j/internal/ParserStreamContentHandler.java new file mode 100644 index 0000000..d251ba7 --- /dev/null +++ b/dom/src/main/java/org/apache/james/mime4j/internal/ParserStreamContentHandler.java @@ -0,0 +1,188 @@ +/**************************************************************** + * 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.james.mime4j.internal; + +import org.apache.james.mime4j.MimeException; +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.message.BodyFactory; +import org.apache.james.mime4j.message.BodyPart; +import org.apache.james.mime4j.message.DefaultMessageImplFactory; +import org.apache.james.mime4j.message.HeaderImpl; +import org.apache.james.mime4j.message.MessageImplFactory; +import org.apache.james.mime4j.message.MultipartImpl; +import org.apache.james.mime4j.parser.ContentHandler; +import org.apache.james.mime4j.stream.BodyDescriptor; +import org.apache.james.mime4j.stream.Field; +import org.apache.james.mime4j.util.ByteArrayBuffer; +import org.apache.james.mime4j.util.ByteSequence; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Stack; + +/** + * A <code>ContentHandler</code> for building an <code>Entity</code> to be + * used in conjunction with a {@link org.apache.james.mime4j.parser.MimeStreamParser}. + */ +public class ParserStreamContentHandler implements ContentHandler { + + private final Entity entity; + private final MessageImplFactory messageImplFactory; + private final BodyFactory bodyFactory; + private final Stack<Object> stack; + + public ParserStreamContentHandler( + final Entity entity, + final BodyFactory bodyFactory) { + this.entity = entity; + this.messageImplFactory = new DefaultMessageImplFactory(); + this.bodyFactory = bodyFactory; + this.stack = new Stack<Object>(); + } + + public ParserStreamContentHandler( + final Entity entity, + final MessageImplFactory messageImplFactory, + final BodyFactory bodyFactory) { + this.entity = entity; + this.messageImplFactory = messageImplFactory; + this.bodyFactory = bodyFactory; + this.stack = new Stack<Object>(); + } + + private void expect(Class<?> c) { + if (!c.isInstance(stack.peek())) { + throw new IllegalStateException("Internal stack error: " + + "Expected '" + c.getName() + "' found '" + + stack.peek().getClass().getName() + "'"); + } + } + + public void startMessage() throws MimeException { + if (stack.isEmpty()) { + stack.push(this.entity); + } else { + expect(Entity.class); + Message m = messageImplFactory.messageImpl(); + ((Entity) stack.peek()).setBody(m); + stack.push(m); + } + } + + public void endMessage() throws MimeException { + expect(Message.class); + stack.pop(); + } + + public void startHeader() throws MimeException { + stack.push(new HeaderImpl()); + } + + public void field(Field field) throws MimeException { + expect(Header.class); + ((Header) stack.peek()).addField(field); + } + + public void endHeader() throws MimeException { + expect(Header.class); + Header h = (Header) stack.pop(); + expect(Entity.class); + ((Entity) stack.peek()).setHeader(h); + } + + public void startMultipart(final BodyDescriptor bd) throws MimeException { + expect(Entity.class); + + final Entity e = (Entity) stack.peek(); + final String subType = bd.getSubType(); + final Multipart multiPart = new MultipartImpl(subType); + e.setBody(multiPart); + stack.push(multiPart); + } + + public void body(BodyDescriptor bd, final InputStream is) throws MimeException, IOException { + expect(Entity.class); + + final Body body; + if (bd.getMimeType().startsWith("text/")) { + body = bodyFactory.textBody(is, bd.getCharset()); + } else { + body = bodyFactory.binaryBody(is); + } + + Entity entity = ((Entity) stack.peek()); + entity.setBody(body); + } + + public void endMultipart() throws MimeException { + stack.pop(); + } + + public void startBodyPart() throws MimeException { + expect(Multipart.class); + + BodyPart bodyPart = new BodyPart(); + ((Multipart) stack.peek()).addBodyPart(bodyPart); + stack.push(bodyPart); + } + + public void endBodyPart() throws MimeException { + expect(BodyPart.class); + stack.pop(); + } + + public void epilogue(InputStream is) throws MimeException, IOException { + expect(MultipartImpl.class); + ByteSequence bytes = loadStream(is); + ((MultipartImpl) stack.peek()).setEpilogueRaw(bytes); + } + + public void preamble(InputStream is) throws MimeException, IOException { + expect(MultipartImpl.class); + ByteSequence bytes = loadStream(is); + ((MultipartImpl) stack.peek()).setPreambleRaw(bytes); + } + + /** + * Unsupported. + * + * @param is the raw contents of the entity. + * @throws UnsupportedOperationException + */ + public void raw(InputStream is) throws MimeException, IOException { + throw new UnsupportedOperationException("Not supported"); + } + + private static ByteSequence loadStream(InputStream in) throws IOException { + ByteArrayBuffer bab = new ByteArrayBuffer(64); + + int b; + while ((b = in.read()) != -1) { + bab.append(b); + } + + return bab; + } + +} http://git-wip-us.apache.org/repos/asf/james-mime4j/blob/5c583037/dom/src/main/java/org/apache/james/mime4j/message/AbstractEntityBuilder.java ---------------------------------------------------------------------- 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 deleted file mode 100644 index 55f736f..0000000 --- a/dom/src/main/java/org/apache/james/mime4j/message/AbstractEntityBuilder.java +++ /dev/null @@ -1,576 +0,0 @@ -/**************************************************************** - * 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.james.mime4j.message; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Date; -import java.util.HashMap; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.Locale; -import java.util.Map; - -import org.apache.james.mime4j.dom.BinaryBody; -import org.apache.james.mime4j.dom.Body; -import org.apache.james.mime4j.dom.Message; -import org.apache.james.mime4j.dom.Multipart; -import org.apache.james.mime4j.dom.TextBody; -import org.apache.james.mime4j.dom.field.ContentDispositionField; -import org.apache.james.mime4j.dom.field.ContentTransferEncodingField; -import org.apache.james.mime4j.dom.field.ContentTypeField; -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.stream.Field; -import org.apache.james.mime4j.stream.NameValuePair; -import org.apache.james.mime4j.util.MimeUtil; - -abstract class AbstractEntityBuilder { - - private final List<Field> fields; - private final Map<String, List<Field>> fieldMap; - - private Body body; - - AbstractEntityBuilder() { - this.fields = new LinkedList<Field>(); - this.fieldMap = new HashMap<String, List<Field>>(); - } - - /** - * Adds a field to the end of the list of fields. - * - * @param field the field to add. - */ - public AbstractEntityBuilder addField(Field field) { - List<Field> values = fieldMap.get(field.getName().toLowerCase(Locale.US)); - if (values == null) { - values = new LinkedList<Field>(); - fieldMap.put(field.getName().toLowerCase(Locale.US), values); - } - values.add(field); - fields.add(field); - return this; - } - - /** - * Gets the fields of this header. The returned list will not be - * modifiable. - * - * @return the list of <code>Field</code> objects. - */ - public List<Field> getFields() { - return Collections.unmodifiableList(fields); - } - - /** - * Gets a <code>Field</code> given a field name. If there are multiple - * such fields defined in this header the first one will be returned. - * - * @param name the field name (e.g. From, Subject). - * @return the field or <code>null</code> if none found. - */ - public Field getField(String name) { - List<Field> l = fieldMap.get(name.toLowerCase(Locale.US)); - if (l != null && !l.isEmpty()) { - return l.get(0); - } - return null; - } - - /** - * Gets a <code>Field</code> given a field name and of the given type. - * If there are multiple such fields defined in this header the first - * one will be returned. - * - * @param name the field name (e.g. From, Subject). - * @param clazz the field class. - * @return the field or <code>null</code> if none found. - */ - public <F extends Field> F getField(final String name, final Class<F> clazz) { - List<Field> l = fieldMap.get(name.toLowerCase(Locale.US)); - for (int i = 0; i < l.size(); i++) { - Field field = l.get(i); - if (clazz.isInstance(field)) { - return clazz.cast(field); - } - } - return null; - } - - /** - * Returns <code>true<code/> if there is at least one explicitly - * set field with the given name. - * - * @param name the field name (e.g. From, Subject). - * @return <code>true<code/> if there is at least one explicitly - * set field with the given name, <code>false<code/> otherwise. - */ - public boolean containsField(String name) { - List<Field> l = fieldMap.get(name.toLowerCase(Locale.US)); - return l != null && !l.isEmpty(); - } - - /** - * Gets all <code>Field</code>s having the specified field name. - * - * @param name the field name (e.g. From, Subject). - * @return the list of fields. - */ - public List<Field> getFields(final String name) { - final String lowerCaseName = name.toLowerCase(Locale.US); - final List<Field> l = fieldMap.get(lowerCaseName); - final List<Field> results; - if (l == null || l.isEmpty()) { - results = Collections.emptyList(); - } else { - results = Collections.unmodifiableList(l); - } - return results; - } - - /** - * Gets all <code>Field</code>s having the specified field name - * and of the given type. - * - * @param name the field name (e.g. From, Subject). - * @param clazz the field class. - * @return the list of fields. - */ - public <F extends Field> List<F> getFields(final String name, final Class<F> clazz) { - final String lowerCaseName = name.toLowerCase(Locale.US); - final List<Field> l = fieldMap.get(lowerCaseName); - if (l == null) { - return Collections.emptyList(); - } - final List<F> results = new ArrayList<F>(); - for (int i = 0; i < l.size(); i++) { - Field field = l.get(i); - if (clazz.isInstance(field)) { - results.add(clazz.cast(field)); - } - } - return results; - } - - /** - * Removes all <code>Field</code>s having the specified field name. - * - * @param name - * the field name (e.g. From, Subject). - */ - public AbstractEntityBuilder removeFields(String name) { - final String lowerCaseName = name.toLowerCase(Locale.US); - List<Field> removed = fieldMap.remove(lowerCaseName); - if (removed == null || removed.isEmpty()) { - return this; - } - for (Iterator<Field> iterator = fields.iterator(); iterator.hasNext();) { - Field field = iterator.next(); - if (field.getName().equalsIgnoreCase(name)) { - iterator.remove(); - } - } - return this; - } - - /** - * Sets or replaces a field. This method is useful for header fields such as - * Subject or Message-ID that should not occur more than once in a message. - * - * If this builder does not already contain a header field of - * the same name as the given field then it is added to the end of the list - * of fields (same behavior as {@link #addField(org.apache.james.mime4j.stream.Field)}). Otherwise the - * first occurrence of a field with the same name is replaced by the given - * field and all further occurrences are removed. - * - * @param field the field to set. - */ - public AbstractEntityBuilder setField(Field field) { - final String lowerCaseName = field.getName().toLowerCase(Locale.US); - List<Field> l = fieldMap.get(lowerCaseName); - if (l == null || l.isEmpty()) { - addField(field); - return this; - } - - l.clear(); - l.add(field); - - int firstOccurrence = -1; - int index = 0; - for (Iterator<Field> iterator = fields.iterator(); iterator.hasNext(); index++) { - Field f = iterator.next(); - if (f.getName().equalsIgnoreCase(field.getName())) { - iterator.remove(); - if (firstOccurrence == -1) { - firstOccurrence = index; - } - } - } - fields.add(firstOccurrence, field); - 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); - } - - /** - * Returns MIME type of this message. - * - * @return the MIME type or <code>null</code> if no MIME - * type has been set. - */ - public String getMimeType() { - ContentTypeField field = obtainField(FieldName.CONTENT_TYPE); - return field != null ? field.getMimeType() : null; - } - - /** - * Returns MIME character set encoding of this message. - * - * @return the MIME character set encoding or <code>null</code> if no charset - * type has been set. - */ - public String getCharset() { - ContentTypeField field = obtainField(FieldName.CONTENT_TYPE); - return field != null ? field.getCharset() : null; - } - - /** - * Sets transfer encoding of this message. - * - * @param mimeType MIME type of this message - * the MIME type to use. - * @param parameters content type parameters to use. - */ - public AbstractEntityBuilder setContentType(String mimeType, NameValuePair... parameters) { - if (mimeType == null) { - removeFields(FieldName.CONTENT_TYPE); - } else { - setField(Fields.contentType(mimeType, parameters)); - } - return this; - } - - /** - * Returns transfer encoding of this message. - * - * @return the transfer encoding. - */ - public String getContentTransferEncoding() { - ContentTransferEncodingField field = obtainField(FieldName.CONTENT_TRANSFER_ENCODING); - return field != null ? field.getEncoding() : null; - } - - /** - * Sets transfer encoding of this message. - * - * @param contentTransferEncoding - * transfer encoding to use. - */ - public AbstractEntityBuilder setContentTransferEncoding(String contentTransferEncoding) { - if (contentTransferEncoding == null) { - removeFields(FieldName.CONTENT_TRANSFER_ENCODING); - } else { - setField(Fields.contentTransferEncoding(contentTransferEncoding)); - } - return this; - } - - /** - * Return disposition type of this message. - * - * @return the disposition type or <code>null</code> if no disposition - * type has been set. - */ - public String getDispositionType() { - ContentDispositionField field = obtainField(FieldName.CONTENT_DISPOSITION); - return field != null ? field.getDispositionType() : null; - } - - /** - * Sets content disposition of this message to the - * specified disposition type. No filename, size or date parameters - * are included in the content disposition. - * - * @param dispositionType - * disposition type value (usually <code>inline</code> or - * <code>attachment</code>). - */ - public AbstractEntityBuilder setContentDisposition(String dispositionType) { - if (dispositionType == null) { - removeFields(FieldName.CONTENT_DISPOSITION); - } else { - setField(Fields.contentDisposition(dispositionType)); - } - return this; - } - - /** - * Sets content disposition of this message to the - * specified disposition type and filename. No size or date parameters are - * included in the content disposition. - * - * @param dispositionType - * disposition type value (usually <code>inline</code> or - * <code>attachment</code>). - * @param filename - * filename parameter value or <code>null</code> if the - * parameter should not be included. - */ - public AbstractEntityBuilder setContentDisposition(String dispositionType, String filename) { - if (dispositionType == null) { - removeFields(FieldName.CONTENT_DISPOSITION); - } else { - setField(Fields.contentDisposition(dispositionType, filename)); - } - return this; - } - - /** - * Sets content disposition of this message to the - * specified values. No date parameters are included in the content - * disposition. - * - * @param dispositionType - * disposition type value (usually <code>inline</code> or - * <code>attachment</code>). - * @param filename - * filename parameter value or <code>null</code> if the - * parameter should not be included. - * @param size - * size parameter value or <code>-1</code> if the parameter - * should not be included. - */ - public AbstractEntityBuilder setContentDisposition(String dispositionType, String filename, - long size) { - if (dispositionType == null) { - removeFields(FieldName.CONTENT_DISPOSITION); - } else { - setField(Fields.contentDisposition(dispositionType, filename, size)); - } - return this; - } - - /** - * Sets content disposition of this message to the - * specified values. - * - * @param dispositionType - * disposition type value (usually <code>inline</code> or - * <code>attachment</code>). - * @param filename - * filename parameter value or <code>null</code> if the - * parameter should not be included. - * @param size - * size parameter value or <code>-1</code> if the parameter - * should not be included. - * @param creationDate - * creation-date parameter value or <code>null</code> if the - * parameter should not be included. - * @param modificationDate - * modification-date parameter value or <code>null</code> if - * the parameter should not be included. - * @param readDate - * read-date parameter value or <code>null</code> if the - * parameter should not be included. - */ - public AbstractEntityBuilder setContentDisposition(String dispositionType, String filename, - long size, Date creationDate, Date modificationDate, Date readDate) { - if (dispositionType == null) { - removeFields(FieldName.CONTENT_DISPOSITION); - } else { - setField(Fields.contentDisposition(dispositionType, filename, size, - creationDate, modificationDate, readDate)); - } - return this; - } - - /** - * Returns filename of the content disposition of this message. - * - * @return the filename parameter of the content disposition or - * <code>null</code> if the filename has not been set. - */ - public String getFilename() { - ContentDispositionField field = obtainField(FieldName.CONTENT_DISPOSITION); - return field != null ? field.getFilename() : null; - } - - /** - * Returns size of the content disposition of this message. - * - * @return the size parameter of the content disposition or - * <code>-1</code> if the filename has not been set. - */ - public long getSize() { - ContentDispositionField field = obtainField(FieldName.CONTENT_DISPOSITION); - return field != null ? field.getSize() : -1; - } - - /** - * Returns creation date of the content disposition of this message. - * - * @return the creation date parameter of the content disposition or - * <code>null</code> if the filename has not been set. - */ - public Date getCreationDate() { - ContentDispositionField field = obtainField(FieldName.CONTENT_DISPOSITION); - return field != null ? field.getCreationDate() : null; - } - - /** - * Returns modification date of the content disposition of this message. - * - * @return the modification date parameter of the content disposition or - * <code>null</code> if the filename has not been set. - */ - public Date getModificationDate() { - ContentDispositionField field = obtainField(FieldName.CONTENT_DISPOSITION); - return field != null ? field.getModificationDate() : null; - } - - /** - * Returns read date of the content disposition of this message. - * - * @return the read date parameter of the content disposition or - * <code>null</code> if the filename has not been set. - */ - public Date getReadDate() { - ContentDispositionField field = obtainField(FieldName.CONTENT_DISPOSITION); - return field != null ? field.getReadDate() : null; - } - - /** - * Sets body of this message. Also sets the content type based on properties of - * the given {@link org.apache.james.mime4j.dom.Body}. - * - * @param body - * the body. - */ - public AbstractEntityBuilder setBody(Body body) { - this.body = body; - if (body == null) { - removeFields(FieldName.CONTENT_TYPE); - } - return this; - } - - /** - * Sets body of this message. Also sets the content type based on properties of - * the given {@link org.apache.james.mime4j.dom.Body}. - * - * @param body - * the body. - */ - public AbstractEntityBuilder setBody(TextBody textBody) { - this.body = textBody; - if (textBody != null) { - String mimeCharset = textBody.getMimeCharset(); - if ("us-ascii".equalsIgnoreCase(mimeCharset)) { - mimeCharset = null; - } - if (mimeCharset != null) { - setField(Fields.contentType("text/plain", new NameValuePair("charset", mimeCharset))); - } else { - setField(Fields.contentType("text/plain")); - } - } else { - removeFields(FieldName.CONTENT_TYPE); - } - return this; - } - - /** - * Sets binaryBody of this message. Also sets the content type based on properties of - * the given {@link org.apache.james.mime4j.dom.Body}. - * - * @param binaryBody - * the binaryBody. - */ - public AbstractEntityBuilder setBody(BinaryBody binaryBody) { - this.body = binaryBody; - if (binaryBody != null) { - setField(Fields.contentType("application/octet-stream")); - } else { - removeFields(FieldName.CONTENT_TYPE); - } - return this; - } - - /** - * Sets body of this message. Also sets the content type based on properties of - * the given {@link org.apache.james.mime4j.dom.Body}. - * - * @param body - * the body. - */ - public AbstractEntityBuilder setBody(Message message) { - this.body = message; - if (message != null) { - setField(Fields.contentType("message/rfc822")); - } else { - removeFields(FieldName.CONTENT_TYPE); - } - return this; - } - - /** - * Sets body of this message. Also sets the content type based on properties of - * the given {@link org.apache.james.mime4j.dom.Body}. - * - * @param body - * the body. - */ - public AbstractEntityBuilder setBody(Multipart multipart) { - this.body = multipart; - if (multipart != null) { - setField(Fields.contentType("multipart/" + multipart.getSubType(), - new NameValuePair("boundary", MimeUtil.createUniqueBoundary()))); - } else { - removeFields(FieldName.CONTENT_TYPE); - } - return this; - } - - /** - * Returns message body. - * - * @return the message body. - */ - public Body getBody() { - return body; - } - -} http://git-wip-us.apache.org/repos/asf/james-mime4j/blob/5c583037/dom/src/main/java/org/apache/james/mime4j/message/BodyPartBuilder.java ---------------------------------------------------------------------- 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 2864cc1..837592f 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 @@ -29,6 +29,7 @@ import org.apache.james.mime4j.dom.Message; import org.apache.james.mime4j.dom.Multipart; import org.apache.james.mime4j.dom.TextBody; import org.apache.james.mime4j.field.Fields; +import org.apache.james.mime4j.internal.AbstractEntityBuilder; import org.apache.james.mime4j.io.InputStreams; import org.apache.james.mime4j.stream.Field; import org.apache.james.mime4j.stream.NameValuePair; http://git-wip-us.apache.org/repos/asf/james-mime4j/blob/5c583037/dom/src/main/java/org/apache/james/mime4j/message/DefaultMessageBuilder.java ---------------------------------------------------------------------- diff --git a/dom/src/main/java/org/apache/james/mime4j/message/DefaultMessageBuilder.java b/dom/src/main/java/org/apache/james/mime4j/message/DefaultMessageBuilder.java index 323e25c..0f35dab 100644 --- a/dom/src/main/java/org/apache/james/mime4j/message/DefaultMessageBuilder.java +++ b/dom/src/main/java/org/apache/james/mime4j/message/DefaultMessageBuilder.java @@ -37,6 +37,7 @@ import org.apache.james.mime4j.dom.SingleBody; import org.apache.james.mime4j.dom.field.ParsedField; import org.apache.james.mime4j.field.DefaultFieldParser; import org.apache.james.mime4j.field.LenientFieldParser; +import org.apache.james.mime4j.internal.ParserStreamContentHandler; import org.apache.james.mime4j.parser.AbstractContentHandler; import org.apache.james.mime4j.parser.MimeStreamParser; import org.apache.james.mime4j.stream.BodyDescriptorBuilder; --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
