You are viewing a plain text version of this content. The canonical link for it is here.
Posted to mime4j-dev@james.apache.org by ol...@apache.org on 2012/01/05 23:13:00 UTC

svn commit: r1227867 - in /james/mime4j/trunk/dom/src: main/java/org/apache/james/mime4j/message/ test/java/org/apache/james/mime4j/message/

Author: olegk
Date: Thu Jan  5 22:13:00 2012
New Revision: 1227867

URL: http://svn.apache.org/viewvc?rev=1227867&view=rev
Log:
More efficient implementation of TextBody backed by a String; body content can be streamed without convering the string to a byte array

Added:
    james/mime4j/trunk/dom/src/main/java/org/apache/james/mime4j/message/StringBody.java   (with props)
    james/mime4j/trunk/dom/src/main/java/org/apache/james/mime4j/message/StringInputStream.java   (with props)
    james/mime4j/trunk/dom/src/test/java/org/apache/james/mime4j/message/StringInputStreamTest.java   (with props)
Modified:
    james/mime4j/trunk/dom/src/main/java/org/apache/james/mime4j/message/BasicBodyFactory.java

Modified: james/mime4j/trunk/dom/src/main/java/org/apache/james/mime4j/message/BasicBodyFactory.java
URL: http://svn.apache.org/viewvc/james/mime4j/trunk/dom/src/main/java/org/apache/james/mime4j/message/BasicBodyFactory.java?rev=1227867&r1=1227866&r2=1227867&view=diff
==============================================================================
--- james/mime4j/trunk/dom/src/main/java/org/apache/james/mime4j/message/BasicBodyFactory.java (original)
+++ james/mime4j/trunk/dom/src/main/java/org/apache/james/mime4j/message/BasicBodyFactory.java Thu Jan  5 22:13:00 2012
@@ -23,6 +23,8 @@ import java.io.ByteArrayOutputStream;
 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 @@ import org.apache.james.mime4j.util.Char
  */
 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 class BasicBodyFactory implements
         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) {

Added: james/mime4j/trunk/dom/src/main/java/org/apache/james/mime4j/message/StringBody.java
URL: http://svn.apache.org/viewvc/james/mime4j/trunk/dom/src/main/java/org/apache/james/mime4j/message/StringBody.java?rev=1227867&view=auto
==============================================================================
--- james/mime4j/trunk/dom/src/main/java/org/apache/james/mime4j/message/StringBody.java (added)
+++ james/mime4j/trunk/dom/src/main/java/org/apache/james/mime4j/message/StringBody.java Thu Jan  5 22:13:00 2012
@@ -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);
+    }
+
+}

Propchange: james/mime4j/trunk/dom/src/main/java/org/apache/james/mime4j/message/StringBody.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: james/mime4j/trunk/dom/src/main/java/org/apache/james/mime4j/message/StringBody.java
------------------------------------------------------------------------------
    svn:executable = *

Propchange: james/mime4j/trunk/dom/src/main/java/org/apache/james/mime4j/message/StringBody.java
------------------------------------------------------------------------------
    svn:keywords = Date Revision

Propchange: james/mime4j/trunk/dom/src/main/java/org/apache/james/mime4j/message/StringBody.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: james/mime4j/trunk/dom/src/main/java/org/apache/james/mime4j/message/StringInputStream.java
URL: http://svn.apache.org/viewvc/james/mime4j/trunk/dom/src/main/java/org/apache/james/mime4j/message/StringInputStream.java?rev=1227867&view=auto
==============================================================================
--- james/mime4j/trunk/dom/src/main/java/org/apache/james/mime4j/message/StringInputStream.java (added)
+++ james/mime4j/trunk/dom/src/main/java/org/apache/james/mime4j/message/StringInputStream.java Thu Jan  5 22:13:00 2012
@@ -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;
+    }
+
+}

Propchange: james/mime4j/trunk/dom/src/main/java/org/apache/james/mime4j/message/StringInputStream.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: james/mime4j/trunk/dom/src/main/java/org/apache/james/mime4j/message/StringInputStream.java
------------------------------------------------------------------------------
    svn:executable = *

Propchange: james/mime4j/trunk/dom/src/main/java/org/apache/james/mime4j/message/StringInputStream.java
------------------------------------------------------------------------------
    svn:keywords = Date Revision

Propchange: james/mime4j/trunk/dom/src/main/java/org/apache/james/mime4j/message/StringInputStream.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: james/mime4j/trunk/dom/src/test/java/org/apache/james/mime4j/message/StringInputStreamTest.java
URL: http://svn.apache.org/viewvc/james/mime4j/trunk/dom/src/test/java/org/apache/james/mime4j/message/StringInputStreamTest.java?rev=1227867&view=auto
==============================================================================
--- james/mime4j/trunk/dom/src/test/java/org/apache/james/mime4j/message/StringInputStreamTest.java (added)
+++ james/mime4j/trunk/dom/src/test/java/org/apache/james/mime4j/message/StringInputStreamTest.java Thu Jan  5 22:13:00 2012
@@ -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();
+    }
+
+}

Propchange: james/mime4j/trunk/dom/src/test/java/org/apache/james/mime4j/message/StringInputStreamTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: james/mime4j/trunk/dom/src/test/java/org/apache/james/mime4j/message/StringInputStreamTest.java
------------------------------------------------------------------------------
    svn:executable = *

Propchange: james/mime4j/trunk/dom/src/test/java/org/apache/james/mime4j/message/StringInputStreamTest.java
------------------------------------------------------------------------------
    svn:keywords = Date Revision

Propchange: james/mime4j/trunk/dom/src/test/java/org/apache/james/mime4j/message/StringInputStreamTest.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain



Re: svn commit: r1227867

Posted by Norman Maurer <no...@googlemail.com>.
Ah I see... I will then start the release process later then.

Bye,
Norman

Sent from my iPhone. Excuse any typos....

Am 07.01.2012 um 14:00 schrieb Oleg Kalnichevski <ol...@apache.org>:

> On Sat, 2012-01-07 at 09:54 +0100, Norman Maurer wrote:
>> Hi Oleg,
>> 
>> wouldnt it better be called CharSequenceInputStream?
>> 
>> Bye
>> Norman
>> 
> 
> Hi Norman
> 
> This class is package private and therefore its name really makes no
> difference. Feel free to rename it, though.
> 
> By the way, I think we are ready for 0.7.2
> 
> Cheers
> 
> Oleg
> 
> 

Re: svn commit: r1227867

Posted by Oleg Kalnichevski <ol...@apache.org>.
On Sat, 2012-01-07 at 09:54 +0100, Norman Maurer wrote:
> Hi Oleg,
> 
> wouldnt it better be called CharSequenceInputStream?
> 
> Bye
> Norman
> 

Hi Norman

This class is package private and therefore its name really makes no
difference. Feel free to rename it, though.

By the way, I think we are ready for 0.7.2

Cheers

Oleg



Re: svn commit: r1227867 - in /james/mime4j/trunk/dom/src: main/java/org/apache/james/mime4j/message/ test/java/org/apache/james/mime4j/message/

Posted by Norman Maurer <no...@googlemail.com>.
Hi Oleg,

wouldnt it better be called CharSequenceInputStream?

Bye
Norman


Am 05.01.2012 um 23:13 schrieb olegk@apache.org:

> Author: olegk
> Date: Thu Jan  5 22:13:00 2012
> New Revision: 1227867
> 
> URL: http://svn.apache.org/viewvc?rev=1227867&view=rev
> Log:
> More efficient implementation of TextBody backed by a String; body content can be streamed without convering the string to a byte array
> 
> Added:
>    james/mime4j/trunk/dom/src/main/java/org/apache/james/mime4j/message/StringBody.java   (with props)
>    james/mime4j/trunk/dom/src/main/java/org/apache/james/mime4j/message/StringInputStream.java   (with props)
>    james/mime4j/trunk/dom/src/test/java/org/apache/james/mime4j/message/StringInputStreamTest.java   (with props)
> Modified:
>    james/mime4j/trunk/dom/src/main/java/org/apache/james/mime4j/message/BasicBodyFactory.java
> 
> Modified: james/mime4j/trunk/dom/src/main/java/org/apache/james/mime4j/message/BasicBodyFactory.java
> URL: http://svn.apache.org/viewvc/james/mime4j/trunk/dom/src/main/java/org/apache/james/mime4j/message/BasicBodyFactory.java?rev=1227867&r1=1227866&r2=1227867&view=diff
> ==============================================================================
> --- james/mime4j/trunk/dom/src/main/java/org/apache/james/mime4j/message/BasicBodyFactory.java (original)
> +++ james/mime4j/trunk/dom/src/main/java/org/apache/james/mime4j/message/BasicBodyFactory.java Thu Jan  5 22:13:00 2012
> @@ -23,6 +23,8 @@ import java.io.ByteArrayOutputStream;
> 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 @@ import org.apache.james.mime4j.util.Char
>  */
> 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 class BasicBodyFactory implements
>         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) {
> 
> Added: james/mime4j/trunk/dom/src/main/java/org/apache/james/mime4j/message/StringBody.java
> URL: http://svn.apache.org/viewvc/james/mime4j/trunk/dom/src/main/java/org/apache/james/mime4j/message/StringBody.java?rev=1227867&view=auto
> ==============================================================================
> --- james/mime4j/trunk/dom/src/main/java/org/apache/james/mime4j/message/StringBody.java (added)
> +++ james/mime4j/trunk/dom/src/main/java/org/apache/james/mime4j/message/StringBody.java Thu Jan  5 22:13:00 2012
> @@ -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);
> +    }
> +
> +}
> 
> Propchange: james/mime4j/trunk/dom/src/main/java/org/apache/james/mime4j/message/StringBody.java
> ------------------------------------------------------------------------------
>    svn:eol-style = native
> 
> Propchange: james/mime4j/trunk/dom/src/main/java/org/apache/james/mime4j/message/StringBody.java
> ------------------------------------------------------------------------------
>    svn:executable = *
> 
> Propchange: james/mime4j/trunk/dom/src/main/java/org/apache/james/mime4j/message/StringBody.java
> ------------------------------------------------------------------------------
>    svn:keywords = Date Revision
> 
> Propchange: james/mime4j/trunk/dom/src/main/java/org/apache/james/mime4j/message/StringBody.java
> ------------------------------------------------------------------------------
>    svn:mime-type = text/plain
> 
> Added: james/mime4j/trunk/dom/src/main/java/org/apache/james/mime4j/message/StringInputStream.java
> URL: http://svn.apache.org/viewvc/james/mime4j/trunk/dom/src/main/java/org/apache/james/mime4j/message/StringInputStream.java?rev=1227867&view=auto
> ==============================================================================
> --- james/mime4j/trunk/dom/src/main/java/org/apache/james/mime4j/message/StringInputStream.java (added)
> +++ james/mime4j/trunk/dom/src/main/java/org/apache/james/mime4j/message/StringInputStream.java Thu Jan  5 22:13:00 2012
> @@ -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;
> +    }
> +
> +}
> 
> Propchange: james/mime4j/trunk/dom/src/main/java/org/apache/james/mime4j/message/StringInputStream.java
> ------------------------------------------------------------------------------
>    svn:eol-style = native
> 
> Propchange: james/mime4j/trunk/dom/src/main/java/org/apache/james/mime4j/message/StringInputStream.java
> ------------------------------------------------------------------------------
>    svn:executable = *
> 
> Propchange: james/mime4j/trunk/dom/src/main/java/org/apache/james/mime4j/message/StringInputStream.java
> ------------------------------------------------------------------------------
>    svn:keywords = Date Revision
> 
> Propchange: james/mime4j/trunk/dom/src/main/java/org/apache/james/mime4j/message/StringInputStream.java
> ------------------------------------------------------------------------------
>    svn:mime-type = text/plain
> 
> Added: james/mime4j/trunk/dom/src/test/java/org/apache/james/mime4j/message/StringInputStreamTest.java
> URL: http://svn.apache.org/viewvc/james/mime4j/trunk/dom/src/test/java/org/apache/james/mime4j/message/StringInputStreamTest.java?rev=1227867&view=auto
> ==============================================================================
> --- james/mime4j/trunk/dom/src/test/java/org/apache/james/mime4j/message/StringInputStreamTest.java (added)
> +++ james/mime4j/trunk/dom/src/test/java/org/apache/james/mime4j/message/StringInputStreamTest.java Thu Jan  5 22:13:00 2012
> @@ -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();
> +    }
> +
> +}
> 
> Propchange: james/mime4j/trunk/dom/src/test/java/org/apache/james/mime4j/message/StringInputStreamTest.java
> ------------------------------------------------------------------------------
>    svn:eol-style = native
> 
> Propchange: james/mime4j/trunk/dom/src/test/java/org/apache/james/mime4j/message/StringInputStreamTest.java
> ------------------------------------------------------------------------------
>    svn:executable = *
> 
> Propchange: james/mime4j/trunk/dom/src/test/java/org/apache/james/mime4j/message/StringInputStreamTest.java
> ------------------------------------------------------------------------------
>    svn:keywords = Date Revision
> 
> Propchange: james/mime4j/trunk/dom/src/test/java/org/apache/james/mime4j/message/StringInputStreamTest.java
> ------------------------------------------------------------------------------
>    svn:mime-type = text/plain
> 
>