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 <btell...@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-unsubscr...@james.apache.org
For additional commands, e-mail: server-dev-h...@james.apache.org


Reply via email to