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 rd...@apache.org on 2008/07/01 22:31:51 UTC

svn commit: r673195 - in /james/mime4j/trunk/src: main/java/org/apache/james/mime4j/ test/java/org/apache/james/mime4j/

Author: rdonkin
Date: Tue Jul  1 13:31:51 2008
New Revision: 673195

URL: http://svn.apache.org/viewvc?rev=673195&view=rev
Log:
MIME4J-5 Performance improvements. First of a sequence of patches. Contributed by Oleg Kalnichevski. See https://issues.apache.org/jira/browse/MIME4J-5.

Added:
    james/mime4j/trunk/src/main/java/org/apache/james/mime4j/BufferingInputStream.java
    james/mime4j/trunk/src/main/java/org/apache/james/mime4j/InputBuffer.java
    james/mime4j/trunk/src/test/java/org/apache/james/mime4j/InputBufferTest.java   (with props)
Modified:
    james/mime4j/trunk/src/main/java/org/apache/james/mime4j/Cursor.java
    james/mime4j/trunk/src/main/java/org/apache/james/mime4j/MimeBoundaryInputStream.java
    james/mime4j/trunk/src/main/java/org/apache/james/mime4j/MimeException.java
    james/mime4j/trunk/src/main/java/org/apache/james/mime4j/MimeTokenStream.java
    james/mime4j/trunk/src/main/java/org/apache/james/mime4j/StreamCursor.java
    james/mime4j/trunk/src/test/java/org/apache/james/mime4j/MimeBoundaryInputStreamTest.java
    james/mime4j/trunk/src/test/java/org/apache/james/mime4j/MimeTokenNoRecurseTest.java

Added: james/mime4j/trunk/src/main/java/org/apache/james/mime4j/BufferingInputStream.java
URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/main/java/org/apache/james/mime4j/BufferingInputStream.java?rev=673195&view=auto
==============================================================================
--- james/mime4j/trunk/src/main/java/org/apache/james/mime4j/BufferingInputStream.java (added)
+++ james/mime4j/trunk/src/main/java/org/apache/james/mime4j/BufferingInputStream.java Tue Jul  1 13:31:51 2008
@@ -0,0 +1,53 @@
+/****************************************************************
+ * 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;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Implementation of {@link InputStream} backed by an {@link InputBuffer} instance. 
+ */
+public class BufferingInputStream extends InputStream {
+
+    private final InputBuffer buffer;
+    
+    public BufferingInputStream(final InputBuffer buffer) {
+        super();
+        this.buffer = buffer;
+    }
+    
+    public void close() throws IOException {
+        this.buffer.closeStream();
+    }
+
+    public boolean markSupported() {
+        return false;
+    }
+
+    public int read() throws IOException {
+        return this.buffer.read();
+    }
+
+    public int read(byte[] b, int off, int len) throws IOException {
+        return this.buffer.read(b, off, len);
+    }
+    
+}

Modified: james/mime4j/trunk/src/main/java/org/apache/james/mime4j/Cursor.java
URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/main/java/org/apache/james/mime4j/Cursor.java?rev=673195&r1=673194&r2=673195&view=diff
==============================================================================
--- james/mime4j/trunk/src/main/java/org/apache/james/mime4j/Cursor.java (original)
+++ james/mime4j/trunk/src/main/java/org/apache/james/mime4j/Cursor.java Tue Jul  1 13:31:51 2008
@@ -112,4 +112,11 @@
      * @return <code>InputStream</code>, not null
      */
     public InputStream rest();
+
+    /**
+     * Gets root stream for this message.
+     * @return <code>InputStream</code>, not null
+     */
+    public InputStream root();
+
 }

Added: james/mime4j/trunk/src/main/java/org/apache/james/mime4j/InputBuffer.java
URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/main/java/org/apache/james/mime4j/InputBuffer.java?rev=673195&view=auto
==============================================================================
--- james/mime4j/trunk/src/main/java/org/apache/james/mime4j/InputBuffer.java (added)
+++ james/mime4j/trunk/src/main/java/org/apache/james/mime4j/InputBuffer.java Tue Jul  1 13:31:51 2008
@@ -0,0 +1,200 @@
+/****************************************************************
+ * 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;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Input buffer that can be used to search for patterns using Quick Search 
+ * algorithm in data read from an {@link InputStream}. 
+ */
+public class InputBuffer {
+
+    private final InputStream instream;
+    private final byte[] buffer;
+    
+    private int bufpos;
+    private int buflen;
+    
+    public InputBuffer(final InputStream instream, int buffersize) {
+        super();
+        if (instream == null) {
+            throw new IllegalArgumentException("Input stream may not be null");
+        }
+        if (buffersize <= 0) {
+            throw new IllegalArgumentException("Buffer size may not be negative or zero");
+        }
+        this.instream = instream;
+        this.buffer = new byte[buffersize];
+        this.bufpos = 0;
+        this.buflen = 0;
+    }
+    
+    public int fillBuffer() throws IOException {
+        // compact the buffer if necessary
+        if (this.bufpos > 0) {
+            int len = this.buflen - this.bufpos;
+            if (len > 0) {
+                System.arraycopy(this.buffer, this.bufpos, this.buffer, 0, len);
+            }
+            this.bufpos = 0;
+            this.buflen = len;
+        }
+        int l;
+        int off = this.buflen;
+        int len = this.buffer.length - off;
+        l = this.instream.read(this.buffer, off, len);
+        if (l == -1) {
+            return -1;
+        } else {
+            this.buflen = off + l;
+            return l;
+        }
+    }
+
+    public boolean hasBufferedData() {
+        return this.bufpos < this.buflen;
+    }
+
+    public void closeStream() throws IOException {
+        this.instream.close();
+    }
+    
+    public int read() throws IOException {
+        int noRead = 0;
+        while (!hasBufferedData()) {
+            noRead = fillBuffer();
+            if (noRead == -1) {
+                return -1;
+            }
+        }
+        return this.buffer[this.bufpos++] & 0xff;
+    }
+    
+    public int read(final byte[] b, int off, int len) throws IOException {
+        if (b == null) {
+            return 0;
+        }
+        int noRead = 0;
+        while (!hasBufferedData()) {
+            noRead = fillBuffer();
+            if (noRead == -1) {
+                return -1;
+            }
+        }
+        int chunk = this.buflen - this.bufpos;
+        if (chunk > len) {
+            chunk = len;
+        }
+        System.arraycopy(this.buffer, this.bufpos, b, off, chunk);
+        this.bufpos += chunk;
+        return chunk;
+    }
+    
+    public int read(final byte[] b) throws IOException {
+        if (b == null) {
+            return 0;
+        }
+        return read(b, 0, b.length);
+    }
+    
+    /**
+     * Implements quick search algorithm as published by
+     * <p> 
+     * SUNDAY D.M., 1990, 
+     * A very fast substring search algorithm, 
+     * Communications of the ACM . 33(8):132-142.
+     * </p>
+     */
+    public int indexOf(final byte[] pattern) {
+        int len = this.buflen - this.bufpos;
+        if (len < pattern.length) {
+            return -1;
+        }
+        
+        int[] shiftTable = new int[255];
+        for (int i = 0; i < shiftTable.length; i++) {
+            shiftTable[i] = pattern.length + 1;
+        }
+        for (int i = 0; i < pattern.length; i++) {
+            int x = pattern[i] & 0xff;
+            shiftTable[x] = pattern.length - i;
+        }
+        
+        int j = 0;
+        while (j <= len - pattern.length) {
+            int cur = this.bufpos + j;
+            boolean match = true;
+            for (int i = 0; i < pattern.length; i++) {
+                if (this.buffer[cur + i] != pattern[i]) {
+                    match = false;
+                    break;
+                }
+            }
+            if (match) {
+                return cur;
+            }
+            
+            int pos = cur + pattern.length; 
+            if (pos >= this.buffer.length) {
+                break;
+            }
+            int x = this.buffer[pos] & 0xff;
+            j += shiftTable[x];
+        }
+        return -1;
+    }
+    
+    public int length() {
+        return this.buflen;
+    }
+    
+    public byte charAt(int pos) {
+        return this.buffer[pos];
+    }
+    
+    public int pos() {
+        return this.bufpos;
+    }
+    
+    public int skip(int n) {
+        int chunk = Math.min(n, this.buflen - this.bufpos);
+        this.bufpos += chunk; 
+        return chunk;
+    }
+
+    public String toString() {
+        StringBuffer buffer = new StringBuffer();
+        buffer.append("[pos: ");
+        buffer.append(this.bufpos);
+        buffer.append("]");
+        buffer.append("[len: ");
+        buffer.append(this.buflen);
+        buffer.append("]");
+        buffer.append("[");
+        for (int i = this.bufpos; i < this.buflen; i++) {
+            buffer.append((char) this.buffer[i]);
+        }
+        buffer.append("]");
+        return buffer.toString();
+    }
+    
+}

Modified: james/mime4j/trunk/src/main/java/org/apache/james/mime4j/MimeBoundaryInputStream.java
URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/main/java/org/apache/james/mime4j/MimeBoundaryInputStream.java?rev=673195&r1=673194&r2=673195&view=diff
==============================================================================
--- james/mime4j/trunk/src/main/java/org/apache/james/mime4j/MimeBoundaryInputStream.java (original)
+++ james/mime4j/trunk/src/main/java/org/apache/james/mime4j/MimeBoundaryInputStream.java Tue Jul  1 13:31:51 2008
@@ -21,52 +21,52 @@
 
 import java.io.IOException;
 import java.io.InputStream;
-import java.io.PushbackInputStream;
 
 /**
  * Stream that constrains itself to a single MIME body part.
- * After the stream ends (i.e. read() returns -1) {@link #hasMoreParts()}
+ * After the stream ends (i.e. read() returns -1) {@link #isLastPart()}
  * can be used to determine if a final boundary has been seen or not.
- * If {@link #parentEOF()} is <code>true</code> an unexpected end of stream
- * has been detected in the parent stream.
- * 
- * 
  * 
  * @version $Id: MimeBoundaryInputStream.java,v 1.2 2004/11/29 13:15:42 ntherning Exp $
  */
 public class MimeBoundaryInputStream extends InputStream {
+
+    private final InputBuffer buffer;
+    private final byte[] boundary;
     
-    private PushbackInputStream s;
-    private byte[] boundary;
-    private boolean first = true;
     private boolean eof;
-    private boolean parenteof;
-    private boolean moreParts = true;
+    private int limit;
+    private boolean atBoundary;
+    private int boundaryLen;
+    private boolean lastPart;
+    private boolean completed;
 
     /**
      * Creates a new MimeBoundaryInputStream.
      * @param s The underlying stream.
      * @param boundary Boundary string (not including leading hyphens).
      */
-    public MimeBoundaryInputStream(InputStream s, String boundary) 
+    public MimeBoundaryInputStream(InputBuffer inbuffer, String boundary) 
             throws IOException {
-        
-        this.s = new PushbackInputStream(s, boundary.length() + 4);
-
-        boundary = "--" + boundary;
-        this.boundary = new byte[boundary.length()];
-        for (int i = 0; i < this.boundary.length; i++) {
-            this.boundary[i] = (byte) boundary.charAt(i);
-        }
-        
-        /*
-         * By reading one byte we will update moreParts to be as expected
-         * before any bytes have been read.
-         */
-        int b = read();
-        if (b != -1) {
-            this.s.unread(b);
+        this.buffer = inbuffer;
+        this.eof = false;
+        this.limit = 0;
+        this.atBoundary = false;
+        this.boundaryLen = 0;
+        this.lastPart = false;
+        this.completed = false;
+        
+        this.boundary = new byte[boundary.length() + 2];
+        this.boundary[0] = (byte) '-';
+        this.boundary[1] = (byte) '-';
+        for (int i = 0; i < boundary.length(); i++) {
+            byte ch = (byte) boundary.charAt(i);
+            if (ch == '\r' || ch == '\n') {
+                throw new IllegalArgumentException("Boundary may not contain CR or LF");
+            }
+            this.boundary[i + 2] = ch;
         }
+        fillBuffer();
     }
 
     /**
@@ -75,110 +75,144 @@
      * @throws IOException on I/O errors.
      */
     public void close() throws IOException {
-        s.close();
     }
 
     /**
-     * Determines if the underlying stream has more parts (this stream has
-     * not seen an end boundary).
-     * 
-     * @return <code>true</code> if there are more parts in the underlying 
-     *         stream, <code>false</code> otherwise.
+     * @see java.io.InputStream#markSupported()
      */
-    public boolean hasMoreParts() {
-        return moreParts;
+    public boolean markSupported() {
+        return false;
     }
 
     /**
-     * Determines if the parent stream has reached EOF
-     * 
-     * @return <code>true</code>  if EOF has been reached for the parent stream, 
-     *         <code>false</code> otherwise.
+     * @see java.io.InputStream#read()
      */
-    public boolean parentEOF() {
-        return parenteof;
+    public int read() throws IOException {
+        if (completed) {
+            return -1;
+        }
+        if (endOfStream() && !hasData()) {
+            skipBoundary();            
+            return -1;
+        }
+        for (;;) {
+            if (hasData()) {
+                return buffer.read();
+            } else if (endOfStream()) {
+                skipBoundary();            
+                return -1;
+            }
+            fillBuffer();
+        }
     }
     
-    /**
-     * Consumes all unread bytes of this stream. After a call to this method
-     * this stream will have reached EOF.
-     * 
-     * @throws IOException on I/O errors.
-     */
-    public void consume() throws IOException {
-        while (read() != -1) {
-        }
+    public int read(byte[] b, int off, int len) throws IOException {
+        if (completed) {
+            return -1;
+        }
+        if (endOfStream() && !hasData()) {
+            skipBoundary();            
+            return -1;
+        }
+        fillBuffer();
+        if (!hasData()) {
+            return 0;
+        }
+        int chunk = Math.min(len, limit - buffer.pos());
+        return buffer.read(b, off, chunk);
+    }
+
+    private boolean endOfStream() {
+        return eof || atBoundary;
     }
     
-    /**
-     * @see java.io.InputStream#read()
-     */
-    public int read() throws IOException {
+    private boolean hasData() {
+        return limit > buffer.pos() && limit < buffer.length();
+    }
+    
+    private int fillBuffer() throws IOException {
         if (eof) {
             return -1;
         }
-        
-        if (first) {
-            first = false;
-            if (matchBoundary()) {
-                return -1;
+        int bytesRead;
+        if (!hasData()) {
+            bytesRead = buffer.fillBuffer();
+        } else {
+            bytesRead = 0;
+        }
+        eof = bytesRead == -1;
+        
+        int i = buffer.indexOf(boundary);
+        if (i != -1) {
+            limit = i;
+            atBoundary = true;
+            calculateBoundaryLen();
+        } else {
+            if (eof) {
+                limit = buffer.length();
+            } else {
+                limit = buffer.length() - (boundary.length + 1); 
+                                          // \r\n + (boundary - one char)
             }
         }
-        
-        int b1 = s.read();
-        int b2 = s.read();
-        
-        if (b1 == '\r' && b2 == '\n') {
-            if (matchBoundary()) {
-                return -1;
+        return bytesRead;
+    }
+    
+    private void calculateBoundaryLen() throws IOException {
+        boundaryLen = boundary.length;
+        int len = limit - buffer.pos();
+        if (len > 0) {
+            if (buffer.charAt(limit - 1) == '\n') {
+                boundaryLen++;
+                limit--;
             }
         }
-        
-        if (b2 != -1) {
-            s.unread(b2);
+        if (len > 1) {
+            if (buffer.charAt(limit - 1) == '\r') {
+                boundaryLen++;
+                limit--;
+            }
         }
-
-        parenteof = b1 == -1;
-        eof = parenteof;
-        
-        return b1;
     }
     
-    private boolean matchBoundary() throws IOException {
-        
-        for (int i = 0; i < boundary.length; i++) {
-            int b = s.read();
-            if (b != boundary[i]) {
-                if (b != -1) {
-                    s.unread(b);
+    private void skipBoundary() throws IOException {
+        if (!completed) {
+            completed = true;
+            buffer.skip(boundaryLen);
+            for (;;) {
+                if (buffer.length() > 1) {
+                    int ch1 = buffer.charAt(buffer.pos());
+                    int ch2 = buffer.charAt(buffer.pos() + 1);
+                    if (ch1 == '-' && ch2 == '-') {
+                        this.lastPart = true;
+                        buffer.skip(2);
+                        if (buffer.length() > 1) {
+                            ch1 = buffer.charAt(buffer.pos());
+                            ch2 = buffer.charAt(buffer.pos() + 1);
+                            if (ch1 == '\r' && ch2 == '\n') {
+                                buffer.skip(2);
+                            }
+                        }
+                    } else if (ch1 == '\r' && ch2 == '\n') {
+                        buffer.skip(2);
+                    }
+                    break;
+                } else {
+                    fillBuffer();
                 }
-                for (int j = i - 1; j >= 0; j--) {
-                    s.unread(boundary[j]);
+                if (eof) {
+                    break;
                 }
-                return false;
             }
         }
-        
-        /*
-         * We have a match. Is it an end boundary?
-         */
-        int prev = s.read();
-        int curr = s.read();
-        moreParts = !(prev == '-' && curr == '-');
-        do {
-            if (curr == '\n' && prev == '\r') {
-                break;
-            }
-            prev = curr;
-        } while ((curr = s.read()) != -1);
-        
-        if (curr == -1) {
-            moreParts = false;
-            parenteof = true;
-        }
-        
-        eof = true;
-        
-        return true;
+    }
+    
+    public boolean isLastPart() {
+        return lastPart;        
+    }
+    
+    public boolean eof() {
+        return eof && !buffer.hasBufferedData();
     }
+    
 }

Modified: james/mime4j/trunk/src/main/java/org/apache/james/mime4j/MimeException.java
URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/main/java/org/apache/james/mime4j/MimeException.java?rev=673195&r1=673194&r2=673195&view=diff
==============================================================================
--- james/mime4j/trunk/src/main/java/org/apache/james/mime4j/MimeException.java (original)
+++ james/mime4j/trunk/src/main/java/org/apache/james/mime4j/MimeException.java Tue Jul  1 13:31:51 2008
@@ -23,6 +23,8 @@
  */
 public class MimeException extends IOException {
 
+    private static final long serialVersionUID = 8352821278714188542L;
+
     /**
      * Constructs a new MIME exception with the specified detail message.
      *

Modified: james/mime4j/trunk/src/main/java/org/apache/james/mime4j/MimeTokenStream.java
URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/main/java/org/apache/james/mime4j/MimeTokenStream.java?rev=673195&r1=673194&r2=673195&view=diff
==============================================================================
--- james/mime4j/trunk/src/main/java/org/apache/james/mime4j/MimeTokenStream.java (original)
+++ james/mime4j/trunk/src/main/java/org/apache/james/mime4j/MimeTokenStream.java Tue Jul  1 13:31:51 2008
@@ -341,6 +341,7 @@
         private int pos, start;
         private int lineNumber, startLineNumber;
         private final int endState;
+        private boolean raw;
         
         String field, fieldName, fieldValue;
 
@@ -357,7 +358,7 @@
 
         private int setParseBodyPartState() throws IOException, MimeException {
             cursor.advanceToBoundary();
-            if (cursor.isEnded()) {
+            if (cursor.isEnded() && cursor.moreMimeParts()) {
                 monitor(Event.MIME_BODY_PREMATURE_END);
             } else {
                 if (cursor.moreMimeParts()) {
@@ -414,7 +415,7 @@
                     break;
                 case T_START_MULTIPART:
                     cursor.boundary(body.getBoundary());
-                    if (cursor.isEnded()) {
+                    if (cursor.isEnded() || raw) {
                         state = T_END_MULTIPART;
                     } else {
                         state = T_PREAMBLE;
@@ -449,6 +450,7 @@
                 case T_BODY:
                     return cursor.nextSection();
                 case T_START_MULTIPART:
+                    raw = true;
                     return cursor.rest();
                 default:
                     throw new IllegalStateException("Expected state to be either of T_RAW_ENTITY, T_PREAMBLE, or T_EPILOGUE.");
@@ -535,6 +537,16 @@
         Message(Cursor cursor, BodyDescriptor parent) {
             super(cursor, parent, T_START_MESSAGE, T_END_MESSAGE);
         }
+
+        InputStream read() {
+            switch (getState()) {
+            case T_EPILOGUE:
+                return cursor.root();
+            default:
+                return super.read();
+            }
+        }
+        
     }
 
     private class BodyPart extends Entity {

Modified: james/mime4j/trunk/src/main/java/org/apache/james/mime4j/StreamCursor.java
URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/main/java/org/apache/james/mime4j/StreamCursor.java?rev=673195&r1=673194&r2=673195&view=diff
==============================================================================
--- james/mime4j/trunk/src/main/java/org/apache/james/mime4j/StreamCursor.java (original)
+++ james/mime4j/trunk/src/main/java/org/apache/james/mime4j/StreamCursor.java Tue Jul  1 13:31:51 2008
@@ -30,19 +30,21 @@
  */
 public class StreamCursor implements Cursor {
 
-    final RootInputStream rootInputStream;
-    final InputStream contents;
-    MimeBoundaryInputStream mbis;
-    InputStream stream;
+    protected final InputBuffer buffer;
+    protected final BufferingInputStream bufferingInputStream;
+    protected final RootInputStream rootInputStream;
+    
+    protected MimeBoundaryInputStream mbis;
+    protected InputStream contentStream;
     
     /**
      * Constructs a child cursor.
      * @param parent <code>Cursor</code>, not null 
-     * @param contents <code>InputStream</code> contents, not null
      */
-    StreamCursor(StreamCursor parent, InputStream contents) {
+    StreamCursor(StreamCursor parent) {
+        this.buffer = parent.buffer;
+        this.bufferingInputStream = parent.bufferingInputStream;
         this.rootInputStream = parent.rootInputStream;
-        this.contents = contents;
     }
     
     /**
@@ -50,18 +52,22 @@
      * @param stream <code>InputStream</code>, not null
      */
     StreamCursor(InputStream stream) {
-        rootInputStream = new RootInputStream(stream);
-        contents = rootInputStream;
+        this.buffer = new InputBuffer(stream, 1024 * 4);
+        this.bufferingInputStream = new BufferingInputStream(this.buffer);
+        this.rootInputStream = new RootInputStream(this.bufferingInputStream);
+        this.contentStream = this.rootInputStream;
     }
     
     /**
-     * Constructs a cursor with the given root and contents.
-     * @param rootInputStream <code>RootInputStream</code>, not null
-     * @param contents <code>InputStream</code>, not null
-     */
-    StreamCursor(RootInputStream rootInputStream, InputStream contents) {
-        this.rootInputStream = rootInputStream;
-        this.contents = contents;
+     * Constructs a cursor from the previous cursor.
+     * @param previous <code>StreamCursor</code>, not null
+     * @param contentStream <code>InputStream</code>, not null
+     */
+    StreamCursor(StreamCursor previous, InputStream contentStream) {
+        this.buffer = previous.buffer;
+        this.bufferingInputStream = previous.bufferingInputStream;
+        this.rootInputStream = previous.rootInputStream;
+        this.contentStream = contentStream;
     }
     
     /**
@@ -82,65 +88,61 @@
      * @see Cursor#decodeBase64()
      */
     public Cursor decodeBase64() throws IOException {
-        InputStream is = new EOLConvertingInputStream(new Base64InputStream(contents));
-        return new StreamCursor(rootInputStream, is);
+        return new StreamCursor(this, 
+                new EOLConvertingInputStream(new Base64InputStream(mbis)));
     }
     
     /**
      * @see Cursor#decodeQuotedPrintable()
      */
     public Cursor decodeQuotedPrintable() throws IOException {
-        InputStream is = new EOLConvertingInputStream(new QuotedPrintableInputStream(contents));
-        return new StreamCursor(rootInputStream, is);
+        return new StreamCursor(this, 
+                new EOLConvertingInputStream(new QuotedPrintableInputStream(mbis)));
     }
     
     /**
      * @see Cursor#advanceToBoundary()
      */
     public void advanceToBoundary() throws IOException {
-        mbis.consume();
-        stream = null;
+        byte[] tmp = new byte[2048];
+        while (mbis.read(tmp)!= -1) {
+        }
     }
 
     /**
      * @see Cursor#isEnded()
      */
     public boolean isEnded() throws IOException {
-        return mbis.parentEOF();
+        return mbis.eof();
     }
 
     /**
      * @see Cursor#moreMimeParts()
      */
     public boolean moreMimeParts() throws IOException {
-        return mbis.hasMoreParts();
+        return !mbis.isLastPart();
     }
 
     /**
      * @see Cursor#boundary(String)
      */
     public void boundary(String boundary) throws IOException {
-        mbis = new MimeBoundaryInputStream(contents, boundary);
-        stream = new CloseShieldInputStream(mbis);
+        mbis = new MimeBoundaryInputStream(buffer, boundary);
+        contentStream = new CloseShieldInputStream(mbis);
     }
 
     /**
      * @see Cursor#nextMimePartCursor()
      */
     public Cursor nextMimePartCursor() {
-        return new StreamCursor(rootInputStream, mbis);
+        return new StreamCursor(this, mbis);
     }
 
     /**
      * @see Cursor#nextSection()
      */
     public InputStream nextSection() {
-        InputStream result = stream;
-        if (result == null)
-        {
-            result = contents;
-        }
-        return result;
+        return contentStream;
     }
 
     /**
@@ -151,8 +153,11 @@
     }
 
     public InputStream rest() {
-        return contents;
+        return contentStream;
+    }
+
+    public InputStream root() {
+        return rootInputStream;
     }
-    
     
 }

Added: james/mime4j/trunk/src/test/java/org/apache/james/mime4j/InputBufferTest.java
URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/test/java/org/apache/james/mime4j/InputBufferTest.java?rev=673195&view=auto
==============================================================================
--- james/mime4j/trunk/src/test/java/org/apache/james/mime4j/InputBufferTest.java (added)
+++ james/mime4j/trunk/src/test/java/org/apache/james/mime4j/InputBufferTest.java Tue Jul  1 13:31:51 2008
@@ -0,0 +1,61 @@
+/****************************************************************
+ * 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;
+
+import java.io.ByteArrayInputStream;
+
+import junit.framework.TestCase;
+
+public class InputBufferTest extends TestCase {
+
+    public void testPatternMatching1() throws Exception {
+        String text = "blabla d is the word";
+        String pattern = "d";
+        byte[] b1 = text.getBytes("US-ASCII");
+        byte[] b2 = pattern.getBytes("US-ASCII");
+        InputBuffer inbuffer = new InputBuffer(new ByteArrayInputStream(b1), 4096);
+        inbuffer.fillBuffer();
+        int i = inbuffer.indexOf(b2);
+        assertEquals(7, i);
+    }
+    
+    public void testPatternMatching2() throws Exception {
+        String text = "disddisdissdsidsidsiid";
+        String pattern = "siid";
+        byte[] b1 = text.getBytes("US-ASCII");
+        byte[] b2 = pattern.getBytes("US-ASCII");
+        InputBuffer inbuffer = new InputBuffer(new ByteArrayInputStream(b1), 4096);
+        inbuffer.fillBuffer();
+        int i = inbuffer.indexOf(b2);
+        assertEquals(18, i);
+    }
+    
+    public void testPatternMatching3() throws Exception {
+        String text = "bla bla yada yada haha haha";
+        String pattern = "blah";
+        byte[] b1 = text.getBytes("US-ASCII");
+        byte[] b2 = pattern.getBytes("US-ASCII");
+        InputBuffer inbuffer = new InputBuffer(new ByteArrayInputStream(b1), 4096);
+        inbuffer.fillBuffer();
+        int i = inbuffer.indexOf(b2);
+        assertEquals(-1, i);
+    }
+    
+}

Propchange: james/mime4j/trunk/src/test/java/org/apache/james/mime4j/InputBufferTest.java
------------------------------------------------------------------------------
    svn:mergeinfo = 

Modified: james/mime4j/trunk/src/test/java/org/apache/james/mime4j/MimeBoundaryInputStreamTest.java
URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/test/java/org/apache/james/mime4j/MimeBoundaryInputStreamTest.java?rev=673195&r1=673194&r2=673195&view=diff
==============================================================================
--- james/mime4j/trunk/src/test/java/org/apache/james/mime4j/MimeBoundaryInputStreamTest.java (original)
+++ james/mime4j/trunk/src/test/java/org/apache/james/mime4j/MimeBoundaryInputStreamTest.java Tue Jul  1 13:31:51 2008
@@ -35,42 +35,112 @@
  * @version $Id: MimeBoundaryInputStreamTest.java,v 1.2 2004/10/02 12:41:11 ntherning Exp $
  */
 public class MimeBoundaryInputStreamTest extends TestCase {
-    /**
-     * Tests that a CRLF immediately preceding a boundary isn't included in
-     * the stream.
-     */
-    public void testCRLFPrecedingBoundary() throws IOException {
+
+    public void testBasicReading() throws IOException {
         String text = "Line 1\r\nLine 2\r\n--boundary\r\n" +
-                "Line 3\r\nLine 4\r\n\r\n--boundary\r\n";
-        
-        ByteArrayInputStream bis = new ByteArrayInputStream(text.getBytes());
-        
-        assertEquals("Line 1\r\nLine 2", 
-                     read(new MimeBoundaryInputStream(bis, "boundary")));
-        
-        assertEquals("Line 3\r\nLine 4\r\n", 
-                     read(new MimeBoundaryInputStream(bis, "boundary")));
-    }
-    
-    public void testBigEnoughPushbackBuffer() throws IOException {
-        String text = "Line 1\r\nLine 2\r\n--boundar\r\n" +
-                "Line 3\r\nLine 4\r\n\r\n--boundary\r\n";
-        
-        ByteArrayInputStream bis = new ByteArrayInputStream(text.getBytes());
-        
-        assertEquals("Line 1\r\nLine 2\r\n--boundar\r\n" +
-                     "Line 3\r\nLine 4\r\n", 
-                     read(new MimeBoundaryInputStream(bis, "boundary")));
+                "Line 3\r\nLine 4\r\n--boundary--";
+        
+        ByteArrayInputStream bis = new ByteArrayInputStream(text.getBytes("US-ASCII"));
+        
+        InputBuffer buffer = new InputBuffer(bis, 4096); 
+        
+        MimeBoundaryInputStream mime1 = new MimeBoundaryInputStream(buffer, "boundary");
+        assertEquals("Line 1\r\nLine 2", read(mime1, 5));
+        
+        assertFalse(mime1.isLastPart());
+        
+        MimeBoundaryInputStream mime2 = new MimeBoundaryInputStream(buffer, "boundary");
+        assertEquals("Line 3\r\nLine 4", read(mime2, 5));
+
+        assertTrue(mime2.isLastPart());
+    }
+    
+    public void testBasicReadingSmallBuffer1() throws IOException {
+        String text = "yadayadayadayadayadayadayadayadayadayadayadayadayadayadayadayada\r\n--boundary\r\n" +
+                "blahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblah\r\n--boundary--";
+        
+        ByteArrayInputStream bis = new ByteArrayInputStream(text.getBytes("US-ASCII"));
+        
+        InputBuffer buffer = new InputBuffer(bis, 20); 
+        
+        MimeBoundaryInputStream mime1 = new MimeBoundaryInputStream(buffer, "boundary");
+        assertEquals("yadayadayadayadayadayadayadayadayadayadayadayadayadayadayadayada", 
+                read(mime1, 10));
+        
+        assertFalse(mime1.isLastPart());
+        
+        MimeBoundaryInputStream mime2 = new MimeBoundaryInputStream(buffer, "boundary");
+        assertEquals("blahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblah", 
+                read(mime2, 10));
+
+        assertTrue(mime2.isLastPart());
+    }
+    
+    public void testBasicReadingSmallBuffer2() throws IOException {
+        String text = "yadayadayadayadayadayadayadayadayadayadayadayadayadayadayadayada\r\n--boundary\r\n" +
+                "blahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblah\r\n--boundary--";
+        
+        ByteArrayInputStream bis = new ByteArrayInputStream(text.getBytes("US-ASCII"));
+        
+        InputBuffer buffer = new InputBuffer(bis, 20); 
+        
+        MimeBoundaryInputStream mime1 = new MimeBoundaryInputStream(buffer, "boundary");
+        
+        assertEquals("yadayadayadayadayadayadayadayadayadayadayadayadayadayadayadayada", 
+                read(mime1, 25));
+        
+        assertFalse(mime1.isLastPart());
+        
+        MimeBoundaryInputStream mime2 = new MimeBoundaryInputStream(buffer, "boundary");
+        assertEquals("blahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblah", 
+                read(mime2, 25));
+
+        assertTrue(mime2.isLastPart());
+    }
+    
+    public void testBasicReadingByOneByte() throws IOException {
+        String text = "Line 1\r\nLine 2\r\n--boundary\r\n" +
+                "Line 3\r\nLine 4\r\n--boundary--";
+        
+        ByteArrayInputStream bis = new ByteArrayInputStream(text.getBytes("US-ASCII"));
+        
+        InputBuffer buffer = new InputBuffer(bis, 4096); 
+        
+        MimeBoundaryInputStream mime1 = new MimeBoundaryInputStream(buffer, "boundary");
+        assertEquals("Line 1\r\nLine 2", readByOneByte(mime1));
+        
+        assertFalse(mime1.isLastPart());
+        
+        MimeBoundaryInputStream mime2 = new MimeBoundaryInputStream(buffer, "boundary");
+        assertEquals("Line 3\r\nLine 4", readByOneByte(mime2));
+
+        assertTrue(mime2.isLastPart());
     }
     
     /**
-     * Tests that CR characters are ignored.
+     * Tests that a CRLF immediately preceding a boundary isn't included in
+     * the stream.
      */
-    public void testCRIgnored() throws IOException {
-        
+    public void testCRLFPrecedingBoundary() throws IOException {
+        String text = "Line 1\r\nLine 2\r\n--boundary\r\n" +
+                "Line 3\r\nLine 4\r\n\r\n--boundary\r\n";
+        
+        ByteArrayInputStream bis = new ByteArrayInputStream(text.getBytes("US-ASCII"));
+        
+        InputBuffer buffer = new InputBuffer(bis, 4096); 
+        
+        MimeBoundaryInputStream mime1 = new MimeBoundaryInputStream(buffer, "boundary");
+        assertEquals("Line 1\r\nLine 2", read(mime1, 5));
+        
+        assertFalse(mime1.isLastPart());
+        
+        MimeBoundaryInputStream mime2 = new MimeBoundaryInputStream(buffer, "boundary");
+        assertEquals("Line 3\r\nLine 4\r\n", read(mime2, 5));
+
+        assertFalse(mime2.isLastPart());
     }
     
-    private String read(InputStream is) throws IOException {
+    private String readByOneByte(InputStream is) throws IOException {
         StringBuffer sb = new StringBuffer();
         int b = 0;
         while ((b = is.read()) != -1) {
@@ -78,6 +148,18 @@
         }
         return sb.toString();
     }
+
+    private String read(InputStream is, int bufsize) throws IOException {
+        StringBuffer sb = new StringBuffer();
+        int l;
+        byte[] tmp = new byte[bufsize];
+        while ((l = is.read(tmp)) != -1) {
+            for (int i = 0; i < l; i++) {
+                sb.append((char) tmp[i]);
+            }
+        }
+        return sb.toString();
+    }
     
     /**
      * Tests that a stream containing only a boundary is empty.
@@ -86,15 +168,18 @@
         String text = "--boundary\r\n";
         
         ByteArrayInputStream bis = new ByteArrayInputStream(text.getBytes());
+        InputBuffer buffer = new InputBuffer(bis, 4096); 
+        
         MimeBoundaryInputStream stream = 
-            new MimeBoundaryInputStream(bis, "boundary");
+            new MimeBoundaryInputStream(buffer, "boundary");
         assertEquals(-1, stream.read());
         
         text = "\r\n--boundary\r\n";
         
         bis = new ByteArrayInputStream(text.getBytes());
+        buffer = new InputBuffer(bis, 4096); 
         stream = 
-            new MimeBoundaryInputStream(bis, "boundary");
+            new MimeBoundaryInputStream(buffer, "boundary");
         assertEquals(-1, stream.read());
     }
     
@@ -105,11 +190,11 @@
         String text = "--boundary--\r\n";
         
         ByteArrayInputStream bis = new ByteArrayInputStream(text.getBytes());
+        InputBuffer buffer = new InputBuffer(bis, 4096); 
         MimeBoundaryInputStream stream = 
-            new MimeBoundaryInputStream(bis, "boundary");
-        assertFalse(stream.hasMoreParts());
+            new MimeBoundaryInputStream(buffer, "boundary");
         assertEquals(-1, stream.read());
-
+        assertTrue(stream.isLastPart());
     }
     
     /**
@@ -119,15 +204,16 @@
         String text = "Line 1\r\n\r\n--boundaryyada\r\n";
         
         ByteArrayInputStream bis = new ByteArrayInputStream(text.getBytes());
+        InputBuffer buffer = new InputBuffer(bis, 4096); 
         MimeBoundaryInputStream stream = 
-            new MimeBoundaryInputStream(bis, "boundary");
-        assertEquals("Line 1\r\n", read(stream));
+            new MimeBoundaryInputStream(buffer, "boundary");
+        assertEquals("Line 1\r\n", read(stream, 100));
         
         text = "--boundaryyada\r\n";
         
         bis = new ByteArrayInputStream(text.getBytes());
-        stream = 
-            new MimeBoundaryInputStream(bis, "boundary");
+        buffer = new InputBuffer(bis, 4096); 
+        stream = new MimeBoundaryInputStream(buffer, "boundary");
         assertEquals(-1, stream.read());
     }    
 }

Modified: james/mime4j/trunk/src/test/java/org/apache/james/mime4j/MimeTokenNoRecurseTest.java
URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/test/java/org/apache/james/mime4j/MimeTokenNoRecurseTest.java?rev=673195&r1=673194&r2=673195&view=diff
==============================================================================
--- james/mime4j/trunk/src/test/java/org/apache/james/mime4j/MimeTokenNoRecurseTest.java (original)
+++ james/mime4j/trunk/src/test/java/org/apache/james/mime4j/MimeTokenNoRecurseTest.java Tue Jul  1 13:31:51 2008
@@ -38,7 +38,7 @@
                 "--42\r\n" +
                 "Content-Type:text/plain; charset=US-ASCII\r\n\r\n" +
                 "Second part of this mail\r\n" +
-                "--42\r\n";
+                "--42--\r\n";
 
     private static final String MAIL_WITH_RFC822_PART = "MIME-Version: 1.0\r\n" +
             "From: Timothy Tayler <ti...@example.org>\r\n" +



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