Permalink
Browse files

More efficient implementation of TextBody backed by a String; body co…

…ntent can be streamed without convering the string to a byte array

git-svn-id: https://svn.apache.org/repos/asf/james/mime4j/trunk@1227867 13f79535-47bb-0310-9956-ffa450edef68
  • Loading branch information...
1 parent 649c21e commit 4eb7fbf21ef35c49044baf7ce76a6de34a06a509 @ok2c ok2c committed Jan 5, 2012
@@ -23,6 +23,8 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
+import java.nio.charset.Charset;
+import java.nio.charset.UnsupportedCharsetException;
import org.apache.james.mime4j.dom.BinaryBody;
import org.apache.james.mime4j.dom.TextBody;
@@ -33,8 +35,6 @@
*/
public class BasicBodyFactory implements BodyFactory {
- private static String DEFAULT_CHARSET = CharsetUtil.DEFAULT_CHARSET.name();
-
public BinaryBody binaryBody(final InputStream is) throws IOException {
return new BasicBinaryBody(bufferContent(is));
}
@@ -60,11 +60,23 @@ public TextBody textBody(final String text, final String mimeCharset) throws Uns
if (text == null) {
throw new IllegalArgumentException("Text may not be null");
}
- return new BasicTextBody(text.getBytes(mimeCharset), mimeCharset);
+ Charset charset = Charset.forName(mimeCharset);
+ try {
+ return new StringBody(text, charset);
+ } catch (UnsupportedCharsetException ex) {
+ throw new UnsupportedEncodingException(ex.getMessage());
+ }
+ }
+
+ public TextBody textBody(final String text, final Charset charset) {
+ if (text == null) {
+ throw new IllegalArgumentException("Text may not be null");
+ }
+ return new StringBody(text, charset);
}
- public TextBody textBody(final String text) throws UnsupportedEncodingException {
- return textBody(text, DEFAULT_CHARSET);
+ public TextBody textBody(final String text) {
+ return textBody(text, CharsetUtil.DEFAULT_CHARSET);
}
public BinaryBody binaryBody(final byte[] buf) {
@@ -0,0 +1,62 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one *
+ * or more contributor license agreements. See the NOTICE file *
+ * distributed with this work for additional information *
+ * regarding copyright ownership. The ASF licenses this file *
+ * to you under the Apache License, Version 2.0 (the *
+ * "License"); you may not use this file except in compliance *
+ * with the License. You may obtain a copy of the License at *
+ * *
+ * http://www.apache.org/licenses/LICENSE-2.0 *
+ * *
+ * Unless required by applicable law or agreed to in writing, *
+ * software distributed under the License is distributed on an *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
+ * KIND, either express or implied. See the License for the *
+ * specific language governing permissions and limitations *
+ * under the License. *
+ ****************************************************************/
+
+package org.apache.james.mime4j.message;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.io.StringReader;
+import java.nio.charset.Charset;
+
+import org.apache.james.mime4j.dom.SingleBody;
+import org.apache.james.mime4j.dom.TextBody;
+
+class StringBody extends TextBody {
+
+ private final String content;
+ private final Charset charset;
+
+ StringBody(final String content, final Charset charset) {
+ super();
+ this.content = content;
+ this.charset = charset;
+ }
+
+ @Override
+ public String getMimeCharset() {
+ return this.charset.name();
+ }
+
+ @Override
+ public Reader getReader() throws IOException {
+ return new StringReader(this.content);
+ }
+
+ @Override
+ public InputStream getInputStream() throws IOException {
+ return new StringInputStream(this.content, this.charset, 2048);
+ }
+
+ @Override
+ public SingleBody copy() {
+ return new StringBody(this.content, this.charset);
+ }
+
+}
@@ -0,0 +1,150 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one *
+ * or more contributor license agreements. See the NOTICE file *
+ * distributed with this work for additional information *
+ * regarding copyright ownership. The ASF licenses this file *
+ * to you under the Apache License, Version 2.0 (the *
+ * "License"); you may not use this file except in compliance *
+ * with the License. You may obtain a copy of the License at *
+ * *
+ * http://www.apache.org/licenses/LICENSE-2.0 *
+ * *
+ * Unless required by applicable law or agreed to in writing, *
+ * software distributed under the License is distributed on an *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
+ * KIND, either express or implied. See the License for the *
+ * specific language governing permissions and limitations *
+ * under the License. *
+ ****************************************************************/
+
+package org.apache.james.mime4j.message;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetEncoder;
+import java.nio.charset.CoderResult;
+import java.nio.charset.CodingErrorAction;
+
+class StringInputStream extends InputStream {
+
+ private final CharsetEncoder encoder;
+ private final CharBuffer cbuf;
+ private final ByteBuffer bbuf;
+
+ private int mark;
+
+ StringInputStream(final CharSequence s, final Charset charset, int bufferSize) {
+ super();
+ this.encoder = charset.newEncoder()
+ .onMalformedInput(CodingErrorAction.REPLACE)
+ .onUnmappableCharacter(CodingErrorAction.REPLACE);
+ this.bbuf = ByteBuffer.allocate(124);
+ this.bbuf.flip();
+ this.cbuf = CharBuffer.wrap(s);
+ this.mark = -1;
+ }
+
+ StringInputStream(final CharSequence s, final Charset charset) {
+ this(s, charset, 2048);
+ }
+
+ private void fillBuffer() throws IOException {
+ this.bbuf.compact();
+ CoderResult result = this.encoder.encode(this.cbuf, this.bbuf, true);
+ if (result.isError()) {
+ result.throwException();
+ }
+ this.bbuf.flip();
+ }
+
+ @Override
+ public int read(byte[] b, int off, int len) throws IOException {
+ if (b == null) {
+ throw new NullPointerException("Byte array is null");
+ }
+ if (len < 0 || (off + len) > b.length) {
+ throw new IndexOutOfBoundsException("Array Size=" + b.length +
+ ", offset=" + off + ", length=" + len);
+ }
+ if (!this.bbuf.hasRemaining() && !this.cbuf.hasRemaining()) {
+ return -1;
+ }
+ int bytesRead = 0;
+ while (len > 0) {
+ if (this.bbuf.hasRemaining()) {
+ int chunk = Math.min(this.bbuf.remaining(), len);
+ this.bbuf.get(b, off, chunk);
+ off += chunk;
+ len -= chunk;
+ bytesRead += chunk;
+ } else {
+ fillBuffer();
+ if (!this.bbuf.hasRemaining() && !this.cbuf.hasRemaining()) {
+ break;
+ }
+ }
+ }
+ return bytesRead == 0 && !this.cbuf.hasRemaining() ? -1 : bytesRead;
+ }
+
+ @Override
+ public int read() throws IOException {
+ for (;;) {
+ if (this.bbuf.hasRemaining()) {
+ return this.bbuf.get() & 0xFF;
+ } else {
+ fillBuffer();
+ if (!this.bbuf.hasRemaining() && !this.cbuf.hasRemaining()) {
+ return -1;
+ }
+ }
+ }
+ }
+
+ @Override
+ public int read(byte[] b) throws IOException {
+ return read(b, 0, b.length);
+ }
+
+ @Override
+ public long skip(long n) throws IOException {
+ int skipped = 0;
+ while (n > 0 && this.cbuf.hasRemaining()) {
+ this.cbuf.get();
+ n--;
+ skipped++;
+ }
+ return skipped;
+ }
+
+ @Override
+ public int available() throws IOException {
+ return this.cbuf.remaining();
+ }
+
+ @Override
+ public void close() throws IOException {
+ }
+
+ @Override
+ public void mark(int readlimit) {
+ this.mark = this.cbuf.position();
+ }
+
+ @Override
+ public void reset() throws IOException {
+ if (this.mark != -1) {
+ this.cbuf.position(this.mark);
+ this.mark = -1;
+ }
+ }
+
+ @Override
+ public boolean markSupported() {
+ return true;
+ }
+
+}
@@ -0,0 +1,129 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one *
+ * or more contributor license agreements. See the NOTICE file *
+ * distributed with this work for additional information *
+ * regarding copyright ownership. The ASF licenses this file *
+ * to you under the Apache License, Version 2.0 (the *
+ * "License"); you may not use this file except in compliance *
+ * with the License. You may obtain a copy of the License at *
+ * *
+ * http://www.apache.org/licenses/LICENSE-2.0 *
+ * *
+ * Unless required by applicable law or agreed to in writing, *
+ * software distributed under the License is distributed on an *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
+ * KIND, either express or implied. See the License for the *
+ * specific language governing permissions and limitations *
+ * under the License. *
+ ****************************************************************/
+
+package org.apache.james.mime4j.message;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.SecureRandom;
+
+import org.apache.james.mime4j.util.CharsetUtil;
+
+import junit.framework.TestCase;
+
+public class StringInputStreamTest extends TestCase {
+
+ private static final String SWISS_GERMAN_HELLO = "Gr\374ezi_z\344m\344";
+ private static final String RUSSIAN_HELLO = "\u0412\u0441\u0435\u043C_\u043F\u0440\u0438\u0432\u0435\u0442";
+ private static final String TEST_STRING = "Hello and stuff " + SWISS_GERMAN_HELLO + " " + RUSSIAN_HELLO;
+ private static final String LARGE_TEST_STRING;
+
+ static {
+ StringBuilder buffer = new StringBuilder();
+ for (int i=0; i<100; i++) {
+ buffer.append(TEST_STRING);
+ }
+ LARGE_TEST_STRING = buffer.toString();
+ }
+
+ private static void singleByteReadTest(final String testString) throws IOException {
+ byte[] bytes = testString.getBytes(CharsetUtil.UTF_8.name());
+ InputStream in = new StringInputStream(testString, CharsetUtil.UTF_8);
+ for (byte b : bytes) {
+ int read = in.read();
+ assertTrue(read >= 0);
+ assertTrue(read <= 255);
+ assertEquals(b, (byte)read);
+ }
+ assertEquals(-1, in.read());
+ }
+
+ private static void bufferedReadTest(final String testString) throws IOException {
+ SecureRandom rnd = new SecureRandom();
+ byte[] expected = testString.getBytes(CharsetUtil.UTF_8.name());
+ InputStream in = new StringInputStream(testString, CharsetUtil.UTF_8);
+ byte[] buffer = new byte[128];
+ int offset = 0;
+ while (true) {
+ int bufferOffset = rnd.nextInt(64);
+ int bufferLength = rnd.nextInt(64);
+ int read = in.read(buffer, bufferOffset, bufferLength);
+ if (read == -1) {
+ assertEquals(offset, expected.length);
+ break;
+ } else {
+ assertTrue(read <= bufferLength);
+ while (read > 0) {
+ assertTrue(offset < expected.length);
+ assertEquals(expected[offset], buffer[bufferOffset]);
+ offset++;
+ bufferOffset++;
+ read--;
+ }
+ }
+ }
+ }
+
+ public void testSingleByteRead() throws IOException {
+ singleByteReadTest(TEST_STRING);
+ }
+
+ public void testLargeSingleByteRead() throws IOException {
+ singleByteReadTest(LARGE_TEST_STRING);
+ }
+
+ public void testBufferedRead() throws IOException {
+ bufferedReadTest(TEST_STRING);
+ }
+
+ public void testLargeBufferedRead() throws IOException {
+ bufferedReadTest(LARGE_TEST_STRING);
+ }
+
+ public void testReadZero() throws Exception {
+ InputStream r = new StringInputStream("test", CharsetUtil.UTF_8);
+ byte[] bytes = new byte[30];
+ assertEquals(0, r.read(bytes, 0, 0));
+ }
+
+ public void testSkip() throws Exception {
+ InputStream r = new StringInputStream("test", CharsetUtil.UTF_8);
+ r.skip(1);
+ r.skip(2);
+ assertEquals('t', r.read());
+ r.skip(100);
+ assertEquals(-1, r.read());
+ }
+
+ public void testMarkReset() throws Exception {
+ InputStream r = new StringInputStream("test", CharsetUtil.UTF_8);
+ r.skip(2);
+ r.mark(0);
+ assertEquals('s', r.read());
+ assertEquals('t', r.read());
+ assertEquals(-1, r.read());
+ r.reset();
+ assertEquals('s', r.read());
+ assertEquals('t', r.read());
+ assertEquals(-1, r.read());
+ r.reset();
+ r.reset();
+ }
+
+}

0 comments on commit 4eb7fbf

Please sign in to comment.