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/02/09 18:41:55 UTC

svn commit: r620163 - in /james/server/trunk: core-library/src/main/java/org/apache/james/mailboxmanager/ core-library/src/main/java/org/apache/james/mailboxmanager/impl/ core-library/src/test/java/org/apache/james/mailboxmanager/ imap-api/src/main/jav...

Author: rdonkin
Date: Sat Feb  9 09:41:53 2008
New Revision: 620163

URL: http://svn.apache.org/viewvc?rev=620163&view=rev
Log:
Decode fetch body commands in the codec.

Added:
    james/server/trunk/imap-codec-library/src/main/java/org/apache/james/imapserver/codec/decode/FetchPartPathDecoder.java
    james/server/trunk/imap-codec-library/src/test/java/org/apache/james/imapserver/codec/decode/FetchPartPathDecoderTest.java
Modified:
    james/server/trunk/core-library/src/main/java/org/apache/james/mailboxmanager/MessageResult.java
    james/server/trunk/core-library/src/main/java/org/apache/james/mailboxmanager/MessageResultUtils.java
    james/server/trunk/core-library/src/main/java/org/apache/james/mailboxmanager/impl/FetchGroupImpl.java
    james/server/trunk/core-library/src/main/java/org/apache/james/mailboxmanager/impl/MessageResultImpl.java
    james/server/trunk/core-library/src/test/java/org/apache/james/mailboxmanager/MessageResultImplIncludedResultsTest.java
    james/server/trunk/imap-api/src/main/java/org/apache/james/api/imap/ImapConstants.java
    james/server/trunk/imap-api/src/main/java/org/apache/james/api/imap/message/BodyFetchElement.java
    james/server/trunk/imap-codec-library/src/main/java/org/apache/james/imapserver/codec/decode/imap4rev1/FetchCommandParser.java
    james/server/trunk/imap-mailbox-processor-function/src/main/java/org/apache/james/imapserver/processor/imap4rev1/FetchProcessor.java
    james/server/trunk/imapserver-function/src/main/java/org/apache/james/imapserver/commands/FetchCommand.java
    james/server/trunk/torque-mailboxmanager-function/src/main/java/org/apache/james/mailboxmanager/torque/MessageRowUtils.java
    james/server/trunk/torque-mailboxmanager-function/src/main/java/org/apache/james/mailboxmanager/torque/OrFetchGroup.java
    james/server/trunk/torque-mailboxmanager-function/src/main/java/org/apache/james/mailboxmanager/torque/TorqueResultIterator.java

Modified: james/server/trunk/core-library/src/main/java/org/apache/james/mailboxmanager/MessageResult.java
URL: http://svn.apache.org/viewvc/james/server/trunk/core-library/src/main/java/org/apache/james/mailboxmanager/MessageResult.java?rev=620163&r1=620162&r2=620163&view=diff
==============================================================================
--- james/server/trunk/core-library/src/main/java/org/apache/james/mailboxmanager/MessageResult.java (original)
+++ james/server/trunk/core-library/src/main/java/org/apache/james/mailboxmanager/MessageResult.java Sat Feb  9 09:41:53 2008
@@ -86,23 +86,21 @@
         public static final int BODY_CONTENT = 0x400;
 
         /**
-         * Contents to be retu
-         * @return  
+         * Contents to be fetched.
+         * Composed bitwise.
+         * 
+         * @return bitwise descripion
+         * @see #MINIMAL
+         * @see #MIME_MESSAGE
+         * @see #KEY
+         * @see #SIZE
+         * @see #INTERNAL_DATE
+         * @see #FLAGS
+         * @see #HEADERS
+         * @see #FULL_CONTENT
+         * @see #BODY_CONTENT
          */
         public int content();
-        
-        /**
-         * Mime parts to be included in the results.
-         * @return array of mime parts numbered from one,
-         * or null
-         */
-        public int[] mimeParts();
-        
-        /**
-         * Mime headers to be included in the results.
-         * @return array of mime headers numbered from one
-         */
-        public int[] mimeHeaders();
     }
     
     /**
@@ -154,9 +152,21 @@
     Iterator iterateHeaders() throws MailboxManagerException;
     
     /**
+     * Iterates the MIME headers for the given
+     * part in a multipart message.
+     * @param path describing the part's position within
+     * a multipart message
+     * @return  <code>Header</code> <code>Iterator</code>, 
+     * or null when {@link FetchGroup#mimeHeaders()} does not
+     * include the index and when the mime part cannot be found
+     * @throws MailboxManagerException
+     */
+    Iterator iterateHeaders(MimePath path) throws MailboxManagerException;
+    
+    /**
      * A header.
      */
-    public interface Header extends Content{
+    public interface Header extends Content {
         
         /**
          * Gets the name of this header.
@@ -180,16 +190,37 @@
      * or or null if {@link FetchGroup#FULL_CONTENT} has not been included in the 
      * results
      */
-    Content getFullMessage() throws MailboxManagerException;
+    Content getFullContent() throws MailboxManagerException;
+
 
     /**
+     * Gets the full content of the given mime part.
+     * @param path describes the part
+     * @return <code>Content</code>,
+     * or null when {@link FetchGroup#mimeBodies()} did not been include 
+     * the given index and when the mime part cannot be found
+     * @throws MailboxManagerException
+     */
+    Content getFullContent(MimePath path) throws MailboxManagerException;
+    
+    /**
      * Gets the body of the message excluding headers.
      * The message data should have normalised line endings (CRLF).
      * @return <code>Content</code>,
      * or or null if {@link FetchGroup#FULL_CONTENT} has not been included in the 
      * results 
      */
-    Content getMessageBody() throws MailboxManagerException;
+    Content getBody() throws MailboxManagerException;
+    
+    /**
+     * Gets the body of the given mime part.
+     * @param path describes the part
+     * @return <code>Content</code>,
+     * or null when {@link FetchGroup#mimeBodies()} did not been include 
+     * the given index and when the mime part cannot be found
+     * @throws MailboxManagerException
+     */
+    Content getBody(MimePath path) throws MailboxManagerException;
 
     /**
      * IMAP needs to know the size of the content before it starts to write it out.
@@ -217,5 +248,17 @@
          * @throws MessagingException
          */
         public long size();
+    }
+    
+    /**
+     * Describes a path within a multipart MIME message.
+     */
+    public interface MimePath {
+        
+        /**
+         * Gets the positions of each part in the path.
+         * @return part positions describing the path
+         */
+        public int[] getPositions();
     }
 }

Modified: james/server/trunk/core-library/src/main/java/org/apache/james/mailboxmanager/MessageResultUtils.java
URL: http://svn.apache.org/viewvc/james/server/trunk/core-library/src/main/java/org/apache/james/mailboxmanager/MessageResultUtils.java?rev=620163&r1=620162&r2=620163&view=diff
==============================================================================
--- james/server/trunk/core-library/src/main/java/org/apache/james/mailboxmanager/MessageResultUtils.java (original)
+++ james/server/trunk/core-library/src/main/java/org/apache/james/mailboxmanager/MessageResultUtils.java Sat Feb  9 09:41:53 2008
@@ -20,6 +20,7 @@
 package org.apache.james.mailboxmanager;
 
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Iterator;
 import java.util.List;
 
@@ -66,6 +67,56 @@
                             results.add(header);
                             break;
                         }
+                    }
+                }
+            }
+        }
+        return results;
+    }
+    
+    /**
+     * Gets header lines whose header names matches (ignoring case)
+     * any of those given.
+     * @param names header names to be matched, not null
+     * @param iterator {@link MessageResult.Header} <code>Iterator</code>
+     * @return <code>List</code> of <code>MessageResult.Header</code>'s,
+     * in their natural order
+     * @throws MessagingException
+     */
+    public static List getMatching(final Collection names, final Iterator iterator) throws MessagingException {
+        final List results = new ArrayList(names.size());
+        if (iterator != null) {
+            while(iterator.hasNext()) {
+                MessageResult.Header header = (MessageResult.Header) iterator.next();
+                final String headerName = header.getName();
+                if (headerName != null) {
+                    if (names.contains(headerName)) {
+                        results.add(header);
+                    }
+                }
+            }
+        }
+        return results;
+    }
+    
+    /**
+     * Gets header lines whose header names matches (ignoring case)
+     * any of those given.
+     * @param names header names to be matched, not null
+     * @param iterator {@link MessageResult.Header} <code>Iterator</code>
+     * @return <code>List</code> of <code>MessageResult.Header</code>'s,
+     * in their natural order
+     * @throws MessagingException
+     */
+    public static List getNotMatching(final Collection names, final Iterator iterator) throws MessagingException {
+        final List results = new ArrayList(names.size());
+        if (iterator != null) {
+            while(iterator.hasNext()) {
+                MessageResult.Header header = (MessageResult.Header) iterator.next();
+                final String headerName = header.getName();
+                if (headerName != null) {
+                    if (!names.contains(headerName)) {
+                        results.add(header);
                     }
                 }
             }

Modified: james/server/trunk/core-library/src/main/java/org/apache/james/mailboxmanager/impl/FetchGroupImpl.java
URL: http://svn.apache.org/viewvc/james/server/trunk/core-library/src/main/java/org/apache/james/mailboxmanager/impl/FetchGroupImpl.java?rev=620163&r1=620162&r2=620163&view=diff
==============================================================================
--- james/server/trunk/core-library/src/main/java/org/apache/james/mailboxmanager/impl/FetchGroupImpl.java (original)
+++ james/server/trunk/core-library/src/main/java/org/apache/james/mailboxmanager/impl/FetchGroupImpl.java Sat Feb  9 09:41:53 2008
@@ -74,7 +74,7 @@
         return mimeHeaders;
     }
 
-    public int[] mimeParts() {
+    public int[] mimeBodies() {
         return mimeParts;
     }
     

Modified: james/server/trunk/core-library/src/main/java/org/apache/james/mailboxmanager/impl/MessageResultImpl.java
URL: http://svn.apache.org/viewvc/james/server/trunk/core-library/src/main/java/org/apache/james/mailboxmanager/impl/MessageResultImpl.java?rev=620163&r1=620162&r2=620163&view=diff
==============================================================================
--- james/server/trunk/core-library/src/main/java/org/apache/james/mailboxmanager/impl/MessageResultImpl.java (original)
+++ james/server/trunk/core-library/src/main/java/org/apache/james/mailboxmanager/impl/MessageResultImpl.java Sat Feb  9 09:41:53 2008
@@ -44,10 +44,10 @@
     private Date internalDate;
     private String key;
     private List headers;
-    private Content messageBody;
-    private Content fullMessage;
+    private Content body;
+    private Content fullContent;
     private int includedResults = FetchGroup.MINIMAL;
-
+    
     public MessageResultImpl(long uid) {
         setUid(uid);
     }
@@ -81,10 +81,10 @@
             setHeaders(IteratorUtils.toList(result.iterateHeaders()));
         }
         if (MessageResultUtils.isFullContentIncluded(result)) {
-            setFullMessage(result.getFullMessage());
+            setFullContent(result.getFullContent());
         }
         if (MessageResultUtils.isBodyContentIncluded(result)) {
-            setMessageBody(result.getMessageBody());
+            setBody(result.getBody());
         }
     }
 
@@ -188,23 +188,23 @@
         }
     }
 
-    public final Content getFullMessage() {
-        return fullMessage;
+    public final Content getFullContent() {
+        return fullContent;
     }
 
-    public final void setFullMessage(Content fullMessage) {
-        this.fullMessage = fullMessage;
+    public final void setFullContent(Content fullMessage) {
+        this.fullContent = fullMessage;
         if (fullMessage != null) {
             includedResults |= FetchGroup.FULL_CONTENT;
         }
     }
 
-    public final Content getMessageBody() {
-        return messageBody;
+    public final Content getBody() {
+        return body;
     }
 
-    public final void setMessageBody(Content messageBody) {
-        this.messageBody = messageBody;
+    public final void setBody(Content messageBody) {
+        this.body = messageBody;
         if (messageBody != null) {
             includedResults |= FetchGroup.BODY_CONTENT;
         }
@@ -230,6 +230,18 @@
             + " )";
     
         return retValue;
+    }
+
+    public Content getBody(MimePath path) throws MailboxManagerException {
+        throw new MailboxManagerException("Unsupported operation");
+    }
+
+    public Content getFullContent(MimePath path) throws MailboxManagerException {
+        throw new MailboxManagerException("Unsupported operation");
+    }
+
+    public Iterator iterateHeaders(MimePath path) throws MailboxManagerException {
+        throw new MailboxManagerException("Unsupported operation");
     }
     
     

Modified: james/server/trunk/core-library/src/test/java/org/apache/james/mailboxmanager/MessageResultImplIncludedResultsTest.java
URL: http://svn.apache.org/viewvc/james/server/trunk/core-library/src/test/java/org/apache/james/mailboxmanager/MessageResultImplIncludedResultsTest.java?rev=620163&r1=620162&r2=620163&view=diff
==============================================================================
--- james/server/trunk/core-library/src/test/java/org/apache/james/mailboxmanager/MessageResultImplIncludedResultsTest.java (original)
+++ james/server/trunk/core-library/src/test/java/org/apache/james/mailboxmanager/MessageResultImplIncludedResultsTest.java Sat Feb  9 09:41:53 2008
@@ -104,18 +104,18 @@
     }
     
     public void testShouldIncludedResultsWhenFullMessageSet() throws Exception  {
-        result.setFullMessage(null);
+        result.setFullContent(null);
         assertEquals(FetchGroup.MINIMAL, result.getIncludedResults().content());
-        result.setFullMessage(content);
+        result.setFullContent(content);
         assertEquals(FetchGroup.FULL_CONTENT, result.getIncludedResults().content());
         result = new MessageResultImpl(this.result);
         assertEquals(FetchGroup.FULL_CONTENT, result.getIncludedResults().content());
     }
 
     public void testShouldIncludedResultsWhenMessageBodySet() throws Exception {
-        result.setMessageBody(null);
+        result.setBody(null);
         assertEquals(FetchGroup.MINIMAL, result.getIncludedResults().content());
-        result.setMessageBody(content);
+        result.setBody(content);
         assertEquals(FetchGroup.BODY_CONTENT, result.getIncludedResults().content());
         result = new MessageResultImpl(this.result);
         assertEquals(FetchGroup.BODY_CONTENT, result.getIncludedResults().content());
@@ -129,11 +129,11 @@
         result.setUid(99);
         assertEquals(FetchGroup.FLAGS, result.getIncludedResults().content());
         assertTrue(MessageResultUtils.isFlagsIncluded(result));
-        result.setMessageBody(content);
+        result.setBody(content);
         assertEquals(FetchGroup.FLAGS | FetchGroup.BODY_CONTENT, result.getIncludedResults().content());
         assertTrue(MessageResultUtils.isFlagsIncluded(result));
         assertTrue(MessageResultUtils.isBodyContentIncluded(result));
-        result.setFullMessage(content);
+        result.setFullContent(content);
         assertEquals(FetchGroup.FLAGS | 
                 FetchGroup.BODY_CONTENT | FetchGroup.FULL_CONTENT, result.getIncludedResults().content());
         assertTrue(MessageResultUtils.isFlagsIncluded(result));

Modified: james/server/trunk/imap-api/src/main/java/org/apache/james/api/imap/ImapConstants.java
URL: http://svn.apache.org/viewvc/james/server/trunk/imap-api/src/main/java/org/apache/james/api/imap/ImapConstants.java?rev=620163&r1=620162&r2=620163&view=diff
==============================================================================
--- james/server/trunk/imap-api/src/main/java/org/apache/james/api/imap/ImapConstants.java (original)
+++ james/server/trunk/imap-api/src/main/java/org/apache/james/api/imap/ImapConstants.java Sat Feb  9 09:41:53 2008
@@ -108,4 +108,11 @@
     public static final String NAME_ATTRIBUTE_NOSELECT = "\\Noselect";
     public static final String NAME_ATTRIBUTE_MARKED = "\\Marked";
     public static final String NAME_ATTRIBUTE_UNMARKED = "\\Unmarked";
+    
+    public static final String PS_TEXT = "TEXT";
+    public static final String PS_HEADER = "HEADER";
+    public static final String PS_MIME = "MIME";
+    public static final String FETCH_RFC822 = "RFC822";
+    public static final String FETCH_RFC822_HEADER = "RFC822.HEADER";
+    public static final String FETCH_RFC822_TEXT = "RFC822.TEXT";
 }

Modified: james/server/trunk/imap-api/src/main/java/org/apache/james/api/imap/message/BodyFetchElement.java
URL: http://svn.apache.org/viewvc/james/server/trunk/imap-api/src/main/java/org/apache/james/api/imap/message/BodyFetchElement.java?rev=620163&r1=620162&r2=620163&view=diff
==============================================================================
--- james/server/trunk/imap-api/src/main/java/org/apache/james/api/imap/message/BodyFetchElement.java (original)
+++ james/server/trunk/imap-api/src/main/java/org/apache/james/api/imap/message/BodyFetchElement.java Sat Feb  9 09:41:53 2008
@@ -18,23 +18,80 @@
  ****************************************************************/
 package org.apache.james.api.imap.message;
 
+import java.util.Collection;
+
+import org.apache.james.api.imap.ImapConstants;
+
 public class BodyFetchElement
 {
-    private String name;
-    private String sectionIdentifier;
 
-    public BodyFetchElement( String name, String sectionIdentifier)
-    {
-        this.name = name;
-        this.sectionIdentifier = sectionIdentifier;
+    public static final int TEXT = 0;
+    public static final int MIME = 1;
+    public static final int HEADER = 2;
+    public static final int HEADER_FIELDS = 3;
+    public static final int HEADER_NOT_FIELDS = 4;
+    public static final int CONTENT = 5;
+    
+    private static final BodyFetchElement rfc822 = new BodyFetchElement(ImapConstants.FETCH_RFC822, CONTENT, null, null);
+    private static final BodyFetchElement rfc822Header = new BodyFetchElement(ImapConstants.FETCH_RFC822_HEADER, HEADER, null, null);
+    private static final BodyFetchElement rfc822Text = new BodyFetchElement(ImapConstants.FETCH_RFC822_TEXT, TEXT, null, null);
+
+    public static final BodyFetchElement createRFC822() {
+        return rfc822;
+    }
+     
+    public static final BodyFetchElement createRFC822Header() {
+        return rfc822Header;
     }
+    
+    public static final BodyFetchElement createRFC822Text() {
+        return rfc822Text;
+    }
+    
+    
+    private final String name;
+    private final int sectionType;
+    private final int[] path; 
+    private final Collection fieldNames;
 
-    public String getParameters()
+    public BodyFetchElement( final String name, final int sectionType, 
+            final int[] path, final Collection fieldNames)
     {
-        return this.sectionIdentifier;
+        this.name = name;
+        this.sectionType = sectionType;
+        this.fieldNames = fieldNames;
+        this.path = path;
     }
-
+    
     public String getResponseName() {
         return this.name;
+    }
+
+    /**
+     * Gets field names.
+     * @return <code>String</code> collection, when {@link #HEADER_FIELDS} 
+     * or {@link #HEADER_NOT_FIELDS}
+     * or null otherwise
+     */
+    public final Collection getFieldNames() {
+        return fieldNames;
+    }
+
+    /**
+     * Gets the MIME path.
+     * @return the path, 
+     * or null if the section is the base message
+     */
+    public final int[] getPath() {
+        return path;
+    }
+
+    /**
+     * Gets the type of section.
+     * @return {@link #HEADER_FIELDS}, {@link #TEXT}, {@link #CONTENT}, {@link #HEADER},
+     * {@link #MIME} or {@link #HEADER_NOT_FIELDS}
+     */
+    public final int getSectionType() {
+        return sectionType;
     }
 }

Added: james/server/trunk/imap-codec-library/src/main/java/org/apache/james/imapserver/codec/decode/FetchPartPathDecoder.java
URL: http://svn.apache.org/viewvc/james/server/trunk/imap-codec-library/src/main/java/org/apache/james/imapserver/codec/decode/FetchPartPathDecoder.java?rev=620163&view=auto
==============================================================================
--- james/server/trunk/imap-codec-library/src/main/java/org/apache/james/imapserver/codec/decode/FetchPartPathDecoder.java (added)
+++ james/server/trunk/imap-codec-library/src/main/java/org/apache/james/imapserver/codec/decode/FetchPartPathDecoder.java Sat Feb  9 09:41:53 2008
@@ -0,0 +1,461 @@
+/****************************************************************
+ * 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.codec.decode;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.james.api.imap.ProtocolException;
+
+
+public class FetchPartPathDecoder {
+        
+    public static final int TEXT = 0;
+    public static final int MIME = 1;
+    public static final int HEADER = 2;
+    public static final int HEADER_FIELDS = 3;
+    public static final int HEADER_NOT_FIELDS = 4;
+    public static final int CONTENT = 5;
+    /** Going to need to make one array copy so might as well ensure plenty of space */
+    private static final int ARRAY_INCREMENT = 20;
+    /** Embedded RFC882 mesages are rare so start size one array */
+    private static final int ARRAY_INITIAL_SIZE = 1;
+    
+    private int sectionType;
+    private int[] path;
+    private int partial;
+    private int used;
+    private List names;
+    
+    public FetchPartPathDecoder() {}
+
+    public int decode(final CharSequence sectionSpecification) throws ProtocolException {
+        init();
+        sectionType = decode(0, sectionSpecification);
+        prunePath();
+        return sectionType;
+    }
+    
+    private void prunePath() {
+        if (path != null) {
+            final int length = path.length;
+            if (used < length) {
+                final int[] newPath = new int[used];
+                System.arraycopy(path, 0, newPath, 0, used);
+                path = newPath;
+            }
+        }
+    }
+    
+    private int decode(final int at, final CharSequence sectionSpecification) throws ProtocolException {
+        final int result;
+        final int length = sectionSpecification.length();
+        if (at<length)
+        {
+            final char next = sectionSpecification.charAt(at);
+            switch (next) {
+                case '.':
+                    separator();
+                    result = decode(at + 1, sectionSpecification);
+                    break;
+
+                case '0':
+                    result = digit(at, sectionSpecification, 0);
+                    break;
+                    
+                case '1':
+                    result = digit(at, sectionSpecification, 1);
+                    break;
+
+                case '2':
+                    result = digit(at, sectionSpecification, 2);
+                    break;
+
+                case '3':
+                    result = digit(at, sectionSpecification, 3);
+                    break;
+
+                case '4':
+                    result = digit(at, sectionSpecification, 4);
+                    break;
+
+                case '5':
+                    result = digit(at, sectionSpecification, 5);
+                    break;
+
+                case '6':
+                    result = digit(at, sectionSpecification, 6);
+                    break;
+
+                case '7':
+                    result = digit(at, sectionSpecification, 7);
+                    break;
+
+                case '8':
+                    result = digit(at, sectionSpecification, 8);
+                    break;
+
+                case '9':
+                    result = digit(at, sectionSpecification, 9);
+                    break;
+                
+                case 't':
+                case 'T':
+                    result = text(at, sectionSpecification);
+                    break;
+                    
+                case 'h':
+                case 'H':
+                    result = header(at, sectionSpecification);
+                    break;
+                    
+                case 'm':
+                case 'M':
+                    result = mime(at, sectionSpecification);
+                    break;
+
+                default:
+                    throw new ProtocolException("Did not expect '" + next + "' here in body specification.");
+            }
+        } else {
+            storePartial();
+            result = CONTENT;
+        }
+        return result;
+    }
+    
+
+    private int mime(int at, CharSequence sectionSpecification) throws ProtocolException {
+        if (sectionSpecification.length() == at+4) {
+            mustBeI(sectionSpecification, at+1);
+            mustBeM(sectionSpecification, at+2);
+            mustBeE(sectionSpecification, at+3);
+            storePartial();
+        } else {
+            throw new ProtocolException("Unknown body specification");
+        }   
+        return MIME;
+    }
+
+    private void mustBeI(CharSequence sectionSpecification, int position) throws ProtocolException {
+        final char i = sectionSpecification.charAt(position);
+        if (!(i == 'i'|| i == 'I')) {
+            throw new ProtocolException("Unknown body specification");
+        }
+    }
+
+    private void mustBeM(CharSequence sectionSpecification, int position) throws ProtocolException {
+        final char next = sectionSpecification.charAt(position);
+        if (!(next == 'm' || next == 'M')) {
+            throw new ProtocolException("Unknown body specification");
+        }
+    }
+    
+    private void mustBeN(CharSequence sectionSpecification, int position) throws ProtocolException {
+        final char next = sectionSpecification.charAt(position);
+        if (!(next == 'n' || next == 'N')) {
+            throw new ProtocolException("Unknown body specification");
+        }
+    }
+    
+    private void mustBeO(CharSequence sectionSpecification, int position) throws ProtocolException {
+        final char next = sectionSpecification.charAt(position);
+        if (!(next == 'o' || next == 'O')) {
+            throw new ProtocolException("Unknown body specification");
+        }
+    }
+    
+    private void mustBeE(CharSequence sectionSpecification, int position) throws ProtocolException {
+        final char next = sectionSpecification.charAt(position);
+        if (!(next == 'e' || next == 'E')) {
+            throw new ProtocolException("Unknown body specification");
+        }
+    }
+    
+    private void mustBeA(CharSequence sectionSpecification, int position) throws ProtocolException {
+        final char next = sectionSpecification.charAt(position);
+        if (!(next == 'a' || next == 'A')) {
+            throw new ProtocolException("Unknown body specification");
+        }
+    }
+    
+    private void mustBeD(CharSequence sectionSpecification, int position) throws ProtocolException {
+        final char next = sectionSpecification.charAt(position);
+        if (!(next == 'd' || next == 'D')) {
+            throw new ProtocolException("Unknown body specification");
+        }
+    }
+    
+    private void mustBeR(CharSequence sectionSpecification, int position) throws ProtocolException {
+        final char next = sectionSpecification.charAt(position);
+        if (!(next == 'r' || next == 'R')) {
+            throw new ProtocolException("Unknown body specification");
+        }
+    }
+    
+    private void mustBeX(CharSequence sectionSpecification, int position) throws ProtocolException {
+        final char next = sectionSpecification.charAt(position);
+        if (!(next == 'x' || next == 'X')) {
+            throw new ProtocolException("Unknown body specification");
+        }
+    }
+    
+    private void mustBeT(CharSequence sectionSpecification, int position) throws ProtocolException {
+        final char next = sectionSpecification.charAt(position);
+        if (!(next == 't' || next == 'T')) {
+            throw new ProtocolException("Unknown body specification");
+        }
+    }
+    
+    private void mustBeF(CharSequence sectionSpecification, int position) throws ProtocolException {
+        final char next = sectionSpecification.charAt(position);
+        if (!(next == 'f' || next == 'F')) {
+            throw new ProtocolException("Unknown body specification");
+        }
+    }
+    
+    private void mustBeL(CharSequence sectionSpecification, int position) throws ProtocolException {
+        final char next = sectionSpecification.charAt(position);
+        if (!(next == 'l' || next == 'L')) {
+            throw new ProtocolException("Unknown body specification");
+        }
+    }
+    
+    private void mustBeS(CharSequence sectionSpecification, int position) throws ProtocolException {
+        final char next = sectionSpecification.charAt(position);
+        if (!(next == 's' || next == 'S')) {
+            throw new ProtocolException("Unknown body specification");
+        }
+    }
+    
+    private void mustBeDot(CharSequence sectionSpecification, int position) throws ProtocolException {
+        final char next = sectionSpecification.charAt(position);
+        if (!(next == '.')) {
+            throw new ProtocolException("Unknown body specification");
+        }
+    }
+    
+    private void mustBeOpenParen(CharSequence sectionSpecification, int position) throws ProtocolException {
+        final char next = sectionSpecification.charAt(position);
+        if (!(next == '(')) {
+            throw new ProtocolException("Unknown body specification");
+        }
+    }
+    
+    private int header(int at, CharSequence sectionSpecification) throws ProtocolException {
+        final int result;
+        final int length = sectionSpecification.length();
+        if (length > at+5) {
+            mustBeE(sectionSpecification, at+1);
+            mustBeA(sectionSpecification, at+2);
+            mustBeD(sectionSpecification, at+3);
+            mustBeE(sectionSpecification, at+4);
+            mustBeR(sectionSpecification, at+5);
+            storePartial();
+            if (length == at+6) {
+                result = HEADER;
+            } else {
+                result = headerFields(at+6, sectionSpecification);
+            }
+        } else {
+            throw new ProtocolException("Unknown body specification");
+        }
+        return result;
+    }
+    
+    private int headerFields(int at, CharSequence sectionSpecification) throws ProtocolException {
+        final int result;
+        final int length = sectionSpecification.length();
+        if (length > at + 7) {
+            mustBeDot(sectionSpecification, at);
+            mustBeF(sectionSpecification, at + 1);
+            mustBeI(sectionSpecification, at + 2);
+            mustBeE(sectionSpecification, at + 3);
+            mustBeL(sectionSpecification, at + 4);
+            mustBeD(sectionSpecification, at + 5);
+            mustBeS(sectionSpecification, at + 6);
+            final char next = sectionSpecification.charAt(at + 7);
+            final int namesStartAt;
+            switch (next) {
+                case ' ':
+                    result = HEADER_FIELDS;
+                    namesStartAt = skipSpaces(at + 7, sectionSpecification);
+                    break;
+                case '.':
+                    if (length > at + 10) {
+                        mustBeN(sectionSpecification, at + 8);
+                        mustBeO(sectionSpecification, at + 9);
+                        mustBeT(sectionSpecification, at + 10);
+                        result = HEADER_NOT_FIELDS;
+                        namesStartAt = skipSpaces(at + 11, sectionSpecification);
+                    } else {
+                        throw new ProtocolException("Unknown body specification");
+                    }
+                    break;
+                default:
+                    throw new ProtocolException("Unknown body specification");
+            }
+            mustBeOpenParen(sectionSpecification, namesStartAt);
+            readHeaderNames(namesStartAt + 1, sectionSpecification);
+            
+        } else {
+            throw new ProtocolException("Unknown body specification");
+        }
+        return result;
+    }
+    
+    private void readHeaderNames(final int at, final CharSequence sectionSpecification) throws ProtocolException {
+        names = new ArrayList();
+        final int firstWordStart = skipSpaces(at, sectionSpecification);
+        readHeaderNames(firstWordStart, firstWordStart, sectionSpecification);
+    }
+    
+    private void readHeaderNames(final int at, final int lastWordStart, final CharSequence sectionSpecification) throws ProtocolException {
+        if (at < sectionSpecification.length()) {
+            final char next = sectionSpecification.charAt(at);
+            switch (next) {
+                case ' ':
+                    readName(lastWordStart, at, sectionSpecification);
+                    final int nextWord = skipSpaces(at, sectionSpecification);
+                    readHeaderNames(nextWord, nextWord, sectionSpecification);
+                    break;
+                case ')':
+                    readName(lastWordStart, at, sectionSpecification);
+                    break;
+                default:
+                    readHeaderNames(at + 1, lastWordStart, sectionSpecification);
+            }
+        } else {
+            throw new ProtocolException("Closing parenthesis missing.");
+        }
+    }
+    
+    private void readName(final int wordStart, final int wordFinish, final CharSequence sectionSpecification) {
+        if (wordStart <= wordFinish) {
+            final CharSequence word = sectionSpecification.subSequence(wordStart, wordFinish);
+            final String name = word.toString();
+            names.add(name);
+        }
+    }
+    
+    private int skipSpaces(final int at, final CharSequence sectionSpecification) {
+        final int result;
+        if (at < sectionSpecification.length()) {
+            final char next = sectionSpecification.charAt(at);
+            if (next == ' ') {
+                result = skipSpaces(at+1, sectionSpecification);
+            } else {
+                result = at;
+            }
+        } else {
+            result = at;
+        }
+        return result;
+    }
+    
+    private int text(int at, CharSequence sectionSpecification) throws ProtocolException {
+        if (sectionSpecification.length() == at+4) {
+            mustBeE(sectionSpecification, at+1);
+            mustBeX(sectionSpecification, at+2);
+            mustBeT(sectionSpecification, at+3);
+            storePartial();
+        } else {
+            throw new ProtocolException("Unknown body specification");
+        }   
+        return TEXT;
+    }
+
+    private int digit(final int at, final CharSequence sectionSpecification, int digit) throws ProtocolException {
+        final int result;
+        digit(digit);
+        result = decode(at + 1, sectionSpecification);
+        return result;
+    }
+
+    private void init() {
+        sectionType = CONTENT;
+        resetPartial();
+        path = null;
+        used = 0;
+        names = null;
+    }
+
+    private void resetPartial() {
+        partial = 0;
+    }
+    
+    private void separator() {
+        storePartial();
+    }
+
+    private void storePartial() {
+        if (partial > 0) {
+            ensureSpaceForOneInPath();
+            path[used++] = partial;
+            resetPartial();
+        }
+    }
+
+    private void ensureSpaceForOneInPath() {
+        if (path == null) {
+            path = new int[ARRAY_INITIAL_SIZE];
+        } else {
+            final int length = path.length;
+            if (used >= length) {
+                int[] newPath = new int[length + ARRAY_INCREMENT];
+                System.arraycopy(path, 0, newPath, 0, length);
+                path = newPath;
+            }
+        }
+    }
+
+    private void digit(int digit) {
+        partial = (partial * 10) + digit;
+    }
+    
+    /**
+     * Gets the decoded path.
+     * @return the path
+     */
+    public final int[] getPath() {
+        return path;
+    }
+
+    /**
+     * Gets the 
+     * @return {@link #TEXT}, {@link #MIME}, {@link #HEADER}, {@link #HEADER_FIELDS},
+     *  {@link #HEADER_NOT_FIELDS} or {@link #CONTENT}
+     */
+    public final int getSpecifier() {
+        return sectionType;
+    }
+
+    /**
+     * Gets field names.
+     * @return <code>List</code> of <code>String</code> names when 
+     * {@link #HEADER_FIELDS} or {@link #HEADER_NOT_FIELDS},
+     * null otherwise
+     */
+    public final List getNames() {
+        return names;
+    }
+    
+    
+}

Modified: james/server/trunk/imap-codec-library/src/main/java/org/apache/james/imapserver/codec/decode/imap4rev1/FetchCommandParser.java
URL: http://svn.apache.org/viewvc/james/server/trunk/imap-codec-library/src/main/java/org/apache/james/imapserver/codec/decode/imap4rev1/FetchCommandParser.java?rev=620163&r1=620162&r2=620163&view=diff
==============================================================================
--- james/server/trunk/imap-codec-library/src/main/java/org/apache/james/imapserver/codec/decode/imap4rev1/FetchCommandParser.java (original)
+++ james/server/trunk/imap-codec-library/src/main/java/org/apache/james/imapserver/codec/decode/imap4rev1/FetchCommandParser.java Sat Feb  9 09:41:53 2008
@@ -18,6 +18,8 @@
  ****************************************************************/
 package org.apache.james.imapserver.codec.decode.imap4rev1;
 
+import java.util.List;
+
 import org.apache.james.api.imap.ImapCommand;
 import org.apache.james.api.imap.ImapMessage;
 import org.apache.james.api.imap.ProtocolException;
@@ -26,6 +28,7 @@
 import org.apache.james.api.imap.message.BodyFetchElement;
 import org.apache.james.api.imap.message.FetchData;
 import org.apache.james.api.imap.message.IdRange;
+import org.apache.james.imapserver.codec.decode.FetchPartPathDecoder;
 import org.apache.james.imapserver.codec.decode.ImapRequestLineReader;
 import org.apache.james.imapserver.codec.decode.InitialisableCommandFactory;
 
@@ -114,11 +117,11 @@
                 } else if ("UID".equalsIgnoreCase(name)) {
                     fetch.setUid(true);
                 } else if ("RFC822".equalsIgnoreCase(name)) {
-                    fetch.add(new BodyFetchElement("RFC822", ""), false);
+                    fetch.add(BodyFetchElement.createRFC822(), false);
                 } else if ("RFC822.HEADER".equalsIgnoreCase(name)) {
-                    fetch.add(new BodyFetchElement("RFC822.HEADER", "HEADER"), true);
+                    fetch.add(BodyFetchElement.createRFC822Header(), true);
                 } else if ("RFC822.TEXT".equalsIgnoreCase(name)) {
-                    fetch.add(new BodyFetchElement("RFC822.TEXT", "TEXT"), false);
+                    fetch.add(BodyFetchElement.createRFC822Text(), false);
                 } else {
                     throw new ProtocolException( "Invalid fetch attribute: " + name );
                 }
@@ -126,19 +129,68 @@
             else {
                 consumeChar( command, '[' );
 
-                
                 String parameter = readWord(command, "]");
 
                 consumeChar( command, ']');
-                if ( "BODY".equalsIgnoreCase( name ) ) {
-                    fetch.add(new BodyFetchElement("BODY[" + parameter + "]", parameter), false);
-                } else if ( "BODY.PEEK".equalsIgnoreCase( name ) ) {
-                    fetch.add(new BodyFetchElement("BODY[" + parameter + "]", parameter), true);
-                } else {
-                    throw new ProtocolException( "Invalid fetch attibute: " + name + "[]" );
-                }
+                
+                final BodyFetchElement bodyFetchElement = createBodyElement(parameter);
+                final boolean isPeek = isPeek(name);
+                fetch.add(bodyFetchElement, isPeek);
             }
         }
+
+    private boolean isPeek(String name) throws ProtocolException {
+        final boolean isPeek;
+        if ( "BODY".equalsIgnoreCase( name ) ) {
+            isPeek = false;
+        } else if ( "BODY.PEEK".equalsIgnoreCase( name ) ) {
+            isPeek = true;
+        } else {
+            throw new ProtocolException( "Invalid fetch attibute: " + name + "[]" );
+        }
+        return isPeek;
+    }
+
+    private BodyFetchElement createBodyElement(String parameter) throws ProtocolException {
+        final String responseName = "BODY[" + parameter + "]";
+        FetchPartPathDecoder decoder = new FetchPartPathDecoder();
+        decoder.decode(parameter);
+        final int sectionType = getSectionType(decoder);
+        
+        final List names = decoder.getNames();
+        final int[] path = decoder.getPath();
+        final BodyFetchElement bodyFetchElement 
+            = new BodyFetchElement(responseName, sectionType, path, names);
+        return bodyFetchElement;
+    }
+
+    private int getSectionType(FetchPartPathDecoder decoder) throws ProtocolException {
+        final int specifier = decoder.getSpecifier();
+        final int sectionType;
+        switch (specifier) {
+            case FetchPartPathDecoder.CONTENT:
+                sectionType = BodyFetchElement.CONTENT;
+                break;
+            case FetchPartPathDecoder.HEADER:
+                sectionType = BodyFetchElement.HEADER;
+                break;
+            case FetchPartPathDecoder.HEADER_FIELDS:
+                sectionType = BodyFetchElement.HEADER_FIELDS;
+                break;
+            case FetchPartPathDecoder.HEADER_NOT_FIELDS:
+                sectionType = BodyFetchElement.HEADER_NOT_FIELDS;
+                break;    
+            case FetchPartPathDecoder.MIME:
+                sectionType = BodyFetchElement.MIME;
+                break;
+            case FetchPartPathDecoder.TEXT:
+                sectionType = BodyFetchElement.TEXT;
+                break;
+            default:
+                throw new ProtocolException("Section type is unsupported.");
+        }
+        return sectionType;
+    }
 
     private String readWord(ImapRequestLineReader request, String terminator) throws ProtocolException {
         StringBuffer buf = new StringBuffer();

Added: james/server/trunk/imap-codec-library/src/test/java/org/apache/james/imapserver/codec/decode/FetchPartPathDecoderTest.java
URL: http://svn.apache.org/viewvc/james/server/trunk/imap-codec-library/src/test/java/org/apache/james/imapserver/codec/decode/FetchPartPathDecoderTest.java?rev=620163&view=auto
==============================================================================
--- james/server/trunk/imap-codec-library/src/test/java/org/apache/james/imapserver/codec/decode/FetchPartPathDecoderTest.java (added)
+++ james/server/trunk/imap-codec-library/src/test/java/org/apache/james/imapserver/codec/decode/FetchPartPathDecoderTest.java Sat Feb  9 09:41:53 2008
@@ -0,0 +1,201 @@
+/****************************************************************
+ * 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.codec.decode;
+
+import java.util.Collection;
+import java.util.Iterator;
+
+import org.apache.james.api.imap.ProtocolException;
+
+import junit.framework.TestCase;
+
+public class FetchPartPathDecoderTest extends TestCase {
+
+    FetchPartPathDecoder decoder;
+    
+    protected void setUp() throws Exception {
+        super.setUp();
+        decoder = new FetchPartPathDecoder();
+    }
+
+    protected void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    public void testShouldDetectText() throws Exception {
+        assertEquals(FetchPartPathDecoder.TEXT, decoder.decode("TEXT"));
+        assertEquals(FetchPartPathDecoder.TEXT, decoder.decode("3.TEXT"));
+        assertEquals(FetchPartPathDecoder.TEXT, decoder.decode("3.1.TEXT"));
+        assertEquals(FetchPartPathDecoder.TEXT, decoder.decode("3.2.5.7.8.TEXT"));
+    }
+
+    public void testShouldDetectHeader() throws Exception {
+        assertEquals(FetchPartPathDecoder.HEADER, decoder.decode("HEADER"));
+        assertEquals(FetchPartPathDecoder.HEADER, decoder.decode("4.HEADER"));
+        assertEquals(FetchPartPathDecoder.HEADER, decoder.decode("10.1.HEADER"));
+        assertEquals(FetchPartPathDecoder.HEADER, decoder.decode("8.3.5.11.HEADER"));
+    }
+
+    public void testShouldDetectHeaderFields() throws Exception {
+        assertEquals(FetchPartPathDecoder.HEADER_FIELDS, decoder.decode("HEADER.FIELDS ()"));
+        assertEquals(FetchPartPathDecoder.HEADER_FIELDS, decoder.decode("4.HEADER.FIELDS ()"));
+        assertEquals(FetchPartPathDecoder.HEADER_FIELDS, decoder.decode("10.1.HEADER.FIELDS ()"));
+        assertEquals(FetchPartPathDecoder.HEADER_FIELDS, decoder.decode("8.3.5.11.HEADER.FIELDS ()"));
+    }
+
+    public void testShouldDetectHeaderFieldsNot() throws Exception {
+        assertEquals(FetchPartPathDecoder.HEADER_NOT_FIELDS, decoder.decode("HEADER.FIELDS.NOT ()"));
+        assertEquals(FetchPartPathDecoder.HEADER_NOT_FIELDS, decoder.decode("4.HEADER.FIELDS.NOT ()"));
+        assertEquals(FetchPartPathDecoder.HEADER_NOT_FIELDS, decoder.decode("10.1.HEADER.FIELDS.NOT ()"));
+        assertEquals(FetchPartPathDecoder.HEADER_NOT_FIELDS, decoder.decode("8.3.5.11.HEADER.FIELDS.NOT ()"));
+    }
+    
+    public void testShouldDetectMime() throws Exception {
+        assertEquals(FetchPartPathDecoder.MIME, decoder.decode("MIME"));
+        assertEquals(FetchPartPathDecoder.MIME, decoder.decode("6.MIME"));
+        assertEquals(FetchPartPathDecoder.MIME, decoder.decode("2.88.MIME"));
+        assertEquals(FetchPartPathDecoder.MIME, decoder.decode("32.3.15.11.MIME"));
+    }
+    
+    public void testShouldDetectContent() throws Exception {
+        assertEquals(FetchPartPathDecoder.CONTENT, decoder.decode("34"));
+        assertEquals(FetchPartPathDecoder.CONTENT, decoder.decode("6"));
+        assertEquals(FetchPartPathDecoder.CONTENT, decoder.decode("9.88"));
+        assertEquals(FetchPartPathDecoder.CONTENT, decoder.decode("17.3.15.11"));
+    }
+    
+    public void testShouldIgnoreCase() throws Exception {
+        assertEquals(FetchPartPathDecoder.MIME, decoder.decode("6.MIME"));
+        assertEquals(FetchPartPathDecoder.MIME, decoder.decode("6.mime"));
+        assertEquals(FetchPartPathDecoder.MIME, decoder.decode("6.miME"));
+        assertEquals(FetchPartPathDecoder.MIME, decoder.decode("6.MIme"));
+        assertEquals(FetchPartPathDecoder.HEADER, decoder.decode("6.HEADER"));
+        assertEquals(FetchPartPathDecoder.HEADER, decoder.decode("6.header"));
+        assertEquals(FetchPartPathDecoder.HEADER, decoder.decode("6.HEadER"));
+        assertEquals(FetchPartPathDecoder.HEADER, decoder.decode("6.heADEr"));
+        assertEquals(FetchPartPathDecoder.TEXT, decoder.decode("6.TEXT"));
+        assertEquals(FetchPartPathDecoder.TEXT, decoder.decode("6.text"));
+        assertEquals(FetchPartPathDecoder.TEXT, decoder.decode("6.TExt"));
+        assertEquals(FetchPartPathDecoder.TEXT, decoder.decode("6.teXT"));
+    }
+    
+    public void testMimimalPath() throws Exception {
+        int[] values = {6};
+        checkEndingPermutations(values);
+    }
+    
+    public void testLongPath() throws Exception {
+        int[] values = {3, 11, 345, 231, 11, 3, 1, 1, 1, 3, 8, 8};
+        checkEndingPermutations(values);
+    }
+    
+    public void testShouldThrowProtocolExceptionWhenSpecifierBogus() throws Exception {
+        try {
+            decoder.decode("1.34.BOGUS");
+            fail("Expected protocol exception to be thrown");
+        } catch (ProtocolException e) {
+            // expected 
+        }
+    }
+    
+    public void testShouldThrowProtocolExceptionWhenPathBogus() throws Exception {
+        try {
+            decoder.decode("1.34.BOGUS.44.34234.324");
+            fail("Expected protocol exception to be thrown");
+        } catch (ProtocolException e) {
+            // expected 
+        }
+    }
+    
+    public void testShouldReadShortFieldNames() throws Exception {
+        String[] names = {"A", "B", "C", "D", "E", "F"};
+        checkReadNames("1.8.HEADER.FIELDS", names);
+    }
+    
+    public void testShouldReadShortFieldNotNames() throws Exception {
+        String[] names = {"A", "B", "C", "D", "E", "F"};
+        checkReadNames("1.8.9.HEADER.FIELDS.NOT", names);
+    }
+    
+    
+    public void testShouldReadOneFieldNames() throws Exception {
+        String[] names = {"AFieldName"};
+        checkReadNames("1.8.HEADER.FIELDS", names);
+    }
+    
+    public void testShouldReadOneFieldNotNames() throws Exception {
+        String[] names = {"AFieldName"};
+        checkReadNames("1.8.9.HEADER.FIELDS.NOT", names);
+    }
+    
+    public void testShouldReadManyFieldNames() throws Exception {
+        String[] names = {"ONE", "TWO", "THREE", "FOUR", "FIVE", "345345"};
+        checkReadNames("1.8.HEADER.FIELDS", names);
+    }
+    
+    public void testShouldReadManyFieldNotNames() throws Exception {
+        String[] names = {"ONE", "TWO", "THREE", "FOUR", "FIVE", "345345"};
+        checkReadNames("1.8.HEADER.FIELDS.NOT", names);
+    }
+    
+    private void checkReadNames(String base, String[] names) throws Exception {
+        base = base + " (";
+        for (int i=0;i<names.length;i++) {
+            base = base + ' ' + names[i];
+        }
+        base = base + ')';
+        decoder.decode(base);
+        Collection results = decoder.getNames();
+        assertNotNull(results);
+        Iterator it = results.iterator();
+        for (int i=0;i<names.length;i++) {
+            assertEquals(names[i], it.next());
+        }
+    }
+    
+    private void checkEndingPermutations(int[] values) throws Exception {
+        String base = "";
+        boolean first = true;
+        for (int i=0;i<values.length; i++) {
+            if (first) {
+                first = false;
+            } else {
+                base = base + ".";
+            }
+            base = base + values[i];
+        }
+        checkPath(values, base);
+        checkPath(values, base + ".TEXT");
+        checkPath(values, base + ".HEADER");
+        checkPath(values, base + ".MIME");
+        checkPath(values, base + ".HEADER.FIELDS.NOT ()");
+        checkPath(values, base + ".HEADER.FIELDS ()");
+    }
+    
+    private void checkPath(int[] expected, String encoded) throws Exception {
+        decoder.decode(encoded);
+        final int[] path = decoder.getPath();
+        assertNotNull(path);
+        assertEquals(expected.length, path.length);
+        for (int i=0;i<expected.length; i++) {
+            assertEquals(expected[i], path[i]);
+        }
+    }
+}

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=620163&r1=620162&r2=620163&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 Feb  9 09:41:53 2008
@@ -131,7 +131,7 @@
         }
     }
 
-    private FetchGroup getFetchGroup(FetchData fetch) {
+    private FetchGroup getFetchGroup(FetchData fetch) throws ProtocolException {
         int result = FetchGroup.MINIMAL;
         if (fetch.isFlags() || fetch.isSetSeen()) {
             result |= FetchGroup.FLAGS;
@@ -155,24 +155,27 @@
         return new FetchGroupImpl(result);
     }
 
-    private int fetchForBodyElements(final Collection bodyElements) {
+    private int fetchForBodyElements(final Collection bodyElements) throws ProtocolException {
         int result = 0;
         if (bodyElements != null) {
             for (final Iterator it = bodyElements.iterator(); it.hasNext();) {
                 final BodyFetchElement element = (BodyFetchElement) it.next();
-                final String section = element.getParameters();
-                if ("HEADER".equalsIgnoreCase(section)) {
-                    result |= FetchGroup.HEADERS;
-                } else if (section.startsWith("HEADER.FIELDS.NOT ")) {
-                    result |= FetchGroup.HEADERS;
-                } else if (section.startsWith("HEADER.FIELDS ")) {
-                    result |= FetchGroup.HEADERS;
-                } else if (section.equalsIgnoreCase("TEXT")) {
-                    ;
-                    result |= FetchGroup.BODY_CONTENT;
-                } else if (section.length() == 0) {
-                    result |= FetchGroup.FULL_CONTENT;
+                final int sectionType = element.getSectionType();
+                switch(sectionType) {
+                    case BodyFetchElement.CONTENT:
+                        result = result | MessageResult.FetchGroup.FULL_CONTENT;
+                        break;
+                    case BodyFetchElement.HEADER:
+                    case BodyFetchElement.HEADER_NOT_FIELDS:
+                    case BodyFetchElement.HEADER_FIELDS:
+                    case BodyFetchElement.MIME:
+                        result = result | MessageResult.FetchGroup.HEADERS;
+                        break;
+                    case BodyFetchElement.TEXT:
+                        result = result | MessageResult.FetchGroup.BODY_CONTENT;
+                        break;
                 }
+                
             }
         }
         return result;
@@ -238,9 +241,7 @@
                     result.getFlags().add(Flags.Flag.SEEN);
                     ensureFlagsResponse = true;
                 }
-
                 
-
                 // FLAGS response
                 if (fetch.isFlags() || ensureFlagsResponse) {
                     setFlags(result.getFlags());
@@ -252,8 +253,6 @@
                             .getInternalDate());
                 }
 
-                // TODO: RFC822.HEADER
-
                 // RFC822.SIZE response
                 if (fetch.isSize()) {
                     setSize(result.getSize());
@@ -292,10 +291,7 @@
                 this.elements = new ArrayList();
                 for (Iterator iterator = elements.iterator(); iterator.hasNext();) {
                     BodyFetchElement fetchElement = (BodyFetchElement) iterator.next();
-                    final String sectionSpecifier = fetchElement.getParameters();
-                    final String responseName = fetchElement.getResponseName();
-                    final FetchResponse.BodyElement element 
-                        = bodyFetch(result, sectionSpecifier, responseName);
+                    final FetchResponse.BodyElement element = bodyFetch(result, fetchElement);
                     if (element != null) {
                         this.elements.add(element);
                     }
@@ -465,88 +461,123 @@
         }
 
         private FetchResponse.BodyElement bodyFetch(final MessageResult messageResult,
-                String sectionSpecifier, String name) throws MessagingException {
+                BodyFetchElement fetchElement) throws MessagingException, ProtocolException {
+            
             final FetchResponse.BodyElement result;
-            // TODO: section specifier should be fully parsed during parsing phase
-            if (sectionSpecifier.length() == 0) {
-                final MessageResult.Content fullMessage = messageResult.getFullMessage();
-                result = new ContentBodyElement(name, fullMessage);
-                
-            } else if (sectionSpecifier.equalsIgnoreCase("HEADER")) {
-                final Iterator headers = messageResult.iterateHeaders();
-                List lines = MessageResultUtils.getAll(headers);
-                result = new HeaderBodyElement(name, lines);
-                
-            } else if (sectionSpecifier.startsWith("HEADER.FIELDS.NOT ")) {
-                String[] excludeNames = extractHeaderList(sectionSpecifier,
-                        "HEADER.FIELDS.NOT ".length());
-                final Iterator headers = messageResult.iterateHeaders();
-                List lines = MessageResultUtils.getNotMatching(excludeNames, headers);
-                result = new HeaderBodyElement(name, lines);
-                
-            } else if (sectionSpecifier.startsWith("HEADER.FIELDS ")) {
-                String[] includeNames = extractHeaderList(sectionSpecifier,
-                        "HEADER.FIELDS ".length());
-                final Iterator headers = messageResult.iterateHeaders();
-                List lines = MessageResultUtils.getMatching(includeNames, headers);
-                result = new HeaderBodyElement(name, lines);
-                
-            } else if (sectionSpecifier.equalsIgnoreCase("MIME")) {
-                // TODO implement
-                throw new MailboxManagerException("MIME not yet implemented.");
-
-            } else if (sectionSpecifier.equalsIgnoreCase("TEXT")) {
-                final MessageResult.Content messageBody = messageResult.getMessageBody();
-                result = new ContentBodyElement(name, messageBody);
+            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);
+            switch (specifier) {
+                case BodyFetchElement.CONTENT:
+                    result = content(messageResult, name, path, isBase);
+                    break;
+                    
+                case BodyFetchElement.HEADER_FIELDS:
+                    result = fields(messageResult, name, path, names, isBase);
+                    break;
+                    
+                case BodyFetchElement.HEADER_NOT_FIELDS:
+                    result = fieldsNot(messageResult, name, path, names, isBase);
+                    break;
+                    
+                case BodyFetchElement.MIME:
+                case BodyFetchElement.HEADER:
+                    result = headers(messageResult, name, path, isBase);
+                    break;
+                    
+                case BodyFetchElement.TEXT:
+                    result = text(messageResult, name, path, isBase);
+                    break;
+                    
+                default:
+                    result = null;
+                break;
+            }
+
+            return result;
 
+        }
+
+        private FetchResponse.BodyElement text(final MessageResult messageResult, String name, final int[] path, final boolean isBase) throws MailboxManagerException {
+            final FetchResponse.BodyElement result;
+            final MessageResult.Content body;
+            if (isBase) {
+                body = messageResult.getBody();
             } else {
-                // Should be a part specifier followed by a section specifier.
-                // See if there's a leading part specifier.
-                // If so, get the number, get the part, and call this recursively.
-                int dotPos = sectionSpecifier.indexOf('.');
-                if (dotPos == -1) {
-                    throw new MailboxManagerException("Malformed fetch attribute: "
-                            + sectionSpecifier);
-                }
-                int partNumber = Integer.parseInt(sectionSpecifier.substring(0,
-                        dotPos));
-                String partSectionSpecifier = sectionSpecifier
-                        .substring(dotPos + 1);
-
-                // TODO - get the MimePart of the mimeMessage, and call this method
-                // with the new partSectionSpecifier.
-                // MimeMessage part;
-                // handleBodyFetch( part, partSectionSpecifier, response );
-                throw new MailboxManagerException(
-                        "Mime parts not yet implemented for fetch.");
+                MessageResult.MimePath mimePath = new MimePathImpl(path);
+                body = messageResult.getBody(mimePath);
             }
+            result = new ContentBodyElement(name, body);
             return result;
+        }
 
+        private FetchResponse.BodyElement headers(final MessageResult messageResult, String name, final int[] path, final boolean isBase) throws MailboxManagerException, MessagingException {
+            final FetchResponse.BodyElement result;
+            final Iterator headers = getHeaders(messageResult, path, isBase);
+            List lines = MessageResultUtils.getAll(headers);
+            result = new HeaderBodyElement(name, lines);
+            return result;
         }
 
-        // TODO should do this at parse time.
-        private String[] extractHeaderList(String headerList, int prefixLen) {
-            // Remove the trailing and leading ')('
-            String tmp = headerList.substring(prefixLen + 1,
-                    headerList.length() - 1);
-            String[] headerNames = split(tmp, " ");
-            return headerNames;
-        }
-
-        private String[] split(String value, String delimiter) {
-            ArrayList strings = new ArrayList();
-            int startPos = 0;
-            int delimPos;
-            while ((delimPos = value.indexOf(delimiter, startPos)) != -1) {
-                String sub = value.substring(startPos, delimPos);
-                strings.add(sub);
-                startPos = delimPos + 1;
+        private FetchResponse.BodyElement fieldsNot(final MessageResult messageResult, String name, 
+                final int[] path, Collection names, final boolean isBase) throws MailboxManagerException, MessagingException {
+            final FetchResponse.BodyElement result;
+            
+            final Iterator headers = getHeaders(messageResult, path, isBase);
+            
+            List lines = MessageResultUtils.getNotMatching(names, headers);
+            result = new HeaderBodyElement(name, lines);
+            return result;
+        }
+
+        private FetchResponse.BodyElement fields(final MessageResult messageResult, String name, 
+                final int[] path, Collection names, final boolean isBase) throws MailboxManagerException, MessagingException {
+            final FetchResponse.BodyElement result;
+            final Iterator headers = getHeaders(messageResult, path, isBase);
+            List lines = MessageResultUtils.getMatching(names, headers);
+            result = new HeaderBodyElement(name, lines);
+            return result;
+        }
+
+        private Iterator getHeaders(final MessageResult messageResult, final int[] path, final boolean isBase) throws MailboxManagerException {
+            final Iterator headers;
+            if (isBase) {
+                headers = messageResult.iterateHeaders();
+            } else {
+                MessageResult.MimePath mimePath = new MimePathImpl(path);
+                headers = messageResult.iterateHeaders(mimePath);
             }
-            String sub = value.substring(startPos);
-            strings.add(sub);
+            return headers;
+        }
 
-            return (String[]) strings.toArray(new String[0]);
+        private FetchResponse.BodyElement content(final MessageResult messageResult, String name, final int[] path, final boolean isBase) throws MailboxManagerException {
+            final FetchResponse.BodyElement result;
+            final MessageResult.Content full;
+            if (isBase) {
+                full = messageResult.getFullContent();
+            } else {
+                MessageResult.MimePath mimePath = new MimePathImpl(path);
+                full = messageResult.getFullContent(mimePath);
+            }
+            result = new ContentBodyElement(name, full);
+            return result;
         }
+    }
+    
+    private static final class MimePathImpl implements MessageResult.MimePath {
+        private final int[] positions;
+        
+        public MimePathImpl(final int[] positions) {
+            super();
+            this.positions = positions;
+        }
+
+        public int[] getPositions() {
+            return positions;
+        }
+        
     }
     
     private static final class EnvelopeImpl implements FetchResponse.Envelope {

Modified: james/server/trunk/imapserver-function/src/main/java/org/apache/james/imapserver/commands/FetchCommand.java
URL: http://svn.apache.org/viewvc/james/server/trunk/imapserver-function/src/main/java/org/apache/james/imapserver/commands/FetchCommand.java?rev=620163&r1=620162&r2=620163&view=diff
==============================================================================
--- james/server/trunk/imapserver-function/src/main/java/org/apache/james/imapserver/commands/FetchCommand.java (original)
+++ james/server/trunk/imapserver-function/src/main/java/org/apache/james/imapserver/commands/FetchCommand.java Sat Feb  9 09:41:53 2008
@@ -248,7 +248,7 @@
             throws ProtocolException, MessagingException
     {
         if ( sectionSpecifier.length() == 0 ) {
-            final Content fullMessage = result.getFullMessage();
+            final Content fullMessage = result.getFullContent();
             addLiteralContent(fullMessage, response);
         }
         else if ( sectionSpecifier.equalsIgnoreCase( "HEADER" ) ) {
@@ -273,7 +273,7 @@
             throw new ProtocolException( "MIME not yet implemented." );
         }
         else if ( sectionSpecifier.equalsIgnoreCase( "TEXT" ) ) {
-            final Content messageBody = result.getMessageBody();
+            final Content messageBody = result.getBody();
             addLiteralContent(messageBody, response);
         }
         else {

Modified: james/server/trunk/torque-mailboxmanager-function/src/main/java/org/apache/james/mailboxmanager/torque/MessageRowUtils.java
URL: http://svn.apache.org/viewvc/james/server/trunk/torque-mailboxmanager-function/src/main/java/org/apache/james/mailboxmanager/torque/MessageRowUtils.java?rev=620163&r1=620162&r2=620163&view=diff
==============================================================================
--- james/server/trunk/torque-mailboxmanager-function/src/main/java/org/apache/james/mailboxmanager/torque/MessageRowUtils.java (original)
+++ james/server/trunk/torque-mailboxmanager-function/src/main/java/org/apache/james/mailboxmanager/torque/MessageRowUtils.java Sat Feb  9 09:41:53 2008
@@ -283,11 +283,11 @@
             result -= FetchGroup.HEADERS;
         }
         if ((result & FetchGroup.BODY_CONTENT) > 0) {
-            messageResult.setMessageBody(createBodyContent(messageRow));
+            messageResult.setBody(createBodyContent(messageRow));
             result -= FetchGroup.BODY_CONTENT;
         }
         if ((result & FetchGroup.FULL_CONTENT) > 0) {
-            messageResult.setFullMessage(createFullContent(messageRow, messageResult.getHeaders()));
+            messageResult.setFullContent(createFullContent(messageRow, messageResult.getHeaders()));
             result -= FetchGroup.FULL_CONTENT;
         }
         if (result != 0) {

Modified: james/server/trunk/torque-mailboxmanager-function/src/main/java/org/apache/james/mailboxmanager/torque/OrFetchGroup.java
URL: http://svn.apache.org/viewvc/james/server/trunk/torque-mailboxmanager-function/src/main/java/org/apache/james/mailboxmanager/torque/OrFetchGroup.java?rev=620163&r1=620162&r2=620163&view=diff
==============================================================================
--- james/server/trunk/torque-mailboxmanager-function/src/main/java/org/apache/james/mailboxmanager/torque/OrFetchGroup.java (original)
+++ james/server/trunk/torque-mailboxmanager-function/src/main/java/org/apache/james/mailboxmanager/torque/OrFetchGroup.java Sat Feb  9 09:41:53 2008
@@ -20,11 +20,13 @@
 package org.apache.james.mailboxmanager.torque;
 
 import org.apache.james.mailboxmanager.MessageResult.FetchGroup;
+import org.apache.james.mailboxmanager.MessageResult.MimePath;
 
 /**
  * Wraps a fetch group and ORs content.
  */
 public final class OrFetchGroup implements FetchGroup {
+    
 
     private final FetchGroup delegate;
     private final int or;
@@ -39,15 +41,8 @@
         return or | delegate.content();
     }
 
-    public int[] mimeHeaders() {
-        return delegate.mimeHeaders();
-    }
-
-    public int[] mimeParts() {
-        return delegate.mimeParts();
-    }
-
     public String toString() {
         return "Fetch " + or + " OR " + delegate;
     }
+
 }

Modified: james/server/trunk/torque-mailboxmanager-function/src/main/java/org/apache/james/mailboxmanager/torque/TorqueResultIterator.java
URL: http://svn.apache.org/viewvc/james/server/trunk/torque-mailboxmanager-function/src/main/java/org/apache/james/mailboxmanager/torque/TorqueResultIterator.java?rev=620163&r1=620162&r2=620163&view=diff
==============================================================================
--- james/server/trunk/torque-mailboxmanager-function/src/main/java/org/apache/james/mailboxmanager/torque/TorqueResultIterator.java (original)
+++ james/server/trunk/torque-mailboxmanager-function/src/main/java/org/apache/james/mailboxmanager/torque/TorqueResultIterator.java Sat Feb  9 09:41:53 2008
@@ -116,7 +116,7 @@
             throw exception;
         }
 
-        public Content getFullMessage() throws MailboxManagerException{
+        public Content getFullContent() throws MailboxManagerException{
             throw exception;
         }
 
@@ -132,7 +132,7 @@
             return null;
         }
 
-        public Content getMessageBody() throws MailboxManagerException {
+        public Content getBody() throws MailboxManagerException {
             throw exception;
         }
 
@@ -165,6 +165,18 @@
             // Java 1.5  return (int) Math.signum(uid - that.getUid());
             long diff = uid - that.getUid();
             return (int) diff == 0 ? 0 : diff > 0 ? 1 : -1;
+        }
+
+        public Content getBody(MimePath path) throws MailboxManagerException {
+            throw exception;
+        }
+
+        public Content getFullContent(MimePath path) throws MailboxManagerException {
+            throw exception;
+        }
+
+        public Iterator iterateHeaders(MimePath path) throws MailboxManagerException {
+            throw exception;
         }
         
     }



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