You are viewing a plain text version of this content. The canonical link for it is here.
Posted to server-dev@james.apache.org by bt...@apache.org on 2022/10/17 03:11:50 UTC

[james-mime4j] branch master updated (2dd9015a -> 4216ca27)

This is an automated email from the ASF dual-hosted git repository.

btellier pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/james-mime4j.git


    from 2dd9015a MIME4J-109 Add (disabled) tests for MIME parameter decoding
     new 215f8ed3 MIME4J-318 Adopt commons-io ByteArrayOutputStream
     new e88d55b6 MIME4J-318 BufferedLineReaderInputStream: shift table can be reused
     new 7f56f650 MIME4J-318 Buffer recycling for ContentUtil copies
     new ad155f97 MIME4J-318 Write single body backed by ByteArrayOutputStream
     new cd9f63be MIME4J-318 Write single body backed by ByteArrayOutputStream, add recycling
     new 2a234381 MIME4J-318 RawBody is used solely for parsing and not exposed nor kept.
     new e2ad9a2d MIME4J-318 Fasten ContentDispositionFieldLenientImpl
     new 2f53da38 MIME4J-318 DefaultBodyDescriptorBuilder should avoid copying all headers
     new c340fe60 MIME4J-318 Do not use Synchronised byte array output streams
     new 639e74d1 MIME4J-318 Fields: rely on lenient parsers
     new a741f65d MIME4J-318 Improve writing recycled buffers
     new 432b786a MIME4J-318 Add a TextBody::getCharset
     new 3b3f09e3 MIME4J-318 Improve writing bodies into an output stream
     new 4216ca27 MIME4J-318 Allow disabling buffer recycling

The 14 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../james/mime4j/LongMultipartReadBench.java       |   4 +-
 core/pom.xml                                       |   1 -
 .../james/mime4j/codec/Base64InputStream.java      |  24 ++-
 .../org/apache/james/mime4j/codec/DecoderUtil.java |  22 ++-
 .../mime4j/codec/QuotedPrintableInputStream.java   |  34 ++++-
 .../mime4j/io/BufferedLineReaderInputStream.java   |  42 +++++-
 .../james/mime4j/io/LineReaderInputStream.java     |   2 +
 .../mime4j/io/LineReaderInputStreamAdaptor.java    |   6 +
 .../james/mime4j/io/MimeBoundaryInputStream.java   |   6 +
 .../james/mime4j/parser/MimeStreamParser.java      |   1 +
 .../james/mime4j/stream/DefaultFieldBuilder.java   |  26 +++-
 .../apache/james/mime4j/stream/FieldBuilder.java   |   5 +-
 .../org/apache/james/mime4j/stream/MimeEntity.java |  46 ++++--
 .../james/mime4j/stream/MimeTokenStream.java       |   8 +-
 .../org/apache/james/mime4j/stream/RawBody.java    |   2 +-
 .../apache/james/mime4j/util/BufferRecycler.java   | 164 +++++++++++++++++++++
 .../mime4j/util/ByteArrayOutputStreamRecycler.java |  66 +++++++++
 .../org/apache/james/mime4j/util/ContentUtil.java  |  50 ++++++-
 .../james/mime4j/util/MimeParameterMapping.java    |  10 +-
 ...rayBuffer.java => RecycledByteArrayBuffer.java} |  26 ++--
 .../mime4j/stream/DefaultFieldBuilderTest.java     |   9 +-
 dom/pom.xml                                        |   1 -
 .../org/apache/james/mime4j/dom/SingleBody.java    |  20 +--
 .../java/org/apache/james/mime4j/dom/TextBody.java |   3 +
 .../mime4j/dom/field/ContentDescriptionField.java  |   4 +
 .../mime4j/dom/field/ContentDispositionField.java  |   4 +
 .../james/mime4j/dom/field/ContentIdField.java     |   4 +
 .../mime4j/dom/field/ContentLanguageField.java     |   4 +
 .../james/mime4j/dom/field/ContentLengthField.java |   4 +
 .../mime4j/dom/field/ContentLocationField.java     |   4 +
 .../james/mime4j/dom/field/ContentMD5Field.java    |   4 +
 .../dom/field/ContentTransferEncodingField.java    |   4 +
 .../james/mime4j/dom/field/ContentTypeField.java   |   4 +
 .../james/mime4j/dom/field/MimeVersionField.java   |   4 +
 .../apache/james/mime4j/dom/field/ParsedField.java |   4 +
 .../field/ContentDispositionFieldLenientImpl.java  |  48 ++----
 .../java/org/apache/james/mime4j/field/Fields.java |  24 +--
 .../james/mime4j/message/BasicBodyFactory.java     | 105 ++++++++++++-
 .../message/DefaultBodyDescriptorBuilder.java      |   2 +-
 .../mime4j/message/DefaultMessageBuilder.java      |   1 +
 .../james/mime4j/message/DefaultMessageWriter.java |   4 +
 .../james/mime4j/message/SingleBodyBuilder.java    |  11 +-
 .../org/apache/james/mime4j/field/FieldsTest.java  |   1 -
 .../james/mime4j/utils/search/MessageMatcher.java  |   5 +-
 .../james/mime4j/storage/StorageTextBody.java      |   5 +
 .../james/mime4j/storage/StringTextBody.java       |   5 +
 46 files changed, 690 insertions(+), 143 deletions(-)
 create mode 100644 core/src/main/java/org/apache/james/mime4j/util/BufferRecycler.java
 create mode 100644 core/src/main/java/org/apache/james/mime4j/util/ByteArrayOutputStreamRecycler.java
 copy core/src/main/java/org/apache/james/mime4j/util/{ByteArrayBuffer.java => RecycledByteArrayBuffer.java} (83%)


---------------------------------------------------------------------
To unsubscribe, e-mail: server-dev-unsubscribe@james.apache.org
For additional commands, e-mail: server-dev-help@james.apache.org


[james-mime4j] 09/14: MIME4J-318 Do not use Synchronised byte array output streams

Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-mime4j.git

commit c340fe60f2f27d47858c67dbde25387c4d94310c
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Thu Jun 23 16:23:16 2022 +0700

    MIME4J-318 Do not use Synchronised byte array output streams
---
 .../james/mime4j/util/ByteArrayOutputStreamRecycler.java  | 15 ++++++++-------
 .../java/org/apache/james/mime4j/util/ContentUtil.java    |  4 ++--
 .../org/apache/james/mime4j/message/BasicBodyFactory.java |  1 -
 3 files changed, 10 insertions(+), 10 deletions(-)

diff --git a/core/src/main/java/org/apache/james/mime4j/util/ByteArrayOutputStreamRecycler.java b/core/src/main/java/org/apache/james/mime4j/util/ByteArrayOutputStreamRecycler.java
index d99dda7e..a77d0c50 100644
--- a/core/src/main/java/org/apache/james/mime4j/util/ByteArrayOutputStreamRecycler.java
+++ b/core/src/main/java/org/apache/james/mime4j/util/ByteArrayOutputStreamRecycler.java
@@ -22,13 +22,14 @@ package org.apache.james.mime4j.util;
 import java.util.concurrent.ConcurrentLinkedQueue;
 
 import org.apache.commons.io.output.ByteArrayOutputStream;
+import org.apache.commons.io.output.UnsynchronizedByteArrayOutputStream;
 
 public class ByteArrayOutputStreamRecycler {
     public static class Wrapper {
         private final ByteArrayOutputStreamRecycler recycler;
-        private final ByteArrayOutputStream value;
+        private final UnsynchronizedByteArrayOutputStream value;
 
-        public Wrapper(ByteArrayOutputStreamRecycler recycler, ByteArrayOutputStream value) {
+        public Wrapper(ByteArrayOutputStreamRecycler recycler, UnsynchronizedByteArrayOutputStream value) {
             this.recycler = recycler;
             this.value = value;
         }
@@ -37,26 +38,26 @@ public class ByteArrayOutputStreamRecycler {
             recycler.release(value);
         }
 
-        public ByteArrayOutputStream getValue() {
+        public UnsynchronizedByteArrayOutputStream getValue() {
             return value;
         }
     }
 
-    protected final ConcurrentLinkedQueue<ByteArrayOutputStream> buffers;
+    protected final ConcurrentLinkedQueue<UnsynchronizedByteArrayOutputStream> buffers;
 
     public ByteArrayOutputStreamRecycler() {
         buffers = new ConcurrentLinkedQueue<>();
     }
 
     public Wrapper allocOutputStream() {
-        ByteArrayOutputStream result = buffers.poll();
+        UnsynchronizedByteArrayOutputStream result = buffers.poll();
         if (result == null) {
-            result = new ByteArrayOutputStream();
+            result = new UnsynchronizedByteArrayOutputStream();
         }
         return new Wrapper(this, result);
     }
 
-    private void release(ByteArrayOutputStream value) {
+    private void release(UnsynchronizedByteArrayOutputStream value) {
         if (value != null) {
             value.reset();
             buffers.offer(value);
diff --git a/core/src/main/java/org/apache/james/mime4j/util/ContentUtil.java b/core/src/main/java/org/apache/james/mime4j/util/ContentUtil.java
index 0e25ce28..d6d2d96e 100644
--- a/core/src/main/java/org/apache/james/mime4j/util/ContentUtil.java
+++ b/core/src/main/java/org/apache/james/mime4j/util/ContentUtil.java
@@ -31,7 +31,7 @@ import java.nio.ByteBuffer;
 import java.nio.CharBuffer;
 import java.nio.charset.Charset;
 
-import org.apache.commons.io.output.ByteArrayOutputStream;
+import org.apache.commons.io.output.UnsynchronizedByteArrayOutputStream;
 import org.apache.james.mime4j.Charsets;
 
 /**
@@ -106,7 +106,7 @@ public class ContentUtil {
         if (in == null) {
             throw new IllegalArgumentException("Input stream may not be null");
         }
-        ByteArrayOutputStream buf = new ByteArrayOutputStream();
+        UnsynchronizedByteArrayOutputStream buf = new UnsynchronizedByteArrayOutputStream();
         copy(in, buf);
         return buf.toByteArray();
     }
diff --git a/dom/src/main/java/org/apache/james/mime4j/message/BasicBodyFactory.java b/dom/src/main/java/org/apache/james/mime4j/message/BasicBodyFactory.java
index f3c74043..10df514b 100644
--- a/dom/src/main/java/org/apache/james/mime4j/message/BasicBodyFactory.java
+++ b/dom/src/main/java/org/apache/james/mime4j/message/BasicBodyFactory.java
@@ -29,7 +29,6 @@ import java.nio.charset.Charset;
 import java.nio.charset.IllegalCharsetNameException;
 import java.nio.charset.UnsupportedCharsetException;
 
-import org.apache.commons.io.output.ByteArrayOutputStream;
 import org.apache.james.mime4j.Charsets;
 import org.apache.james.mime4j.dom.BinaryBody;
 import org.apache.james.mime4j.dom.SingleBody;


---------------------------------------------------------------------
To unsubscribe, e-mail: server-dev-unsubscribe@james.apache.org
For additional commands, e-mail: server-dev-help@james.apache.org


[james-mime4j] 07/14: MIME4J-318 Fasten ContentDispositionFieldLenientImpl

Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-mime4j.git

commit e2ad9a2da0c4385aad2dcd82caf37ccb4b1f6124
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Tue Jun 21 11:06:21 2022 +0700

    MIME4J-318 Fasten ContentDispositionFieldLenientImpl
    
     - Prevent useless copies
     - Rely on Java 8 time API
    
     From 32 ns down to 9ns.
---
 .../james/mime4j/util/MimeParameterMapping.java    | 10 ++---
 .../field/ContentDispositionFieldLenientImpl.java  | 48 ++++++++--------------
 2 files changed, 19 insertions(+), 39 deletions(-)

diff --git a/core/src/main/java/org/apache/james/mime4j/util/MimeParameterMapping.java b/core/src/main/java/org/apache/james/mime4j/util/MimeParameterMapping.java
index 91d370ca..1e98ac4d 100644
--- a/core/src/main/java/org/apache/james/mime4j/util/MimeParameterMapping.java
+++ b/core/src/main/java/org/apache/james/mime4j/util/MimeParameterMapping.java
@@ -28,19 +28,15 @@ public class MimeParameterMapping {
     private final Map<String, String> parameters = new HashMap<>();
 
     public Map<String, String> getParameters() {
-        Map<String,String> result = new HashMap<>();
-        for (Map.Entry<String, String > entry : parameters.entrySet()) {
-            result.put(entry.getKey(), decodeParameterValue(entry.getValue()) );
-        }
-        return result;
+        return parameters;
     }
 
     public void addParameter(String name, String value) {
         String key = removeSectionFromName(name).toLowerCase();
         if (parameters.containsKey(key)) {
-            parameters.put(key, parameters.get(key) + value);
+            parameters.put(key, decodeParameterValue(parameters.get(key) + value));
         } else {
-            parameters.put(key, value);
+            parameters.put(key, decodeParameterValue(value));
         }
     }
 
diff --git a/dom/src/main/java/org/apache/james/mime4j/field/ContentDispositionFieldLenientImpl.java b/dom/src/main/java/org/apache/james/mime4j/field/ContentDispositionFieldLenientImpl.java
index 9d9f01e2..6abc127b 100644
--- a/dom/src/main/java/org/apache/james/mime4j/field/ContentDispositionFieldLenientImpl.java
+++ b/dom/src/main/java/org/apache/james/mime4j/field/ContentDispositionFieldLenientImpl.java
@@ -19,17 +19,17 @@
 
 package org.apache.james.mime4j.field;
 
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
+import java.text.ParsePosition;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.time.format.ResolverStyle;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Date;
 import java.util.HashMap;
-import java.util.List;
 import java.util.Locale;
 import java.util.Map;
-import java.util.TimeZone;
 
 import org.apache.james.mime4j.codec.DecodeMonitor;
 import org.apache.james.mime4j.dom.FieldParser;
@@ -46,9 +46,7 @@ import org.apache.james.mime4j.util.MimeParameterMapping;
  */
 public class ContentDispositionFieldLenientImpl extends AbstractField implements ContentDispositionField {
 
-    private static final String DEFAULT_DATE_FORMAT = "EEE, dd MMM yyyy hh:mm:ss ZZZZ";
-
-    private final List<String> datePatterns;
+    private static final DateTimeFormatter DEFAULT_DATE_FORMAT = DateTimeFieldLenientImpl.RFC_5322;
 
     private boolean parsed = false;
 
@@ -65,14 +63,8 @@ public class ContentDispositionFieldLenientImpl extends AbstractField implements
     private Date readDate;
 
     ContentDispositionFieldLenientImpl(final Field rawField,
-            final Collection<String> dateParsers, final DecodeMonitor monitor) {
+                                       final DecodeMonitor monitor) {
         super(rawField, monitor);
-        this.datePatterns = new ArrayList<String>();
-        if (dateParsers != null) {
-            this.datePatterns.addAll(dateParsers);
-        } else {
-            this.datePatterns.add(DEFAULT_DATE_FORMAT);
-        }
     }
 
     public String getDispositionType() {
@@ -86,7 +78,7 @@ public class ContentDispositionFieldLenientImpl extends AbstractField implements
         if (!parsed) {
             parse();
         }
-        return parameters.get(name.toLowerCase());
+        return parameters.get(name);
     }
 
     public Map<String, String> getParameters() {
@@ -168,14 +160,11 @@ public class ContentDispositionFieldLenientImpl extends AbstractField implements
         } else {
             dispositionType = null;
         }
-        parameters.clear();
         MimeParameterMapping mapping = new MimeParameterMapping();
         for (NameValuePair pair : body.getParams()) {
             mapping.addParameter(pair.getName(), pair.getValue());
         }
-        for (Map.Entry<String, String> entry : mapping.getParameters().entrySet()) {
-            parameters.put(entry.getKey(), entry.getValue());
-        }
+        parameters.putAll(mapping.getParameters());
     }
 
     private Date parseDate(final String paramName) {
@@ -183,18 +172,13 @@ public class ContentDispositionFieldLenientImpl extends AbstractField implements
         if (value == null) {
             return null;
         }
-        for (String datePattern: datePatterns) {
-            try {
-                SimpleDateFormat parser = new SimpleDateFormat(datePattern, Locale.US);
-                parser.setTimeZone(TimeZone.getTimeZone("GMT"));
-                parser.setLenient(true);
-                return parser.parse(value);
-            } catch (ParseException ignore) {
-            }
-        }
-        if (monitor.isListening()) {
-            monitor.warn(paramName + " parameter is invalid: " + value,
+        try {
+            return Date.from(Instant.from(DEFAULT_DATE_FORMAT.parse(value, new ParsePosition(0))));
+        } catch (Exception ignore) {
+            if (monitor.isListening()) {
+                monitor.warn(paramName + " parameter is invalid: " + value,
                     paramName + " parameter is ignored");
+            }
         }
         return null;
     }
@@ -202,7 +186,7 @@ public class ContentDispositionFieldLenientImpl extends AbstractField implements
     public static final FieldParser<ContentDispositionField> PARSER = new FieldParser<ContentDispositionField>() {
 
         public ContentDispositionField parse(final Field rawField, final DecodeMonitor monitor) {
-            return new ContentDispositionFieldLenientImpl(rawField, null, monitor);
+            return new ContentDispositionFieldLenientImpl(rawField, monitor);
         }
 
     };


---------------------------------------------------------------------
To unsubscribe, e-mail: server-dev-unsubscribe@james.apache.org
For additional commands, e-mail: server-dev-help@james.apache.org


[james-mime4j] 02/14: MIME4J-318 BufferedLineReaderInputStream: shift table can be reused

Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-mime4j.git

commit e88d55b60a34b73a7eefc478a310d4616f01e350
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Mon Jun 20 11:57:09 2022 +0700

    MIME4J-318 BufferedLineReaderInputStream: shift table can be reused
---
 .../org/apache/james/mime4j/io/BufferedLineReaderInputStream.java     | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/core/src/main/java/org/apache/james/mime4j/io/BufferedLineReaderInputStream.java b/core/src/main/java/org/apache/james/mime4j/io/BufferedLineReaderInputStream.java
index 887c6262..64d4122b 100644
--- a/core/src/main/java/org/apache/james/mime4j/io/BufferedLineReaderInputStream.java
+++ b/core/src/main/java/org/apache/james/mime4j/io/BufferedLineReaderInputStream.java
@@ -41,6 +41,7 @@ public class BufferedLineReaderInputStream extends LineReaderInputStream {
     private byte[] buffer;
     private int bufpos;
     private int buflen;
+    private int[] shiftTable;
 
     private final int maxLineLen;
 
@@ -60,6 +61,7 @@ public class BufferedLineReaderInputStream extends LineReaderInputStream {
         this.buflen = 0;
         this.maxLineLen = maxLineLen;
         this.truncated = false;
+        this.shiftTable = new int[256];
     }
 
     public BufferedLineReaderInputStream(
@@ -244,7 +246,7 @@ public class BufferedLineReaderInputStream extends LineReaderInputStream {
             return -1;
         }
 
-        int[] shiftTable = new int[256];
+
         for (int i = 0; i < shiftTable.length; i++) {
             shiftTable[i] = pattern.length + 1;
         }


---------------------------------------------------------------------
To unsubscribe, e-mail: server-dev-unsubscribe@james.apache.org
For additional commands, e-mail: server-dev-help@james.apache.org


[james-mime4j] 08/14: MIME4J-318 DefaultBodyDescriptorBuilder should avoid copying all headers

Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-mime4j.git

commit 2f53da3849ae04866c411e431ace3835df80765e
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Tue Jun 21 11:46:55 2022 +0700

    MIME4J-318 DefaultBodyDescriptorBuilder should avoid copying all headers
    
    It only need to copy headers describing body content.
    
      JMHLongMultipartReadBench.benchmark4                               avgt   10     41.344 ±   1.121   us/op
      JMHLongMultipartReadBench.benchmark4:·gc.alloc.rate                avgt   10    609.846 ±  16.101  MB/sec
      JMHLongMultipartReadBench.benchmark4                               avgt   10     39.936 ±   1.612   us/op
      JMHLongMultipartReadBench.benchmark4:·gc.alloc.rate                avgt   10    611.421 ±  23.750  MB/sec
---
 .../org/apache/james/mime4j/dom/field/ContentDescriptionField.java    | 4 ++++
 .../org/apache/james/mime4j/dom/field/ContentDispositionField.java    | 4 ++++
 .../main/java/org/apache/james/mime4j/dom/field/ContentIdField.java   | 4 ++++
 .../java/org/apache/james/mime4j/dom/field/ContentLanguageField.java  | 4 ++++
 .../java/org/apache/james/mime4j/dom/field/ContentLengthField.java    | 4 ++++
 .../java/org/apache/james/mime4j/dom/field/ContentLocationField.java  | 4 ++++
 .../main/java/org/apache/james/mime4j/dom/field/ContentMD5Field.java  | 4 ++++
 .../apache/james/mime4j/dom/field/ContentTransferEncodingField.java   | 4 ++++
 .../main/java/org/apache/james/mime4j/dom/field/ContentTypeField.java | 4 ++++
 .../main/java/org/apache/james/mime4j/dom/field/MimeVersionField.java | 4 ++++
 dom/src/main/java/org/apache/james/mime4j/dom/field/ParsedField.java  | 4 ++++
 .../org/apache/james/mime4j/message/DefaultBodyDescriptorBuilder.java | 2 +-
 12 files changed, 45 insertions(+), 1 deletion(-)

diff --git a/dom/src/main/java/org/apache/james/mime4j/dom/field/ContentDescriptionField.java b/dom/src/main/java/org/apache/james/mime4j/dom/field/ContentDescriptionField.java
index 10667501..f5dac1b3 100644
--- a/dom/src/main/java/org/apache/james/mime4j/dom/field/ContentDescriptionField.java
+++ b/dom/src/main/java/org/apache/james/mime4j/dom/field/ContentDescriptionField.java
@@ -21,6 +21,10 @@ package org.apache.james.mime4j.dom.field;
 
 public interface ContentDescriptionField extends ParsedField {
 
+    default boolean bodyDescriptionField() {
+        return true;
+    }
+
     /**
      * Gets the content description defined in this field.
      *
diff --git a/dom/src/main/java/org/apache/james/mime4j/dom/field/ContentDispositionField.java b/dom/src/main/java/org/apache/james/mime4j/dom/field/ContentDispositionField.java
index 3a0699cc..4d960f14 100644
--- a/dom/src/main/java/org/apache/james/mime4j/dom/field/ContentDispositionField.java
+++ b/dom/src/main/java/org/apache/james/mime4j/dom/field/ContentDispositionField.java
@@ -24,6 +24,10 @@ import java.util.Map;
 
 public interface ContentDispositionField extends ParsedField {
 
+    default boolean bodyDescriptionField() {
+        return true;
+    }
+
     /** The <code>inline</code> disposition type. */
     String DISPOSITION_TYPE_INLINE = "inline";
     /** The <code>attachment</code> disposition type. */
diff --git a/dom/src/main/java/org/apache/james/mime4j/dom/field/ContentIdField.java b/dom/src/main/java/org/apache/james/mime4j/dom/field/ContentIdField.java
index 4df74309..d86d29c9 100644
--- a/dom/src/main/java/org/apache/james/mime4j/dom/field/ContentIdField.java
+++ b/dom/src/main/java/org/apache/james/mime4j/dom/field/ContentIdField.java
@@ -21,6 +21,10 @@ package org.apache.james.mime4j.dom.field;
 
 public interface ContentIdField extends ParsedField {
 
+    default boolean bodyDescriptionField() {
+        return true;
+    }
+
     /**
      * Gets the content ID defined in this field.
      *
diff --git a/dom/src/main/java/org/apache/james/mime4j/dom/field/ContentLanguageField.java b/dom/src/main/java/org/apache/james/mime4j/dom/field/ContentLanguageField.java
index 6270a088..38ca1371 100644
--- a/dom/src/main/java/org/apache/james/mime4j/dom/field/ContentLanguageField.java
+++ b/dom/src/main/java/org/apache/james/mime4j/dom/field/ContentLanguageField.java
@@ -23,6 +23,10 @@ import java.util.List;
 
 public interface ContentLanguageField extends ParsedField {
 
+    default boolean bodyDescriptionField() {
+        return true;
+    }
+
     /**
      * Gets the content language(s) defined in this field.
      *
diff --git a/dom/src/main/java/org/apache/james/mime4j/dom/field/ContentLengthField.java b/dom/src/main/java/org/apache/james/mime4j/dom/field/ContentLengthField.java
index 906566cb..8b4ff0d2 100644
--- a/dom/src/main/java/org/apache/james/mime4j/dom/field/ContentLengthField.java
+++ b/dom/src/main/java/org/apache/james/mime4j/dom/field/ContentLengthField.java
@@ -21,6 +21,10 @@ package org.apache.james.mime4j.dom.field;
 
 public interface ContentLengthField extends ParsedField {
 
+    default boolean bodyDescriptionField() {
+        return true;
+    }
+
     /**
      * Gets the content length value defined in this field.
      *
diff --git a/dom/src/main/java/org/apache/james/mime4j/dom/field/ContentLocationField.java b/dom/src/main/java/org/apache/james/mime4j/dom/field/ContentLocationField.java
index fd22088d..352134d8 100644
--- a/dom/src/main/java/org/apache/james/mime4j/dom/field/ContentLocationField.java
+++ b/dom/src/main/java/org/apache/james/mime4j/dom/field/ContentLocationField.java
@@ -21,6 +21,10 @@ package org.apache.james.mime4j.dom.field;
 
 public interface ContentLocationField extends ParsedField {
 
+    default boolean bodyDescriptionField() {
+        return true;
+    }
+
     /**
      * Gets the content location defined in this field.
      *
diff --git a/dom/src/main/java/org/apache/james/mime4j/dom/field/ContentMD5Field.java b/dom/src/main/java/org/apache/james/mime4j/dom/field/ContentMD5Field.java
index 1a445638..607eec0c 100644
--- a/dom/src/main/java/org/apache/james/mime4j/dom/field/ContentMD5Field.java
+++ b/dom/src/main/java/org/apache/james/mime4j/dom/field/ContentMD5Field.java
@@ -21,6 +21,10 @@ package org.apache.james.mime4j.dom.field;
 
 public interface ContentMD5Field extends ParsedField {
 
+    default boolean bodyDescriptionField() {
+        return true;
+    }
+
     /**
      * Gets the content MD5 raw value defined in this field.
      *
diff --git a/dom/src/main/java/org/apache/james/mime4j/dom/field/ContentTransferEncodingField.java b/dom/src/main/java/org/apache/james/mime4j/dom/field/ContentTransferEncodingField.java
index b0a24a8d..3a6b942c 100644
--- a/dom/src/main/java/org/apache/james/mime4j/dom/field/ContentTransferEncodingField.java
+++ b/dom/src/main/java/org/apache/james/mime4j/dom/field/ContentTransferEncodingField.java
@@ -21,6 +21,10 @@ package org.apache.james.mime4j.dom.field;
 
 public interface ContentTransferEncodingField extends ParsedField {
 
+    default boolean bodyDescriptionField() {
+        return true;
+    }
+
     /**
      * Gets the encoding defined in this field.
      *
diff --git a/dom/src/main/java/org/apache/james/mime4j/dom/field/ContentTypeField.java b/dom/src/main/java/org/apache/james/mime4j/dom/field/ContentTypeField.java
index 2a5670f5..6b8cad88 100644
--- a/dom/src/main/java/org/apache/james/mime4j/dom/field/ContentTypeField.java
+++ b/dom/src/main/java/org/apache/james/mime4j/dom/field/ContentTypeField.java
@@ -23,6 +23,10 @@ import java.util.Map;
 
 public interface ContentTypeField extends ParsedField {
 
+    default boolean bodyDescriptionField() {
+        return true;
+    }
+
     /** The prefix of all <code>multipart</code> MIME types. */
     String TYPE_MULTIPART_PREFIX = "multipart/";
     /** The <code>multipart/digest</code> MIME type. */
diff --git a/dom/src/main/java/org/apache/james/mime4j/dom/field/MimeVersionField.java b/dom/src/main/java/org/apache/james/mime4j/dom/field/MimeVersionField.java
index 48b80590..ad891fdc 100644
--- a/dom/src/main/java/org/apache/james/mime4j/dom/field/MimeVersionField.java
+++ b/dom/src/main/java/org/apache/james/mime4j/dom/field/MimeVersionField.java
@@ -21,6 +21,10 @@ package org.apache.james.mime4j.dom.field;
 
 public interface MimeVersionField extends ParsedField {
 
+    default boolean bodyDescriptionField() {
+        return true;
+    }
+
     int getMinorVersion();
 
     int getMajorVersion();
diff --git a/dom/src/main/java/org/apache/james/mime4j/dom/field/ParsedField.java b/dom/src/main/java/org/apache/james/mime4j/dom/field/ParsedField.java
index 1e58101f..40410901 100644
--- a/dom/src/main/java/org/apache/james/mime4j/dom/field/ParsedField.java
+++ b/dom/src/main/java/org/apache/james/mime4j/dom/field/ParsedField.java
@@ -26,6 +26,10 @@ import org.apache.james.mime4j.stream.Field;
  */
 public interface ParsedField extends Field {
 
+    default boolean bodyDescriptionField() {
+        return false;
+    }
+
     /**
      * Returns <code>true</code> if this field is valid, i.e. no errors were
      * encountered while parsing the field value.
diff --git a/dom/src/main/java/org/apache/james/mime4j/message/DefaultBodyDescriptorBuilder.java b/dom/src/main/java/org/apache/james/mime4j/message/DefaultBodyDescriptorBuilder.java
index de8d5a21..f44cea99 100644
--- a/dom/src/main/java/org/apache/james/mime4j/message/DefaultBodyDescriptorBuilder.java
+++ b/dom/src/main/java/org/apache/james/mime4j/message/DefaultBodyDescriptorBuilder.java
@@ -89,7 +89,7 @@ public class DefaultBodyDescriptorBuilder implements BodyDescriptorBuilder {
     public Field addField(final RawField rawfield) throws MimeException {
         ParsedField field = fieldParser.parse(rawfield, monitor);
         String name = field.getNameLowerCase();
-        if (!fields.containsKey(name)) {
+        if (field.bodyDescriptionField() && !fields.containsKey(name)) {
             fields.put(name, field);
         }
         return field;


---------------------------------------------------------------------
To unsubscribe, e-mail: server-dev-unsubscribe@james.apache.org
For additional commands, e-mail: server-dev-help@james.apache.org


[james-mime4j] 05/14: MIME4J-318 Write single body backed by ByteArrayOutputStream, add recycling

Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-mime4j.git

commit cd9f63be2b784028e8b5602f8436b660793ee6e4
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Mon Jun 20 23:24:27 2022 +0700

    MIME4J-318 Write single body backed by ByteArrayOutputStream, add recycling
    
    Reduce allocation rate by ~30% which allow to fasten Mime
    message parsing from 45 us to 39us, which represents a ~12%
    speedup.
    
    Recycling those output stream drops the GC churn rate below
    500MB/s while yielding a message parsing in 34us.
---
 .../james/mime4j/LongMultipartReadBench.java       |  4 +-
 .../mime4j/util/ByteArrayOutputStreamRecycler.java | 65 ++++++++++++++++++++++
 .../org/apache/james/mime4j/util/ContentUtil.java  | 19 ++++++-
 dom/pom.xml                                        |  1 -
 .../james/mime4j/message/BasicBodyFactory.java     | 17 +++---
 5 files changed, 94 insertions(+), 12 deletions(-)

diff --git a/benchmark/src/main/java/org/apache/james/mime4j/LongMultipartReadBench.java b/benchmark/src/main/java/org/apache/james/mime4j/LongMultipartReadBench.java
index 21f9348e..9dbbd525 100644
--- a/benchmark/src/main/java/org/apache/james/mime4j/LongMultipartReadBench.java
+++ b/benchmark/src/main/java/org/apache/james/mime4j/LongMultipartReadBench.java
@@ -25,6 +25,7 @@ import java.io.IOException;
 import java.io.InputStream;
 
 import org.apache.james.mime4j.dom.Header;
+import org.apache.james.mime4j.dom.Message;
 import org.apache.james.mime4j.dom.MessageBuilder;
 import org.apache.james.mime4j.message.DefaultMessageBuilder;
 import org.apache.james.mime4j.message.SimpleContentHandler;
@@ -174,7 +175,8 @@ public class LongMultipartReadBench {
             MessageBuilder builder = new DefaultMessageBuilder();
 
             for (int i = 0; i < repetitions; i++) {
-                builder.parseMessage(new ByteArrayInputStream(content));
+                Message message = builder.parseMessage(new ByteArrayInputStream(content));
+                message.dispose();
             }
         }
     }
diff --git a/core/src/main/java/org/apache/james/mime4j/util/ByteArrayOutputStreamRecycler.java b/core/src/main/java/org/apache/james/mime4j/util/ByteArrayOutputStreamRecycler.java
new file mode 100644
index 00000000..d99dda7e
--- /dev/null
+++ b/core/src/main/java/org/apache/james/mime4j/util/ByteArrayOutputStreamRecycler.java
@@ -0,0 +1,65 @@
+/****************************************************************
+ * 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.util;
+
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+import org.apache.commons.io.output.ByteArrayOutputStream;
+
+public class ByteArrayOutputStreamRecycler {
+    public static class Wrapper {
+        private final ByteArrayOutputStreamRecycler recycler;
+        private final ByteArrayOutputStream value;
+
+        public Wrapper(ByteArrayOutputStreamRecycler recycler, ByteArrayOutputStream value) {
+            this.recycler = recycler;
+            this.value = value;
+        }
+
+        public void release() {
+            recycler.release(value);
+        }
+
+        public ByteArrayOutputStream getValue() {
+            return value;
+        }
+    }
+
+    protected final ConcurrentLinkedQueue<ByteArrayOutputStream> buffers;
+
+    public ByteArrayOutputStreamRecycler() {
+        buffers = new ConcurrentLinkedQueue<>();
+    }
+
+    public Wrapper allocOutputStream() {
+        ByteArrayOutputStream result = buffers.poll();
+        if (result == null) {
+            result = new ByteArrayOutputStream();
+        }
+        return new Wrapper(this, result);
+    }
+
+    private void release(ByteArrayOutputStream value) {
+        if (value != null) {
+            value.reset();
+            buffers.offer(value);
+        }
+    }
+}
diff --git a/core/src/main/java/org/apache/james/mime4j/util/ContentUtil.java b/core/src/main/java/org/apache/james/mime4j/util/ContentUtil.java
index ce4d4121..0e25ce28 100644
--- a/core/src/main/java/org/apache/james/mime4j/util/ContentUtil.java
+++ b/core/src/main/java/org/apache/james/mime4j/util/ContentUtil.java
@@ -39,6 +39,7 @@ import org.apache.james.mime4j.Charsets;
  */
 public class ContentUtil {
     protected static final ThreadLocal<SoftReference<BufferRecycler>> _recyclerRef = new ThreadLocal<>();
+    protected static final ThreadLocal<SoftReference<ByteArrayOutputStreamRecycler>> _outputStreamRecyclerRef = new ThreadLocal<>();
 
     public static BufferRecycler getBufferRecycler() {
         SoftReference<BufferRecycler> ref = _recyclerRef.get();
@@ -52,6 +53,18 @@ public class ContentUtil {
         return br;
     }
 
+    public static ByteArrayOutputStreamRecycler getOutputStreamRecycler() {
+        SoftReference<ByteArrayOutputStreamRecycler> ref = _outputStreamRecyclerRef.get();
+        ByteArrayOutputStreamRecycler br = (ref == null) ? null : ref.get();
+
+        if (br == null) {
+            br = new ByteArrayOutputStreamRecycler();
+            ref = new SoftReference<>(br);
+            _outputStreamRecyclerRef.set(ref);
+        }
+        return br;
+    }
+
     private ContentUtil() {
     }
 
@@ -98,12 +111,12 @@ public class ContentUtil {
         return buf.toByteArray();
     }
 
-    public static ByteArrayOutputStream bufferEfficient(final InputStream in) throws IOException {
+    public static ByteArrayOutputStreamRecycler.Wrapper bufferEfficient(final InputStream in) throws IOException {
         if (in == null) {
             throw new IllegalArgumentException("Input stream may not be null");
         }
-        ByteArrayOutputStream buf = new ByteArrayOutputStream();
-        copy(in, buf);
+        ByteArrayOutputStreamRecycler.Wrapper buf = getOutputStreamRecycler().allocOutputStream();
+        copy(in, buf.getValue());
         return buf;
     }
 
diff --git a/dom/pom.xml b/dom/pom.xml
index 1dae3046..7e92c191 100644
--- a/dom/pom.xml
+++ b/dom/pom.xml
@@ -63,7 +63,6 @@
         <dependency>
             <groupId>commons-io</groupId>
             <artifactId>commons-io</artifactId>
-            <scope>test</scope>
         </dependency>
     </dependencies>
 
diff --git a/dom/src/main/java/org/apache/james/mime4j/message/BasicBodyFactory.java b/dom/src/main/java/org/apache/james/mime4j/message/BasicBodyFactory.java
index 9bb57ab6..f3c74043 100644
--- a/dom/src/main/java/org/apache/james/mime4j/message/BasicBodyFactory.java
+++ b/dom/src/main/java/org/apache/james/mime4j/message/BasicBodyFactory.java
@@ -35,6 +35,7 @@ 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.ByteArrayOutputStreamRecycler;
 import org.apache.james.mime4j.util.ContentUtil;
 
 /**
@@ -223,10 +224,10 @@ public class BasicBodyFactory implements BodyFactory {
 
     static class StringBody3 extends TextBody {
 
-        private final ByteArrayOutputStream content;
+        private final ByteArrayOutputStreamRecycler.Wrapper content;
         private final Charset charset;
 
-        StringBody3(final ByteArrayOutputStream content, final Charset charset) {
+        StringBody3(final ByteArrayOutputStreamRecycler.Wrapper content, final Charset charset) {
             super();
             this.content = content;
             this.charset = charset;
@@ -239,16 +240,17 @@ public class BasicBodyFactory implements BodyFactory {
 
         @Override
         public Reader getReader() throws IOException {
-            return new InputStreamReader(this.content.toInputStream(), this.charset);
+            return new InputStreamReader(this.content.getValue().toInputStream(), this.charset);
         }
 
         @Override
         public InputStream getInputStream() throws IOException {
-            return this.content.toInputStream();
+            return this.content.getValue().toInputStream();
         }
 
         @Override
         public void dispose() {
+            this.content.release();
         }
 
         @Override
@@ -285,20 +287,21 @@ public class BasicBodyFactory implements BodyFactory {
 
     static class BinaryBody3 extends BinaryBody {
 
-        private final ByteArrayOutputStream content;
+        private final ByteArrayOutputStreamRecycler.Wrapper content;
 
-        BinaryBody3(ByteArrayOutputStream content) {
+        BinaryBody3(ByteArrayOutputStreamRecycler.Wrapper content) {
             super();
             this.content = content;
         }
 
         @Override
         public InputStream getInputStream() throws IOException {
-            return content.toInputStream();
+            return content.getValue().toInputStream();
         }
 
         @Override
         public void dispose() {
+            content.release();
         }
 
         @Override


---------------------------------------------------------------------
To unsubscribe, e-mail: server-dev-unsubscribe@james.apache.org
For additional commands, e-mail: server-dev-help@james.apache.org


[james-mime4j] 04/14: MIME4J-318 Write single body backed by ByteArrayOutputStream

Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-mime4j.git

commit ad155f9797038ba91a6fe79ae7cdc426899da6dc
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Mon Jun 20 22:55:54 2022 +0700

    MIME4J-318 Write single body backed by ByteArrayOutputStream
    
    Reduce allocation rate by ~30% which allow to fasten Mime
    message parsing from 45 us to 39us, which represents a ~12%
    speedup.
---
 .../org/apache/james/mime4j/util/ContentUtil.java  |  9 +++
 .../james/mime4j/message/BasicBodyFactory.java     | 67 +++++++++++++++++++++-
 2 files changed, 74 insertions(+), 2 deletions(-)

diff --git a/core/src/main/java/org/apache/james/mime4j/util/ContentUtil.java b/core/src/main/java/org/apache/james/mime4j/util/ContentUtil.java
index a6c29696..ce4d4121 100644
--- a/core/src/main/java/org/apache/james/mime4j/util/ContentUtil.java
+++ b/core/src/main/java/org/apache/james/mime4j/util/ContentUtil.java
@@ -98,6 +98,15 @@ public class ContentUtil {
         return buf.toByteArray();
     }
 
+    public static ByteArrayOutputStream bufferEfficient(final InputStream in) throws IOException {
+        if (in == null) {
+            throw new IllegalArgumentException("Input stream may not be null");
+        }
+        ByteArrayOutputStream buf = new ByteArrayOutputStream();
+        copy(in, buf);
+        return buf;
+    }
+
     public static String buffer(final Reader in) throws IOException {
         if (in == null) {
             throw new IllegalArgumentException("Reader may not be null");
diff --git a/dom/src/main/java/org/apache/james/mime4j/message/BasicBodyFactory.java b/dom/src/main/java/org/apache/james/mime4j/message/BasicBodyFactory.java
index d4d0571a..9bb57ab6 100644
--- a/dom/src/main/java/org/apache/james/mime4j/message/BasicBodyFactory.java
+++ b/dom/src/main/java/org/apache/james/mime4j/message/BasicBodyFactory.java
@@ -29,6 +29,7 @@ import java.nio.charset.Charset;
 import java.nio.charset.IllegalCharsetNameException;
 import java.nio.charset.UnsupportedCharsetException;
 
+import org.apache.commons.io.output.ByteArrayOutputStream;
 import org.apache.james.mime4j.Charsets;
 import org.apache.james.mime4j.dom.BinaryBody;
 import org.apache.james.mime4j.dom.SingleBody;
@@ -116,7 +117,7 @@ public class BasicBodyFactory implements BodyFactory {
         if (content == null) {
             throw new IllegalArgumentException("Input stream may not be null");
         }
-        return new StringBody2(ContentUtil.buffer(content), resolveCharset(mimeCharset));
+        return new StringBody3(ContentUtil.bufferEfficient(content), resolveCharset(mimeCharset));
     }
 
     public TextBody textBody(final String text, final Charset charset) {
@@ -138,7 +139,7 @@ public class BasicBodyFactory implements BodyFactory {
     }
 
     public BinaryBody binaryBody(final InputStream is) throws IOException {
-        return new BinaryBody1(ContentUtil.buffer(is));
+        return new BinaryBody3(ContentUtil.bufferEfficient(is));
     }
 
     public BinaryBody binaryBody(final byte[] buf) {
@@ -220,6 +221,43 @@ public class BasicBodyFactory implements BodyFactory {
 
     }
 
+    static class StringBody3 extends TextBody {
+
+        private final ByteArrayOutputStream content;
+        private final Charset charset;
+
+        StringBody3(final ByteArrayOutputStream content, final Charset charset) {
+            super();
+            this.content = content;
+            this.charset = charset;
+        }
+
+        @Override
+        public String getMimeCharset() {
+            return this.charset != null ? this.charset.name() : null;
+        }
+
+        @Override
+        public Reader getReader() throws IOException {
+            return new InputStreamReader(this.content.toInputStream(), this.charset);
+        }
+
+        @Override
+        public InputStream getInputStream() throws IOException {
+            return this.content.toInputStream();
+        }
+
+        @Override
+        public void dispose() {
+        }
+
+        @Override
+        public SingleBody copy() {
+            return new StringBody3(this.content, this.charset);
+        }
+
+    }
+
     static class BinaryBody1 extends BinaryBody {
 
         private final byte[] content;
@@ -245,6 +283,31 @@ public class BasicBodyFactory implements BodyFactory {
 
     }
 
+    static class BinaryBody3 extends BinaryBody {
+
+        private final ByteArrayOutputStream content;
+
+        BinaryBody3(ByteArrayOutputStream content) {
+            super();
+            this.content = content;
+        }
+
+        @Override
+        public InputStream getInputStream() throws IOException {
+            return content.toInputStream();
+        }
+
+        @Override
+        public void dispose() {
+        }
+
+        @Override
+        public SingleBody copy() {
+            return new BinaryBody3(content);
+        }
+
+    }
+
     static class BinaryBody2 extends BinaryBody {
 
         private final String content;


---------------------------------------------------------------------
To unsubscribe, e-mail: server-dev-unsubscribe@james.apache.org
For additional commands, e-mail: server-dev-help@james.apache.org


[james-mime4j] 14/14: MIME4J-318 Allow disabling buffer recycling

Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-mime4j.git

commit 4216ca27396b3516ad874d89a60868c5032b5068
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Thu Aug 25 08:19:57 2022 +0700

    MIME4J-318 Allow disabling buffer recycling
---
 .../org/apache/james/mime4j/util/BufferRecycler.java    | 17 +++++++++++------
 .../mime4j/util/ByteArrayOutputStreamRecycler.java      |  2 +-
 2 files changed, 12 insertions(+), 7 deletions(-)

diff --git a/core/src/main/java/org/apache/james/mime4j/util/BufferRecycler.java b/core/src/main/java/org/apache/james/mime4j/util/BufferRecycler.java
index 5fa264b2..c89c66c4 100644
--- a/core/src/main/java/org/apache/james/mime4j/util/BufferRecycler.java
+++ b/core/src/main/java/org/apache/james/mime4j/util/BufferRecycler.java
@@ -19,11 +19,8 @@
 
 package org.apache.james.mime4j.util;
 
-import java.lang.ref.SoftReference;
 import java.util.ArrayList;
-import java.util.Random;
-import java.util.concurrent.ConcurrentLinkedQueue;
-import java.util.concurrent.atomic.AtomicReferenceArray;
+import java.util.Optional;
 
 /**
  * This is a small utility class, whose main functionality is to allow
@@ -37,6 +34,10 @@ import java.util.concurrent.atomic.AtomicReferenceArray;
  * to not rely on {@code ThreadLocal} access.
  */
 public class BufferRecycler {
+    static final boolean ENABLED = Optional.ofNullable(System.getProperty("james.mime4j.buffer.recycling.enabled"))
+        .map(Boolean::parseBoolean)
+        .orElse(true);
+
     protected final ArrayList<byte[]>[] _byteBuffers;
     protected final ArrayList<char[]>[] _charBuffers;
     protected final ArrayList<int[]> _intBuffers;
@@ -122,7 +123,9 @@ public class BufferRecycler {
         if (buffer == null) {
             return;
         }
-        _intBuffers.add(buffer);
+        if (ENABLED) {
+            _intBuffers.add(buffer);
+        }
     }
     
     public final char[] allocCharBuffer(int ix) {
@@ -146,7 +149,9 @@ public class BufferRecycler {
     }
 
     public void releaseCharBuffer(int ix, char[] buffer) {
-        _charBuffers[ix].add(buffer);
+        if (ENABLED) {
+            _charBuffers[ix].add(buffer);
+        }
     }
 
     protected byte[] balloc(int size) {
diff --git a/core/src/main/java/org/apache/james/mime4j/util/ByteArrayOutputStreamRecycler.java b/core/src/main/java/org/apache/james/mime4j/util/ByteArrayOutputStreamRecycler.java
index a77d0c50..05985ff5 100644
--- a/core/src/main/java/org/apache/james/mime4j/util/ByteArrayOutputStreamRecycler.java
+++ b/core/src/main/java/org/apache/james/mime4j/util/ByteArrayOutputStreamRecycler.java
@@ -58,7 +58,7 @@ public class ByteArrayOutputStreamRecycler {
     }
 
     private void release(UnsynchronizedByteArrayOutputStream value) {
-        if (value != null) {
+        if (value != null && BufferRecycler.ENABLED) {
             value.reset();
             buffers.offer(value);
         }


---------------------------------------------------------------------
To unsubscribe, e-mail: server-dev-unsubscribe@james.apache.org
For additional commands, e-mail: server-dev-help@james.apache.org


[james-mime4j] 10/14: MIME4J-318 Fields: rely on lenient parsers

Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-mime4j.git

commit 639e74d1dced1efadefc9b44f1951c2557c2e897
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Mon Jun 27 13:38:47 2022 +0700

    MIME4J-318 Fields: rely on lenient parsers
    
    JMH proves this to be an order of magnitude faster than default implementation.
    
    This provides a 25% boost on message writing. (benchmark7)
---
 .../java/org/apache/james/mime4j/field/Fields.java | 24 +++++++++++-----------
 .../org/apache/james/mime4j/field/FieldsTest.java  |  1 -
 2 files changed, 12 insertions(+), 13 deletions(-)

diff --git a/dom/src/main/java/org/apache/james/mime4j/field/Fields.java b/dom/src/main/java/org/apache/james/mime4j/field/Fields.java
index 72722a1d..c24b0c1b 100644
--- a/dom/src/main/java/org/apache/james/mime4j/field/Fields.java
+++ b/dom/src/main/java/org/apache/james/mime4j/field/Fields.java
@@ -75,7 +75,7 @@ public class Fields {
      * @return the newly created <i>Content-Type</i> field.
      */
     public static ContentTypeField contentType(String contentType) {
-        return parse(ContentTypeFieldImpl.PARSER, FieldName.CONTENT_TYPE,
+        return parse(ContentTypeFieldLenientImpl.PARSER, FieldName.CONTENT_TYPE,
                 contentType);
     }
 
@@ -97,7 +97,7 @@ public class Fields {
             throw new IllegalArgumentException();
 
         if (parameters == null || parameters.isEmpty()) {
-            return parse(ContentTypeFieldImpl.PARSER, FieldName.CONTENT_TYPE,
+            return parse(ContentTypeFieldLenientImpl.PARSER, FieldName.CONTENT_TYPE,
                     mimeType);
         } else {
             StringBuilder sb = new StringBuilder(mimeType);
@@ -128,7 +128,7 @@ public class Fields {
             throw new IllegalArgumentException(mimeType + " is not a valid MIME type");
         }
         if (parameters == null) {
-            return parse(ContentTypeFieldImpl.PARSER, FieldName.CONTENT_TYPE,
+            return parse(ContentTypeFieldLenientImpl.PARSER, FieldName.CONTENT_TYPE,
                     mimeType);
         } else {
             StringBuilder sb = new StringBuilder(mimeType);
@@ -319,22 +319,22 @@ public class Fields {
             Date creationDate, Date modificationDate, Date readDate) {
         Map<String, String> parameters = new HashMap<String, String>();
         if (filename != null) {
-            parameters.put(ContentDispositionFieldImpl.PARAM_FILENAME, filename);
+            parameters.put(ContentDispositionFieldLenientImpl.PARAM_FILENAME, filename);
         }
         if (size >= 0) {
-            parameters.put(ContentDispositionFieldImpl.PARAM_SIZE, Long
+            parameters.put(ContentDispositionFieldLenientImpl.PARAM_SIZE, Long
                     .toString(size));
         }
         if (creationDate != null) {
-            parameters.put(ContentDispositionFieldImpl.PARAM_CREATION_DATE,
+            parameters.put(ContentDispositionFieldLenientImpl.PARAM_CREATION_DATE,
                     MimeUtil.formatDate(creationDate, null));
         }
         if (modificationDate != null) {
-            parameters.put(ContentDispositionFieldImpl.PARAM_MODIFICATION_DATE,
+            parameters.put(ContentDispositionFieldLenientImpl.PARAM_MODIFICATION_DATE,
                     MimeUtil.formatDate(modificationDate, null));
         }
         if (readDate != null) {
-            parameters.put(ContentDispositionFieldImpl.PARAM_READ_DATE, MimeUtil
+            parameters.put(ContentDispositionFieldLenientImpl.PARAM_READ_DATE, MimeUtil
                     .formatDate(readDate, null));
         }
         return contentDisposition(dispositionType, parameters);
@@ -664,24 +664,24 @@ public class Fields {
     private static DateTimeField date0(String fieldName, Date date,
             TimeZone zone) {
         final String formattedDate = MimeUtil.formatDate(date, zone);
-        return parse(DateTimeFieldImpl.PARSER, fieldName, formattedDate);
+        return parse(DateTimeFieldLenientImpl.PARSER, fieldName, formattedDate);
     }
 
     private static MailboxField mailbox0(String fieldName, Mailbox mailbox) {
         String fieldValue = encodeAddresses(Collections.singleton(mailbox));
-        return parse(MailboxFieldImpl.PARSER, fieldName, fieldValue);
+        return parse(MailboxFieldLenientImpl.PARSER, fieldName, fieldValue);
     }
 
     private static MailboxListField mailboxList0(String fieldName,
             Iterable<Mailbox> mailboxes) {
         String fieldValue = encodeAddresses(mailboxes);
-        return parse(MailboxListFieldImpl.PARSER, fieldName, fieldValue);
+        return parse(MailboxListFieldLenientImpl.PARSER, fieldName, fieldValue);
     }
 
     private static AddressListField addressList0(String fieldName,
             Iterable<? extends Address> addresses) {
         String fieldValue = encodeAddresses(addresses);
-        return parse(AddressListFieldImpl.PARSER, fieldName, fieldValue);
+        return parse(AddressListFieldLenientImpl.PARSER, fieldName, fieldValue);
     }
 
     private static void checkValidFieldName(String fieldName) {
diff --git a/dom/src/test/java/org/apache/james/mime4j/field/FieldsTest.java b/dom/src/test/java/org/apache/james/mime4j/field/FieldsTest.java
index 60eac649..63364791 100644
--- a/dom/src/test/java/org/apache/james/mime4j/field/FieldsTest.java
+++ b/dom/src/test/java/org/apache/james/mime4j/field/FieldsTest.java
@@ -100,7 +100,6 @@ public class FieldsTest {
         ContentTypeField field = Fields.contentType("multipart/mixed; "
                 + "boundary=-=Part.0.37877968dd4f6595.11eccf0271c"
                 + ".2dce5678cbc933d5=-");
-        Assert.assertFalse(field.isValidField());
 
         Assert.assertEquals("multipart/mixed", field.getMimeType());
     }


---------------------------------------------------------------------
To unsubscribe, e-mail: server-dev-unsubscribe@james.apache.org
For additional commands, e-mail: server-dev-help@james.apache.org


[james-mime4j] 13/14: MIME4J-318 Improve writing bodies into an output stream

Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-mime4j.git

commit 3b3f09e3b45cb3a090855145dbfe315e11d409c0
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Wed Jun 29 08:35:44 2022 +0700

    MIME4J-318 Improve writing bodies into an output stream
---
 .../james/mime4j/message/BasicBodyFactory.java      | 21 +++++++++++++++++++++
 1 file changed, 21 insertions(+)

diff --git a/dom/src/main/java/org/apache/james/mime4j/message/BasicBodyFactory.java b/dom/src/main/java/org/apache/james/mime4j/message/BasicBodyFactory.java
index 225ec295..7470dc7b 100644
--- a/dom/src/main/java/org/apache/james/mime4j/message/BasicBodyFactory.java
+++ b/dom/src/main/java/org/apache/james/mime4j/message/BasicBodyFactory.java
@@ -22,6 +22,7 @@ package org.apache.james.mime4j.message;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
+import java.io.OutputStream;
 import java.io.Reader;
 import java.io.StringReader;
 import java.io.UnsupportedEncodingException;
@@ -220,6 +221,11 @@ public class BasicBodyFactory implements BodyFactory {
             return InputStreams.create(this.content);
         }
 
+        @Override
+        public void writeTo(OutputStream out) throws IOException {
+            out.write(content);
+        }
+
         @Override
         public void dispose() {
         }
@@ -262,6 +268,11 @@ public class BasicBodyFactory implements BodyFactory {
             return this.content.getValue().toInputStream();
         }
 
+        @Override
+        public void writeTo(OutputStream out) throws IOException {
+            content.getValue().writeTo(out);
+        }
+
         @Override
         public void dispose() {
             this.content.release();
@@ -288,6 +299,11 @@ public class BasicBodyFactory implements BodyFactory {
             return InputStreams.create(this.content);
         }
 
+        @Override
+        public void writeTo(OutputStream out) throws IOException {
+            out.write(content);
+        }
+
         @Override
         public void dispose() {
         }
@@ -313,6 +329,11 @@ public class BasicBodyFactory implements BodyFactory {
             return content.getValue().toInputStream();
         }
 
+        @Override
+        public void writeTo(OutputStream out) throws IOException {
+            content.getValue().writeTo(out);
+        }
+
         @Override
         public void dispose() {
             content.release();


---------------------------------------------------------------------
To unsubscribe, e-mail: server-dev-unsubscribe@james.apache.org
For additional commands, e-mail: server-dev-help@james.apache.org


[james-mime4j] 03/14: MIME4J-318 Buffer recycling for ContentUtil copies

Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-mime4j.git

commit 7f56f6501afac8ce2b26a70b0d4038b1096a241f
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Mon Jun 20 11:20:08 2022 +0700

    MIME4J-318 Buffer recycling for ContentUtil copies
    
    Parsing 100K messages locally drops from 4,9s to 4.6s...
---
 .../james/mime4j/codec/Base64InputStream.java      |  24 ++-
 .../org/apache/james/mime4j/codec/DecoderUtil.java |  22 ++-
 .../mime4j/codec/QuotedPrintableInputStream.java   |  34 +++-
 .../mime4j/io/BufferedLineReaderInputStream.java   |  40 ++++-
 .../james/mime4j/io/LineReaderInputStream.java     |   2 +
 .../mime4j/io/LineReaderInputStreamAdaptor.java    |   6 +
 .../james/mime4j/io/MimeBoundaryInputStream.java   |   6 +
 .../james/mime4j/parser/MimeStreamParser.java      |   1 +
 .../james/mime4j/stream/DefaultFieldBuilder.java   |  26 ++-
 .../apache/james/mime4j/stream/FieldBuilder.java   |   5 +-
 .../org/apache/james/mime4j/stream/MimeEntity.java |  46 +++--
 .../james/mime4j/stream/MimeTokenStream.java       |   8 +-
 .../apache/james/mime4j/util/BufferRecycler.java   | 159 ++++++++++++++++++
 .../org/apache/james/mime4j/util/ContentUtil.java  |  24 ++-
 .../james/mime4j/util/RecycledByteArrayBuffer.java | 187 +++++++++++++++++++++
 .../mime4j/stream/DefaultFieldBuilderTest.java     |   9 +-
 .../mime4j/message/DefaultMessageBuilder.java      |   1 +
 .../james/mime4j/utils/search/MessageMatcher.java  |   5 +-
 18 files changed, 560 insertions(+), 45 deletions(-)

diff --git a/core/src/main/java/org/apache/james/mime4j/codec/Base64InputStream.java b/core/src/main/java/org/apache/james/mime4j/codec/Base64InputStream.java
index 33b9e1d1..b4724316 100644
--- a/core/src/main/java/org/apache/james/mime4j/codec/Base64InputStream.java
+++ b/core/src/main/java/org/apache/james/mime4j/codec/Base64InputStream.java
@@ -21,13 +21,29 @@ package org.apache.james.mime4j.codec;
 
 import java.io.IOException;
 import java.io.InputStream;
+import java.lang.ref.SoftReference;
 
+import org.apache.james.mime4j.util.BufferRecycler;
 import org.apache.james.mime4j.util.ByteArrayBuffer;
+import org.apache.james.mime4j.util.RecycledByteArrayBuffer;
 
 /**
  * Performs Base-64 decoding on an underlying stream.
  */
 public class Base64InputStream extends InputStream {
+    protected static final ThreadLocal<SoftReference<BufferRecycler>> _recyclerRef = new ThreadLocal<>();
+
+    public static BufferRecycler getBufferRecycler() {
+        SoftReference<BufferRecycler> ref = _recyclerRef.get();
+        BufferRecycler br = (ref == null) ? null : ref.get();
+
+        if (br == null) {
+            br = new BufferRecycler();
+            ref = new SoftReference<>(br);
+            _recyclerRef.set(ref);
+        }
+        return br;
+    }
     private static final int ENCODED_BUFFER_SIZE = 1536;
 
     private static final int[] BASE64_DECODE = new int[256];
@@ -47,7 +63,7 @@ public class Base64InputStream extends InputStream {
 
     private final InputStream in;
     private final byte[] encoded;
-    private final ByteArrayBuffer decodedBuf;
+    private final RecycledByteArrayBuffer decodedBuf;
 
     private int position = 0; // current index into encoded buffer
     private int size = 0; // current size of encoded buffer
@@ -64,8 +80,8 @@ public class Base64InputStream extends InputStream {
     protected Base64InputStream(int bufsize, InputStream in, DecodeMonitor monitor) {
         if (in == null)
             throw new IllegalArgumentException();
-        this.encoded = new byte[bufsize];
-        this.decodedBuf = new ByteArrayBuffer(512);
+        this.encoded = getBufferRecycler().allocByteBuffer(1, bufsize);
+        this.decodedBuf = new RecycledByteArrayBuffer(getBufferRecycler(), 512);
         this.in = in;
         this.monitor = monitor;
     }
@@ -130,6 +146,8 @@ public class Base64InputStream extends InputStream {
             return;
 
         closed = true;
+        getBufferRecycler().releaseByteBuffer(1, encoded);
+        decodedBuf.release();
     }
 
     private int read0(final byte[] buffer, final int off, final int len) throws IOException {
diff --git a/core/src/main/java/org/apache/james/mime4j/codec/DecoderUtil.java b/core/src/main/java/org/apache/james/mime4j/codec/DecoderUtil.java
index 8d37f8b9..14a981dd 100644
--- a/core/src/main/java/org/apache/james/mime4j/codec/DecoderUtil.java
+++ b/core/src/main/java/org/apache/james/mime4j/codec/DecoderUtil.java
@@ -21,18 +21,34 @@ package org.apache.james.mime4j.codec;
 
 import java.io.IOException;
 import java.io.UnsupportedEncodingException;
+import java.lang.ref.SoftReference;
 import java.nio.charset.Charset;
 import java.util.Collections;
 import java.util.Map;
 
 import org.apache.james.mime4j.io.InputStreams;
+import org.apache.james.mime4j.util.BufferRecycler;
 import org.apache.james.mime4j.util.ByteArrayBuffer;
 import org.apache.james.mime4j.util.CharsetUtil;
+import org.apache.james.mime4j.util.RecycledByteArrayBuffer;
 
 /**
  * Static methods for decoding strings, byte arrays and encoded words.
  */
 public class DecoderUtil {
+    protected static final ThreadLocal<SoftReference<BufferRecycler>> _recyclerRef = new ThreadLocal<>();
+
+    public static BufferRecycler getBufferRecycler() {
+        SoftReference<BufferRecycler> ref = _recyclerRef.get();
+        BufferRecycler br = (ref == null) ? null : ref.get();
+
+        if (br == null) {
+            br = new BufferRecycler();
+            ref = new SoftReference<>(br);
+            _recyclerRef.set(ref);
+        }
+        return br;
+    }
 
     /**
      * Decodes a string containing quoted-printable encoded data.
@@ -44,8 +60,8 @@ public class DecoderUtil {
         try {
             QuotedPrintableInputStream is = new QuotedPrintableInputStream(
                     InputStreams.createAscii(s), monitor);
+            RecycledByteArrayBuffer buf = new RecycledByteArrayBuffer(getBufferRecycler(), s.length());
             try {
-                ByteArrayBuffer buf = new ByteArrayBuffer(s.length());
                 int b;
                 while ((b = is.read()) != -1) {
                     buf.append(b);
@@ -53,6 +69,7 @@ public class DecoderUtil {
                 return buf.toByteArray();
             } finally {
                 is.close();
+                buf.release();
             }
         } catch (IOException ex) {
             // This should never happen!
@@ -71,8 +88,8 @@ public class DecoderUtil {
         try {
             Base64InputStream is = new Base64InputStream(
                     InputStreams.createAscii(s), monitor);
+            RecycledByteArrayBuffer buf = new RecycledByteArrayBuffer(getBufferRecycler(), s.length());
             try {
-                ByteArrayBuffer buf = new ByteArrayBuffer(s.length());
                 int b;
                 while ((b = is.read()) != -1) {
                     buf.append(b);
@@ -80,6 +97,7 @@ public class DecoderUtil {
                 return buf.toByteArray();
             } finally {
                 is.close();
+                buf.release();
             }
         } catch (IOException ex) {
             // This should never happen!
diff --git a/core/src/main/java/org/apache/james/mime4j/codec/QuotedPrintableInputStream.java b/core/src/main/java/org/apache/james/mime4j/codec/QuotedPrintableInputStream.java
index 39428349..0f43ac52 100644
--- a/core/src/main/java/org/apache/james/mime4j/codec/QuotedPrintableInputStream.java
+++ b/core/src/main/java/org/apache/james/mime4j/codec/QuotedPrintableInputStream.java
@@ -21,13 +21,29 @@ package org.apache.james.mime4j.codec;
 
 import java.io.IOException;
 import java.io.InputStream;
+import java.lang.ref.SoftReference;
 
+import org.apache.james.mime4j.util.BufferRecycler;
 import org.apache.james.mime4j.util.ByteArrayBuffer;
+import org.apache.james.mime4j.util.RecycledByteArrayBuffer;
 
 /**
  * Performs Quoted-Printable decoding on an underlying stream.
  */
 public class QuotedPrintableInputStream extends InputStream {
+    protected static final ThreadLocal<SoftReference<BufferRecycler>> _recyclerRef = new ThreadLocal<>();
+
+    public static BufferRecycler getBufferRecycler() {
+        SoftReference<BufferRecycler> ref = _recyclerRef.get();
+        BufferRecycler br = (ref == null) ? null : ref.get();
+
+        if (br == null) {
+            br = new BufferRecycler();
+            ref = new SoftReference<>(br);
+            _recyclerRef.set(ref);
+        }
+        return br;
+    }
 
     private static final int DEFAULT_BUFFER_SIZE = 1024 * 2;
 
@@ -38,8 +54,8 @@ public class QuotedPrintableInputStream extends InputStream {
     private final byte[] singleByte = new byte[1];
 
     private final InputStream in;
-    private final ByteArrayBuffer decodedBuf;
-    private final ByteArrayBuffer blanks;
+    private final RecycledByteArrayBuffer decodedBuf;
+    private final RecycledByteArrayBuffer blanks;
 
     private final byte[] encoded;
     private int pos = 0; // current index into encoded buffer
@@ -57,9 +73,10 @@ public class QuotedPrintableInputStream extends InputStream {
     protected QuotedPrintableInputStream(final int bufsize, final InputStream in, DecodeMonitor monitor) {
         super();
         this.in = in;
-        this.encoded = new byte[bufsize];
-        this.decodedBuf = new ByteArrayBuffer(512);
-        this.blanks = new ByteArrayBuffer(512);
+        BufferRecycler recycler = getBufferRecycler();
+        this.encoded = recycler.allocByteBuffer(1, bufsize);
+        this.decodedBuf = new RecycledByteArrayBuffer(recycler, 512);
+        this.blanks = new RecycledByteArrayBuffer(recycler, 512);
         this.closed = false;
         this.monitor = monitor;
     }
@@ -79,12 +96,13 @@ public class QuotedPrintableInputStream extends InputStream {
     /**
      * Terminates Quoted-Printable coded content. This method does NOT close
      * the underlying input stream.
-     *
-     * @throws IOException on I/O errors.
      */
     @Override
-    public void close() throws IOException {
+    public void close() {
         closed = true;
+        getBufferRecycler().releaseByteBuffer(1, encoded);
+        decodedBuf.release();
+        blanks.release();
     }
 
     private int fillBuffer() throws IOException {
diff --git a/core/src/main/java/org/apache/james/mime4j/io/BufferedLineReaderInputStream.java b/core/src/main/java/org/apache/james/mime4j/io/BufferedLineReaderInputStream.java
index 64d4122b..4dea0b44 100644
--- a/core/src/main/java/org/apache/james/mime4j/io/BufferedLineReaderInputStream.java
+++ b/core/src/main/java/org/apache/james/mime4j/io/BufferedLineReaderInputStream.java
@@ -19,16 +19,32 @@
 
 package org.apache.james.mime4j.io;
 
+import org.apache.james.mime4j.util.BufferRecycler;
 import org.apache.james.mime4j.util.ByteArrayBuffer;
+import org.apache.james.mime4j.util.RecycledByteArrayBuffer;
 
 import java.io.IOException;
 import java.io.InputStream;
+import java.lang.ref.SoftReference;
 
 /**
  * Input buffer that can be used to search for patterns using Quick Search
  * algorithm in data read from an {@link InputStream}.
  */
 public class BufferedLineReaderInputStream extends LineReaderInputStream {
+    protected static final ThreadLocal<SoftReference<BufferRecycler>> _recyclerRef = new ThreadLocal<>();
+
+    public static BufferRecycler getBufferRecycler() {
+        SoftReference<BufferRecycler> ref = _recyclerRef.get();
+        BufferRecycler br = (ref == null) ? null : ref.get();
+
+        if (br == null) {
+            br = new BufferRecycler();
+            ref = new SoftReference<>(br);
+            _recyclerRef.set(ref);
+        }
+        return br;
+    }
 
     private boolean truncated;
 
@@ -56,12 +72,13 @@ public class BufferedLineReaderInputStream extends LineReaderInputStream {
         if (buffersize <= 0) {
             throw new IllegalArgumentException("Buffer size may not be negative or zero");
         }
-        this.buffer = new byte[buffersize];
+        BufferRecycler bufferRecycler = getBufferRecycler();
+        this.buffer = bufferRecycler.allocByteBuffer(0, buffersize);
         this.bufpos = 0;
         this.buflen = 0;
         this.maxLineLen = maxLineLen;
         this.truncated = false;
-        this.shiftTable = new int[256];
+        this.shiftTable = bufferRecycler.allocintBuffer(256);
     }
 
     public BufferedLineReaderInputStream(
@@ -132,6 +149,12 @@ public class BufferedLineReaderInputStream extends LineReaderInputStream {
         this.truncated = true;
     }
 
+    public void release() {
+        BufferRecycler bufferRecycler = getBufferRecycler();
+        bufferRecycler.releaseByteBuffer(0, buffer);
+        bufferRecycler.releaseIntBuffer(shiftTable);
+    }
+
     protected boolean readAllowed() {
         return !this.truncated;
     }
@@ -388,4 +411,17 @@ public class BufferedLineReaderInputStream extends LineReaderInputStream {
         return true;
     }
 
+    @Override
+    public boolean unread(RecycledByteArrayBuffer buf) {
+        if (tempBuffer) return false;
+        origBuffer = buffer;
+        origBuflen = buflen;
+        origBufpos = bufpos;
+        bufpos = 0;
+        buflen = buf.length();
+        buffer = buf.buffer();
+        tempBuffer = true;
+        return true;
+    }
+
 }
diff --git a/core/src/main/java/org/apache/james/mime4j/io/LineReaderInputStream.java b/core/src/main/java/org/apache/james/mime4j/io/LineReaderInputStream.java
index 22d00ee0..792eb38d 100644
--- a/core/src/main/java/org/apache/james/mime4j/io/LineReaderInputStream.java
+++ b/core/src/main/java/org/apache/james/mime4j/io/LineReaderInputStream.java
@@ -20,6 +20,7 @@
 package org.apache.james.mime4j.io;
 
 import org.apache.james.mime4j.util.ByteArrayBuffer;
+import org.apache.james.mime4j.util.RecycledByteArrayBuffer;
 
 import java.io.FilterInputStream;
 import java.io.IOException;
@@ -60,5 +61,6 @@ public abstract class LineReaderInputStream extends FilterInputStream {
      * @return true if the unread has been succesfull.
      */
     public abstract boolean unread(ByteArrayBuffer buf);
+    public abstract boolean unread(RecycledByteArrayBuffer buf);
 
 }
diff --git a/core/src/main/java/org/apache/james/mime4j/io/LineReaderInputStreamAdaptor.java b/core/src/main/java/org/apache/james/mime4j/io/LineReaderInputStreamAdaptor.java
index 36775846..387c99df 100644
--- a/core/src/main/java/org/apache/james/mime4j/io/LineReaderInputStreamAdaptor.java
+++ b/core/src/main/java/org/apache/james/mime4j/io/LineReaderInputStreamAdaptor.java
@@ -20,6 +20,7 @@
 package org.apache.james.mime4j.io;
 
 import org.apache.james.mime4j.util.ByteArrayBuffer;
+import org.apache.james.mime4j.util.RecycledByteArrayBuffer;
 
 import java.io.IOException;
 import java.io.InputStream;
@@ -123,6 +124,11 @@ public class LineReaderInputStreamAdaptor extends LineReaderInputStream {
         return bis != null && bis.unread(buf);
     }
 
+    @Override
+    public boolean unread(RecycledByteArrayBuffer buf) {
+        return bis != null && bis.unread(buf);
+    }
+
     @Override
     public long skip(long count) throws IOException {
         if (count <= 0) {
diff --git a/core/src/main/java/org/apache/james/mime4j/io/MimeBoundaryInputStream.java b/core/src/main/java/org/apache/james/mime4j/io/MimeBoundaryInputStream.java
index 88cdfc08..140f6b65 100644
--- a/core/src/main/java/org/apache/james/mime4j/io/MimeBoundaryInputStream.java
+++ b/core/src/main/java/org/apache/james/mime4j/io/MimeBoundaryInputStream.java
@@ -23,6 +23,7 @@ import org.apache.james.mime4j.MimeException;
 import org.apache.james.mime4j.MimeIOException;
 import org.apache.james.mime4j.util.ByteArrayBuffer;
 import org.apache.james.mime4j.util.CharsetUtil;
+import org.apache.james.mime4j.util.RecycledByteArrayBuffer;
 
 import java.io.IOException;
 
@@ -356,4 +357,9 @@ public class MimeBoundaryInputStream extends LineReaderInputStream {
     public boolean unread(ByteArrayBuffer buf) {
         return false;
     }
+
+    @Override
+    public boolean unread(RecycledByteArrayBuffer buf) {
+        return false;
+    }
 }
diff --git a/core/src/main/java/org/apache/james/mime4j/parser/MimeStreamParser.java b/core/src/main/java/org/apache/james/mime4j/parser/MimeStreamParser.java
index d72124c6..cc816f85 100644
--- a/core/src/main/java/org/apache/james/mime4j/parser/MimeStreamParser.java
+++ b/core/src/main/java/org/apache/james/mime4j/parser/MimeStreamParser.java
@@ -131,6 +131,7 @@ public class MimeStreamParser {
                         bodyContent = mimeTokenStream.getInputStream();
                     }
                     handler.body(desc, bodyContent);
+                    bodyContent.close();
                     break;
                 case T_END_BODYPART:
                     handler.endBodyPart();
diff --git a/core/src/main/java/org/apache/james/mime4j/stream/DefaultFieldBuilder.java b/core/src/main/java/org/apache/james/mime4j/stream/DefaultFieldBuilder.java
index 6c65613a..1cb67cca 100644
--- a/core/src/main/java/org/apache/james/mime4j/stream/DefaultFieldBuilder.java
+++ b/core/src/main/java/org/apache/james/mime4j/stream/DefaultFieldBuilder.java
@@ -19,17 +19,33 @@
 
 package org.apache.james.mime4j.stream;
 
+import java.lang.ref.SoftReference;
 import java.util.BitSet;
 
 import org.apache.james.mime4j.MimeException;
 import org.apache.james.mime4j.io.MaxHeaderLengthLimitException;
+import org.apache.james.mime4j.util.BufferRecycler;
 import org.apache.james.mime4j.util.ByteArrayBuffer;
+import org.apache.james.mime4j.util.RecycledByteArrayBuffer;
 
 /**
  * Default implementation of {@link FieldBuilder}.
  *
  */
 public class DefaultFieldBuilder implements FieldBuilder {
+    protected static final ThreadLocal<SoftReference<BufferRecycler>> _recyclerRef = new ThreadLocal<>();
+
+    public static BufferRecycler getBufferRecycler() {
+        SoftReference<BufferRecycler> ref = _recyclerRef.get();
+        BufferRecycler br = (ref == null) ? null : ref.get();
+
+        if (br == null) {
+            br = new BufferRecycler();
+            ref = new SoftReference<>(br);
+            _recyclerRef.set(ref);
+        }
+        return br;
+    }
 
     private static final BitSet FIELD_CHARS = new BitSet();
 
@@ -42,11 +58,11 @@ public class DefaultFieldBuilder implements FieldBuilder {
         }
     }
 
-    private final ByteArrayBuffer buf;
+    private final RecycledByteArrayBuffer buf;
     private final int maxlen;
 
     public DefaultFieldBuilder(int maxlen) {
-        this.buf = new ByteArrayBuffer(1024);
+        this.buf = new RecycledByteArrayBuffer(getBufferRecycler(), 4096);
         this.maxlen = maxlen;
     }
 
@@ -88,8 +104,12 @@ public class DefaultFieldBuilder implements FieldBuilder {
         return field;
     }
 
-    public ByteArrayBuffer getRaw() {
+    public RecycledByteArrayBuffer getRaw() {
         return this.buf;
     }
 
+    public void release() {
+        this.buf.release();
+    }
+
 }
diff --git a/core/src/main/java/org/apache/james/mime4j/stream/FieldBuilder.java b/core/src/main/java/org/apache/james/mime4j/stream/FieldBuilder.java
index a7a5815a..76aeccd9 100644
--- a/core/src/main/java/org/apache/james/mime4j/stream/FieldBuilder.java
+++ b/core/src/main/java/org/apache/james/mime4j/stream/FieldBuilder.java
@@ -21,6 +21,7 @@ package org.apache.james.mime4j.stream;
 
 import org.apache.james.mime4j.MimeException;
 import org.apache.james.mime4j.util.ByteArrayBuffer;
+import org.apache.james.mime4j.util.RecycledByteArrayBuffer;
 
 /**
  * <p>
@@ -68,6 +69,8 @@ public interface FieldBuilder {
      * Returns combined content of all lines processed so far or <code>null</code>
      * if the builder does not retain original raw content.
      */
-    ByteArrayBuffer getRaw();
+    RecycledByteArrayBuffer getRaw();
+
+    void release();
 
 }
diff --git a/core/src/main/java/org/apache/james/mime4j/stream/MimeEntity.java b/core/src/main/java/org/apache/james/mime4j/stream/MimeEntity.java
index 657fbd72..2b404a98 100644
--- a/core/src/main/java/org/apache/james/mime4j/stream/MimeEntity.java
+++ b/core/src/main/java/org/apache/james/mime4j/stream/MimeEntity.java
@@ -21,6 +21,7 @@ package org.apache.james.mime4j.stream;
 
 import java.io.IOException;
 import java.io.InputStream;
+import java.lang.ref.SoftReference;
 
 import org.apache.james.mime4j.MimeException;
 import org.apache.james.mime4j.codec.Base64InputStream;
@@ -34,11 +35,26 @@ import org.apache.james.mime4j.io.LineReaderInputStreamAdaptor;
 import org.apache.james.mime4j.io.MaxHeaderLimitException;
 import org.apache.james.mime4j.io.MaxLineLimitException;
 import org.apache.james.mime4j.io.MimeBoundaryInputStream;
+import org.apache.james.mime4j.util.BufferRecycler;
 import org.apache.james.mime4j.util.ByteArrayBuffer;
 import org.apache.james.mime4j.util.CharsetUtil;
 import org.apache.james.mime4j.util.MimeUtil;
+import org.apache.james.mime4j.util.RecycledByteArrayBuffer;
 
 class MimeEntity implements EntityStateMachine {
+    protected static final ThreadLocal<SoftReference<BufferRecycler>> _recyclerRef = new ThreadLocal<>();
+
+    public static BufferRecycler getBufferRecycler() {
+        SoftReference<BufferRecycler> ref = _recyclerRef.get();
+        BufferRecycler br = (ref == null) ? null : ref.get();
+
+        if (br == null) {
+            br = new BufferRecycler();
+            ref = new SoftReference<>(br);
+            _recyclerRef.set(ref);
+        }
+        return br;
+    }
 
     private final EntityState endState;
     private final MimeConfig config;
@@ -93,19 +109,6 @@ class MimeEntity implements EntityStateMachine {
                 config.getMaxLineLen());
     }
 
-    MimeEntity(
-            LineNumberSource lineSource,
-            InputStream instream,
-            MimeConfig config,
-            EntityState startState,
-            EntityState endState,
-            BodyDescriptorBuilder bodyDescBuilder) {
-        this(lineSource, instream, config, startState, endState,
-                config.isStrictParsing() ? DecodeMonitor.STRICT : DecodeMonitor.SILENT,
-                new DefaultFieldBuilder(config.getMaxHeaderLen()),
-                bodyDescBuilder);
-    }
-
     MimeEntity(
             LineNumberSource lineSource,
             InputStream instream,
@@ -154,6 +157,19 @@ class MimeEntity implements EntityStateMachine {
     }
 
     public void stop() {
+        stopSoft();
+        inbuffer.release();
+        getBufferRecycler().releaseByteBuffer(0, tmpbuf);
+    }
+
+    public void stopSoft() {
+        if(currentMimePartStream != null) {
+            try {
+                currentMimePartStream.close();
+            } catch (IOException e) {
+                throw new RuntimeException(e);
+            }
+        }
         this.inbuffer.truncate();
     }
 
@@ -271,7 +287,7 @@ class MimeEntity implements EntityStateMachine {
                 monitor(Event.INVALID_HEADER);
                 if (config.isMalformedHeaderStartsBody()) {
                     LineReaderInputStream instream = getDataStream();
-                    ByteArrayBuffer buf = fieldBuilder.getRaw();
+                    RecycledByteArrayBuffer buf = fieldBuilder.getRaw();
                     // Complain, if raw data is not available or cannot be 'unread'
                     if (buf == null || !instream.unread(buf)) {
                         throw new MimeParseEventException(Event.INVALID_HEADER);
@@ -387,7 +403,7 @@ class MimeEntity implements EntityStateMachine {
     private void advanceToBoundary() throws IOException {
         if (!dataStream.eof()) {
             if (tmpbuf == null) {
-                tmpbuf = new byte[2048];
+                tmpbuf = getBufferRecycler().allocByteBuffer(0, 2048);
             }
             InputStream instream = getLimitedContentStream();
             while (instream.read(tmpbuf)!= -1) {
diff --git a/core/src/main/java/org/apache/james/mime4j/stream/MimeTokenStream.java b/core/src/main/java/org/apache/james/mime4j/stream/MimeTokenStream.java
index 2efc8a51..715eeeb9 100644
--- a/core/src/main/java/org/apache/james/mime4j/stream/MimeTokenStream.java
+++ b/core/src/main/java/org/apache/james/mime4j/stream/MimeTokenStream.java
@@ -258,7 +258,8 @@ public class MimeTokenStream {
      * triggered 'start' events.
      */
     public void stop() {
-        rootentity.stop();
+        rootentity.stopSoft();
+        fieldBuilder.release();
     }
 
     /**
@@ -380,7 +381,10 @@ public class MimeTokenStream {
             if (state != EntityState.T_END_OF_STREAM) {
                 return state;
             }
-            entities.removeLast();
+            final EntityStateMachine entityStateMachine = entities.removeLast();
+            if (entityStateMachine instanceof MimeEntity) {
+                ((MimeEntity) entityStateMachine).stop();
+            }
             if (entities.isEmpty()) {
                 currentStateMachine = null;
             } else {
diff --git a/core/src/main/java/org/apache/james/mime4j/util/BufferRecycler.java b/core/src/main/java/org/apache/james/mime4j/util/BufferRecycler.java
new file mode 100644
index 00000000..5fa264b2
--- /dev/null
+++ b/core/src/main/java/org/apache/james/mime4j/util/BufferRecycler.java
@@ -0,0 +1,159 @@
+/****************************************************************
+ * 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.util;
+
+import java.lang.ref.SoftReference;
+import java.util.ArrayList;
+import java.util.Random;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.atomic.AtomicReferenceArray;
+
+/**
+ * This is a small utility class, whose main functionality is to allow
+ * simple reuse of raw byte/char buffers. It is usually used through
+ * <code>ThreadLocal</code> member of the owning class pointing to
+ * instance of this class through a <code>SoftReference</code>. The
+ * end result is a low-overhead GC-cleanable recycling: hopefully
+ * ideal for use by stream readers.
+ *<p>
+ * Rewritten in 2.10 to be thread-safe (see [jackson-core#479] for details),
+ * to not rely on {@code ThreadLocal} access.
+ */
+public class BufferRecycler {
+    protected final ArrayList<byte[]>[] _byteBuffers;
+    protected final ArrayList<char[]>[] _charBuffers;
+    protected final ArrayList<int[]> _intBuffers;
+
+    /**
+     * Default constructor used for creating instances of this default
+     * implementation.
+     */
+    public BufferRecycler() {
+        this(4, 4);
+    }
+
+    /**
+     * Alternate constructor to be used by sub-classes, to allow customization
+     * of number of low-level buffers in use.
+     *
+     * @param bbCount Number of {@code byte[]} buffers to allocate
+     * @param cbCount Number of {@code char[]} buffers to allocate
+     *
+     * @since 2.4
+     */
+    protected BufferRecycler(int bbCount, int cbCount) {
+        _byteBuffers = new ArrayList[bbCount];
+        for (int i = 0; i < bbCount; i++) {
+            _byteBuffers[i] = new ArrayList<>();
+        }
+        _charBuffers = new ArrayList[cbCount];
+        for (int i = 0; i < cbCount; i++) {
+            _charBuffers[i] = new ArrayList<>();
+        }
+        _intBuffers = new ArrayList<>();
+    }
+    
+    /**
+     * @param ix One of <code>READ_IO_BUFFER</code> constants.
+     *
+     * @return Buffer allocated (possibly recycled)
+     */
+    public final byte[] allocByteBuffer(int ix) {
+        return allocByteBuffer(ix, 0);
+    }
+
+    public final int[] allocintBuffer(int minSize) {
+        final int DEF_SIZE = 256;
+        if (minSize < DEF_SIZE) {
+            minSize = DEF_SIZE;
+        }
+        final ArrayList<int[]> buffers = _intBuffers;
+        int[] buffer = null;
+        if (buffers.size() > 0) {
+            buffer = buffers.remove(buffers.size() -1);
+        }
+        if (buffer == null || buffer.length < minSize) {
+            buffer = new int[minSize];
+        }
+        return buffer;
+    }
+
+    public byte[] allocByteBuffer(int ix, int minSize) {
+        final int DEF_SIZE = 4000;
+        if (minSize < DEF_SIZE) {
+            minSize = DEF_SIZE;
+        }
+        final ArrayList<byte[]> buffers = _byteBuffers[ix];
+        byte[] buffer = null;
+        if (buffers.size() > 0) {
+            buffer = buffers.remove(buffers.size() -1);
+        }
+        if (buffer == null || buffer.length < minSize) {
+            buffer = balloc(minSize);
+        }
+        return buffer;
+    }
+
+    public void releaseByteBuffer(int ix, byte[] buffer) {
+        if (buffer == null) {
+            return;
+        }
+        _byteBuffers[ix].add(buffer);
+    }
+
+    public void releaseIntBuffer(int[] buffer) {
+        if (buffer == null) {
+            return;
+        }
+        _intBuffers.add(buffer);
+    }
+    
+    public final char[] allocCharBuffer(int ix) {
+        return allocCharBuffer(ix, 0);
+    }
+
+    public char[] allocCharBuffer(int ix, int minSize) {
+        final int DEF_SIZE = 4000;
+        if (minSize < DEF_SIZE) {
+            minSize = DEF_SIZE;
+        }
+        final ArrayList<char[]> buffers = _charBuffers[ix];
+        char[] buffer = null;
+        if (buffers.size() > 0) {
+            buffer = buffers.remove(buffers.size() -1);
+        }
+        if (buffer == null || buffer.length < minSize) {
+            buffer = calloc(minSize);
+        }
+        return buffer;
+    }
+
+    public void releaseCharBuffer(int ix, char[] buffer) {
+        _charBuffers[ix].add(buffer);
+    }
+
+    protected byte[] balloc(int size) {
+        return new byte[size];
+    }
+
+    protected char[] calloc(int size) {
+        return new char[size];
+    }
+}
diff --git a/core/src/main/java/org/apache/james/mime4j/util/ContentUtil.java b/core/src/main/java/org/apache/james/mime4j/util/ContentUtil.java
index b519f4dd..a6c29696 100644
--- a/core/src/main/java/org/apache/james/mime4j/util/ContentUtil.java
+++ b/core/src/main/java/org/apache/james/mime4j/util/ContentUtil.java
@@ -26,6 +26,7 @@ import java.io.Reader;
 import java.io.StringWriter;
 import java.io.UnsupportedEncodingException;
 import java.io.Writer;
+import java.lang.ref.SoftReference;
 import java.nio.ByteBuffer;
 import java.nio.CharBuffer;
 import java.nio.charset.Charset;
@@ -37,11 +38,24 @@ import org.apache.james.mime4j.Charsets;
  * Utility methods for converting textual content of a message.
  */
 public class ContentUtil {
+    protected static final ThreadLocal<SoftReference<BufferRecycler>> _recyclerRef = new ThreadLocal<>();
+
+    public static BufferRecycler getBufferRecycler() {
+        SoftReference<BufferRecycler> ref = _recyclerRef.get();
+        BufferRecycler br = (ref == null) ? null : ref.get();
+
+        if (br == null) {
+            br = new BufferRecycler();
+            ref = new SoftReference<>(br);
+            _recyclerRef.set(ref);
+        }
+        return br;
+    }
 
     private ContentUtil() {
     }
 
-    static final int DEFAULT_COPY_BUFFER_SIZE = 1024;
+    static final int DEFAULT_COPY_BUFFER_SIZE = 4096;
 
     /**
      * Copies the contents of one stream to the other.
@@ -50,11 +64,13 @@ public class ContentUtil {
      * @throws IOException
      */
     public static void copy(final InputStream in, final OutputStream out) throws IOException {
-        final byte[] buffer = new byte[DEFAULT_COPY_BUFFER_SIZE];
+        BufferRecycler bufferRecycler = getBufferRecycler();
+        byte[] buffer = bufferRecycler.allocByteBuffer(0, DEFAULT_COPY_BUFFER_SIZE);
         int inputLength;
         while (-1 != (inputLength = in.read(buffer))) {
             out.write(buffer, 0, inputLength);
         }
+        bufferRecycler.releaseByteBuffer(0, buffer);
     }
 
     /**
@@ -64,11 +80,13 @@ public class ContentUtil {
      * @throws IOException
      */
     public static void copy(final Reader in, final Writer out) throws IOException {
-        final char[] buffer = new char[DEFAULT_COPY_BUFFER_SIZE];
+        BufferRecycler bufferRecycler = getBufferRecycler();
+        char[] buffer = bufferRecycler.allocCharBuffer(0, DEFAULT_COPY_BUFFER_SIZE);
         int inputLength;
         while (-1 != (inputLength = in.read(buffer))) {
             out.write(buffer, 0, inputLength);
         }
+        bufferRecycler.releaseCharBuffer(0, buffer);
     }
 
     public static byte[] buffer(final InputStream in) throws IOException {
diff --git a/core/src/main/java/org/apache/james/mime4j/util/RecycledByteArrayBuffer.java b/core/src/main/java/org/apache/james/mime4j/util/RecycledByteArrayBuffer.java
new file mode 100644
index 00000000..900381a4
--- /dev/null
+++ b/core/src/main/java/org/apache/james/mime4j/util/RecycledByteArrayBuffer.java
@@ -0,0 +1,187 @@
+/****************************************************************
+ * 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.util;
+
+
+/**
+ * A resizable byte array.
+ */
+public final class RecycledByteArrayBuffer implements ByteSequence {
+    private final BufferRecycler bufferRecycler;
+    private byte[] buffer;
+    private int len;
+
+    public RecycledByteArrayBuffer(BufferRecycler bufferRecycler, int capacity) {
+        super();
+        if (capacity < 0) {
+            throw new IllegalArgumentException("Buffer capacity may not be negative");
+        }
+        this.buffer = bufferRecycler.allocByteBuffer(0, capacity);
+        this.bufferRecycler = bufferRecycler;
+    }
+
+    public RecycledByteArrayBuffer(BufferRecycler bufferRecycler, byte[] bytes, boolean dontCopy) {
+        this(bufferRecycler, bytes, bytes.length, dontCopy);
+    }
+
+    public RecycledByteArrayBuffer(BufferRecycler bufferRecycler, byte[] bytes, int len, boolean dontCopy) {
+        if (bytes == null)
+            throw new IllegalArgumentException();
+        if (len < 0 || len > bytes.length)
+            throw new IllegalArgumentException();
+
+        if (dontCopy) {
+            this.buffer = bytes;
+        } else {
+            this.buffer = bufferRecycler.allocByteBuffer(0, len);
+            System.arraycopy(bytes, 0, this.buffer, 0, len);
+        }
+        this.bufferRecycler = bufferRecycler;
+        this.len = len;
+    }
+
+    private void expand(int newlen) {
+        byte newbuffer[] = bufferRecycler.allocByteBuffer(0, Math.max(this.buffer.length << 1, newlen));
+        System.arraycopy(this.buffer, 0, newbuffer, 0, this.len);
+        bufferRecycler.releaseByteBuffer(0, buffer);
+        this.buffer = newbuffer;
+    }
+
+    public void append(final byte[] b, int off, int len) {
+        if (b == null) {
+            return;
+        }
+        if ((off < 0) || (off > b.length) || (len < 0) ||
+                ((off + len) < 0) || ((off + len) > b.length)) {
+            throw new IndexOutOfBoundsException();
+        }
+        if (len == 0) {
+            return;
+        }
+        int newlen = this.len + len;
+        if (newlen > this.buffer.length) {
+            expand(newlen);
+        }
+        System.arraycopy(b, off, this.buffer, this.len, len);
+        this.len = newlen;
+    }
+
+    public void append(int b) {
+        int newlen = this.len + 1;
+        if (newlen > this.buffer.length) {
+            expand(newlen);
+        }
+        this.buffer[this.len] = (byte)b;
+        this.len = newlen;
+    }
+
+    public void clear() {
+        this.len = 0;
+    }
+
+    public byte[] toByteArray() {
+        byte[] b = new byte[this.len];
+        if (this.len > 0) {
+            System.arraycopy(this.buffer, 0, b, 0, this.len);
+        }
+        return b;
+    }
+
+    public byte byteAt(int i) {
+        if (i < 0 || i >= this.len)
+            throw new IndexOutOfBoundsException();
+
+        return this.buffer[i];
+    }
+
+    public int capacity() {
+        return this.buffer.length;
+    }
+
+    public int length() {
+        return this.len;
+    }
+
+    public byte[] buffer() {
+        return this.buffer;
+    }
+
+    public int indexOf(byte b) {
+        return indexOf(b, 0, this.len);
+    }
+
+    public int indexOf(byte b, int beginIndex, int endIndex) {
+        if (beginIndex < 0) {
+            beginIndex = 0;
+        }
+        if (endIndex > this.len) {
+            endIndex = this.len;
+        }
+        if (beginIndex > endIndex) {
+            return -1;
+        }
+        for (int i = beginIndex; i < endIndex; i++) {
+            if (this.buffer[i] == b) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    public void setLength(int len) {
+        if (len < 0 || len > this.buffer.length) {
+            throw new IndexOutOfBoundsException();
+        }
+        this.len = len;
+    }
+
+    public void remove(int off, int len) {
+        if ((off < 0) || (off > this.len) || (len < 0) ||
+                ((off + len) < 0) || ((off + len) > this.len)) {
+            throw new IndexOutOfBoundsException();
+        }
+        if (len == 0) {
+            return;
+        }
+        int remaining = this.len - off - len;
+        if (remaining > 0) {
+            System.arraycopy(this.buffer, off + len, this.buffer, off, remaining);
+        }
+        this.len -= len;
+    }
+
+    public boolean isEmpty() {
+        return this.len == 0;
+    }
+
+    public boolean isFull() {
+        return this.len == this.buffer.length;
+    }
+
+    @Override
+    public String toString() {
+        return new String(toByteArray());
+    }
+
+    public void release() {
+        bufferRecycler.releaseByteBuffer(0, buffer);
+    }
+
+}
diff --git a/core/src/test/java/org/apache/james/mime4j/stream/DefaultFieldBuilderTest.java b/core/src/test/java/org/apache/james/mime4j/stream/DefaultFieldBuilderTest.java
index 98a630c1..e9d89fed 100644
--- a/core/src/test/java/org/apache/james/mime4j/stream/DefaultFieldBuilderTest.java
+++ b/core/src/test/java/org/apache/james/mime4j/stream/DefaultFieldBuilderTest.java
@@ -22,6 +22,7 @@ package org.apache.james.mime4j.stream;
 import org.apache.james.mime4j.MimeException;
 import org.apache.james.mime4j.util.ByteArrayBuffer;
 import org.apache.james.mime4j.util.ByteSequence;
+import org.apache.james.mime4j.util.RecycledByteArrayBuffer;
 
 import junit.framework.TestCase;
 
@@ -40,7 +41,7 @@ public class DefaultFieldBuilderTest extends TestCase {
         builder.append(line("raw:   stuff;\r\n"));
         builder.append(line("   more stuff;\r\n"));
         builder.append(line("   a lot more stuff\r\n"));
-        ByteArrayBuffer buf = builder.getRaw();
+        RecycledByteArrayBuffer buf = builder.getRaw();
         assertNotNull(buf);
         assertEquals("raw:   stuff;\r\n   more stuff;\r\n   a lot more stuff\r\n",
                 new String(buf.toByteArray(), "US-ASCII"));
@@ -60,7 +61,7 @@ public class DefaultFieldBuilderTest extends TestCase {
         builder.append(line("raw  : stuff;\r\n"));
         builder.append(line("   more stuff;\r\n"));
         builder.append(line("   a lot more stuff\r\n"));
-        ByteArrayBuffer buf = builder.getRaw();
+        RecycledByteArrayBuffer buf = builder.getRaw();
         assertNotNull(buf);
         assertEquals("raw  : stuff;\r\n   more stuff;\r\n   a lot more stuff\r\n",
                 new String(buf.toByteArray(), "US-ASCII"));
@@ -80,7 +81,7 @@ public class DefaultFieldBuilderTest extends TestCase {
         builder.append(line("raw:   stuff;\r\n"));
         builder.append(line("   more stuff;\r\n"));
         builder.append(line("   a lot more stuff"));
-        ByteArrayBuffer buf = builder.getRaw();
+        RecycledByteArrayBuffer buf = builder.getRaw();
         assertNotNull(buf);
         assertEquals("raw:   stuff;\r\n   more stuff;\r\n   a lot more stuff",
                 new String(buf.toByteArray(), "US-ASCII"));
@@ -109,7 +110,7 @@ public class DefaultFieldBuilderTest extends TestCase {
         DefaultFieldBuilder builder = new DefaultFieldBuilder(0);
         builder.reset();
         builder.append(line("raw: some stuff\r\n"));
-        ByteArrayBuffer buf = builder.getRaw();
+        RecycledByteArrayBuffer buf = builder.getRaw();
         assertNotNull(buf);
         assertEquals("raw: some stuff\r\n", new String(buf.toByteArray(), "US-ASCII"));
         builder.reset();
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 db6cff8e..2a2bb2d3 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
@@ -319,6 +319,7 @@ public class DefaultMessageBuilder implements MessageBuilder {
                 parser.setRecurse();
             }
             parser.parse(is);
+            parser.stop();
             return message;
         } catch (MimeException e) {
             throw new MimeIOException(e);
diff --git a/james-utils/src/main/java/org/apache/james/mime4j/utils/search/MessageMatcher.java b/james-utils/src/main/java/org/apache/james/mime4j/utils/search/MessageMatcher.java
index ad52255c..1881c41f 100644
--- a/james-utils/src/main/java/org/apache/james/mime4j/utils/search/MessageMatcher.java
+++ b/james-utils/src/main/java/org/apache/james/mime4j/utils/search/MessageMatcher.java
@@ -199,8 +199,9 @@ public class MessageMatcher {
     }
 
     private boolean checkBody(final CharBuffer buffer, MimeTokenStream parser) throws IOException {
-        final Reader reader = parser.getReader();
-        return isFoundIn(reader, buffer);
+        try (Reader reader = parser.getReader()) {
+            return isFoundIn(reader, buffer);
+        }
     }
 
     private CharBuffer createBuffer(final CharSequence searchContent) {


---------------------------------------------------------------------
To unsubscribe, e-mail: server-dev-unsubscribe@james.apache.org
For additional commands, e-mail: server-dev-help@james.apache.org


[james-mime4j] 11/14: MIME4J-318 Improve writing recycled buffers

Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-mime4j.git

commit a741f65dd6d2eb21f1be7fcfd37d5fac52d28777
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Tue Jun 28 09:40:10 2022 +0700

    MIME4J-318 Improve writing recycled buffers
---
 .../java/org/apache/james/mime4j/message/DefaultMessageWriter.java    | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/dom/src/main/java/org/apache/james/mime4j/message/DefaultMessageWriter.java b/dom/src/main/java/org/apache/james/mime4j/message/DefaultMessageWriter.java
index 5c4c1ce5..9e52c4f5 100644
--- a/dom/src/main/java/org/apache/james/mime4j/message/DefaultMessageWriter.java
+++ b/dom/src/main/java/org/apache/james/mime4j/message/DefaultMessageWriter.java
@@ -40,6 +40,7 @@ import org.apache.james.mime4j.util.ByteArrayBuffer;
 import org.apache.james.mime4j.util.ByteSequence;
 import org.apache.james.mime4j.util.ContentUtil;
 import org.apache.james.mime4j.util.MimeUtil;
+import org.apache.james.mime4j.util.RecycledByteArrayBuffer;
 
 /**
  * Default implementation of {@link MessageWriter}.
@@ -272,6 +273,9 @@ public class DefaultMessageWriter implements MessageWriter {
         if (byteSequence instanceof ByteArrayBuffer) {
             ByteArrayBuffer bab = (ByteArrayBuffer) byteSequence;
             out.write(bab.buffer(), 0, bab.length());
+        } else if (byteSequence instanceof RecycledByteArrayBuffer) {
+            RecycledByteArrayBuffer bab = (RecycledByteArrayBuffer) byteSequence;
+            out.write(bab.buffer(), 0, bab.length());
         } else {
             out.write(byteSequence.toByteArray());
         }


---------------------------------------------------------------------
To unsubscribe, e-mail: server-dev-unsubscribe@james.apache.org
For additional commands, e-mail: server-dev-help@james.apache.org


[james-mime4j] 06/14: MIME4J-318 RawBody is used solely for parsing and not exposed nor kept.

Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-mime4j.git

commit 2a234381ac67bbf4a8149d79e63faa667c031ca0
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Tue Jun 21 10:50:23 2022 +0700

    MIME4J-318 RawBody is used solely for parsing and not exposed nor kept.
    
    RawBody: prevent a useless defensive copy
---
 core/src/main/java/org/apache/james/mime4j/stream/RawBody.java | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/core/src/main/java/org/apache/james/mime4j/stream/RawBody.java b/core/src/main/java/org/apache/james/mime4j/stream/RawBody.java
index 58d6fe6b..55a1ca2d 100644
--- a/core/src/main/java/org/apache/james/mime4j/stream/RawBody.java
+++ b/core/src/main/java/org/apache/james/mime4j/stream/RawBody.java
@@ -47,7 +47,7 @@ public final class RawBody {
     }
 
     public List<NameValuePair> getParams() {
-        return new ArrayList<NameValuePair>(this.params);
+        return this.params;
     }
 
     @Override


---------------------------------------------------------------------
To unsubscribe, e-mail: server-dev-unsubscribe@james.apache.org
For additional commands, e-mail: server-dev-help@james.apache.org


[james-mime4j] 01/14: MIME4J-318 Adopt commons-io ByteArrayOutputStream

Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-mime4j.git

commit 215f8ed3107909a1b8671b8e0f2c5063d2f626fe
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Mon Jun 20 11:06:02 2022 +0700

    MIME4J-318 Adopt commons-io ByteArrayOutputStream
    
    Parsing 100K messages locally drops from 5.6s to 4.9s...
---
 core/pom.xml                                         |  1 -
 .../org/apache/james/mime4j/util/ContentUtil.java    |  2 +-
 .../java/org/apache/james/mime4j/dom/SingleBody.java | 20 +++-----------------
 3 files changed, 4 insertions(+), 19 deletions(-)

diff --git a/core/pom.xml b/core/pom.xml
index 216e1eb5..0123a688 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -41,7 +41,6 @@
         <dependency>
             <groupId>commons-io</groupId>
             <artifactId>commons-io</artifactId>
-            <scope>test</scope>
         </dependency>
     </dependencies>
 
diff --git a/core/src/main/java/org/apache/james/mime4j/util/ContentUtil.java b/core/src/main/java/org/apache/james/mime4j/util/ContentUtil.java
index 14a636b6..b519f4dd 100644
--- a/core/src/main/java/org/apache/james/mime4j/util/ContentUtil.java
+++ b/core/src/main/java/org/apache/james/mime4j/util/ContentUtil.java
@@ -19,7 +19,6 @@
 
 package org.apache.james.mime4j.util;
 
-import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
@@ -31,6 +30,7 @@ import java.nio.ByteBuffer;
 import java.nio.CharBuffer;
 import java.nio.charset.Charset;
 
+import org.apache.commons.io.output.ByteArrayOutputStream;
 import org.apache.james.mime4j.Charsets;
 
 /**
diff --git a/dom/src/main/java/org/apache/james/mime4j/dom/SingleBody.java b/dom/src/main/java/org/apache/james/mime4j/dom/SingleBody.java
index c75cad0d..ea421114 100644
--- a/dom/src/main/java/org/apache/james/mime4j/dom/SingleBody.java
+++ b/dom/src/main/java/org/apache/james/mime4j/dom/SingleBody.java
@@ -23,6 +23,8 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 
+import org.apache.james.mime4j.util.ContentUtil;
+
 /**
  * Abstract implementation of a single message body; that is, a body that does
  * not contain (directly or indirectly) any other child bodies. It also provides
@@ -76,7 +78,7 @@ public abstract class SingleBody implements Body {
             throw new IllegalArgumentException();
 
         InputStream in = getInputStream();
-        SingleBody.copy(in, out);
+        ContentUtil.copy(in, out);
         in.close();
     }
 
@@ -120,20 +122,4 @@ public abstract class SingleBody implements Body {
     public void dispose() {
     }
 
-    static final int DEFAULT_ENCODING_BUFFER_SIZE = 1024;
-
-    /**
-     * Copies the contents of one stream to the other.
-     * @param in not null
-     * @param out not null
-     * @throws IOException
-     */
-    private static void copy(final InputStream in, final OutputStream out) throws IOException {
-        final byte[] buffer = new byte[DEFAULT_ENCODING_BUFFER_SIZE];
-        int inputLength;
-        while (-1 != (inputLength = in.read(buffer))) {
-            out.write(buffer, 0, inputLength);
-        }
-    }
-
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: server-dev-unsubscribe@james.apache.org
For additional commands, e-mail: server-dev-help@james.apache.org


[james-mime4j] 12/14: MIME4J-318 Add a TextBody::getCharset

Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-mime4j.git

commit 432b786a4b7ca565b3d3b9c94640f3d03c8f58eb
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Wed Jun 29 08:23:08 2022 +0700

    MIME4J-318 Add a TextBody::getCharset
    
    This avoids client application to re-parse the charset
---
 .../main/java/org/apache/james/mime4j/dom/TextBody.java   |  3 +++
 .../org/apache/james/mime4j/message/BasicBodyFactory.java | 15 +++++++++++++++
 .../apache/james/mime4j/message/SingleBodyBuilder.java    | 11 +----------
 .../org/apache/james/mime4j/storage/StorageTextBody.java  |  5 +++++
 .../org/apache/james/mime4j/storage/StringTextBody.java   |  5 +++++
 5 files changed, 29 insertions(+), 10 deletions(-)

diff --git a/dom/src/main/java/org/apache/james/mime4j/dom/TextBody.java b/dom/src/main/java/org/apache/james/mime4j/dom/TextBody.java
index 9929199d..5b49f8c8 100644
--- a/dom/src/main/java/org/apache/james/mime4j/dom/TextBody.java
+++ b/dom/src/main/java/org/apache/james/mime4j/dom/TextBody.java
@@ -21,6 +21,7 @@ package org.apache.james.mime4j.dom;
 
 import java.io.IOException;
 import java.io.Reader;
+import java.nio.charset.Charset;
 
 /**
  * Encapsulates the contents of a <code>text/*</code> entity body.
@@ -40,6 +41,8 @@ public abstract class TextBody extends SingleBody {
      */
     public abstract String getMimeCharset();
 
+    public abstract Charset getCharset();
+
     /**
      * Gets a <code>Reader</code> which may be used to read out the contents
      * of this body.
diff --git a/dom/src/main/java/org/apache/james/mime4j/message/BasicBodyFactory.java b/dom/src/main/java/org/apache/james/mime4j/message/BasicBodyFactory.java
index 10df514b..225ec295 100644
--- a/dom/src/main/java/org/apache/james/mime4j/message/BasicBodyFactory.java
+++ b/dom/src/main/java/org/apache/james/mime4j/message/BasicBodyFactory.java
@@ -162,6 +162,11 @@ public class BasicBodyFactory implements BodyFactory {
             return this.charset != null ? this.charset.name() : null;
         }
 
+        @Override
+        public Charset getCharset() {
+            return charset;
+        }
+
         @Override
         public Reader getReader() throws IOException {
             return new StringReader(this.content);
@@ -200,6 +205,11 @@ public class BasicBodyFactory implements BodyFactory {
             return this.charset != null ? this.charset.name() : null;
         }
 
+        @Override
+        public Charset getCharset() {
+            return charset;
+        }
+
         @Override
         public Reader getReader() throws IOException {
             return new InputStreamReader(InputStreams.create(this.content), this.charset);
@@ -237,6 +247,11 @@ public class BasicBodyFactory implements BodyFactory {
             return this.charset != null ? this.charset.name() : null;
         }
 
+        @Override
+        public Charset getCharset() {
+            return charset;
+        }
+
         @Override
         public Reader getReader() throws IOException {
             return new InputStreamReader(this.content.getValue().toInputStream(), this.charset);
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 66d99caf..82f192a2 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
@@ -96,16 +96,7 @@ public class SingleBodyBuilder {
             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.charset = ((TextBody) other).getCharset();
         }
         this.bin = ContentUtil.buffer(other.getInputStream());
         return this;
diff --git a/storage/src/main/java/org/apache/james/mime4j/storage/StorageTextBody.java b/storage/src/main/java/org/apache/james/mime4j/storage/StorageTextBody.java
index 87bd33b1..85486ef2 100644
--- a/storage/src/main/java/org/apache/james/mime4j/storage/StorageTextBody.java
+++ b/storage/src/main/java/org/apache/james/mime4j/storage/StorageTextBody.java
@@ -45,6 +45,11 @@ class StorageTextBody extends TextBody {
         return charset.name();
     }
 
+    @Override
+    public Charset getCharset() {
+        return charset;
+    }
+
     @Override
     public Reader getReader() throws IOException {
         return new InputStreamReader(storage.getInputStream(), charset);
diff --git a/storage/src/main/java/org/apache/james/mime4j/storage/StringTextBody.java b/storage/src/main/java/org/apache/james/mime4j/storage/StringTextBody.java
index ed5b31d3..8340d87b 100644
--- a/storage/src/main/java/org/apache/james/mime4j/storage/StringTextBody.java
+++ b/storage/src/main/java/org/apache/james/mime4j/storage/StringTextBody.java
@@ -49,6 +49,11 @@ class StringTextBody extends TextBody {
         return charset.name();
     }
 
+    @Override
+    public Charset getCharset() {
+        return charset;
+    }
+
     @Override
     public InputStream getInputStream() throws IOException {
         return new ByteArrayInputStream(text.getBytes(charset.name()));


---------------------------------------------------------------------
To unsubscribe, e-mail: server-dev-unsubscribe@james.apache.org
For additional commands, e-mail: server-dev-help@james.apache.org