You are viewing a plain text version of this content. The canonical link for it is here.
Posted to server-dev@james.apache.org by rd...@apache.org on 2008/03/29 09:02:35 UTC

svn commit: r642517 - in /james/server/trunk/imap-mailbox-processor-function/src: main/java/org/apache/james/imapserver/processor/imap4rev1/ test/java/org/apache/james/imapserver/processor/imap4rev1/

Author: rdonkin
Date: Sat Mar 29 01:02:34 2008
New Revision: 642517

URL: http://svn.apache.org/viewvc?rev=642517&view=rev
Log:
Partial FETCH processing

Added:
    james/server/trunk/imap-mailbox-processor-function/src/main/java/org/apache/james/imapserver/processor/imap4rev1/PartialFetchBodyElement.java
    james/server/trunk/imap-mailbox-processor-function/src/main/java/org/apache/james/imapserver/processor/imap4rev1/PartialWritableByteChannel.java
    james/server/trunk/imap-mailbox-processor-function/src/test/java/org/apache/james/imapserver/processor/imap4rev1/PartialFetchBodyElementTest.java
    james/server/trunk/imap-mailbox-processor-function/src/test/java/org/apache/james/imapserver/processor/imap4rev1/PartialWritableByteChannelTest.java
Modified:
    james/server/trunk/imap-mailbox-processor-function/src/main/java/org/apache/james/imapserver/processor/imap4rev1/FetchProcessor.java

Modified: james/server/trunk/imap-mailbox-processor-function/src/main/java/org/apache/james/imapserver/processor/imap4rev1/FetchProcessor.java
URL: http://svn.apache.org/viewvc/james/server/trunk/imap-mailbox-processor-function/src/main/java/org/apache/james/imapserver/processor/imap4rev1/FetchProcessor.java?rev=642517&r1=642516&r2=642517&view=diff
==============================================================================
--- james/server/trunk/imap-mailbox-processor-function/src/main/java/org/apache/james/imapserver/processor/imap4rev1/FetchProcessor.java (original)
+++ james/server/trunk/imap-mailbox-processor-function/src/main/java/org/apache/james/imapserver/processor/imap4rev1/FetchProcessor.java Sat Mar 29 01:02:34 2008
@@ -476,43 +476,70 @@
         private FetchResponse.BodyElement bodyFetch(final MessageResult messageResult,
                 BodyFetchElement fetchElement) throws MessagingException, ProtocolException {
             
-            final FetchResponse.BodyElement result;
+            final Long firstOctet = fetchElement.getFirstOctet();
+            final Long numberOfOctets = fetchElement.getNumberOfOctets();
             final String name = fetchElement.getResponseName();
             final int specifier = fetchElement.getSectionType();
             final int[] path = fetchElement.getPath();
             final Collection names = fetchElement.getFieldNames();
             final boolean isBase = (path == null || path.length == 0);
+            final FetchResponse.BodyElement fullResult 
+                = bodyContent(messageResult, name, specifier, path, names, isBase);
+            final FetchResponse.BodyElement result 
+                = wrapIfPartialFetch(firstOctet, numberOfOctets, fullResult);
+            return result;
+
+        }
+
+        private FetchResponse.BodyElement bodyContent(final MessageResult messageResult, final String name, final int specifier, final int[] path, final Collection names, final boolean isBase) throws MailboxManagerException, MessagingException {
+            final FetchResponse.BodyElement fullResult;
             switch (specifier) {
                 case BodyFetchElement.CONTENT:
-                    result = content(messageResult, name, path, isBase);
+                    fullResult = content(messageResult, name, path, isBase);
                     break;
                     
                 case BodyFetchElement.HEADER_FIELDS:
-                    result = fields(messageResult, name, path, names, isBase);
+                    fullResult = fields(messageResult, name, path, names, isBase);
                     break;
                     
                 case BodyFetchElement.HEADER_NOT_FIELDS:
-                    result = fieldsNot(messageResult, name, path, names, isBase);
+                    fullResult = fieldsNot(messageResult, name, path, names, isBase);
                     break;
                     
                 case BodyFetchElement.MIME:
-                    result = mimeHeaders(messageResult, name, path, isBase);
+                    fullResult = mimeHeaders(messageResult, name, path, isBase);
                     break;
                 case BodyFetchElement.HEADER:
-                    result = headers(messageResult, name, path, isBase);
+                    fullResult = headers(messageResult, name, path, isBase);
                     break;
                     
                 case BodyFetchElement.TEXT:
-                    result = text(messageResult, name, path, isBase);
+                    fullResult = text(messageResult, name, path, isBase);
                     break;
                     
                 default:
-                    result = null;
+                    fullResult = null;
                 break;
             }
+            return fullResult;
+        }
 
+        private FetchResponse.BodyElement wrapIfPartialFetch(final Long firstOctet, final Long numberOfOctets, final FetchResponse.BodyElement fullResult) {
+            final FetchResponse.BodyElement result;
+            if (firstOctet == null) {
+                result = fullResult;
+            } else {
+                final long numberOfOctetsAsLong;
+                if (numberOfOctets == null) {
+                    numberOfOctetsAsLong = Long.MAX_VALUE;
+                } else {
+                    numberOfOctetsAsLong = numberOfOctets.longValue();
+                }
+                final long firstOctetAsLong = firstOctet.longValue();
+                result = new PartialFetchBodyElement(fullResult, 
+                        firstOctetAsLong, numberOfOctetsAsLong); 
+            }
             return result;
-
         }
 
         private FetchResponse.BodyElement text(final MessageResult messageResult, String name, final int[] path, final boolean isBase) throws MailboxManagerException {
@@ -815,7 +842,5 @@
         public void writeTo(WritableByteChannel channel) throws IOException {
             content.writeTo(channel);
         }
-        
-        
     }
 }

Added: james/server/trunk/imap-mailbox-processor-function/src/main/java/org/apache/james/imapserver/processor/imap4rev1/PartialFetchBodyElement.java
URL: http://svn.apache.org/viewvc/james/server/trunk/imap-mailbox-processor-function/src/main/java/org/apache/james/imapserver/processor/imap4rev1/PartialFetchBodyElement.java?rev=642517&view=auto
==============================================================================
--- james/server/trunk/imap-mailbox-processor-function/src/main/java/org/apache/james/imapserver/processor/imap4rev1/PartialFetchBodyElement.java (added)
+++ james/server/trunk/imap-mailbox-processor-function/src/main/java/org/apache/james/imapserver/processor/imap4rev1/PartialFetchBodyElement.java Sat Mar 29 01:02:34 2008
@@ -0,0 +1,47 @@
+package org.apache.james.imapserver.processor.imap4rev1;
+
+import java.io.IOException;
+import java.nio.channels.WritableByteChannel;
+
+import org.apache.james.imap.message.response.imap4rev1.FetchResponse.BodyElement;
+
+/**
+ * Wraps full content to implement a partial fetch.
+ */
+final class PartialFetchBodyElement implements BodyElement {
+
+    private final BodyElement delegate;
+    private final long firstOctet;
+    private final long numberOfOctets;
+    
+    public PartialFetchBodyElement(final BodyElement delegate, final long firstOctet, 
+            final long numberOfOctets) {
+        super();
+        this.delegate = delegate;
+        this.firstOctet = firstOctet;
+        this.numberOfOctets = numberOfOctets;
+    }
+
+    public String getName() {
+        return delegate.getName();
+    }
+
+    public long size() {
+        final long size = delegate.size();
+        final long numberOfOctets;
+        if (size > this.numberOfOctets) {
+            numberOfOctets = this.numberOfOctets;
+        } else {
+            numberOfOctets = size;
+        }
+        final long result = numberOfOctets - firstOctet;
+        return result;
+    }
+
+    public void writeTo(WritableByteChannel channel) throws IOException {
+        PartialWritableByteChannel partialChannel = 
+            new PartialWritableByteChannel(channel, firstOctet, size());
+        delegate.writeTo(partialChannel);
+    }
+    
+}
\ No newline at end of file

Added: james/server/trunk/imap-mailbox-processor-function/src/main/java/org/apache/james/imapserver/processor/imap4rev1/PartialWritableByteChannel.java
URL: http://svn.apache.org/viewvc/james/server/trunk/imap-mailbox-processor-function/src/main/java/org/apache/james/imapserver/processor/imap4rev1/PartialWritableByteChannel.java?rev=642517&view=auto
==============================================================================
--- james/server/trunk/imap-mailbox-processor-function/src/main/java/org/apache/james/imapserver/processor/imap4rev1/PartialWritableByteChannel.java (added)
+++ james/server/trunk/imap-mailbox-processor-function/src/main/java/org/apache/james/imapserver/processor/imap4rev1/PartialWritableByteChannel.java Sat Mar 29 01:02:34 2008
@@ -0,0 +1,96 @@
+/****************************************************************
+ * 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.imapserver.processor.imap4rev1;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.WritableByteChannel;
+
+/**
+ * Filters a {@link WritableByteChannel} 
+ * to supply a limited byte range
+ */
+class PartialWritableByteChannel implements WritableByteChannel {
+
+    private final WritableByteChannel delegate;
+    private final long firstOctet;
+    private final long numberOfOctets;
+    private long bytesWritten;
+    
+    public PartialWritableByteChannel(final WritableByteChannel delegate, final long firstOctet, final long numberOfOctets) {
+        super();
+        this.delegate = delegate;
+        this.firstOctet = firstOctet;
+        this.numberOfOctets = numberOfOctets;
+        bytesWritten = 0;
+    }
+
+    public int write(ByteBuffer src) throws IOException {
+        final int result;
+        final long bytesToIgnore = firstOctet - bytesWritten;
+        if (bytesToIgnore > 0) {
+            final int remaining = src.remaining();
+            if (remaining <= bytesToIgnore) {
+                result = ignoreRemaining(src);
+            } else {
+                final int remainingBytesToIgnore = (int) bytesToIgnore;
+                src.position(src.position() + remainingBytesToIgnore);
+                result = writeRemaining(src, numberOfOctets) + remainingBytesToIgnore;
+            }
+        } else {
+            final long bytesToWrite = numberOfOctets - bytesWritten + firstOctet;
+            result = writeRemaining(src, bytesToWrite);
+        }
+        bytesWritten += result;
+        return result;
+    }
+
+    private int writeRemaining(ByteBuffer src, final long bytesToWrite) throws IOException {
+        final int remaining = src.remaining();
+        final int result;
+        if (bytesToWrite <= 0) {
+            result = ignoreRemaining(src);
+        } else if (remaining < bytesToWrite ) {
+            result = delegate.write(src);
+        } else {
+            final ByteBuffer slice = src.asReadOnlyBuffer();
+            slice.limit(slice.position() + (int) bytesToWrite);
+            delegate.write(slice);
+            result = ignoreRemaining(src);
+        }
+        return result;
+    }
+
+    private int ignoreRemaining(ByteBuffer src) {
+        final int result;
+        result = src.remaining();
+        src.position(src.limit());
+        return result;
+    }
+
+    public void close() throws IOException {
+        delegate.close();
+    }
+
+    public boolean isOpen() {
+        return delegate.isOpen();
+    }
+
+}

Added: james/server/trunk/imap-mailbox-processor-function/src/test/java/org/apache/james/imapserver/processor/imap4rev1/PartialFetchBodyElementTest.java
URL: http://svn.apache.org/viewvc/james/server/trunk/imap-mailbox-processor-function/src/test/java/org/apache/james/imapserver/processor/imap4rev1/PartialFetchBodyElementTest.java?rev=642517&view=auto
==============================================================================
--- james/server/trunk/imap-mailbox-processor-function/src/test/java/org/apache/james/imapserver/processor/imap4rev1/PartialFetchBodyElementTest.java (added)
+++ james/server/trunk/imap-mailbox-processor-function/src/test/java/org/apache/james/imapserver/processor/imap4rev1/PartialFetchBodyElementTest.java Sat Mar 29 01:02:34 2008
@@ -0,0 +1,72 @@
+/****************************************************************
+ * 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.imapserver.processor.imap4rev1;
+
+import org.apache.james.imap.message.response.imap4rev1.FetchResponse.BodyElement;
+import org.jmock.Mock;
+import org.jmock.MockObjectTestCase;
+
+public class PartialFetchBodyElementTest extends MockObjectTestCase {
+
+    private static final long NUMBER_OF_OCTETS = 100;
+    
+    Mock mockBodyElement;
+    
+    protected void setUp() throws Exception {
+        super.setUp();
+        mockBodyElement = mock(BodyElement.class);
+    }
+
+    protected void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    public void testSizeShouldBeNumberOfOctetsWhenSizeMoreWhenStartIsZero() throws Exception {
+        long moreThanNumberOfOctets = NUMBER_OF_OCTETS + 1;
+        PartialFetchBodyElement element = new PartialFetchBodyElement((BodyElement) mockBodyElement.proxy(), 
+                0, NUMBER_OF_OCTETS);
+        mockBodyElement.expects(once()).method("size").will(returnValue(new Long(moreThanNumberOfOctets)));
+        assertEquals("Size is more than number of octets so should be number of octets", NUMBER_OF_OCTETS, element.size());
+    }
+    
+    public void testSizeShouldBeSizeWhenNumberOfOctetsMoreWhenStartIsZero() throws Exception {
+        long lessThanNumberOfOctets = NUMBER_OF_OCTETS -1;
+        PartialFetchBodyElement element = new PartialFetchBodyElement((BodyElement) mockBodyElement.proxy(), 
+                0, NUMBER_OF_OCTETS);
+        mockBodyElement.expects(once()).method("size").will(returnValue(new Long(lessThanNumberOfOctets)));
+        assertEquals("Size is less than number of octets so should be size", lessThanNumberOfOctets, element.size());
+    }
+    
+    public void testWhenStartPlusNumberOfOctetsIsMoreThanSizeSizeShouldBeSizeMinusStart() throws Exception {
+        long size = 60;
+        PartialFetchBodyElement element = new PartialFetchBodyElement((BodyElement) mockBodyElement.proxy(), 
+                10, NUMBER_OF_OCTETS);
+        mockBodyElement.expects(once()).method("size").will(returnValue(new Long(size)));
+        assertEquals("Size is less than number of octets so should be size", 50, element.size());
+    }
+    
+    public void testWhenStartPlusNumberOfOctetsIsLessThanSizeSizeShouldBeNumberOfOctetsMinusStart() throws Exception {
+        long size = 600;
+        PartialFetchBodyElement element = new PartialFetchBodyElement((BodyElement) mockBodyElement.proxy(), 
+                10, NUMBER_OF_OCTETS);
+        mockBodyElement.expects(once()).method("size").will(returnValue(new Long(size)));
+        assertEquals("Size is less than number of octets so should be size", 90, element.size());
+    }
+}

Added: james/server/trunk/imap-mailbox-processor-function/src/test/java/org/apache/james/imapserver/processor/imap4rev1/PartialWritableByteChannelTest.java
URL: http://svn.apache.org/viewvc/james/server/trunk/imap-mailbox-processor-function/src/test/java/org/apache/james/imapserver/processor/imap4rev1/PartialWritableByteChannelTest.java?rev=642517&view=auto
==============================================================================
--- james/server/trunk/imap-mailbox-processor-function/src/test/java/org/apache/james/imapserver/processor/imap4rev1/PartialWritableByteChannelTest.java (added)
+++ james/server/trunk/imap-mailbox-processor-function/src/test/java/org/apache/james/imapserver/processor/imap4rev1/PartialWritableByteChannelTest.java Sat Mar 29 01:02:34 2008
@@ -0,0 +1,484 @@
+/****************************************************************
+ * 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.imapserver.processor.imap4rev1;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.WritableByteChannel;
+
+import junit.framework.TestCase;
+
+public class PartialWritableByteChannelTest extends TestCase implements WritableByteChannel  {
+
+    private static final int CAPACITY = 2048;
+    
+    ByteBuffer sink;
+    
+    protected void setUp() throws Exception {
+        super.setUp();
+        sink = ByteBuffer.allocate(CAPACITY);
+    }
+
+    protected void tearDown() throws Exception {
+        super.tearDown();
+    }
+    
+    public void testShouldPassFullBufferWhenStartZeroSizeLimit() throws Exception {
+        byte [] bytes = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+        ByteBuffer src = ByteBuffer.wrap(bytes);
+        PartialWritableByteChannel channel = new PartialWritableByteChannel(this, 0, 10);
+        assertEquals(10, channel.write(src));
+        assertEquals(10, sink.position());
+        sink.flip();
+        for (int i=0;i<10;i++) {
+            assertEquals(i, sink.get());
+        }
+    }
+    
+    public void testShouldIgnoreBytesAfterLimit() throws Exception {
+        byte [] bytes = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+        ByteBuffer src = ByteBuffer.wrap(bytes);
+        PartialWritableByteChannel channel = new PartialWritableByteChannel(this, 0, 4);
+        assertEquals(10, channel.write(src));
+        assertEquals(4, sink.position());
+        sink.flip();
+        for (int i=0;i<4;i++) {
+            assertEquals(i, sink.get());
+        }
+    }
+    
+    public void testShouldIgnoreBytesBeforeStart() throws Exception {
+        byte [] bytes = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+        ByteBuffer src = ByteBuffer.wrap(bytes);
+        PartialWritableByteChannel channel = new PartialWritableByteChannel(this, 4, 6);
+        assertEquals(10, channel.write(src));
+        assertEquals(6, sink.position());
+        sink.flip();
+        for (int i=4;i<10;i++) {
+            assertEquals(i, sink.get());
+        }
+    }
+    
+    public void testShouldIgnoreBytesBeforeStartAndAfterLimit() throws Exception {
+        byte [] bytes = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+        ByteBuffer src = ByteBuffer.wrap(bytes);
+        PartialWritableByteChannel channel = new PartialWritableByteChannel(this, 4, 2);
+        assertEquals(10, channel.write(src));
+        assertEquals(2, sink.position());
+        sink.flip();
+        for (int i=4;i<6;i++) {
+            assertEquals(i, sink.get());
+        }
+    }
+
+    public void testMultiBufferShouldPassFullBufferWhenStartZeroSizeLimit() throws Exception {
+        byte [] bytes = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+        ByteBuffer src = ByteBuffer.wrap(bytes);
+        PartialWritableByteChannel channel = new PartialWritableByteChannel(this, 0, 50);
+        for (int i=0;i<5;i++) {
+            assertEquals(10, channel.write(src));
+            src.rewind();
+        }
+        assertEquals(50, sink.position());
+        sink.flip();
+        for (int l=0;l<5;l++) {
+            for (int i=0;i<10;i++) {
+                assertEquals(i, sink.get());
+            }
+        }
+    }
+    
+    public void testMultiBufferOnBoundaryShouldIgnoreBytesAfterLimit() throws Exception {
+        byte [] bytes = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+        ByteBuffer src = ByteBuffer.wrap(bytes);
+        PartialWritableByteChannel channel = new PartialWritableByteChannel(this, 0, 30);
+        for (int i=0;i<5;i++) {
+            assertEquals(10, channel.write(src));
+            src.rewind();
+        }
+        assertEquals(30, sink.position());
+        sink.flip();
+        for (int l=0;l<3;l++) {
+            for (int i=0;i<10;i++) {
+                assertEquals(i, sink.get());
+            }
+        }
+    }
+    
+    public void testMultiBufferBeforeBoundaryShouldIgnoreBytesAfterLimit() throws Exception {
+        byte [] bytes = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+        ByteBuffer src = ByteBuffer.wrap(bytes);
+        PartialWritableByteChannel channel = new PartialWritableByteChannel(this, 0, 39);
+        for (int i=0;i<5;i++) {
+            assertEquals(10, channel.write(src));
+            src.rewind();
+        }
+        assertEquals(39, sink.position());
+        sink.flip();
+        for (int l=0;l<3;l++) {
+            for (int i=0;i<10;i++) {
+                assertEquals(i, sink.get());
+            }
+        }
+        for (int i=0;i<9;i++) {
+            assertEquals(i, sink.get());
+        }
+    }
+    
+    public void testMultiBufferAfterBoundaryShouldIgnoreBytesAfterLimit() throws Exception {
+        byte [] bytes = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+        ByteBuffer src = ByteBuffer.wrap(bytes);
+        PartialWritableByteChannel channel = new PartialWritableByteChannel(this, 0, 31);
+        for (int i=0;i<5;i++) {
+            assertEquals(10, channel.write(src));
+            src.rewind();
+        }
+        assertEquals(31, sink.position());
+        sink.flip();
+        for (int l=0;l<3;l++) {
+            for (int i=0;i<10;i++) {
+                assertEquals(i, sink.get());
+            }
+        }
+        assertEquals(0, sink.get());
+    }
+    
+    public void testMultiBufferOnBoundaryOctetsOverBufferShouldIgnoreBytesBeforeStart() throws Exception {
+        byte [] bytes = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+        ByteBuffer src = ByteBuffer.wrap(bytes);
+        PartialWritableByteChannel channel = new PartialWritableByteChannel(this, 20, 21);
+        for (int i=0;i<5;i++) {
+            assertEquals(10, channel.write(src));
+            src.rewind();
+        }
+        assertEquals(21, sink.position());
+        sink.flip();
+        for (int l=0;l<2;l++) {
+            for (int i=0;i<10;i++) {
+                assertEquals(i, sink.get());
+            }
+        }
+        assertEquals(0, sink.get());
+    }
+    
+    public void testMultiBufferAfterBoundaryOctetsOverBufferShouldIgnoreBytesBeforeStart() throws Exception {
+        byte [] bytes = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+        ByteBuffer src = ByteBuffer.wrap(bytes);
+        PartialWritableByteChannel channel = new PartialWritableByteChannel(this, 21, 21);
+        for (int i=0;i<5;i++) {
+            assertEquals(10, channel.write(src));
+            src.rewind();
+        }
+        assertEquals(21, sink.position());
+        sink.flip();
+        for (int l=0;l<2;l++) {
+            for (int i=1;i<11;i++) {
+                assertEquals(i % 10, sink.get());
+            }
+        }
+        assertEquals(1, sink.get());
+    }
+    
+    public void testMultiBufferBeforeBoundaryOctetsOverBufferShouldIgnoreBytesBeforeStart() throws Exception {
+        byte [] bytes = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+        ByteBuffer src = ByteBuffer.wrap(bytes);
+        PartialWritableByteChannel channel = new PartialWritableByteChannel(this, 19, 21);
+        for (int i=0;i<5;i++) {
+            assertEquals(10, channel.write(src));
+            src.rewind();
+        }
+        assertEquals(21, sink.position());
+        sink.flip();
+        for (int l=0;l<2;l++) {
+            for (int i=9;i<19;i++) {
+                assertEquals(i % 10, sink.get());
+            }
+        }
+        assertEquals(9, sink.get());
+    }
+    
+    public void testMultiBufferOnBoundaryOctetsOnBufferShouldIgnoreBytesBeforeStart() throws Exception {
+        byte [] bytes = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+        ByteBuffer src = ByteBuffer.wrap(bytes);
+        PartialWritableByteChannel channel = new PartialWritableByteChannel(this, 20, 20);
+        for (int i=0;i<5;i++) {
+            assertEquals(10, channel.write(src));
+            src.rewind();
+        }
+        assertEquals(20, sink.position());
+        sink.flip();
+        for (int l=0;l<2;l++) {
+            for (int i=0;i<10;i++) {
+                assertEquals(i, sink.get());
+            }
+        }
+    }
+    
+    public void testMultiBufferAfterBoundaryOctetsObBufferShouldIgnoreBytesBeforeStart() throws Exception {
+        byte [] bytes = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+        ByteBuffer src = ByteBuffer.wrap(bytes);
+        PartialWritableByteChannel channel = new PartialWritableByteChannel(this, 21, 20);
+        for (int i=0;i<5;i++) {
+            assertEquals(10, channel.write(src));
+            src.rewind();
+        }
+        assertEquals(20, sink.position());
+        sink.flip();
+        for (int l=0;l<2;l++) {
+            for (int i=1;i<11;i++) {
+                assertEquals(i % 10, sink.get());
+            }
+        }
+    }
+    
+    public void testMultiBufferBeforeBoundaryOctetsOnBufferShouldIgnoreBytesBeforeStart() throws Exception {
+        byte [] bytes = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+        ByteBuffer src = ByteBuffer.wrap(bytes);
+        PartialWritableByteChannel channel = new PartialWritableByteChannel(this, 19, 20);
+        for (int i=0;i<5;i++) {
+            assertEquals(10, channel.write(src));
+            src.rewind();
+        }
+        assertEquals(20, sink.position());
+        sink.flip();
+        for (int l=0;l<2;l++) {
+            for (int i=9;i<19;i++) {
+                assertEquals(i % 10, sink.get());
+            }
+        }
+    }
+    
+    public void testMultiBufferOnBoundaryOctetsUnderBufferShouldIgnoreBytesBeforeStart() throws Exception {
+        byte [] bytes = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+        ByteBuffer src = ByteBuffer.wrap(bytes);
+        PartialWritableByteChannel channel = new PartialWritableByteChannel(this, 20, 19);
+        for (int i=0;i<5;i++) {
+            assertEquals(10, channel.write(src));
+            src.rewind();
+        }
+        assertEquals(19, sink.position());
+        sink.flip();
+        for (int l=0;l<2;l++) {
+            for (int i=0;i<10;i++) {
+                if (sink.hasRemaining()) {
+                    assertEquals(i, sink.get());
+                }
+            }
+        }
+    }
+    
+    public void testMultiBufferAfterBoundaryOctetsUnderBufferShouldIgnoreBytesBeforeStart() throws Exception {
+        byte [] bytes = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+        ByteBuffer src = ByteBuffer.wrap(bytes);
+        PartialWritableByteChannel channel = new PartialWritableByteChannel(this, 21, 19);
+        for (int i=0;i<5;i++) {
+            assertEquals(10, channel.write(src));
+            src.rewind();
+        }
+        assertEquals(19, sink.position());
+        sink.flip();
+        for (int l=0;l<2;l++) {
+            for (int i=1;i<11;i++) {
+                if (sink.hasRemaining()) {
+                    assertEquals(i % 10, sink.get());
+                }
+            }
+        }
+    }
+    
+    public void testMultiBufferBeforeBoundaryOctetsUnderBufferShouldIgnoreBytesBeforeStart() throws Exception {
+        byte [] bytes = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+        ByteBuffer src = ByteBuffer.wrap(bytes);
+        PartialWritableByteChannel channel = new PartialWritableByteChannel(this, 19, 19);
+        for (int i=0;i<5;i++) {
+            assertEquals(10, channel.write(src));
+            src.rewind();
+        }
+        assertEquals(19, sink.position());
+        sink.flip();
+        for (int l=0;l<2;l++) {
+            for (int i=9;i<19;i++) {
+                if (sink.hasRemaining()) {
+                    assertEquals(i % 10, sink.get());
+                }
+            }
+        }
+    }
+    
+    public void testMultiBufferShouldIgnoreBytesBeforeStartAndAfterLimit() throws Exception {
+        byte [] bytes = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+        ByteBuffer src = ByteBuffer.wrap(bytes);
+        PartialWritableByteChannel channel = new PartialWritableByteChannel(this, 4, 2);
+        assertEquals(10, channel.write(src));
+        assertEquals(2, sink.position());
+        sink.flip();
+        for (int i=4;i<6;i++) {
+            assertEquals(i, sink.get());
+        }
+    }
+    
+    public void testMultiBufferOnBoundaryOnBufferShouldIgnoreBytesBeforeStartAndAfterLimit() throws Exception {
+        byte [] bytes = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+        ByteBuffer src = ByteBuffer.wrap(bytes);
+        PartialWritableByteChannel channel = new PartialWritableByteChannel(this, 30, 30);
+        for (int i=0;i<8;i++) {
+            assertEquals(10, channel.write(src));
+            src.rewind();
+        }
+        assertEquals(30, sink.position());
+        sink.flip();
+        for (int i=0;i<30;i++) {
+            assertEquals(i % 10, sink.get());
+        }
+    }
+    
+    public void testMultiBufferBeforeBoundaryOnBufferShouldIgnoreBytesBeforeStartAndAfterLimit() throws Exception {
+        byte [] bytes = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+        ByteBuffer src = ByteBuffer.wrap(bytes);
+        PartialWritableByteChannel channel = new PartialWritableByteChannel(this, 29, 30);
+        for (int i=0;i<8;i++) {
+            assertEquals(10, channel.write(src));
+            src.rewind();
+        }
+        assertEquals(30, sink.position());
+        sink.flip();
+        for (int i=9;i<39;i++) {
+            assertEquals(i % 10, sink.get());
+        }
+    }
+    
+    public void testMultiBufferAfterBoundaryOnBufferShouldIgnoreBytesBeforeStartAndAfterLimit() throws Exception {
+        byte [] bytes = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+        ByteBuffer src = ByteBuffer.wrap(bytes);
+        PartialWritableByteChannel channel = new PartialWritableByteChannel(this, 31, 30);
+        for (int i=0;i<8;i++) {
+            assertEquals(10, channel.write(src));
+            src.rewind();
+        }
+        assertEquals(30, sink.position());
+        sink.flip();
+        for (int i=1;i<31;i++) {
+            assertEquals(i % 10, sink.get());
+        }
+    }
+    
+    public void testMultiBufferOnBoundaryAfterBufferShouldIgnoreBytesBeforeStartAndAfterLimit() throws Exception {
+        byte [] bytes = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+        ByteBuffer src = ByteBuffer.wrap(bytes);
+        PartialWritableByteChannel channel = new PartialWritableByteChannel(this, 30, 31);
+        for (int i=0;i<8;i++) {
+            assertEquals(10, channel.write(src));
+            src.rewind();
+        }
+        assertEquals(31, sink.position());
+        sink.flip();
+        for (int i=0;i<31;i++) {
+            assertEquals(i % 10, sink.get());
+        }
+    }
+    
+    public void testMultiBufferBeforeBoundaryAfterBufferShouldIgnoreBytesBeforeStartAndAfterLimit() throws Exception {
+        byte [] bytes = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+        ByteBuffer src = ByteBuffer.wrap(bytes);
+        PartialWritableByteChannel channel = new PartialWritableByteChannel(this, 29, 31);
+        for (int i=0;i<8;i++) {
+            assertEquals(10, channel.write(src));
+            src.rewind();
+        }
+        assertEquals(31, sink.position());
+        sink.flip();
+        for (int i=9;i<40;i++) {
+            assertEquals(i % 10, sink.get());
+        }
+    }
+    
+    public void testMultiBufferAfterBoundaryAfterBufferShouldIgnoreBytesBeforeStartAndAfterLimit() throws Exception {
+        byte [] bytes = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+        ByteBuffer src = ByteBuffer.wrap(bytes);
+        PartialWritableByteChannel channel = new PartialWritableByteChannel(this, 31, 31);
+        for (int i=0;i<8;i++) {
+            assertEquals(10, channel.write(src));
+            src.rewind();
+        }
+        assertEquals(31, sink.position());
+        sink.flip();
+        for (int i=1;i<32;i++) {
+            assertEquals(i % 10, sink.get());
+        }
+    }
+    
+    public void testMultiBufferOnBoundaryBeforeBufferShouldIgnoreBytesBeforeStartAndAfterLimit() throws Exception {
+        byte [] bytes = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+        ByteBuffer src = ByteBuffer.wrap(bytes);
+        PartialWritableByteChannel channel = new PartialWritableByteChannel(this, 30, 29);
+        for (int i=0;i<8;i++) {
+            assertEquals(10, channel.write(src));
+            src.rewind();
+        }
+        assertEquals(29, sink.position());
+        sink.flip();
+        for (int i=0;i<29;i++) {
+            assertEquals(i % 10, sink.get());
+        }
+    }
+    
+    public void testMultiBufferBeforeBoundaryBeforeBufferShouldIgnoreBytesBeforeStartAndAfterLimit() throws Exception {
+        byte [] bytes = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+        ByteBuffer src = ByteBuffer.wrap(bytes);
+        PartialWritableByteChannel channel = new PartialWritableByteChannel(this, 29, 29);
+        for (int i=0;i<8;i++) {
+            assertEquals(10, channel.write(src));
+            src.rewind();
+        }
+        assertEquals(29, sink.position());
+        sink.flip();
+        for (int i=9;i<38;i++) {
+            assertEquals(i % 10, sink.get());
+        }
+    }
+    
+    public void testMultiBufferAfterBoundaryBeforeBufferShouldIgnoreBytesBeforeStartAndAfterLimit() throws Exception {
+        byte [] bytes = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+        ByteBuffer src = ByteBuffer.wrap(bytes);
+        PartialWritableByteChannel channel = new PartialWritableByteChannel(this, 31, 29);
+        for (int i=0;i<8;i++) {
+            assertEquals(10, channel.write(src));
+            src.rewind();
+        }
+        assertEquals(29, sink.position());
+        sink.flip();
+        for (int i=1;i<30;i++) {
+            assertEquals(i % 10, sink.get());
+        }
+    }
+    
+    public int write(ByteBuffer src) throws IOException {
+        int result = src.remaining();
+        sink.put(src);
+        return result;
+    }
+
+    public void close() throws IOException {
+    }
+
+    public boolean isOpen() {
+        return true;
+    }
+}



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