From d438fb0d1d9f2c25e85114c1ecc73263c1cc21d5 Mon Sep 17 00:00:00 2001
From: Robert Burrell Donkin
Date: Tue, 8 Jul 2008 20:44:06 +0000
Subject: [PATCH] MIME4J-5 Performance patch 3,
https://issues.apache.org/jira/browse/MIME4J-5. Contributed by Oleg
Kalnichevski. This patch eliminates one-byte-reads for common use cases;
eliminates the synchronised StringBuffer and reduces memory footprint.
git-svn-id: https://svn.eu.apache.org/repos/asf/james/mime4j/trunk@674944 13f79535-47bb-0310-9956-ffa450edef68
---
.../org/apache/james/mime4j/AbstractEntity.java | 158 ++++-----
...tStream.java => BasicBufferingInputStream.java} | 48 ++-
.../apache/james/mime4j/BufferingInputStream.java | 94 +++---
...tream.java => BufferingInputStreamAdaptor.java} | 164 ++++++----
.../org/apache/james/mime4j/ByteArrayBuffer.java | 141 ++++++++
.../org/apache/james/mime4j/CharArrayBuffer.java | 247 ++++++++++++++
src/main/java/org/apache/james/mime4j/Event.java | 3 +
.../java/org/apache/james/mime4j/InputBuffer.java | 54 +++-
.../james/mime4j/MimeBoundaryInputStream.java | 88 ++++-
.../java/org/apache/james/mime4j/MimeEntity.java | 29 +-
.../org/apache/james/mime4j/MimeTokenStream.java | 4 +-
.../org/apache/james/mime4j/util/MessageUtils.java | 10 +
.../mime4j/BasicBufferingInputStreamTest.java | 144 +++++++++
.../mime4j/BufferingInputStreamAdaptorTest.java | 144 +++++++++
.../org/apache/james/mime4j/InputBufferTest.java | 129 ++++++++
.../james/mime4j/MimeBoundaryInputStreamTest.java | 116 ++++++-
.../org/apache/james/mime4j/MimeEntityTest.java | 6 +-
.../james/mime4j/StrictMimeTokenStreamTest.java | 1 +
.../apache/james/mime4j/TestByteArrayBuffer.java | 229 +++++++++++++
.../apache/james/mime4j/TestCharArrayBuffer.java | 357 +++++++++++++++++++++
20 files changed, 1927 insertions(+), 239 deletions(-)
copy src/main/java/org/apache/james/mime4j/{BufferingInputStream.java => BasicBufferingInputStream.java} (56%)
rename src/main/java/org/apache/james/mime4j/{EOFSensitiveInputStream.java => BufferingInputStreamAdaptor.java} (62%)
create mode 100644 src/main/java/org/apache/james/mime4j/ByteArrayBuffer.java
create mode 100644 src/main/java/org/apache/james/mime4j/CharArrayBuffer.java
create mode 100644 src/test/java/org/apache/james/mime4j/BasicBufferingInputStreamTest.java
create mode 100644 src/test/java/org/apache/james/mime4j/BufferingInputStreamAdaptorTest.java
create mode 100644 src/test/java/org/apache/james/mime4j/TestByteArrayBuffer.java
create mode 100644 src/test/java/org/apache/james/mime4j/TestCharArrayBuffer.java
diff --git a/src/main/java/org/apache/james/mime4j/AbstractEntity.java b/src/main/java/org/apache/james/mime4j/AbstractEntity.java
index 5a2efc0..1eaa2ba 100644
--- a/src/main/java/org/apache/james/mime4j/AbstractEntity.java
+++ b/src/main/java/org/apache/james/mime4j/AbstractEntity.java
@@ -20,11 +20,11 @@
package org.apache.james.mime4j;
import java.io.IOException;
-import java.io.InputStream;
import java.util.BitSet;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
+import org.apache.james.mime4j.util.MessageUtils;
/**
* Abstract MIME entity.
@@ -33,7 +33,6 @@ public abstract class AbstractEntity implements EntityStateMachine {
protected final Log log;
- protected final RootInputStream rootStream;
protected final BodyDescriptor parent;
protected final int startState;
protected final int endState;
@@ -43,12 +42,12 @@ public abstract class AbstractEntity implements EntityStateMachine {
protected int state;
- private final StringBuffer sb = new StringBuffer();
-
- private int pos, start;
- private int lineNumber, startLineNumber;
-
+ private final ByteArrayBuffer linebuf;
+ private final CharArrayBuffer fieldbuf;
+
+ private int lineCount;
private String field, fieldName, fieldValue;
+ private boolean endOfHeader;
private static final BitSet fieldChars = new BitSet();
@@ -71,14 +70,12 @@ public abstract class AbstractEntity implements EntityStateMachine {
private static final int T_IN_MESSAGE = -3;
AbstractEntity(
- RootInputStream rootStream,
BodyDescriptor parent,
int startState,
int endState,
boolean maximalBodyDescriptor,
boolean strictParsing) {
this.log = LogFactory.getLog(getClass());
- this.rootStream = rootStream;
this.parent = parent;
this.state = startState;
this.startState = startState;
@@ -86,6 +83,10 @@ public abstract class AbstractEntity implements EntityStateMachine {
this.maximalBodyDescriptor = maximalBodyDescriptor;
this.strictParsing = strictParsing;
this.body = newBodyDescriptor(parent);
+ this.linebuf = new ByteArrayBuffer(64);
+ this.fieldbuf = new CharArrayBuffer(64);
+ this.lineCount = 0;
+ this.endOfHeader = false;
}
public int getState() {
@@ -106,83 +107,92 @@ public abstract class AbstractEntity implements EntityStateMachine {
}
return result;
}
+
+ protected abstract int getLineNumber();
- protected abstract InputStream getDataStream();
+ protected abstract BufferingInputStream getDataStream();
- protected void initHeaderParsing() throws IOException, MimeException {
- startLineNumber = lineNumber = rootStream.getLineNumber();
-
- InputStream instream = getDataStream();
-
- int curr = 0;
- int prev = 0;
- while ((curr = instream.read()) != -1) {
- if (curr == '\n' && (prev == '\n' || prev == 0)) {
- /*
- * [\r]\n[\r]\n or an immediate \r\n have been seen.
- */
- sb.deleteCharAt(sb.length() - 1);
+ private void fillFieldBuffer() throws IOException, MimeException {
+ if (endOfHeader) {
+ return;
+ }
+ BufferingInputStream instream = getDataStream();
+ fieldbuf.clear();
+ for (;;) {
+ // If there's still data stuck in the line buffer
+ // copy it to the field buffer
+ int len = linebuf.length();
+ if (len > 0) {
+ fieldbuf.append(linebuf, 0, len);
+ }
+ linebuf.clear();
+ if (instream.readLine(linebuf) == -1) {
+ monitor(Event.HEADERS_PREMATURE_END);
+ endOfHeader = true;
break;
}
- sb.append((char) curr);
- prev = curr == '\r' ? prev : curr;
- }
-
- if (curr == -1) {
- monitor(Event.HEADERS_PREMATURE_END);
+ len = linebuf.length();
+ if (len > 0 && linebuf.byteAt(len - 1) == '\n') {
+ len--;
+ }
+ if (len > 0 && linebuf.byteAt(len - 1) == '\r') {
+ len--;
+ }
+ if (len == 0) {
+ // empty line detected
+ endOfHeader = true;
+ break;
+ }
+ lineCount++;
+ if (lineCount > 1) {
+ int ch = linebuf.byteAt(0);
+ if (ch != MessageUtils.SP && ch != MessageUtils.HT) {
+ // new header detected
+ break;
+ }
+ }
}
}
- protected boolean parseField() {
- while (pos < sb.length()) {
- while (pos < sb.length() && sb.charAt(pos) != '\r') {
- pos++;
+ protected boolean parseField() throws IOException {
+ for (;;) {
+ if (endOfHeader) {
+ return false;
}
- if (pos < sb.length() - 1 && sb.charAt(pos + 1) != '\n') {
- pos++;
- continue;
+ fillFieldBuffer();
+
+ // Strip away line delimiter
+ int len = fieldbuf.length();
+ if (len > 0 && fieldbuf.charAt(len - 1) == '\n') {
+ len--;
}
- if (pos >= sb.length() - 2 || fieldChars.get(sb.charAt(pos + 2))) {
- /*
- * field should be the complete field data excluding the
- * trailing \r\n.
- */
- field = sb.substring(start, pos);
- start = pos + 2;
-
- /*
- * Check for a valid field.
- */
- int index = field.indexOf(':');
- boolean valid = false;
- if (index != -1 && fieldChars.get(field.charAt(0))) {
- valid = true;
- fieldName = field.substring(0, index).trim();
- for (int i = 0; i < fieldName.length(); i++) {
- if (!fieldChars.get(fieldName.charAt(i))) {
- valid = false;
- break;
- }
- }
- if (valid) {
- fieldValue = field.substring(index + 1);
- body.addField(fieldName, fieldValue);
- startLineNumber = lineNumber;
- pos += 2;
- lineNumber++;
- return true;
+ if (len > 0 && fieldbuf.charAt(len - 1) == '\r') {
+ len--;
+ }
+ fieldbuf.setLength(len);
+
+ boolean valid = true;
+ field = fieldbuf.toString();
+ int pos = fieldbuf.indexOf(':');
+ if (pos == -1) {
+ monitor(Event.INALID_HEADER);
+ valid = false;
+ } else {
+ fieldName = fieldbuf.substring(0, pos);
+ for (int i = 0; i < fieldName.length(); i++) {
+ if (!fieldChars.get(fieldName.charAt(i))) {
+ monitor(Event.INALID_HEADER);
+ valid = false;
+ break;
}
}
- if (log.isWarnEnabled()) {
- log.warn("Line " + startLineNumber
- + ": Ignoring invalid field: '" + field.trim() + "'");
- }
- startLineNumber = lineNumber;
+ fieldValue = fieldbuf.substring(pos + 1, fieldbuf.length());
+ }
+ if (valid) {
+ body.addField(fieldName, fieldValue);
+ return true;
}
- pos += 2;
- lineNumber++;
}
- return false;
}
/**
@@ -278,7 +288,7 @@ public abstract class AbstractEntity implements EntityStateMachine {
* or for logging
*/
protected String message(Event event) {
- String preamble = "Line " + rootStream.getLineNumber() + ": ";
+ String preamble = "Line " + getLineNumber() + ": ";
final String message;
if (event == null) {
message = "Event is unexpectedly null.";
diff --git a/src/main/java/org/apache/james/mime4j/BufferingInputStream.java b/src/main/java/org/apache/james/mime4j/BasicBufferingInputStream.java
similarity index 56%
copy from src/main/java/org/apache/james/mime4j/BufferingInputStream.java
copy to src/main/java/org/apache/james/mime4j/BasicBufferingInputStream.java
index 62034b4..50bc06e 100644
--- a/src/main/java/org/apache/james/mime4j/BufferingInputStream.java
+++ b/src/main/java/org/apache/james/mime4j/BasicBufferingInputStream.java
@@ -20,20 +20,21 @@
package org.apache.james.mime4j;
import java.io.IOException;
-import java.io.InputStream;
/**
- * Implementation of {@link InputStream} backed by an {@link InputBuffer} instance.
+ * Implementation of {@link BufferingInputStream} backed by an {@link InputBuffer} instance.
+ *
+ * @version $Id$
*/
-public class BufferingInputStream extends InputStream {
+public class BasicBufferingInputStream extends BufferingInputStream {
private final InputBuffer buffer;
-
- public BufferingInputStream(final InputBuffer buffer) {
+
+ public BasicBufferingInputStream(final InputBuffer buffer) {
super();
this.buffer = buffer;
}
-
+
public void close() throws IOException {
this.buffer.closeStream();
}
@@ -50,4 +51,39 @@ public class BufferingInputStream extends InputStream {
return this.buffer.read(b, off, len);
}
+ public int readLine(final ByteArrayBuffer linebuf) throws IOException {
+ if (linebuf == null) {
+ throw new IllegalArgumentException("Buffer may not be null");
+ }
+ int total = 0;
+ boolean found = false;
+ int bytesRead = 0;
+ while (!found) {
+ if (!this.buffer.hasBufferedData()) {
+ bytesRead = this.buffer.fillBuffer();
+ if (bytesRead == -1) {
+ break;
+ }
+ }
+ int i = this.buffer.indexOf((byte)'\n');
+ int chunk;
+ if (i != -1) {
+ found = true;
+ chunk = i + 1 - this.buffer.pos();
+ } else {
+ chunk = this.buffer.length();
+ }
+ if (chunk > 0) {
+ linebuf.append(this.buffer.buf(), this.buffer.pos(), chunk);
+ this.buffer.skip(chunk);
+ total += chunk;
+ }
+ }
+ if (total == 0 && bytesRead == -1) {
+ return -1;
+ } else {
+ return total;
+ }
+ }
+
}
diff --git a/src/main/java/org/apache/james/mime4j/BufferingInputStream.java b/src/main/java/org/apache/james/mime4j/BufferingInputStream.java
index 62034b4..a33f17f 100644
--- a/src/main/java/org/apache/james/mime4j/BufferingInputStream.java
+++ b/src/main/java/org/apache/james/mime4j/BufferingInputStream.java
@@ -1,53 +1,41 @@
-/****************************************************************
- * 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);
- }
-
-}
+/****************************************************************
+ * 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 stream capable of reading lines of text.
+ */
+public abstract class BufferingInputStream extends InputStream {
+
+ /**
+ * Reads one line of text into the given {@link ByteArrayBuffer}.
+ *
+ * @param dst Destination
+ * @return number of bytes copied or -1
if the end of
+ * the stream has been reached.
+ *
+ * @throws IOException in case of an I/O error.
+ */
+ public abstract int readLine(final ByteArrayBuffer dst) throws IOException;
+
+}
diff --git a/src/main/java/org/apache/james/mime4j/EOFSensitiveInputStream.java b/src/main/java/org/apache/james/mime4j/BufferingInputStreamAdaptor.java
similarity index 62%
rename from src/main/java/org/apache/james/mime4j/EOFSensitiveInputStream.java
rename to src/main/java/org/apache/james/mime4j/BufferingInputStreamAdaptor.java
index 565cf96..d1c9751 100644
--- a/src/main/java/org/apache/james/mime4j/EOFSensitiveInputStream.java
+++ b/src/main/java/org/apache/james/mime4j/BufferingInputStreamAdaptor.java
@@ -1,64 +1,100 @@
-/****************************************************************
- * 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.FilterInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-
-/**
- * InputStream
used by the MIME parser to detect whether the
- * underlying data stream was used (read from) and whether the end of the
- * stream was reached.
- *
- * @version $Id$
- */
-class EOFSensitiveInputStream extends FilterInputStream {
-
- private boolean used = false;
- private boolean eof = false;
-
- public EOFSensitiveInputStream(InputStream is) {
- super(is);
- }
-
- public int read() throws IOException {
- int i = super.read();
- this.eof = i == -1;
- this.used = true;
- return i;
- }
-
- public int read(byte[] b, int off, int len) throws IOException {
- int i = super.read(b, off, len);
- this.eof = i == -1;
- this.used = true;
- return i;
- }
-
- public boolean eof() {
- return this.eof;
- }
-
- public boolean isUsed() {
- return this.used;
- }
-
-}
+/****************************************************************
+ * 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;
+
+/**
+ * InputStream
used by the MIME parser to detect whether the
+ * underlying data stream was used (read from) and whether the end of the
+ * stream was reached.
+ *
+ * @version $Id$
+ */
+class BufferingInputStreamAdaptor extends BufferingInputStream {
+
+ private final InputStream is;
+ private final BufferingInputStream bis;
+ private boolean used = false;
+ private boolean eof = false;
+
+ public BufferingInputStreamAdaptor(final InputStream is) {
+ super();
+ this.is = is;
+ if (is instanceof BufferingInputStream) {
+ this.bis = (BufferingInputStream) is;
+ } else {
+ this.bis = null;
+ }
+ }
+
+ public int read() throws IOException {
+ int i = this.is.read();
+ this.eof = i == -1;
+ this.used = true;
+ return i;
+ }
+
+ public int read(byte[] b, int off, int len) throws IOException {
+ int i = this.is.read(b, off, len);
+ this.eof = i == -1;
+ this.used = true;
+ return i;
+ }
+
+ public int readLine(final ByteArrayBuffer dst) throws IOException {
+ int i;
+ if (this.bis != null) {
+ i = this.bis.readLine(dst);
+ } else {
+ i = doReadLine(dst);
+ }
+ this.eof = i == -1;
+ this.used = true;
+ return i;
+ }
+
+ private int doReadLine(final ByteArrayBuffer dst) throws IOException {
+ int total = 0;
+ int ch;
+ while ((ch = this.is.read()) != -1) {
+ dst.append(ch);
+ total++;
+ if (ch == '\n') {
+ break;
+ }
+ }
+ if (total == 0 && ch == -1) {
+ return -1;
+ } else {
+ return total;
+ }
+ }
+
+ public boolean eof() {
+ return this.eof;
+ }
+
+ public boolean isUsed() {
+ return this.used;
+ }
+
+}
diff --git a/src/main/java/org/apache/james/mime4j/ByteArrayBuffer.java b/src/main/java/org/apache/james/mime4j/ByteArrayBuffer.java
new file mode 100644
index 0000000..7617b58
--- /dev/null
+++ b/src/main/java/org/apache/james/mime4j/ByteArrayBuffer.java
@@ -0,0 +1,141 @@
+/****************************************************************
+ * 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;
+
+/**
+ * A resizable byte array.
+ */
+public final class ByteArrayBuffer {
+
+ private byte[] buffer;
+ private int len;
+
+ public ByteArrayBuffer(int capacity) {
+ super();
+ if (capacity < 0) {
+ throw new IllegalArgumentException("Buffer capacity may not be negative");
+ }
+ this.buffer = new byte[capacity];
+ }
+
+ private void expand(int newlen) {
+ byte newbuffer[] = new byte[Math.max(this.buffer.length << 1, newlen)];
+ System.arraycopy(this.buffer, 0, newbuffer, 0, this.len);
+ 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 append(final char[] 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 oldlen = this.len;
+ int newlen = oldlen + len;
+ if (newlen > this.buffer.length) {
+ expand(newlen);
+ }
+ for (int i1 = off, i2 = oldlen; i2 < newlen; i1++, i2++) {
+ this.buffer[i2] = (byte) b[i1];
+ }
+ 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 int byteAt(int i) {
+ return this.buffer[i];
+ }
+
+ public int capacity() {
+ return this.buffer.length;
+ }
+
+ public int length() {
+ return this.len;
+ }
+
+ public byte[] buffer() {
+ return this.buffer;
+ }
+
+ public void setLength(int len) {
+ if (len < 0 || len > this.buffer.length) {
+ throw new IndexOutOfBoundsException();
+ }
+ this.len = len;
+ }
+
+ public boolean isEmpty() {
+ return this.len == 0;
+ }
+
+ public boolean isFull() {
+ return this.len == this.buffer.length;
+ }
+
+ public String toString() {
+ return new String(toByteArray());
+ }
+
+}
diff --git a/src/main/java/org/apache/james/mime4j/CharArrayBuffer.java b/src/main/java/org/apache/james/mime4j/CharArrayBuffer.java
new file mode 100644
index 0000000..fd9453f
--- /dev/null
+++ b/src/main/java/org/apache/james/mime4j/CharArrayBuffer.java
@@ -0,0 +1,247 @@
+/****************************************************************
+ * 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 org.apache.james.mime4j.util.MessageUtils;
+
+/**
+ * A resizable char array.
+ *
+ */
+public final class CharArrayBuffer {
+
+ private char[] buffer;
+ private int len;
+
+ public CharArrayBuffer(int capacity) {
+ super();
+ if (capacity < 0) {
+ throw new IllegalArgumentException("Buffer capacity may not be negative");
+ }
+ this.buffer = new char[capacity];
+ }
+
+ private void expand(int newlen) {
+ char newbuffer[] = new char[Math.max(this.buffer.length << 1, newlen)];
+ System.arraycopy(this.buffer, 0, newbuffer, 0, this.len);
+ this.buffer = newbuffer;
+ }
+
+ public void append(final char[] 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(String str) {
+ if (str == null) {
+ str = "null";
+ }
+ int strlen = str.length();
+ int newlen = this.len + strlen;
+ if (newlen > this.buffer.length) {
+ expand(newlen);
+ }
+ str.getChars(0, strlen, this.buffer, this.len);
+ this.len = newlen;
+ }
+
+ public void append(final CharArrayBuffer b, int off, int len) {
+ if (b == null) {
+ return;
+ }
+ append(b.buffer, off, len);
+ }
+
+ public void append(final CharArrayBuffer b) {
+ if (b == null) {
+ return;
+ }
+ append(b.buffer,0, b.len);
+ }
+
+ public void append(char ch) {
+ int newlen = this.len + 1;
+ if (newlen > this.buffer.length) {
+ expand(newlen);
+ }
+ this.buffer[this.len] = ch;
+ this.len = newlen;
+ }
+
+ 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 oldlen = this.len;
+ int newlen = oldlen + len;
+ if (newlen > this.buffer.length) {
+ expand(newlen);
+ }
+ for (int i1 = off, i2 = oldlen; i2 < newlen; i1++, i2++) {
+ int ch = b[i1];
+ if (ch < 0) {
+ ch = 256 + ch;
+ }
+ this.buffer[i2] = (char) ch;
+ }
+ this.len = newlen;
+ }
+
+ public void append(final ByteArrayBuffer b, int off, int len) {
+ if (b == null) {
+ return;
+ }
+ append(b.buffer(), off, len);
+ }
+
+ public void append(final Object obj) {
+ append(String.valueOf(obj));
+ }
+
+ public void clear() {
+ this.len = 0;
+ }
+
+ public char[] toCharArray() {
+ char[] b = new char[this.len];
+ if (this.len > 0) {
+ System.arraycopy(this.buffer, 0, b, 0, this.len);
+ }
+ return b;
+ }
+
+ public char charAt(int i) {
+ return this.buffer[i];
+ }
+
+ public char[] buffer() {
+ return this.buffer;
+ }
+
+ public int capacity() {
+ return this.buffer.length;
+ }
+
+ public int length() {
+ return this.len;
+ }
+
+ public void ensureCapacity(int required) {
+ int available = this.buffer.length - this.len;
+ if (required > available) {
+ expand(this.len + required);
+ }
+ }
+
+ public void setLength(int len) {
+ if (len < 0 || len > this.buffer.length) {
+ throw new IndexOutOfBoundsException();
+ }
+ this.len = len;
+ }
+
+ public boolean isEmpty() {
+ return this.len == 0;
+ }
+
+ public boolean isFull() {
+ return this.len == this.buffer.length;
+ }
+
+ public int indexOf(int ch, 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] == ch) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ public int indexOf(int ch) {
+ return indexOf(ch, 0, this.len);
+ }
+
+ public String substring(int beginIndex, int endIndex) {
+ if (beginIndex < 0) {
+ throw new IndexOutOfBoundsException();
+ }
+ if (endIndex > this.len) {
+ throw new IndexOutOfBoundsException();
+ }
+ if (beginIndex > endIndex) {
+ throw new IndexOutOfBoundsException();
+ }
+ return new String(this.buffer, beginIndex, endIndex - beginIndex);
+ }
+
+ public String substringTrimmed(int beginIndex, int endIndex) {
+ if (beginIndex < 0) {
+ throw new IndexOutOfBoundsException();
+ }
+ if (endIndex > this.len) {
+ throw new IndexOutOfBoundsException();
+ }
+ if (beginIndex > endIndex) {
+ throw new IndexOutOfBoundsException();
+ }
+ while (beginIndex < endIndex && MessageUtils.isWhitespace(this.buffer[beginIndex])) {
+ beginIndex++;
+ }
+ while (endIndex > beginIndex && MessageUtils.isWhitespace(this.buffer[endIndex - 1])) {
+ endIndex--;
+ }
+ return new String(this.buffer, beginIndex, endIndex - beginIndex);
+ }
+
+ public String toString() {
+ return new String(this.buffer, 0, this.len);
+ }
+
+}
diff --git a/src/main/java/org/apache/james/mime4j/Event.java b/src/main/java/org/apache/james/mime4j/Event.java
index 1a6d96f..88dcd4d 100644
--- a/src/main/java/org/apache/james/mime4j/Event.java
+++ b/src/main/java/org/apache/james/mime4j/Event.java
@@ -13,6 +13,9 @@ public final class Event {
public static final Event HEADERS_PREMATURE_END
= new Event("Unexpected end of headers detected. " +
"Higher level boundary detected or EOF reached.");
+ /** Indicates that unexpected end of headers detected.*/
+ public static final Event INALID_HEADER
+ = new Event("Invalid header encountered");
private final String code;
diff --git a/src/main/java/org/apache/james/mime4j/InputBuffer.java b/src/main/java/org/apache/james/mime4j/InputBuffer.java
index d441cad..eb92ecf 100644
--- a/src/main/java/org/apache/james/mime4j/InputBuffer.java
+++ b/src/main/java/org/apache/james/mime4j/InputBuffer.java
@@ -124,8 +124,13 @@ public class InputBuffer {
* Communications of the ACM . 33(8):132-142.
*
*/
- public int indexOf(final byte[] pattern) {
- int len = this.buflen - this.bufpos;
+ public int indexOf(final byte[] pattern, int off, int len) {
+ if (pattern == null) {
+ throw new IllegalArgumentException("Pattern may not be null");
+ }
+ if (off < this.bufpos || len < 0 || off + len > this.buflen) {
+ throw new IndexOutOfBoundsException();
+ }
if (len < pattern.length) {
return -1;
}
@@ -163,18 +168,57 @@ public class InputBuffer {
return -1;
}
- public int length() {
- return this.buflen;
+ /**
+ * Implements quick search algorithm as published by
+ *
+ * SUNDAY D.M., 1990,
+ * A very fast substring search algorithm,
+ * Communications of the ACM . 33(8):132-142.
+ *
+ */
+ public int indexOf(final byte[] pattern) {
+ return indexOf(pattern, this.bufpos, this.buflen - this.bufpos);
+ }
+
+ public int indexOf(byte b, int off, int len) {
+ if (off < this.bufpos || len < 0 || off + len > this.buflen) {
+ throw new IndexOutOfBoundsException();
+ }
+ for (int i = off; i < off + len; i++) {
+ if (this.buffer[i] == b) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ public int indexOf(byte b) {
+ return indexOf(b, this.bufpos, this.buflen - this.bufpos);
}
public byte charAt(int pos) {
+ if (pos < this.bufpos || pos > this.buflen) {
+ throw new IndexOutOfBoundsException();
+ }
return this.buffer[pos];
}
+ public byte[] buf() {
+ return this.buffer;
+ }
+
public int pos() {
return this.bufpos;
}
+ public int limit() {
+ return this.buflen;
+ }
+
+ public int length() {
+ return this.buflen - this.bufpos;
+ }
+
public int skip(int n) {
int chunk = Math.min(n, this.buflen - this.bufpos);
this.bufpos += chunk;
@@ -191,7 +235,7 @@ public class InputBuffer {
buffer.append("[pos: ");
buffer.append(this.bufpos);
buffer.append("]");
- buffer.append("[len: ");
+ buffer.append("[limit: ");
buffer.append(this.buflen);
buffer.append("]");
buffer.append("[");
diff --git a/src/main/java/org/apache/james/mime4j/MimeBoundaryInputStream.java b/src/main/java/org/apache/james/mime4j/MimeBoundaryInputStream.java
index d489b33..9246d2d 100644
--- a/src/main/java/org/apache/james/mime4j/MimeBoundaryInputStream.java
+++ b/src/main/java/org/apache/james/mime4j/MimeBoundaryInputStream.java
@@ -20,7 +20,6 @@
package org.apache.james.mime4j;
import java.io.IOException;
-import java.io.InputStream;
/**
* Stream that constrains itself to a single MIME body part.
@@ -29,7 +28,7 @@ import java.io.InputStream;
*
* @version $Id: MimeBoundaryInputStream.java,v 1.2 2004/11/29 13:15:42 ntherning Exp $
*/
-public class MimeBoundaryInputStream extends InputStream {
+public class MimeBoundaryInputStream extends BufferingInputStream {
private final InputBuffer buffer;
private final byte[] boundary;
@@ -121,13 +120,59 @@ public class MimeBoundaryInputStream extends InputStream {
int chunk = Math.min(len, limit - buffer.pos());
return buffer.read(b, off, chunk);
}
-
+
+ public int readLine(final ByteArrayBuffer dst) throws IOException {
+ if (dst == null) {
+ throw new IllegalArgumentException("Destination buffer may not be null");
+ }
+ if (completed) {
+ return -1;
+ }
+ if (endOfStream() && !hasData()) {
+ skipBoundary();
+ return -1;
+ }
+
+ int total = 0;
+ boolean found = false;
+ int bytesRead = 0;
+ while (!found) {
+ if (!hasData()) {
+ bytesRead = fillBuffer();
+ if (!hasData() && endOfStream()) {
+ skipBoundary();
+ bytesRead = -1;
+ break;
+ }
+ }
+ int len = this.limit - this.buffer.pos();
+ int i = this.buffer.indexOf((byte)'\n', this.buffer.pos(), len);
+ int chunk;
+ if (i != -1) {
+ found = true;
+ chunk = i + 1 - this.buffer.pos();
+ } else {
+ chunk = len;
+ }
+ if (chunk > 0) {
+ dst.append(this.buffer.buf(), this.buffer.pos(), chunk);
+ this.buffer.skip(chunk);
+ total += chunk;
+ }
+ }
+ if (total == 0 && bytesRead == -1) {
+ return -1;
+ } else {
+ return total;
+ }
+ }
+
private boolean endOfStream() {
return eof || atBoundary;
}
private boolean hasData() {
- return limit > buffer.pos() && limit < buffer.length();
+ return limit > buffer.pos() && limit < buffer.limit();
}
private int fillBuffer() throws IOException {
@@ -149,9 +194,9 @@ public class MimeBoundaryInputStream extends InputStream {
calculateBoundaryLen();
} else {
if (eof) {
- limit = buffer.length();
+ limit = buffer.limit();
} else {
- limit = buffer.length() - (boundary.length + 1);
+ limit = buffer.limit() - (boundary.length + 1);
// \r\n + (boundary - one char)
}
}
@@ -179,23 +224,15 @@ public class MimeBoundaryInputStream extends InputStream {
if (!completed) {
completed = true;
buffer.skip(boundaryLen);
- for (;;) {
+ 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);
- }
+ }
+ skipLineDelimiter();
break;
} else {
fillBuffer();
@@ -206,6 +243,23 @@ public class MimeBoundaryInputStream extends InputStream {
}
}
}
+
+ private void skipLineDelimiter() {
+ int ch1 = 0;
+ int ch2 = 0;
+ int len = buffer.length();
+ if (len > 0) {
+ ch1 = buffer.charAt(buffer.pos());
+ }
+ if (len > 1) {
+ ch2 = buffer.charAt(buffer.pos() + 1);
+ }
+ if (ch1 == '\r' && ch2 == '\n') {
+ buffer.skip(2);
+ } else if (ch1 == '\n') {
+ buffer.skip(1);
+ }
+ }
public boolean isLastPart() {
return lastPart;
diff --git a/src/main/java/org/apache/james/mime4j/MimeEntity.java b/src/main/java/org/apache/james/mime4j/MimeEntity.java
index d31a453..6d8dc0c 100644
--- a/src/main/java/org/apache/james/mime4j/MimeEntity.java
+++ b/src/main/java/org/apache/james/mime4j/MimeEntity.java
@@ -18,12 +18,13 @@ public class MimeEntity extends AbstractEntity {
*/
private static final int T_IN_MESSAGE = -3;
- private final InputBuffer inbuffer;
+ private final RootInputStream rootStream;
private final InputStream rawStream;
+ private final InputBuffer inbuffer;
private int recursionMode;
private MimeBoundaryInputStream mimeStream;
- private EOFSensitiveInputStream dataStream;
+ private BufferingInputStreamAdaptor dataStream;
private boolean skipHeader;
public MimeEntity(
@@ -35,10 +36,11 @@ public class MimeEntity extends AbstractEntity {
int endState,
boolean maximalBodyDescriptor,
boolean strictParsing) {
- super(rootStream, parent, startState, endState, maximalBodyDescriptor, strictParsing);
+ super(parent, startState, endState, maximalBodyDescriptor, strictParsing);
+ this.rootStream = rootStream;
this.inbuffer = inbuffer;
this.rawStream = rawStream;
- this.dataStream = new EOFSensitiveInputStream(rawStream);
+ this.dataStream = new BufferingInputStreamAdaptor(rawStream);
this.skipHeader = false;
}
@@ -68,7 +70,11 @@ public class MimeEntity extends AbstractEntity {
body.addField("Content-Type", contentType);
}
- protected InputStream getDataStream() {
+ protected int getLineNumber() {
+ return rootStream.getLineNumber();
+ }
+
+ protected BufferingInputStream getDataStream() {
return dataStream;
}
@@ -85,9 +91,6 @@ public class MimeEntity extends AbstractEntity {
state = EntityStates.T_START_HEADER;
break;
case EntityStates.T_START_HEADER:
- initHeaderParsing();
- state = parseField() ? EntityStates.T_FIELD : EntityStates.T_END_HEADER;
- break;
case EntityStates.T_FIELD:
state = parseField() ? EntityStates.T_FIELD : EntityStates.T_END_HEADER;
break;
@@ -160,12 +163,12 @@ public class MimeEntity extends AbstractEntity {
private void createMimeStream() throws IOException {
mimeStream = new MimeBoundaryInputStream(inbuffer, body.getBoundary());
- dataStream = new EOFSensitiveInputStream(mimeStream);
+ dataStream = new BufferingInputStreamAdaptor(mimeStream);
}
private void clearMimeStream() {
mimeStream = null;
- dataStream = new EOFSensitiveInputStream(rawStream);
+ dataStream = new BufferingInputStreamAdaptor(rawStream);
}
private void advanceToBoundary() throws IOException {
@@ -181,12 +184,10 @@ public class MimeEntity extends AbstractEntity {
InputStream instream;
if (MimeUtil.isBase64Encoding(transferEncoding)) {
log.debug("base64 encoded message/rfc822 detected");
- instream = new EOLConvertingInputStream(
- new Base64InputStream(mimeStream));
+ instream = new Base64InputStream(mimeStream);
} else if (MimeUtil.isQuotedPrintableEncoded(transferEncoding)) {
log.debug("quoted-printable encoded message/rfc822 detected");
- instream = new EOLConvertingInputStream(
- new QuotedPrintableInputStream(mimeStream));
+ instream = new QuotedPrintableInputStream(mimeStream);
} else {
instream = dataStream;
}
diff --git a/src/main/java/org/apache/james/mime4j/MimeTokenStream.java b/src/main/java/org/apache/james/mime4j/MimeTokenStream.java
index 20d2d69..f3d9dd0 100644
--- a/src/main/java/org/apache/james/mime4j/MimeTokenStream.java
+++ b/src/main/java/org/apache/james/mime4j/MimeTokenStream.java
@@ -152,7 +152,7 @@ public class MimeTokenStream implements EntityStates, RecursionMode {
inbuffer = new InputBuffer(rootInputStream, 4 * 1024);
switch (recursionMode) {
case M_RAW:
- RawEntity rawentity = new RawEntity(new BufferingInputStream(inbuffer));
+ RawEntity rawentity = new RawEntity(new BasicBufferingInputStream(inbuffer));
currentStateMachine = rawentity;
break;
case M_NO_RECURSE:
@@ -161,7 +161,7 @@ public class MimeTokenStream implements EntityStates, RecursionMode {
case M_RECURSE:
MimeEntity mimeentity = new MimeEntity(
rootInputStream,
- new BufferingInputStream(inbuffer),
+ new BasicBufferingInputStream(inbuffer),
inbuffer,
null,
T_START_MESSAGE,
diff --git a/src/main/java/org/apache/james/mime4j/util/MessageUtils.java b/src/main/java/org/apache/james/mime4j/util/MessageUtils.java
index ab47324..fc4a3b6 100644
--- a/src/main/java/org/apache/james/mime4j/util/MessageUtils.java
+++ b/src/main/java/org/apache/james/mime4j/util/MessageUtils.java
@@ -41,6 +41,11 @@ public final class MessageUtils {
public static final String CRLF = "\r\n";
+ public static final int CR = 13; //
+ public static final int LF = 10; //
+ public static final int SP = 32; //
+ public static final int HT = 9; //
+
public static boolean isASCII(char ch) {
return (0xFF80 & ch) == 0;
}
@@ -57,4 +62,9 @@ public final class MessageUtils {
}
return true;
}
+
+ public static boolean isWhitespace(char ch) {
+ return ch == SP || ch == HT || ch == CR || ch == LF;
+ }
+
}
diff --git a/src/test/java/org/apache/james/mime4j/BasicBufferingInputStreamTest.java b/src/test/java/org/apache/james/mime4j/BasicBufferingInputStreamTest.java
new file mode 100644
index 0000000..cdfdca9
--- /dev/null
+++ b/src/test/java/org/apache/james/mime4j/BasicBufferingInputStreamTest.java
@@ -0,0 +1,144 @@
+/****************************************************************
+ * 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 java.io.ByteArrayOutputStream;
+
+import junit.framework.TestCase;
+
+public class BasicBufferingInputStreamTest extends TestCase {
+
+ public void testBasicOperations() throws Exception {
+ String text = "ah blahblah";
+ byte[] b1 = text.getBytes("US-ASCII");
+ InputBuffer inbuffer = new InputBuffer(new ByteArrayInputStream(b1), 4096);
+
+ BasicBufferingInputStream instream = new BasicBufferingInputStream(inbuffer);
+
+ assertEquals((byte)'a', instream.read());
+ assertEquals((byte)'h', instream.read());
+ assertEquals((byte)' ', instream.read());
+
+ byte[] tmp1 = new byte[4];
+ assertEquals(4, instream.read(tmp1));
+ assertEquals(4, instream.read(tmp1));
+
+ assertEquals(-1, instream.read(tmp1));
+ assertEquals(-1, instream.read(tmp1));
+ assertEquals(-1, instream.read());
+ assertEquals(-1, instream.read());
+ }
+
+ public void testBasicReadLine() throws Exception {
+
+ String[] teststrs = new String[5];
+ teststrs[0] = "Hello\r\n";
+ teststrs[1] = "This string should be much longer than the size of the input buffer " +
+ "which is only 16 bytes for this test\r\n";
+ StringBuffer sb = new StringBuffer();
+ for (int i = 0; i < 15; i++) {
+ sb.append("123456789 ");
+ }
+ sb.append("and stuff like that\r\n");
+ teststrs[2] = sb.toString();
+ teststrs[3] = "\r\n";
+ teststrs[4] = "And goodbye\r\n";
+
+ ByteArrayOutputStream outstream = new ByteArrayOutputStream();
+
+ for (int i = 0; i < teststrs.length; i++) {
+ outstream.write(teststrs[i].getBytes("US-ASCII"));
+ }
+ byte[] raw = outstream.toByteArray();
+
+ InputBuffer inbuffer = new InputBuffer(new ByteArrayInputStream(raw), 16);
+ BasicBufferingInputStream instream = new BasicBufferingInputStream(inbuffer);
+
+ ByteArrayBuffer linebuf = new ByteArrayBuffer(8);
+ for (int i = 0; i < teststrs.length; i++) {
+ linebuf.clear();
+ instream.readLine(linebuf);
+ String s = new String(linebuf.toByteArray(), "US-ASCII");
+ assertEquals(teststrs[i], s);
+ }
+ assertEquals(-1, instream.readLine(linebuf));
+ assertEquals(-1, instream.readLine(linebuf));
+ }
+
+ public void testReadEmptyLine() throws Exception {
+
+ String teststr = "\n\n\r\n\r\r\n\n\n\n\n\n";
+ byte[] raw = teststr.getBytes("US-ASCII");
+
+ InputBuffer inbuffer = new InputBuffer(new ByteArrayInputStream(raw), 4);
+ BufferingInputStream instream = new BasicBufferingInputStream(inbuffer);
+
+ ByteArrayBuffer linebuf = new ByteArrayBuffer(8);
+ linebuf.clear();
+ instream.readLine(linebuf);
+ String s = new String(linebuf.toByteArray(), "US-ASCII");
+ assertEquals("\n", s);
+
+ linebuf.clear();
+ instream.readLine(linebuf);
+ s = new String(linebuf.toByteArray(), "US-ASCII");
+ assertEquals("\n", s);
+
+ linebuf.clear();
+ instream.readLine(linebuf);
+ s = new String(linebuf.toByteArray(), "US-ASCII");
+ assertEquals("\r\n", s);
+
+ linebuf.clear();
+ instream.readLine(linebuf);
+ s = new String(linebuf.toByteArray(), "US-ASCII");
+ assertEquals("\r\r\n", s);
+
+ linebuf.clear();
+ instream.readLine(linebuf);
+ s = new String(linebuf.toByteArray(), "US-ASCII");
+ assertEquals("\n", s);
+
+ linebuf.clear();
+ instream.readLine(linebuf);
+ s = new String(linebuf.toByteArray(), "US-ASCII");
+ assertEquals("\n", s);
+
+ linebuf.clear();
+ instream.readLine(linebuf);
+ s = new String(linebuf.toByteArray(), "US-ASCII");
+ assertEquals("\n", s);
+
+ linebuf.clear();
+ instream.readLine(linebuf);
+ s = new String(linebuf.toByteArray(), "US-ASCII");
+ assertEquals("\n", s);
+
+ linebuf.clear();
+ instream.readLine(linebuf);
+ s = new String(linebuf.toByteArray(), "US-ASCII");
+ assertEquals("\n", s);
+
+ assertEquals(-1, instream.readLine(linebuf));
+ assertEquals(-1, instream.readLine(linebuf));
+ }
+
+}
diff --git a/src/test/java/org/apache/james/mime4j/BufferingInputStreamAdaptorTest.java b/src/test/java/org/apache/james/mime4j/BufferingInputStreamAdaptorTest.java
new file mode 100644
index 0000000..ed62a6a
--- /dev/null
+++ b/src/test/java/org/apache/james/mime4j/BufferingInputStreamAdaptorTest.java
@@ -0,0 +1,144 @@
+/****************************************************************
+ * 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 java.io.ByteArrayOutputStream;
+
+import junit.framework.TestCase;
+
+public class BufferingInputStreamAdaptorTest extends TestCase {
+
+ public void testBasicOperations() throws Exception {
+ String text = "ah blahblah";
+ byte[] b1 = text.getBytes("US-ASCII");
+
+ BufferingInputStreamAdaptor instream = new BufferingInputStreamAdaptor(
+ new ByteArrayInputStream(b1));
+
+ assertEquals((byte)'a', instream.read());
+ assertEquals((byte)'h', instream.read());
+ assertEquals((byte)' ', instream.read());
+
+ byte[] tmp1 = new byte[4];
+ assertEquals(4, instream.read(tmp1));
+ assertEquals(4, instream.read(tmp1));
+
+ assertEquals(-1, instream.read(tmp1));
+ assertEquals(-1, instream.read(tmp1));
+ assertEquals(-1, instream.read());
+ assertEquals(-1, instream.read());
+ }
+
+ public void testBasicReadLine() throws Exception {
+
+ String[] teststrs = new String[5];
+ teststrs[0] = "Hello\r\n";
+ teststrs[1] = "This string should be much longer than the size of the input buffer " +
+ "which is only 16 bytes for this test\r\n";
+ StringBuffer sb = new StringBuffer();
+ for (int i = 0; i < 15; i++) {
+ sb.append("123456789 ");
+ }
+ sb.append("and stuff like that\r\n");
+ teststrs[2] = sb.toString();
+ teststrs[3] = "\r\n";
+ teststrs[4] = "And goodbye\r\n";
+
+ ByteArrayOutputStream outstream = new ByteArrayOutputStream();
+
+ for (int i = 0; i < teststrs.length; i++) {
+ outstream.write(teststrs[i].getBytes("US-ASCII"));
+ }
+ byte[] raw = outstream.toByteArray();
+
+ BufferingInputStreamAdaptor instream = new BufferingInputStreamAdaptor(
+ new ByteArrayInputStream(raw));
+
+ ByteArrayBuffer linebuf = new ByteArrayBuffer(8);
+ for (int i = 0; i < teststrs.length; i++) {
+ linebuf.clear();
+ instream.readLine(linebuf);
+ String s = new String(linebuf.toByteArray(), "US-ASCII");
+ assertEquals(teststrs[i], s);
+ }
+ assertEquals(-1, instream.readLine(linebuf));
+ assertEquals(-1, instream.readLine(linebuf));
+ }
+
+ public void testReadEmptyLine() throws Exception {
+
+ String teststr = "\n\n\r\n\r\r\n\n\n\n\n\n";
+ byte[] raw = teststr.getBytes("US-ASCII");
+
+ BufferingInputStreamAdaptor instream = new BufferingInputStreamAdaptor(
+ new ByteArrayInputStream(raw));
+
+ ByteArrayBuffer linebuf = new ByteArrayBuffer(8);
+ linebuf.clear();
+ instream.readLine(linebuf);
+ String s = new String(linebuf.toByteArray(), "US-ASCII");
+ assertEquals("\n", s);
+
+ linebuf.clear();
+ instream.readLine(linebuf);
+ s = new String(linebuf.toByteArray(), "US-ASCII");
+ assertEquals("\n", s);
+
+ linebuf.clear();
+ instream.readLine(linebuf);
+ s = new String(linebuf.toByteArray(), "US-ASCII");
+ assertEquals("\r\n", s);
+
+ linebuf.clear();
+ instream.readLine(linebuf);
+ s = new String(linebuf.toByteArray(), "US-ASCII");
+ assertEquals("\r\r\n", s);
+
+ linebuf.clear();
+ instream.readLine(linebuf);
+ s = new String(linebuf.toByteArray(), "US-ASCII");
+ assertEquals("\n", s);
+
+ linebuf.clear();
+ instream.readLine(linebuf);
+ s = new String(linebuf.toByteArray(), "US-ASCII");
+ assertEquals("\n", s);
+
+ linebuf.clear();
+ instream.readLine(linebuf);
+ s = new String(linebuf.toByteArray(), "US-ASCII");
+ assertEquals("\n", s);
+
+ linebuf.clear();
+ instream.readLine(linebuf);
+ s = new String(linebuf.toByteArray(), "US-ASCII");
+ assertEquals("\n", s);
+
+ linebuf.clear();
+ instream.readLine(linebuf);
+ s = new String(linebuf.toByteArray(), "US-ASCII");
+ assertEquals("\n", s);
+
+ assertEquals(-1, instream.readLine(linebuf));
+ assertEquals(-1, instream.readLine(linebuf));
+ }
+
+}
diff --git a/src/test/java/org/apache/james/mime4j/InputBufferTest.java b/src/test/java/org/apache/james/mime4j/InputBufferTest.java
index 9619e18..c5951ee 100644
--- a/src/test/java/org/apache/james/mime4j/InputBufferTest.java
+++ b/src/test/java/org/apache/james/mime4j/InputBufferTest.java
@@ -25,6 +25,107 @@ import junit.framework.TestCase;
public class InputBufferTest extends TestCase {
+ public void testInvalidInput() throws Exception {
+ String text = "blah blah yada yada";
+ byte[] b1 = text.getBytes("US-ASCII");
+ String pattern = "blah";
+ byte[] b2 = pattern.getBytes("US-ASCII");
+ InputBuffer inbuffer = new InputBuffer(new ByteArrayInputStream(b1), 4096);
+ inbuffer.fillBuffer();
+
+ assertEquals((int)'b', inbuffer.read());
+ assertEquals((int)'l', inbuffer.read());
+
+ try {
+ inbuffer.charAt(1);
+ fail("IndexOutOfBoundsException should have been thrown");
+ } catch (IndexOutOfBoundsException expected) {
+ }
+ try {
+ inbuffer.charAt(20);
+ fail("IndexOutOfBoundsException should have been thrown");
+ } catch (IndexOutOfBoundsException expected) {
+ }
+ try {
+ inbuffer.indexOf(b2, -1, 3);
+ fail("IndexOutOfBoundsException should have been thrown");
+ } catch (IndexOutOfBoundsException expected) {
+ }
+ try {
+ inbuffer.indexOf(b2, 1, 3);
+ fail("IndexOutOfBoundsException should have been thrown");
+ } catch (IndexOutOfBoundsException expected) {
+ }
+ try {
+ inbuffer.indexOf(b2, 2, -1);
+ fail("IndexOutOfBoundsException should have been thrown");
+ } catch (IndexOutOfBoundsException expected) {
+ }
+ try {
+ inbuffer.indexOf(b2, 2, 18);
+ fail("IndexOutOfBoundsException should have been thrown");
+ } catch (IndexOutOfBoundsException expected) {
+ }
+ assertEquals(5, inbuffer.indexOf(b2, 2, 17));
+ try {
+ inbuffer.indexOf((byte)' ', -1, 3);
+ fail("IndexOutOfBoundsException should have been thrown");
+ } catch (IndexOutOfBoundsException expected) {
+ }
+ try {
+ inbuffer.indexOf((byte)' ', 1, 3);
+ fail("IndexOutOfBoundsException should have been thrown");
+ } catch (IndexOutOfBoundsException expected) {
+ }
+ try {
+ inbuffer.indexOf((byte)' ', 2, -1);
+ fail("IndexOutOfBoundsException should have been thrown");
+ } catch (IndexOutOfBoundsException expected) {
+ }
+ try {
+ inbuffer.indexOf((byte)' ', 2, 18);
+ fail("IndexOutOfBoundsException should have been thrown");
+ } catch (IndexOutOfBoundsException expected) {
+ }
+ assertEquals(10, inbuffer.indexOf((byte)'y', 2, 17));
+ }
+
+ public void testBasicOperations() throws Exception {
+ String text = "bla bla yada yada haha haha";
+ byte[] b1 = text.getBytes("US-ASCII");
+ InputBuffer inbuffer = new InputBuffer(new ByteArrayInputStream(b1), 4096);
+ inbuffer.fillBuffer();
+ assertEquals(0, inbuffer.pos());
+ assertEquals(27, inbuffer.limit());
+ assertEquals(27, inbuffer.length());
+
+ inbuffer.read();
+ inbuffer.read();
+
+ assertEquals(2, inbuffer.pos());
+ assertEquals(27, inbuffer.limit());
+ assertEquals(25, inbuffer.length());
+
+ byte[] tmp1 = new byte[3];
+ assertEquals(3, inbuffer.read(tmp1));
+
+ assertEquals(5, inbuffer.pos());
+ assertEquals(27, inbuffer.limit());
+ assertEquals(22, inbuffer.length());
+
+ byte[] tmp2 = new byte[22];
+ assertEquals(22, inbuffer.read(tmp2));
+
+ assertEquals(27, inbuffer.pos());
+ assertEquals(27, inbuffer.limit());
+ assertEquals(0, inbuffer.length());
+
+ assertEquals(-1, inbuffer.read(tmp1));
+ assertEquals(-1, inbuffer.read(tmp1));
+ assertEquals(-1, inbuffer.read());
+ assertEquals(-1, inbuffer.read());
+ }
+
public void testPatternMatching1() throws Exception {
String text = "blabla d is the word";
String pattern = "d";
@@ -68,4 +169,32 @@ public class InputBufferTest extends TestCase {
int i = inbuffer.indexOf(b2);
assertEquals(0, i);
}
+
+ public void testPatternOutOfBound() throws Exception {
+ String text = "bla bla yada yada haha haha";
+ String pattern1 = "bla bla";
+ byte[] b1 = text.getBytes("US-ASCII");
+ byte[] b2 = pattern1.getBytes("US-ASCII");
+ InputBuffer inbuffer = new InputBuffer(new ByteArrayInputStream(b1), 4096);
+ inbuffer.fillBuffer();
+ byte[] tmp = new byte[3];
+ inbuffer.read(tmp);
+ int i = inbuffer.indexOf(b2, inbuffer.pos(), inbuffer.length());
+ assertEquals(-1, i);
+ i = inbuffer.indexOf(b2, inbuffer.pos(), inbuffer.length() - 1);
+ assertEquals(-1, i);
+ }
+
+ public void testCharOutOfBound() throws Exception {
+ String text = "zzz blah blah blah ggg";
+ byte[] b1 = text.getBytes("US-ASCII");
+ InputBuffer inbuffer = new InputBuffer(new ByteArrayInputStream(b1), 4096);
+ inbuffer.fillBuffer();
+ byte[] tmp = new byte[3];
+ inbuffer.read(tmp);
+ int i = inbuffer.indexOf((byte)'z', inbuffer.pos(), inbuffer.length());
+ assertEquals(-1, i);
+ i = inbuffer.indexOf((byte)'g', inbuffer.pos(), inbuffer.length() - 3);
+ assertEquals(-1, i);
+ }
}
diff --git a/src/test/java/org/apache/james/mime4j/MimeBoundaryInputStreamTest.java b/src/test/java/org/apache/james/mime4j/MimeBoundaryInputStreamTest.java
index ec5fab4..4f8c7ff 100644
--- a/src/test/java/org/apache/james/mime4j/MimeBoundaryInputStreamTest.java
+++ b/src/test/java/org/apache/james/mime4j/MimeBoundaryInputStreamTest.java
@@ -20,6 +20,7 @@
package org.apache.james.mime4j;
import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -55,6 +56,25 @@ public class MimeBoundaryInputStreamTest extends TestCase {
assertTrue(mime2.isLastPart());
}
+ public void testLenientLineDelimiterReading() throws IOException {
+ String text = "Line 1\r\nLine 2\n--boundary\n" +
+ "Line 3\r\nLine 4\n--boundary--\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", read(mime2, 5));
+
+ assertTrue(mime2.isLastPart());
+ }
+
public void testBasicReadingSmallBuffer1() throws IOException {
String text = "yadayadayadayadayadayadayadayadayadayadayadayadayadayadayadayada\r\n--boundary\r\n" +
"blahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblah\r\n--boundary--";
@@ -215,5 +235,99 @@ public class MimeBoundaryInputStreamTest extends TestCase {
buffer = new InputBuffer(bis, 4096);
stream = new MimeBoundaryInputStream(buffer, "boundary");
assertEquals(-1, stream.read());
- }
+ }
+
+
+ public void testBasicReadLine() throws Exception {
+
+ String[] teststrs = new String[5];
+ teststrs[0] = "Hello\r\n";
+ teststrs[1] = "This string should be much longer than the size of the input buffer " +
+ "which is only 20 bytes for this test\r\n";
+ StringBuffer sb = new StringBuffer();
+ for (int i = 0; i < 15; i++) {
+ sb.append("123456789 ");
+ }
+ sb.append("and stuff like that\r\n");
+ teststrs[2] = sb.toString();
+ teststrs[3] = "\r\n";
+ teststrs[4] = "And goodbye\r\n";
+
+ String term = "\r\n--1234\r\n";
+
+ ByteArrayOutputStream outstream = new ByteArrayOutputStream();
+
+ for (int i = 0; i < teststrs.length; i++) {
+ outstream.write(teststrs[i].getBytes("US-ASCII"));
+ }
+ outstream.write(term.getBytes("US-ASCII"));
+ byte[] raw = outstream.toByteArray();
+
+ InputBuffer inbuffer = new InputBuffer(new ByteArrayInputStream(raw), 20);
+ BufferingInputStream instream = new MimeBoundaryInputStream(inbuffer, "1234");
+
+ ByteArrayBuffer linebuf = new ByteArrayBuffer(8);
+ for (int i = 0; i < teststrs.length; i++) {
+ linebuf.clear();
+ instream.readLine(linebuf);
+ String s = new String(linebuf.toByteArray(), "US-ASCII");
+ assertEquals(teststrs[i], s);
+ }
+ assertEquals(-1, instream.readLine(linebuf));
+ assertEquals(-1, instream.readLine(linebuf));
+ }
+
+ public void testReadEmptyLine() throws Exception {
+
+ String teststr = "01234567890123456789\n\n\r\n\r\r\n\n\n\n\n\n--1234\r\n";
+ byte[] raw = teststr.getBytes("US-ASCII");
+
+ InputBuffer inbuffer = new InputBuffer(new ByteArrayInputStream(raw), 20);
+ BufferingInputStream instream = new MimeBoundaryInputStream(inbuffer, "1234");
+
+ ByteArrayBuffer linebuf = new ByteArrayBuffer(8);
+ linebuf.clear();
+ instream.readLine(linebuf);
+ String s = new String(linebuf.toByteArray(), "US-ASCII");
+ assertEquals("01234567890123456789\n", s);
+
+ linebuf.clear();
+ instream.readLine(linebuf);
+ s = new String(linebuf.toByteArray(), "US-ASCII");
+ assertEquals("\n", s);
+
+ linebuf.clear();
+ instream.readLine(linebuf);
+ s = new String(linebuf.toByteArray(), "US-ASCII");
+ assertEquals("\r\n", s);
+
+ linebuf.clear();
+ instream.readLine(linebuf);
+ s = new String(linebuf.toByteArray(), "US-ASCII");
+ assertEquals("\r\r\n", s);
+
+ linebuf.clear();
+ instream.readLine(linebuf);
+ s = new String(linebuf.toByteArray(), "US-ASCII");
+ assertEquals("\n", s);
+
+ linebuf.clear();
+ instream.readLine(linebuf);
+ s = new String(linebuf.toByteArray(), "US-ASCII");
+ assertEquals("\n", s);
+
+ linebuf.clear();
+ instream.readLine(linebuf);
+ s = new String(linebuf.toByteArray(), "US-ASCII");
+ assertEquals("\n", s);
+
+ linebuf.clear();
+ instream.readLine(linebuf);
+ s = new String(linebuf.toByteArray(), "US-ASCII");
+ assertEquals("\n", s);
+
+ assertEquals(-1, instream.readLine(linebuf));
+ assertEquals(-1, instream.readLine(linebuf));
+ }
+
}
diff --git a/src/test/java/org/apache/james/mime4j/MimeEntityTest.java b/src/test/java/org/apache/james/mime4j/MimeEntityTest.java
index 8a43917..d5204df 100644
--- a/src/test/java/org/apache/james/mime4j/MimeEntityTest.java
+++ b/src/test/java/org/apache/james/mime4j/MimeEntityTest.java
@@ -40,7 +40,7 @@ public class MimeEntityTest extends TestCase {
ByteArrayInputStream instream = new ByteArrayInputStream(raw);
RootInputStream rootStream = new RootInputStream(instream);
InputBuffer inbuffer = new InputBuffer(rootStream, 12);
- BufferingInputStream rawstream = new BufferingInputStream(inbuffer);
+ BasicBufferingInputStream rawstream = new BasicBufferingInputStream(inbuffer);
MimeEntity entity = new MimeEntity(
rootStream,
@@ -129,7 +129,7 @@ public class MimeEntityTest extends TestCase {
ByteArrayInputStream instream = new ByteArrayInputStream(raw);
RootInputStream rootStream = new RootInputStream(instream);
InputBuffer inbuffer = new InputBuffer(rootStream, 24);
- BufferingInputStream rawstream = new BufferingInputStream(inbuffer);
+ BasicBufferingInputStream rawstream = new BasicBufferingInputStream(inbuffer);
MimeEntity entity = new MimeEntity(
rootStream,
@@ -244,7 +244,7 @@ public class MimeEntityTest extends TestCase {
ByteArrayInputStream instream = new ByteArrayInputStream(raw);
RootInputStream rootStream = new RootInputStream(instream);
InputBuffer inbuffer = new InputBuffer(rootStream, 24);
- BufferingInputStream rawstream = new BufferingInputStream(inbuffer);
+ BasicBufferingInputStream rawstream = new BasicBufferingInputStream(inbuffer);
MimeEntity entity = new MimeEntity(
rootStream,
diff --git a/src/test/java/org/apache/james/mime4j/StrictMimeTokenStreamTest.java b/src/test/java/org/apache/james/mime4j/StrictMimeTokenStreamTest.java
index a7a0848..f8ad235 100644
--- a/src/test/java/org/apache/james/mime4j/StrictMimeTokenStreamTest.java
+++ b/src/test/java/org/apache/james/mime4j/StrictMimeTokenStreamTest.java
@@ -35,6 +35,7 @@ public class StrictMimeTokenStreamTest extends TestCase {
parser.parse(new ByteArrayInputStream(HEADER_ONLY.getBytes()));
assertEquals("Headers start", MimeTokenStream.T_START_HEADER, parser.next());
+ assertEquals("Field", MimeTokenStream.T_FIELD, parser.next());
try {
parser.next();
fail("Expected exception to be thrown");
diff --git a/src/test/java/org/apache/james/mime4j/TestByteArrayBuffer.java b/src/test/java/org/apache/james/mime4j/TestByteArrayBuffer.java
new file mode 100644
index 0000000..c5d872b
--- /dev/null
+++ b/src/test/java/org/apache/james/mime4j/TestByteArrayBuffer.java
@@ -0,0 +1,229 @@
+/****************************************************************
+ * 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 junit.framework.TestCase;
+
+/**
+ * Unit tests for {@link ByteArrayBuffer}.
+ */
+public class TestByteArrayBuffer extends TestCase {
+
+ public void testConstructor() throws Exception {
+ ByteArrayBuffer buffer = new ByteArrayBuffer(16);
+ assertEquals(16, buffer.capacity());
+ assertEquals(0, buffer.length());
+ assertNotNull(buffer.buffer());
+ assertEquals(16, buffer.buffer().length);
+ try {
+ new ByteArrayBuffer(-1);
+ fail("IllegalArgumentException should have been thrown");
+ } catch (IllegalArgumentException ex) {
+ // expected
+ }
+ }
+
+ public void testSimpleAppend() throws Exception {
+ ByteArrayBuffer buffer = new ByteArrayBuffer(16);
+ assertEquals(16, buffer.capacity());
+ assertEquals(0, buffer.length());
+ byte[] b1 = buffer.toByteArray();
+ assertNotNull(b1);
+ assertEquals(0, b1.length);
+ assertTrue(buffer.isEmpty());
+ assertFalse(buffer.isFull());
+
+ byte[] tmp = new byte[] { 1, 2, 3, 4};
+ buffer.append(tmp, 0, tmp.length);
+ assertEquals(16, buffer.capacity());
+ assertEquals(4, buffer.length());
+ assertFalse(buffer.isEmpty());
+ assertFalse(buffer.isFull());
+
+ byte[] b2 = buffer.toByteArray();
+ assertNotNull(b2);
+ assertEquals(4, b2.length);
+ for (int i = 0; i < tmp.length; i++) {
+ assertEquals(tmp[i], b2[i]);
+ assertEquals(tmp[i], buffer.byteAt(i));
+ }
+ buffer.clear();
+ assertEquals(16, buffer.capacity());
+ assertEquals(0, buffer.length());
+ assertTrue(buffer.isEmpty());
+ assertFalse(buffer.isFull());
+ }
+
+ public void testExpandAppend() throws Exception {
+ ByteArrayBuffer buffer = new ByteArrayBuffer(4);
+ assertEquals(4, buffer.capacity());
+
+ byte[] tmp = new byte[] { 1, 2, 3, 4};
+ buffer.append(tmp, 0, 2);
+ buffer.append(tmp, 0, 4);
+ buffer.append(tmp, 0, 0);
+
+ assertEquals(8, buffer.capacity());
+ assertEquals(6, buffer.length());
+
+ buffer.append(tmp, 0, 4);
+
+ assertEquals(16, buffer.capacity());
+ assertEquals(10, buffer.length());
+ }
+
+ public void testInvalidAppend() throws Exception {
+ ByteArrayBuffer buffer = new ByteArrayBuffer(4);
+ buffer.append((byte[])null, 0, 0);
+
+ byte[] tmp = new byte[] { 1, 2, 3, 4};
+ try {
+ buffer.append(tmp, -1, 0);
+ fail("IndexOutOfBoundsException should have been thrown");
+ } catch (IndexOutOfBoundsException ex) {
+ // expected
+ }
+ try {
+ buffer.append(tmp, 0, -1);
+ fail("IndexOutOfBoundsException should have been thrown");
+ } catch (IndexOutOfBoundsException ex) {
+ // expected
+ }
+ try {
+ buffer.append(tmp, 0, 8);
+ fail("IndexOutOfBoundsException should have been thrown");
+ } catch (IndexOutOfBoundsException ex) {
+ // expected
+ }
+ try {
+ buffer.append(tmp, 10, Integer.MAX_VALUE);
+ fail("IndexOutOfBoundsException should have been thrown");
+ } catch (IndexOutOfBoundsException ex) {
+ // expected
+ }
+ try {
+ buffer.append(tmp, 2, 4);
+ fail("IndexOutOfBoundsException should have been thrown");
+ } catch (IndexOutOfBoundsException ex) {
+ // expected
+ }
+ }
+
+ public void testAppendOneByte() throws Exception {
+ ByteArrayBuffer buffer = new ByteArrayBuffer(4);
+ assertEquals(4, buffer.capacity());
+
+ byte[] tmp = new byte[] { 1, 127, -1, -128, 1, -2};
+ for (int i = 0; i < tmp.length; i++) {
+ buffer.append(tmp[i]);
+ }
+ assertEquals(8, buffer.capacity());
+ assertEquals(6, buffer.length());
+
+ for (int i = 0; i < tmp.length; i++) {
+ assertEquals(tmp[i], buffer.byteAt(i));
+ }
+ }
+
+ public void testSetLength() throws Exception {
+ ByteArrayBuffer buffer = new ByteArrayBuffer(4);
+ buffer.setLength(2);
+ assertEquals(2, buffer.length());
+ }
+
+ public void testSetInvalidLength() throws Exception {
+ ByteArrayBuffer buffer = new ByteArrayBuffer(4);
+ try {
+ buffer.setLength(-2);
+ fail("IndexOutOfBoundsException should have been thrown");
+ } catch (IndexOutOfBoundsException ex) {
+ // expected
+ }
+ try {
+ buffer.setLength(200);
+ fail("IndexOutOfBoundsException should have been thrown");
+ } catch (IndexOutOfBoundsException ex) {
+ // expected
+ }
+ }
+
+ public void testAppendCharArrayAsAscii() throws Exception {
+ String s1 = "stuff";
+ String s2 = " and more stuff";
+ char[] b1 = s1.toCharArray();
+ char[] b2 = s2.toCharArray();
+
+ ByteArrayBuffer buffer = new ByteArrayBuffer(8);
+ buffer.append(b1, 0, b1.length);
+ buffer.append(b2, 0, b2.length);
+
+ assertEquals(s1 + s2, new String(buffer.toByteArray(), "US-ASCII"));
+ }
+
+ public void testAppendNullCharArray() throws Exception {
+ ByteArrayBuffer buffer = new ByteArrayBuffer(8);
+ buffer.append((char[])null, 0, 0);
+ assertEquals(0, buffer.length());
+ }
+
+ public void testAppendEmptyCharArray() throws Exception {
+ ByteArrayBuffer buffer = new ByteArrayBuffer(8);
+ buffer.append(new char[] {}, 0, 0);
+ assertEquals(0, buffer.length());
+ }
+
+ public void testInvalidAppendCharArrayAsAscii() throws Exception {
+ ByteArrayBuffer buffer = new ByteArrayBuffer(4);
+ buffer.append((char[])null, 0, 0);
+
+ char[] tmp = new char[] { '1', '2', '3', '4'};
+ try {
+ buffer.append(tmp, -1, 0);
+ fail("IndexOutOfBoundsException should have been thrown");
+ } catch (IndexOutOfBoundsException ex) {
+ // expected
+ }
+ try {
+ buffer.append(tmp, 0, -1);
+ fail("IndexOutOfBoundsException should have been thrown");
+ } catch (IndexOutOfBoundsException ex) {
+ // expected
+ }
+ try {
+ buffer.append(tmp, 0, 8);
+ fail("IndexOutOfBoundsException should have been thrown");
+ } catch (IndexOutOfBoundsException ex) {
+ // expected
+ }
+ try {
+ buffer.append(tmp, 10, Integer.MAX_VALUE);
+ fail("IndexOutOfBoundsException should have been thrown");
+ } catch (IndexOutOfBoundsException ex) {
+ // expected
+ }
+ try {
+ buffer.append(tmp, 2, 4);
+ fail("IndexOutOfBoundsException should have been thrown");
+ } catch (IndexOutOfBoundsException ex) {
+ // expected
+ }
+ }
+
+}
diff --git a/src/test/java/org/apache/james/mime4j/TestCharArrayBuffer.java b/src/test/java/org/apache/james/mime4j/TestCharArrayBuffer.java
new file mode 100644
index 0000000..d6cf5ba
--- /dev/null
+++ b/src/test/java/org/apache/james/mime4j/TestCharArrayBuffer.java
@@ -0,0 +1,357 @@
+/****************************************************************
+ * 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 junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+/**
+ * Unit tests for {@link CharArrayBuffer}.
+ */
+public class TestCharArrayBuffer extends TestCase {
+
+ public TestCharArrayBuffer(String testName) {
+ super(testName);
+ }
+
+ public static void main(String args[]) {
+ String[] testCaseName = { TestCharArrayBuffer.class.getName() };
+ junit.textui.TestRunner.main(testCaseName);
+ }
+
+ public static Test suite() {
+ return new TestSuite(TestCharArrayBuffer.class);
+ }
+
+ public void testConstructor() throws Exception {
+ CharArrayBuffer buffer = new CharArrayBuffer(16);
+ assertEquals(16, buffer.capacity());
+ assertEquals(0, buffer.length());
+ assertNotNull(buffer.buffer());
+ assertEquals(16, buffer.buffer().length);
+ try {
+ new CharArrayBuffer(-1);
+ fail("IllegalArgumentException should have been thrown");
+ } catch (IllegalArgumentException ex) {
+ // expected
+ }
+ }
+
+ public void testSimpleAppend() throws Exception {
+ CharArrayBuffer buffer = new CharArrayBuffer(16);
+ assertEquals(16, buffer.capacity());
+ assertEquals(0, buffer.length());
+ char[] b1 = buffer.toCharArray();
+ assertNotNull(b1);
+ assertEquals(0, b1.length);
+ assertTrue(buffer.isEmpty());
+ assertFalse(buffer.isFull());
+
+ char[] tmp = new char[] { '1', '2', '3', '4'};
+ buffer.append(tmp, 0, tmp.length);
+ assertEquals(16, buffer.capacity());
+ assertEquals(4, buffer.length());
+ assertFalse(buffer.isEmpty());
+ assertFalse(buffer.isFull());
+
+ char[] b2 = buffer.toCharArray();
+ assertNotNull(b2);
+ assertEquals(4, b2.length);
+ for (int i = 0; i < tmp.length; i++) {
+ assertEquals(tmp[i], b2[i]);
+ assertEquals(tmp[i], buffer.charAt(i));
+ }
+ assertEquals("1234", buffer.toString());
+
+ buffer.clear();
+ assertEquals(16, buffer.capacity());
+ assertEquals(0, buffer.length());
+ assertTrue(buffer.isEmpty());
+ assertFalse(buffer.isFull());
+ }
+
+ public void testExpandAppend() throws Exception {
+ CharArrayBuffer buffer = new CharArrayBuffer(4);
+ assertEquals(4, buffer.capacity());
+
+ char[] tmp = new char[] { '1', '2', '3', '4'};
+ buffer.append(tmp, 0, 2);
+ buffer.append(tmp, 0, 4);
+ buffer.append(tmp, 0, 0);
+
+ assertEquals(8, buffer.capacity());
+ assertEquals(6, buffer.length());
+
+ buffer.append(tmp, 0, 4);
+
+ assertEquals(16, buffer.capacity());
+ assertEquals(10, buffer.length());
+
+ assertEquals("1212341234", buffer.toString());
+ }
+
+ public void testAppendString() throws Exception {
+ CharArrayBuffer buffer = new CharArrayBuffer(8);
+ buffer.append("stuff");
+ buffer.append(" and more stuff");
+ assertEquals("stuff and more stuff", buffer.toString());
+ }
+
+ public void testAppendNullString() throws Exception {
+ CharArrayBuffer buffer = new CharArrayBuffer(8);
+ buffer.append((String)null);
+ assertEquals("null", buffer.toString());
+ }
+
+ public void testAppendCharArrayBuffer() throws Exception {
+ CharArrayBuffer buffer1 = new CharArrayBuffer(8);
+ buffer1.append(" and more stuff");
+ CharArrayBuffer buffer2 = new CharArrayBuffer(8);
+ buffer2.append("stuff");
+ buffer2.append(buffer1);
+ assertEquals("stuff and more stuff", buffer2.toString());
+ }
+
+ public void testAppendNullCharArrayBuffer() throws Exception {
+ CharArrayBuffer buffer = new CharArrayBuffer(8);
+ buffer.append((CharArrayBuffer)null);
+ buffer.append((CharArrayBuffer)null, 0, 0);
+ assertEquals("", buffer.toString());
+ }
+
+ public void testAppendSingleChar() throws Exception {
+ CharArrayBuffer buffer = new CharArrayBuffer(4);
+ buffer.append('1');
+ buffer.append('2');
+ buffer.append('3');
+ buffer.append('4');
+ buffer.append('5');
+ buffer.append('6');
+ assertEquals("123456", buffer.toString());
+ }
+
+ public void testInvalidCharArrayAppend() throws Exception {
+ CharArrayBuffer buffer = new CharArrayBuffer(4);
+ buffer.append((char[])null, 0, 0);
+
+ char[] tmp = new char[] { '1', '2', '3', '4'};
+ try {
+ buffer.append(tmp, -1, 0);
+ fail("IndexOutOfBoundsException should have been thrown");
+ } catch (IndexOutOfBoundsException ex) {
+ // expected
+ }
+ try {
+ buffer.append(tmp, 0, -1);
+ fail("IndexOutOfBoundsException should have been thrown");
+ } catch (IndexOutOfBoundsException ex) {
+ // expected
+ }
+ try {
+ buffer.append(tmp, 0, 8);
+ fail("IndexOutOfBoundsException should have been thrown");
+ } catch (IndexOutOfBoundsException ex) {
+ // expected
+ }
+ try {
+ buffer.append(tmp, 10, Integer.MAX_VALUE);
+ fail("IndexOutOfBoundsException should have been thrown");
+ } catch (IndexOutOfBoundsException ex) {
+ // expected
+ }
+ try {
+ buffer.append(tmp, 2, 4);
+ fail("IndexOutOfBoundsException should have been thrown");
+ } catch (IndexOutOfBoundsException ex) {
+ // expected
+ }
+ }
+
+ public void testSetLength() throws Exception {
+ CharArrayBuffer buffer = new CharArrayBuffer(4);
+ buffer.setLength(2);
+ assertEquals(2, buffer.length());
+ }
+
+ public void testSetInvalidLength() throws Exception {
+ CharArrayBuffer buffer = new CharArrayBuffer(4);
+ try {
+ buffer.setLength(-2);
+ fail("IndexOutOfBoundsException should have been thrown");
+ } catch (IndexOutOfBoundsException ex) {
+ // expected
+ }
+ try {
+ buffer.setLength(200);
+ fail("IndexOutOfBoundsException should have been thrown");
+ } catch (IndexOutOfBoundsException ex) {
+ // expected
+ }
+ }
+
+ public void testEnsureCapacity() throws Exception {
+ CharArrayBuffer buffer = new CharArrayBuffer(4);
+ buffer.ensureCapacity(2);
+ assertEquals(4, buffer.capacity());
+ buffer.ensureCapacity(8);
+ assertEquals(8, buffer.capacity());
+ }
+
+ public void testIndexOf() {
+ CharArrayBuffer buffer = new CharArrayBuffer(16);
+ buffer.append("name: value");
+ assertEquals(4, buffer.indexOf(':'));
+ assertEquals(-1, buffer.indexOf(','));
+ assertEquals(4, buffer.indexOf(':', -1, 11));
+ assertEquals(4, buffer.indexOf(':', 0, 1000));
+ assertEquals(-1, buffer.indexOf(':', 2, 1));
+ }
+
+ public void testSubstring() {
+ CharArrayBuffer buffer = new CharArrayBuffer(16);
+ buffer.append(" name: value ");
+ assertEquals(5, buffer.indexOf(':'));
+ assertEquals(" name", buffer.substring(0, 5));
+ assertEquals(" value ", buffer.substring(6, buffer.length()));
+ assertEquals("name", buffer.substringTrimmed(0, 5));
+ assertEquals("value", buffer.substringTrimmed(6, buffer.length()));
+ assertEquals("", buffer.substringTrimmed(13, buffer.length()));
+ }
+
+ public void testSubstringIndexOfOutBound() {
+ CharArrayBuffer buffer = new CharArrayBuffer(16);
+ buffer.append("stuff");
+ try {
+ buffer.substring(-2, 10);
+ fail("IndexOutOfBoundsException should have been thrown");
+ } catch (IndexOutOfBoundsException ex) {
+ // expected
+ }
+ try {
+ buffer.substringTrimmed(-2, 10);
+ fail("IndexOutOfBoundsException should have been thrown");
+ } catch (IndexOutOfBoundsException ex) {
+ // expected
+ }
+ try {
+ buffer.substring(12, 10);
+ fail("IndexOutOfBoundsException should have been thrown");
+ } catch (IndexOutOfBoundsException ex) {
+ // expected
+ }
+ try {
+ buffer.substringTrimmed(12, 10);
+ fail("IndexOutOfBoundsException should have been thrown");
+ } catch (IndexOutOfBoundsException ex) {
+ // expected
+ }
+ try {
+ buffer.substring(2, 1);
+ fail("IndexOutOfBoundsException should have been thrown");
+ } catch (IndexOutOfBoundsException ex) {
+ // expected
+ }
+ try {
+ buffer.substringTrimmed(2, 1);
+ fail("IndexOutOfBoundsException should have been thrown");
+ } catch (IndexOutOfBoundsException ex) {
+ // expected
+ }
+ }
+
+ public void testAppendAsciiByteArray() throws Exception {
+ String s1 = "stuff";
+ String s2 = " and more stuff";
+ byte[] b1 = s1.getBytes("US-ASCII");
+ byte[] b2 = s2.getBytes("US-ASCII");
+
+ CharArrayBuffer buffer = new CharArrayBuffer(8);
+ buffer.append(b1, 0, b1.length);
+ buffer.append(b2, 0, b2.length);
+
+ assertEquals("stuff and more stuff", buffer.toString());
+ }
+
+ public void testAppendISOByteArray() throws Exception {
+ byte[] b = new byte[] {0x00, 0x20, 0x7F, -0x80, -0x01};
+
+ CharArrayBuffer buffer = new CharArrayBuffer(8);
+ buffer.append(b, 0, b.length);
+ char[] ch = buffer.toCharArray();
+ assertNotNull(ch);
+ assertEquals(5, ch.length);
+ assertEquals(0x00, ch[0]);
+ assertEquals(0x20, ch[1]);
+ assertEquals(0x7F, ch[2]);
+ assertEquals(0x80, ch[3]);
+ assertEquals(0xFF, ch[4]);
+ }
+
+ public void testAppendNullByteArray() throws Exception {
+ CharArrayBuffer buffer = new CharArrayBuffer(8);
+ buffer.append((byte[])null, 0, 0);
+ assertEquals("", buffer.toString());
+ }
+
+ public void testAppendNullByteArrayBuffer() throws Exception {
+ CharArrayBuffer buffer = new CharArrayBuffer(8);
+ buffer.append((ByteArrayBuffer)null, 0, 0);
+ assertEquals("", buffer.toString());
+ }
+
+ public void testInvalidAppendAsciiByteArray() throws Exception {
+ CharArrayBuffer buffer = new CharArrayBuffer(4);
+ buffer.append((byte[])null, 0, 0);
+
+ byte[] tmp = new byte[] { '1', '2', '3', '4'};
+ try {
+ buffer.append(tmp, -1, 0);
+ fail("IndexOutOfBoundsException should have been thrown");
+ } catch (IndexOutOfBoundsException ex) {
+ // expected
+ }
+ try {
+ buffer.append(tmp, 0, -1);
+ fail("IndexOutOfBoundsException should have been thrown");
+ } catch (IndexOutOfBoundsException ex) {
+ // expected
+ }
+ try {
+ buffer.append(tmp, 0, 8);
+ fail("IndexOutOfBoundsException should have been thrown");
+ } catch (IndexOutOfBoundsException ex) {
+ // expected
+ }
+ try {
+ buffer.append(tmp, 10, Integer.MAX_VALUE);
+ fail("IndexOutOfBoundsException should have been thrown");
+ } catch (IndexOutOfBoundsException ex) {
+ // expected
+ }
+ try {
+ buffer.append(tmp, 2, 4);
+ fail("IndexOutOfBoundsException should have been thrown");
+ } catch (IndexOutOfBoundsException ex) {
+ // expected
+ }
+ }
+
+}
--
2.11.4.GIT