[maven-scm] copy for tag jsieve-project-0.4

git-svn-id: https://svn.apache.org/repos/asf/james/jsieve/tags/jsieve-project-0.4@940580 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/trunk/LICENSE.txt b/trunk/LICENSE.txt
new file mode 100644
index 0000000..94d8c58
--- /dev/null
+++ b/trunk/LICENSE.txt
@@ -0,0 +1,176 @@
+                                 Apache License

+                           Version 2.0, January 2004

+                        http://www.apache.org/licenses/

+

+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

+

+   1. Definitions.

+

+      "License" shall mean the terms and conditions for use, reproduction,

+      and distribution as defined by Sections 1 through 9 of this document.

+

+      "Licensor" shall mean the copyright owner or entity authorized by

+      the copyright owner that is granting the License.

+

+      "Legal Entity" shall mean the union of the acting entity and all

+      other entities that control, are controlled by, or are under common

+      control with that entity. For the purposes of this definition,

+      "control" means (i) the power, direct or indirect, to cause the

+      direction or management of such entity, whether by contract or

+      otherwise, or (ii) ownership of fifty percent (50%) or more of the

+      outstanding shares, or (iii) beneficial ownership of such entity.

+

+      "You" (or "Your") shall mean an individual or Legal Entity

+      exercising permissions granted by this License.

+

+      "Source" form shall mean the preferred form for making modifications,

+      including but not limited to software source code, documentation

+      source, and configuration files.

+

+      "Object" form shall mean any form resulting from mechanical

+      transformation or translation of a Source form, including but

+      not limited to compiled object code, generated documentation,

+      and conversions to other media types.

+

+      "Work" shall mean the work of authorship, whether in Source or

+      Object form, made available under the License, as indicated by a

+      copyright notice that is included in or attached to the work

+      (an example is provided in the Appendix below).

+

+      "Derivative Works" shall mean any work, whether in Source or Object

+      form, that is based on (or derived from) the Work and for which the

+      editorial revisions, annotations, elaborations, or other modifications

+      represent, as a whole, an original work of authorship. For the purposes

+      of this License, Derivative Works shall not include works that remain

+      separable from, or merely link (or bind by name) to the interfaces of,

+      the Work and Derivative Works thereof.

+

+      "Contribution" shall mean any work of authorship, including

+      the original version of the Work and any modifications or additions

+      to that Work or Derivative Works thereof, that is intentionally

+      submitted to Licensor for inclusion in the Work by the copyright owner

+      or by an individual or Legal Entity authorized to submit on behalf of

+      the copyright owner. For the purposes of this definition, "submitted"

+      means any form of electronic, verbal, or written communication sent

+      to the Licensor or its representatives, including but not limited to

+      communication on electronic mailing lists, source code control systems,

+      and issue tracking systems that are managed by, or on behalf of, the

+      Licensor for the purpose of discussing and improving the Work, but

+      excluding communication that is conspicuously marked or otherwise

+      designated in writing by the copyright owner as "Not a Contribution."

+

+      "Contributor" shall mean Licensor and any individual or Legal Entity

+      on behalf of whom a Contribution has been received by Licensor and

+      subsequently incorporated within the Work.

+

+   2. Grant of Copyright License. Subject to the terms and conditions of

+      this License, each Contributor hereby grants to You a perpetual,

+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable

+      copyright license to reproduce, prepare Derivative Works of,

+      publicly display, publicly perform, sublicense, and distribute the

+      Work and such Derivative Works in Source or Object form.

+

+   3. Grant of Patent License. Subject to the terms and conditions of

+      this License, each Contributor hereby grants to You a perpetual,

+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable

+      (except as stated in this section) patent license to make, have made,

+      use, offer to sell, sell, import, and otherwise transfer the Work,

+      where such license applies only to those patent claims licensable

+      by such Contributor that are necessarily infringed by their

+      Contribution(s) alone or by combination of their Contribution(s)

+      with the Work to which such Contribution(s) was submitted. If You

+      institute patent litigation against any entity (including a

+      cross-claim or counterclaim in a lawsuit) alleging that the Work

+      or a Contribution incorporated within the Work constitutes direct

+      or contributory patent infringement, then any patent licenses

+      granted to You under this License for that Work shall terminate

+      as of the date such litigation is filed.

+

+   4. Redistribution. You may reproduce and distribute copies of the

+      Work or Derivative Works thereof in any medium, with or without

+      modifications, and in Source or Object form, provided that You

+      meet the following conditions:

+

+      (a) You must give any other recipients of the Work or

+          Derivative Works a copy of this License; and

+

+      (b) You must cause any modified files to carry prominent notices

+          stating that You changed the files; and

+

+      (c) You must retain, in the Source form of any Derivative Works

+          that You distribute, all copyright, patent, trademark, and

+          attribution notices from the Source form of the Work,

+          excluding those notices that do not pertain to any part of

+          the Derivative Works; and

+

+      (d) If the Work includes a "NOTICE" text file as part of its

+          distribution, then any Derivative Works that You distribute must

+          include a readable copy of the attribution notices contained

+          within such NOTICE file, excluding those notices that do not

+          pertain to any part of the Derivative Works, in at least one

+          of the following places: within a NOTICE text file distributed

+          as part of the Derivative Works; within the Source form or

+          documentation, if provided along with the Derivative Works; or,

+          within a display generated by the Derivative Works, if and

+          wherever such third-party notices normally appear. The contents

+          of the NOTICE file are for informational purposes only and

+          do not modify the License. You may add Your own attribution

+          notices within Derivative Works that You distribute, alongside

+          or as an addendum to the NOTICE text from the Work, provided

+          that such additional attribution notices cannot be construed

+          as modifying the License.

+

+      You may add Your own copyright statement to Your modifications and

+      may provide additional or different license terms and conditions

+      for use, reproduction, or distribution of Your modifications, or

+      for any such Derivative Works as a whole, provided Your use,

+      reproduction, and distribution of the Work otherwise complies with

+      the conditions stated in this License.

+

+   5. Submission of Contributions. Unless You explicitly state otherwise,

+      any Contribution intentionally submitted for inclusion in the Work

+      by You to the Licensor shall be under the terms and conditions of

+      this License, without any additional terms or conditions.

+      Notwithstanding the above, nothing herein shall supersede or modify

+      the terms of any separate license agreement you may have executed

+      with Licensor regarding such Contributions.

+

+   6. Trademarks. This License does not grant permission to use the trade

+      names, trademarks, service marks, or product names of the Licensor,

+      except as required for reasonable and customary use in describing the

+      origin of the Work and reproducing the content of the NOTICE file.

+

+   7. Disclaimer of Warranty. Unless required by applicable law or

+      agreed to in writing, Licensor provides the Work (and each

+      Contributor provides its Contributions) on an "AS IS" BASIS,

+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or

+      implied, including, without limitation, any warranties or conditions

+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A

+      PARTICULAR PURPOSE. You are solely responsible for determining the

+      appropriateness of using or redistributing the Work and assume any

+      risks associated with Your exercise of permissions under this License.

+

+   8. Limitation of Liability. In no event and under no legal theory,

+      whether in tort (including negligence), contract, or otherwise,

+      unless required by applicable law (such as deliberate and grossly

+      negligent acts) or agreed to in writing, shall any Contributor be

+      liable to You for damages, including any direct, indirect, special,

+      incidental, or consequential damages of any character arising as a

+      result of this License or out of the use or inability to use the

+      Work (including but not limited to damages for loss of goodwill,

+      work stoppage, computer failure or malfunction, or any and all

+      other commercial damages or losses), even if such Contributor

+      has been advised of the possibility of such damages.

+

+   9. Accepting Warranty or Additional Liability. While redistributing

+      the Work or Derivative Works thereof, You may choose to offer,

+      and charge a fee for, acceptance of support, warranty, indemnity,

+      or other liability obligations and/or rights consistent with this

+      License. However, in accepting such obligations, You may act only

+      on Your own behalf and on Your sole responsibility, not on behalf

+      of any other Contributor, and only if You agree to indemnify,

+      defend, and hold each Contributor harmless for any liability

+      incurred by, or claims asserted against, such Contributor by reason

+      of your accepting any such warranty or additional liability.

+

+   END OF TERMS AND CONDITIONS	
\ No newline at end of file
diff --git a/trunk/NOTICE.txt b/trunk/NOTICE.txt
new file mode 100644
index 0000000..02c1448
--- /dev/null
+++ b/trunk/NOTICE.txt
@@ -0,0 +1,6 @@
+Apache James JSieve Library

+Copyright 2009 The Apache Software Foundation

+

+This product includes software developed at

+The Apache Software Foundation (http://www.apache.org/).

+   

diff --git a/trunk/README.txt b/trunk/README.txt
new file mode 100644
index 0000000..0369457
--- /dev/null
+++ b/trunk/README.txt
@@ -0,0 +1,5 @@
+JSieve is built using Maven 2 (http://maven.apache.org). To build all modules type
+
+% mvn install
+
+ 
diff --git a/trunk/RELEASE_NOTES.txt b/trunk/RELEASE_NOTES.txt
new file mode 100644
index 0000000..1c892d1
--- /dev/null
+++ b/trunk/RELEASE_NOTES.txt
@@ -0,0 +1,65 @@
+                        Release Notes
+                        -------------
+
+Apache JSieve is a Java implementation of the Sieve mail filtering language defined by 
+RFC 3028. jSieve is implemented as a langauge processor that can be plugged into any
+internet mail application to add Sieve support.
+
+Comments, Questions and Issues
+------------------------------
+jSieve is a sub-project of Apache James. Please direct your comments and questions to 
+the relevant James list.
+
+To report issues, such as bugs, go to 
+http://issues.apache.org/jira/browse/JSIEVE
+As jSieve comes with a fairly extensive suite of jUnit Tests, it would be most 
+helpful for bug reports to be accompanied by an illustrative jUnit test case.
+
+Licensing and legal issues
+--------------------------
+For legal and licensing issues, please look in the legal section of
+the documentation or read the LICENSE and NOTICE files.
+
+Version 0.3
+----------- 
+This release is the first to include mailet and utility modules, in addition to the core parsing
+library. The configuration API has changed significantly to replace magic singletons with POJOs
+suitable for IoC. The default configuration mechanism has been retained so though calls have
+changed, existing configurations should still be compatible.
+ 
+Utilities include node serializers to xml and sieve script. The mailet module provides Sieve
+in the James 3 codebase.
+
+Java 1.5 is now the minimum version required.
+ 
+Changes   
+-------
+Sub-tasks Completed
+ * https://issues.apache.org/jira/browse/JSIEVE-27 - Refactor ComparatorManager
+ * https://issues.apache.org/jira/browse/JSIEVE-29 - Refactor ConfigurationManager
+ * https://issues.apache.org/jira/browse/JSIEVE-32 - Refactor TestManager
+ * https://issues.apache.org/jira/browse/JSIEVE-37 - Push main source down a level
+ * https://issues.apache.org/jira/browse/JSIEVE-38 - Modular Ant Build
+ * https://issues.apache.org/jira/browse/JSIEVE-44 - Create Check Module
+ * https://issues.apache.org/jira/browse/JSIEVE-45 - Extract Reusable Ant Macros
+ * https://issues.apache.org/jira/browse/JSIEVE-46 - Unified Distribution
+ * https://issues.apache.org/jira/browse/JSIEVE-48 - Release Quality Maven Build
+    
+Bugs Fixed
+ * https://issues.apache.org/jira/browse/JSIEVE-49 - CPU spins when :matches expression 
+                                                     contains "*************"
+                                                     
+Improvements Made
+ * https://issues.apache.org/jira/browse/JSIEVE-47 - Access to script comments
+ * https://issues.apache.org/jira/browse/JSIEVE-52 - Check Support For Numeric Quantifiers
+    
+New Features Added
+ * https://issues.apache.org/jira/browse/JSIEVE-43 - Add API for generating a Sieve script from a parse tree 
+ * https://issues.apache.org/jira/browse/JSIEVE-50 - Sieve-In-XML (Experimental Preview)
+                
+Tasks Completed
+ * https://issues.apache.org/jira/browse/JSIEVE-16 - Singletons -> IoC
+ * https://issues.apache.org/jira/browse/JSIEVE-24 - Include build time libraries (javacc, 
+                                                     javamail, activation) in the source distribution
+ * https://issues.apache.org/jira/browse/JSIEVE-33 - Remove .junit. package name from tests.
+ * https://issues.apache.org/jira/browse/JSIEVE-34 - make jsieve a multimodule project
\ No newline at end of file
diff --git a/trunk/assemble/pom.xml b/trunk/assemble/pom.xml
new file mode 100644
index 0000000..6b0a099
--- /dev/null
+++ b/trunk/assemble/pom.xml
@@ -0,0 +1,122 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <!--
+    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.    
+  -->
+  <parent>
+    <artifactId>jsieve-project</artifactId>
+    <groupId>org.apache.james</groupId>
+    <version>0.4</version>
+  </parent>
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>org.apache.james</groupId>
+  <artifactId>apache-james-jsieve</artifactId>
+  <name>Apache JAMES jSieve Assembly</name>
+  <version>0.4</version>
+  <packaging>pom</packaging>
+  <description>
+Apache jSieve is a server side mail filtering system 
+implementing RFC3028. Apache jSieve is developed by the 
+JAMES project. This module is used by Maven to create
+fully assembled and packaged distributions.
+  </description>
+  <url>http://james.apache.org/jsieve</url>
+  <inceptionYear>2004</inceptionYear>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.james</groupId>
+      <artifactId>apache-jsieve</artifactId>
+    </dependency>
+    
+    <dependency>
+      <groupId>org.apache.james</groupId>
+      <artifactId>apache-jsieve-mailet</artifactId>
+    </dependency>
+    
+    <dependency>
+      <groupId>org.apache.james</groupId>
+      <artifactId>apache-jsieve-util</artifactId>
+    </dependency>
+    
+    <dependency>
+      <groupId>org.apache.james</groupId>
+      <artifactId>apache-mailet</artifactId>
+    </dependency>
+    
+    <dependency>
+      <groupId>org.apache.james</groupId>
+      <artifactId>apache-mailet-base</artifactId>
+    </dependency>
+      
+    <dependency>
+      <groupId>org.apache.james</groupId>
+      <artifactId>apache-mime4j</artifactId>
+    </dependency>
+      
+    <dependency>
+      <groupId>commons-logging</groupId>
+      <artifactId>commons-logging</artifactId>
+    </dependency>
+
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <scope>test</scope>
+    </dependency>
+    
+    <dependency>
+      <groupId>log4j</groupId>
+      <artifactId>log4j</artifactId>
+      <scope>test</scope>
+    </dependency>
+
+    <dependency>
+      <groupId>javax.mail</groupId>
+      <artifactId>mail</artifactId>
+    </dependency>
+    
+    <dependency>
+      <groupId>javax.activation</groupId>
+      <artifactId>activation</artifactId>
+    </dependency>
+
+  </dependencies>
+
+  <build>    
+    <plugins>
+
+      <plugin>
+        <artifactId>maven-assembly-plugin</artifactId>
+        <configuration>
+          <descriptorSourceDirectory>${basedir}/src/assemble/</descriptorSourceDirectory>
+          <tarLongFileMode>gnu</tarLongFileMode> 
+        </configuration>
+        <executions>
+          <execution>
+            <id>make-assembly</id>
+            <phase>package</phase> <!-- append to the packaging phase. -->
+            <goals>
+              <goal>attached</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+  </build>
+</project>
diff --git a/trunk/assemble/src/assemble/bin.xml b/trunk/assemble/src/assemble/bin.xml
new file mode 100644
index 0000000..897149b
--- /dev/null
+++ b/trunk/assemble/src/assemble/bin.xml
@@ -0,0 +1,89 @@
+<assembly>
+  <!--
+  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.
+  -->
+  <id>bin</id>
+  <!-- 
+  Generates a jar file including the binary jar and the runtime dependencies.
+  including NOTICE and LICENSE in the root.
+  -->
+  <formats>
+    <format>zip</format>
+    <format>tar.gz</format>
+  </formats>
+  <fileSets>
+    <fileSet>
+      <directory>${project.basedir}/..</directory>
+      <outputDirectory>/</outputDirectory>
+      <includes>
+        <include>LICENSE.*</include>
+        <include>NOTICE.*</include>
+        <include>RELEASE_NOTES.txt</include>
+      </includes>
+    </fileSet>
+    <fileSet>
+      <directory>${project.basedir}/../mailet/target</directory>
+      <outputDirectory>/</outputDirectory>
+      <includes>
+        <include>apache-jsieve*.jar</include>
+      </includes>
+    </fileSet>
+    <fileSet>
+      <directory>${project.basedir}/../util/target</directory>
+      <outputDirectory>/</outputDirectory>
+      <includes>
+        <include>apache-jsieve*.jar</include>
+      </includes>
+    </fileSet>
+    <fileSet>
+      <directory>${project.basedir}/../main/target</directory>
+      <outputDirectory>/</outputDirectory>
+      <includes>
+        <include>apache-jsieve*.jar</include>
+      </includes>
+    </fileSet>
+    <fileSet>
+      <directory>${project.basedir}/../target/site/apidocs</directory>
+      <outputDirectory>/apidocs</outputDirectory>
+    </fileSet>
+    <fileSet>
+      <directory>../stage/javax.activation/jars/</directory>
+      <outputDirectory>/lib</outputDirectory>
+      <includes>
+        <include>activation.LICENSE</include>
+      </includes>
+    </fileSet>
+    <fileSet>
+      <directory>../stage/javax.mail/jars/</directory>
+      <outputDirectory>/lib</outputDirectory>
+      <includes>
+        <include>mail.LICENSE</include>
+      </includes>
+    </fileSet>
+  </fileSets>
+  <dependencySets>
+    <dependencySet>
+      <outputDirectory>/lib/</outputDirectory>
+      <useProjectArtifact>false</useProjectArtifact>
+      <scope>runtime</scope>
+      <excludes>
+        <exclude>apache-jsieve*</exclude>
+      </excludes>
+    </dependencySet>
+  </dependencySets>
+</assembly>
\ No newline at end of file
diff --git a/trunk/assemble/src/assemble/src.xml b/trunk/assemble/src/assemble/src.xml
new file mode 100644
index 0000000..037af0a
--- /dev/null
+++ b/trunk/assemble/src/assemble/src.xml
@@ -0,0 +1,46 @@
+<assembly>
+  <!--
+  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.
+  -->
+  <id>src</id>
+  <!-- 
+  Generates a file including all the source tree excluding files/folders
+  starting with "." , *.bak and the target
+  -->
+  <formats>
+    <format>zip</format>
+    <format>tar.gz</format>
+  </formats>
+  <fileSets>
+    <fileSet>
+      <directory>${project.basedir}/..</directory>
+      <outputDirectory></outputDirectory>
+      <excludes>
+        <exclude>**/target/**</exclude>
+        <exclude>.*</exclude>
+        <exclude>.*/**</exclude>
+        <exclude>**.bak</exclude>
+        <exclude>*.jar</exclude>
+        <exclude>**/james-project/**</exclude>
+        <exclude>**/site/**</exclude>
+        <exclude>**/.*</exclude>
+      </excludes>
+      <useDefaultExcludes>true</useDefaultExcludes>
+    </fileSet>
+  </fileSets>
+</assembly>
\ No newline at end of file
diff --git a/trunk/mailet/LICENSE.apache b/trunk/mailet/LICENSE.apache
new file mode 100644
index 0000000..94d8c58
--- /dev/null
+++ b/trunk/mailet/LICENSE.apache
@@ -0,0 +1,176 @@
+                                 Apache License

+                           Version 2.0, January 2004

+                        http://www.apache.org/licenses/

+

+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

+

+   1. Definitions.

+

+      "License" shall mean the terms and conditions for use, reproduction,

+      and distribution as defined by Sections 1 through 9 of this document.

+

+      "Licensor" shall mean the copyright owner or entity authorized by

+      the copyright owner that is granting the License.

+

+      "Legal Entity" shall mean the union of the acting entity and all

+      other entities that control, are controlled by, or are under common

+      control with that entity. For the purposes of this definition,

+      "control" means (i) the power, direct or indirect, to cause the

+      direction or management of such entity, whether by contract or

+      otherwise, or (ii) ownership of fifty percent (50%) or more of the

+      outstanding shares, or (iii) beneficial ownership of such entity.

+

+      "You" (or "Your") shall mean an individual or Legal Entity

+      exercising permissions granted by this License.

+

+      "Source" form shall mean the preferred form for making modifications,

+      including but not limited to software source code, documentation

+      source, and configuration files.

+

+      "Object" form shall mean any form resulting from mechanical

+      transformation or translation of a Source form, including but

+      not limited to compiled object code, generated documentation,

+      and conversions to other media types.

+

+      "Work" shall mean the work of authorship, whether in Source or

+      Object form, made available under the License, as indicated by a

+      copyright notice that is included in or attached to the work

+      (an example is provided in the Appendix below).

+

+      "Derivative Works" shall mean any work, whether in Source or Object

+      form, that is based on (or derived from) the Work and for which the

+      editorial revisions, annotations, elaborations, or other modifications

+      represent, as a whole, an original work of authorship. For the purposes

+      of this License, Derivative Works shall not include works that remain

+      separable from, or merely link (or bind by name) to the interfaces of,

+      the Work and Derivative Works thereof.

+

+      "Contribution" shall mean any work of authorship, including

+      the original version of the Work and any modifications or additions

+      to that Work or Derivative Works thereof, that is intentionally

+      submitted to Licensor for inclusion in the Work by the copyright owner

+      or by an individual or Legal Entity authorized to submit on behalf of

+      the copyright owner. For the purposes of this definition, "submitted"

+      means any form of electronic, verbal, or written communication sent

+      to the Licensor or its representatives, including but not limited to

+      communication on electronic mailing lists, source code control systems,

+      and issue tracking systems that are managed by, or on behalf of, the

+      Licensor for the purpose of discussing and improving the Work, but

+      excluding communication that is conspicuously marked or otherwise

+      designated in writing by the copyright owner as "Not a Contribution."

+

+      "Contributor" shall mean Licensor and any individual or Legal Entity

+      on behalf of whom a Contribution has been received by Licensor and

+      subsequently incorporated within the Work.

+

+   2. Grant of Copyright License. Subject to the terms and conditions of

+      this License, each Contributor hereby grants to You a perpetual,

+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable

+      copyright license to reproduce, prepare Derivative Works of,

+      publicly display, publicly perform, sublicense, and distribute the

+      Work and such Derivative Works in Source or Object form.

+

+   3. Grant of Patent License. Subject to the terms and conditions of

+      this License, each Contributor hereby grants to You a perpetual,

+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable

+      (except as stated in this section) patent license to make, have made,

+      use, offer to sell, sell, import, and otherwise transfer the Work,

+      where such license applies only to those patent claims licensable

+      by such Contributor that are necessarily infringed by their

+      Contribution(s) alone or by combination of their Contribution(s)

+      with the Work to which such Contribution(s) was submitted. If You

+      institute patent litigation against any entity (including a

+      cross-claim or counterclaim in a lawsuit) alleging that the Work

+      or a Contribution incorporated within the Work constitutes direct

+      or contributory patent infringement, then any patent licenses

+      granted to You under this License for that Work shall terminate

+      as of the date such litigation is filed.

+

+   4. Redistribution. You may reproduce and distribute copies of the

+      Work or Derivative Works thereof in any medium, with or without

+      modifications, and in Source or Object form, provided that You

+      meet the following conditions:

+

+      (a) You must give any other recipients of the Work or

+          Derivative Works a copy of this License; and

+

+      (b) You must cause any modified files to carry prominent notices

+          stating that You changed the files; and

+

+      (c) You must retain, in the Source form of any Derivative Works

+          that You distribute, all copyright, patent, trademark, and

+          attribution notices from the Source form of the Work,

+          excluding those notices that do not pertain to any part of

+          the Derivative Works; and

+

+      (d) If the Work includes a "NOTICE" text file as part of its

+          distribution, then any Derivative Works that You distribute must

+          include a readable copy of the attribution notices contained

+          within such NOTICE file, excluding those notices that do not

+          pertain to any part of the Derivative Works, in at least one

+          of the following places: within a NOTICE text file distributed

+          as part of the Derivative Works; within the Source form or

+          documentation, if provided along with the Derivative Works; or,

+          within a display generated by the Derivative Works, if and

+          wherever such third-party notices normally appear. The contents

+          of the NOTICE file are for informational purposes only and

+          do not modify the License. You may add Your own attribution

+          notices within Derivative Works that You distribute, alongside

+          or as an addendum to the NOTICE text from the Work, provided

+          that such additional attribution notices cannot be construed

+          as modifying the License.

+

+      You may add Your own copyright statement to Your modifications and

+      may provide additional or different license terms and conditions

+      for use, reproduction, or distribution of Your modifications, or

+      for any such Derivative Works as a whole, provided Your use,

+      reproduction, and distribution of the Work otherwise complies with

+      the conditions stated in this License.

+

+   5. Submission of Contributions. Unless You explicitly state otherwise,

+      any Contribution intentionally submitted for inclusion in the Work

+      by You to the Licensor shall be under the terms and conditions of

+      this License, without any additional terms or conditions.

+      Notwithstanding the above, nothing herein shall supersede or modify

+      the terms of any separate license agreement you may have executed

+      with Licensor regarding such Contributions.

+

+   6. Trademarks. This License does not grant permission to use the trade

+      names, trademarks, service marks, or product names of the Licensor,

+      except as required for reasonable and customary use in describing the

+      origin of the Work and reproducing the content of the NOTICE file.

+

+   7. Disclaimer of Warranty. Unless required by applicable law or

+      agreed to in writing, Licensor provides the Work (and each

+      Contributor provides its Contributions) on an "AS IS" BASIS,

+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or

+      implied, including, without limitation, any warranties or conditions

+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A

+      PARTICULAR PURPOSE. You are solely responsible for determining the

+      appropriateness of using or redistributing the Work and assume any

+      risks associated with Your exercise of permissions under this License.

+

+   8. Limitation of Liability. In no event and under no legal theory,

+      whether in tort (including negligence), contract, or otherwise,

+      unless required by applicable law (such as deliberate and grossly

+      negligent acts) or agreed to in writing, shall any Contributor be

+      liable to You for damages, including any direct, indirect, special,

+      incidental, or consequential damages of any character arising as a

+      result of this License or out of the use or inability to use the

+      Work (including but not limited to damages for loss of goodwill,

+      work stoppage, computer failure or malfunction, or any and all

+      other commercial damages or losses), even if such Contributor

+      has been advised of the possibility of such damages.

+

+   9. Accepting Warranty or Additional Liability. While redistributing

+      the Work or Derivative Works thereof, You may choose to offer,

+      and charge a fee for, acceptance of support, warranty, indemnity,

+      or other liability obligations and/or rights consistent with this

+      License. However, in accepting such obligations, You may act only

+      on Your own behalf and on Your sole responsibility, not on behalf

+      of any other Contributor, and only if You agree to indemnify,

+      defend, and hold each Contributor harmless for any liability

+      incurred by, or claims asserted against, such Contributor by reason

+      of your accepting any such warranty or additional liability.

+

+   END OF TERMS AND CONDITIONS	
\ No newline at end of file
diff --git a/trunk/mailet/NOTICE.base b/trunk/mailet/NOTICE.base
new file mode 100644
index 0000000..76a0011
--- /dev/null
+++ b/trunk/mailet/NOTICE.base
@@ -0,0 +1,10 @@
+   =========================================================================

+   ==      NOTICE file for use with the Apache License, Version 2.0,      ==

+   =========================================================================

+

+   Apache James JSieve Library

+   Copyright 2009 The Apache Software Foundation

+

+   This product includes software developed at

+   The Apache Software Foundation (http://www.apache.org/).

+
diff --git a/trunk/mailet/build.xml b/trunk/mailet/build.xml
new file mode 100644
index 0000000..b2dd89d
--- /dev/null
+++ b/trunk/mailet/build.xml
@@ -0,0 +1,91 @@
+<?xml version="1.0"?>
+<!-- 
+==========================================================================
+
+ jSieve build file 
+
+
+  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.                                           
+ 
+==============================================================================
+-->
+
+<project name='jsieve-mailet' default="main" basedir=".">
+     
+    <!--
+    Give user a chance to override without editing this file
+    (and without typing -D each time he compiles it)
+    -->
+    <property file=".ant.properties" />
+    <property file="${user.home}/.ant.properties" />
+    <property file="../include.properties" />
+    <property file="../default.properties" />
+
+    <path id="mail.class.path">
+        <pathelement location='${mail.jar}' />
+        <pathelement location='${activation.jar}' />
+    </path>
+
+    <path id="project.class.path">
+        <pathelement location="${commons-logging.jar}" />
+        <pathelement location="${log4j.jar}" />
+        <pathelement location="${junit.jar}" />
+        <pathelement location='${lib.jsieve.dir}/${name}-${version}.jar' />
+        <pathelement location="${mailet.jar}" />
+        <pathelement location="${mailet-base.jar}" />
+        <pathelement location="${mime4j.jar}" />
+        <path refid="mail.class.path" />
+        <pathelement path="${java.class.path}" />
+        <pathelement path="${build.classes}" />
+    </path>
+
+    <path id="project.test.class.path">
+        <path refid="project.class.path" />
+    </path>
+
+     <import file='../build.xml'/>
+    
+    <target name="main" depends="run-tests" description=" - main target"/>
+
+    <target name="prepare" description=" - paparations                   [internal]">
+        <CheckMailConditions/>
+    </target>
+
+    <target name="compile" depends="prepare" description=" - compiles test and main source">
+        <CompileMain>
+            <src path="${java.dir}" />
+        </CompileMain>
+        <CompileTests/>
+    </target>
+
+    <target name="jar" depends="compile" description=" - jars classes">
+        <Jar name='mailet'/> 
+    </target>
+
+    <target name="run-tests" depends="jar" description=" - runs all tests">
+        <RunTests/>
+    </target>
+
+    <target name="clean" description=" - cleans build files">
+        <Clean/>
+    </target>
+
+    <target name="usage" description=" - prints help">
+        <Usage/>
+    </target>
+</project>
diff --git a/trunk/mailet/pom.xml b/trunk/mailet/pom.xml
new file mode 100644
index 0000000..a35c01d
--- /dev/null
+++ b/trunk/mailet/pom.xml
@@ -0,0 +1,247 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <!--
+    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.    
+  -->
+  <parent>
+    <artifactId>jsieve-project</artifactId>
+    <groupId>org.apache.james</groupId>
+    <version>0.4</version>
+  </parent>
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>org.apache.james</groupId>
+  <artifactId>apache-jsieve-mailet</artifactId>
+  <name>Apache JAMES jSieve Mailet</name>
+  <version>0.4</version>
+  <packaging>jar</packaging>
+  <description>
+Apache jSieve is a server side mail filtering system 
+implementing RFC3028. Apache jSieve is developed by the 
+JAMES project.
+  </description>
+  <url>http://james.apache.org/jsieve</url>
+  <inceptionYear>2004</inceptionYear>
+
+  <properties>
+    <!--
+    The website is committed to subversion. This property can be overriden
+    to upload the site to a local staging location.
+    For example, adding the following to ~/.m2/settings.xml will upload
+    to localhost:
+    
+      <profiles>
+        <profile>
+           <id>main</id>
+           <activation>
+              <activeByDefault>true</activeByDefault>
+           </activation>
+           <properties>
+              <james.www>scp://localhost/www</james.www>
+              <james.www.id>localhost</james.www.id>
+      ...
+    -->
+    <!-- General location for site stage -->
+    <james.www>scp://people.apache.org/www/james.apache.org/</james.www>
+    <!-- Project specific location, allowing specific override -->
+    <james.jsieve.www>${james.www}/jsieve/</james.jsieve.www>
+    <!-- Overridding this value allows single set of loopback settings to be maintained -->
+    <james.www.id>jsieve-website</james.www.id>
+  </properties>
+
+  <distributionManagement>
+    <site>
+      <id>${james.www.id}</id>
+      <url>${james.jsieve.www}/mailet</url>
+    </site>
+  </distributionManagement>
+
+
+  <issueManagement>
+    <system>JIRA</system>
+    <url>http://issues.apache.org/jira/browse/JSIEVE</url>
+  </issueManagement>
+
+  <scm>
+    <connection>scm:svn:http://svn.apache.org/repos/asf/james/jsieve/tags/jsieve-project-0.4</connection>
+    <developerConnection>scm:svn:https://svn.apache.org/repos/asf/james/jsieve/tags/jsieve-project-0.4</developerConnection>
+    <url>http://svn.apache.org/viewvc/james/jsieve/tags/jsieve-project-0.4</url>
+  </scm>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.james</groupId>
+      <artifactId>apache-jsieve</artifactId>
+    </dependency>
+    
+    <dependency>
+      <groupId>org.apache.james</groupId>
+      <artifactId>apache-mailet</artifactId>
+    </dependency>
+    
+    <dependency>
+      <groupId>org.apache.james</groupId>
+      <artifactId>apache-mailet-base</artifactId>
+    </dependency>
+      
+    <dependency>
+      <groupId>org.apache.james</groupId>
+      <artifactId>apache-mime4j</artifactId>
+    </dependency>
+    
+    <dependency>
+      <groupId>commons-logging</groupId>
+      <artifactId>commons-logging</artifactId>
+    </dependency>
+
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <scope>test</scope>
+    </dependency>
+    
+    <dependency>
+      <groupId>log4j</groupId>
+      <artifactId>log4j</artifactId>
+      <scope>test</scope>
+    </dependency>
+
+    <dependency>
+      <groupId>javax.mail</groupId>
+      <artifactId>mail</artifactId>
+    </dependency>
+    
+    <dependency>
+      <groupId>javax.activation</groupId>
+      <artifactId>activation</artifactId>
+    </dependency>
+
+    <dependency>
+      <groupId>jmock</groupId>
+      <artifactId>jmock</artifactId>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <resources>
+      <resource>
+        <directory>src/main/resources</directory>
+      </resource>
+    </resources>
+    
+    <plugins>     
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-jar-plugin</artifactId>
+      </plugin>
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>rat-maven-plugin</artifactId>
+      </plugin>
+      <plugin>
+      	<groupId>org.apache.felix</groupId>
+      	<artifactId>maven-bundle-plugin</artifactId>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-doap-plugin</artifactId>
+      </plugin>
+    </plugins>
+  </build>
+
+  <reporting>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.james</groupId>
+        <artifactId>maven-mailetdocs-plugin</artifactId>
+        <version>0.1</version>
+      </plugin>
+      <plugin>
+        <artifactId>maven-surefire-plugin</artifactId>
+        <version>2.4.3</version>
+      </plugin>
+      <plugin>
+        <artifactId>maven-surefire-report-plugin</artifactId>
+        <version>2.4.3</version>
+      </plugin> 
+      <plugin>
+        <artifactId>maven-javadoc-plugin</artifactId>
+        <version>2.4</version>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-jxr-plugin</artifactId>
+        <version>2.1</version>
+      </plugin>
+      <plugin>
+        <artifactId>maven-pmd-plugin</artifactId>
+        <version>2.4</version>
+        <configuration>
+          <targetJdk>1.5</targetJdk>
+          <rulesets>
+            <ruleset>/rulesets/basic.xml</ruleset>
+            <ruleset>/rulesets/controversial.xml</ruleset>
+          </rulesets>
+          <format>xml</format>
+          <linkXref>true</linkXref>
+          <sourceEncoding>utf-8</sourceEncoding>
+          <minimumTokens>100</minimumTokens>
+        </configuration>
+      </plugin>
+      <plugin>
+        <artifactId>maven-site-plugin</artifactId>
+        <version>2.0-beta-7</version>
+      </plugin>
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>rat-maven-plugin</artifactId>
+        <version>1.0-alpha-3</version>
+        <configuration>
+          <excludes>
+            <exclude>NOTICE.base</exclude>
+            <exclude>LICENSE.apache</exclude>
+            <exclude>release.properties</exclude>
+          </excludes>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>taglist-maven-plugin</artifactId>
+        <version>2.2</version>
+      </plugin>
+    </plugins>
+  </reporting>
+  
+  <mailingLists>
+    <mailingList>
+      <name>Apache James User</name>
+      <subscribe>server-user-subscribe@james.apache.org</subscribe>
+      <unsubscribe>server-user-unsubscribe@james.apache.org</unsubscribe>
+      <post>server-user@james.apache.org</post>
+      <archive>http://mail-archives.apache.org/mod_mbox/james-server-user/</archive>
+    </mailingList>
+    <mailingList>
+      <name>Apache James Developer</name>
+      <subscribe>server-dev-subscribe@james.apache.org</subscribe>
+      <unsubscribe>server-dev-unsubscribe@james.apache.org</unsubscribe>
+      <post>server-dev@james.apache.org</post>
+      <archive>http://mail-archives.apache.org/mod_mbox/james-server-dev/</archive>
+    </mailingList>
+  </mailingLists>
+
+</project>
diff --git a/trunk/mailet/src/main/java/org/apache/jsieve/mailet/ActionContext.java b/trunk/mailet/src/main/java/org/apache/jsieve/mailet/ActionContext.java
new file mode 100644
index 0000000..33a94f4
--- /dev/null
+++ b/trunk/mailet/src/main/java/org/apache/jsieve/mailet/ActionContext.java
@@ -0,0 +1,65 @@
+/****************************************************************
+ * 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.jsieve.mailet;
+
+import java.util.Collection;
+
+import javax.mail.MessagingException;
+import javax.mail.internet.MimeMessage;
+
+import org.apache.commons.logging.Log;
+import org.apache.mailet.MailAddress;
+
+/**
+ * Provides context for action execution.
+ */
+public interface ActionContext {
+    
+    /**
+     * Gets the log.
+     * @return not null
+     */
+    public Log getLog();
+    
+    /**
+     * Experimental mail delivery. 
+     * POST verb indicate that mail should be attached to the collection
+     * indicated by the given URI.
+     * 
+     * @param uri indicates the destination to which the mail to added. ATM 
+     * the value should be mailbox://<user>@localhost/<mailbox-path>
+     * @param mail not null
+     */
+    public void post(String uri, MimeMessage mail) throws MessagingException;
+
+    /**
+     * Posts the given mail.
+     * @param sender possibly null
+     * @param recipients not null
+     * @param mail not null
+     * @throws MessagingException when mail cannot be posted
+     */
+    public void post(MailAddress sender, Collection<MailAddress> recipients, MimeMessage mail) throws MessagingException;
+
+    /**
+     * Gets name (including version) of this server.
+     * @return not nul
+     */
+    public String getServerInfo();
+}
diff --git a/trunk/mailet/src/main/java/org/apache/jsieve/mailet/ActionDispatcher.java b/trunk/mailet/src/main/java/org/apache/jsieve/mailet/ActionDispatcher.java
new file mode 100644
index 0000000..5ce604f
--- /dev/null
+++ b/trunk/mailet/src/main/java/org/apache/jsieve/mailet/ActionDispatcher.java
@@ -0,0 +1,103 @@
+/****************************************************************
+ * 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.jsieve.mailet;
+
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import javax.mail.MessagingException;
+
+import org.apache.jsieve.mail.Action;
+import org.apache.jsieve.mail.ActionFileInto;
+import org.apache.jsieve.mail.ActionKeep;
+import org.apache.jsieve.mail.ActionRedirect;
+import org.apache.jsieve.mail.ActionReject;
+import org.apache.mailet.Mail;
+
+/**
+ * Dynamically dispatches an Action depending on the type of Action received at runtime. 
+ * <h4>Thread Safety</h4>
+ * <p>An instance maybe safe accessed concurrently by multiple threads.</p>
+ */
+public class ActionDispatcher
+{   
+    /**
+     * A Map keyed by the type of Action. The values are the methods to invoke to 
+     * handle the Action.
+     * <Action, MailAction>
+     */ 
+    private ConcurrentMap<Class, MailAction> fieldMailActionMap;
+
+    /**
+     * Constructor for ActionDispatcher.
+     * @throws NoSuchMethodException 
+     */
+    public ActionDispatcher() throws MessagingException
+    {
+        super();
+        setMethodMap(defaultMethodMap());
+    }
+
+    /**
+     * Method execute executes the passed Action by invoking the method mapped by the
+     * receiver with a parameter of the EXACT type of Action.
+     * @param anAction not null
+     * @param aMail not null
+     * @param context not null
+     * @throws MessagingException
+     */
+    public void execute(final Action anAction, final Mail aMail, final ActionContext context) throws MessagingException
+    {
+        final MailAction mailAction = getMethodMap().get(anAction.getClass());
+        mailAction.execute(anAction, aMail, context);
+    }
+
+    /**
+     * Returns the methodMap.
+     * @return Map
+     */
+    public ConcurrentMap<Class, MailAction> getMethodMap()
+    {
+        return fieldMailActionMap;
+    }    
+
+    /**
+     * Returns a new methodMap.
+     * @return Map
+     */
+    private ConcurrentMap<Class, MailAction> defaultMethodMap()
+    {
+        final ConcurrentMap<Class, MailAction> actionMap = new ConcurrentHashMap<Class, MailAction>(4);
+        actionMap.put(ActionFileInto.class, new FileIntoAction());
+        actionMap.put(ActionKeep.class, new KeepAction());
+        actionMap.put(ActionRedirect.class, new RedirectAction());
+        actionMap.put(ActionReject.class, new RejectAction());
+        return actionMap;
+    }    
+
+    /**
+     * Sets the mail action mail.
+     * @param mailActionMap <Action, MailAction> not null
+     */
+    protected void setMethodMap(ConcurrentMap<Class, MailAction>  mailActionMap)
+    {
+        fieldMailActionMap = mailActionMap;
+    }
+}
diff --git a/trunk/mailet/src/main/java/org/apache/jsieve/mailet/ActionUtils.java b/trunk/mailet/src/main/java/org/apache/jsieve/mailet/ActionUtils.java
new file mode 100644
index 0000000..ddf3cf3
--- /dev/null
+++ b/trunk/mailet/src/main/java/org/apache/jsieve/mailet/ActionUtils.java
@@ -0,0 +1,80 @@
+/****************************************************************
+ * 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.jsieve.mailet;
+
+import javax.mail.MessagingException;
+
+import org.apache.mailet.Mail;
+import org.apache.mailet.MailAddress;
+
+/**
+ * Utility methods helpful for actions.
+ */
+public class ActionUtils
+{
+    
+    private final static String ATTRIBUTE_PREFIX = ActionUtils.class.getPackage().getName() + ".";
+
+    /**
+     * Answers the sole intended recipient for aMail.
+     * 
+     * @param aMail
+     * @return String
+     * @throws MessagingException
+     */
+    public static MailAddress getSoleRecipient(Mail aMail) throws MessagingException
+    {
+        if (aMail.getRecipients() == null) {
+            throw new MessagingException("Invalid number of recipients - 0"
+                    + ". Exactly 1 recipient is expected.");
+        } else if (1 != aMail.getRecipients().size())
+            throw new MessagingException("Invalid number of recipients - "
+                    + new Integer(aMail.getRecipients().size()).toString()
+                    + ". Exactly 1 recipient is expected.");
+        return (MailAddress) aMail.getRecipients().iterator().next();
+    }
+
+    /**
+     * Detect and handle locally looping mail. External loop detection is left
+     * to the MTA.
+     * 
+     * @param aMail
+     * @param context not null
+     * @param anAttributeSuffix
+     * @throws MessagingException
+     */
+    public static void detectAndHandleLocalLooping(Mail aMail, ActionContext context, String anAttributeSuffix)
+            throws MessagingException
+    {
+        MailAddress thisRecipient = getSoleRecipient(aMail);
+        MailAddress lastRecipient = (MailAddress) aMail
+                .getAttribute(ATTRIBUTE_PREFIX + anAttributeSuffix);
+        if (null != lastRecipient && lastRecipient.equals(thisRecipient))
+        {
+            MessagingException ex = new MessagingException(
+                    "This message is looping! Message ID: "
+                            + aMail.getMessage().getMessageID());
+            context.getLog().warn(ex.getMessage(), ex);
+            throw ex;
+        }
+        aMail.setAttribute(ATTRIBUTE_PREFIX + anAttributeSuffix,
+                thisRecipient);
+    }
+}
diff --git a/trunk/mailet/src/main/java/org/apache/jsieve/mailet/CommonsLoggingAdapter.java b/trunk/mailet/src/main/java/org/apache/jsieve/mailet/CommonsLoggingAdapter.java
new file mode 100644
index 0000000..809a0d5
--- /dev/null
+++ b/trunk/mailet/src/main/java/org/apache/jsieve/mailet/CommonsLoggingAdapter.java
@@ -0,0 +1,142 @@
+/****************************************************************
+ * 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.jsieve.mailet;
+
+import org.apache.commons.logging.Log;
+import org.apache.mailet.base.GenericMailet;
+
+/**
+ * Adapts commons logging to mailet logging.
+ */
+class CommonsLoggingAdapter implements Log {
+    
+    public static final int TRACE = 6;
+    public static final int DEBUG = 5;
+    public static final int INFO = 4;
+    public static final int WARN = 3;
+    public static final int ERROR = 2;
+    public static final int FATAL = 1;
+    
+    private final GenericMailet mailet;
+    private final int level;
+    
+    public CommonsLoggingAdapter(final GenericMailet mailet, final int level) {
+        super();
+        this.mailet = mailet;
+        this.level = level;
+    }
+
+    public void debug(Object message) {
+        if (isDebugEnabled()) {
+            mailet.log(message == null ? "NULL" : message.toString());
+        }
+    }
+
+    public void debug(Object message, Throwable t) {
+        if (isDebugEnabled()) {
+            mailet.log(message == null ? "NULL" : message.toString(), t);
+        } 
+    }
+
+    public void error(Object message) {
+        if (isErrorEnabled()) {
+            mailet.log(message == null ? "NULL" : message.toString());
+        }
+    }
+
+    public void error(Object message, Throwable t) {
+        if (isErrorEnabled()) {
+            mailet.log(message == null ? "NULL" : message.toString(), t);
+        }
+    }
+
+    public void fatal(Object message) {
+        if (isFatalEnabled()) {
+            mailet.log(message == null ? "NULL" : message.toString());
+        }
+    }
+
+    public void fatal(Object message, Throwable t) {
+        if (isFatalEnabled()) {
+            mailet.log(message == null ? "NULL" : message.toString(), t);
+        }
+    }
+
+    public void info(Object message) {
+        if (isInfoEnabled()) {
+            mailet.log(message == null ? "NULL" : message.toString());
+        }
+    }
+
+    public void info(Object message, Throwable t) {
+        if (isInfoEnabled()) {
+            mailet.log(message == null ? "NULL" : message.toString(), t);
+        }
+    }
+
+    public boolean isDebugEnabled() {
+        return level <= DEBUG;
+    }
+
+    public boolean isErrorEnabled() {
+        return level <= ERROR;
+    }
+
+    public boolean isFatalEnabled() {
+        return level <= FATAL;
+    }
+
+    public boolean isInfoEnabled() {
+        return level <= INFO;
+    }
+
+    public boolean isTraceEnabled() {
+        return level <= TRACE;
+    }
+
+    public boolean isWarnEnabled() {
+        return level <= WARN;
+    }
+
+    public void trace(Object message) {
+        if (isTraceEnabled()) {
+            mailet.log(message == null ? "NULL" : message.toString());
+        }
+    }
+
+    public void trace(Object message, Throwable t) {
+        if (isTraceEnabled()) {
+            mailet.log(message == null ? "NULL" : message.toString(), t);
+        }
+    }
+
+    public void warn(Object message) {
+        if (isWarnEnabled()) {
+            mailet.log(message == null ? "NULL" : message.toString());
+        }
+    }
+
+    public void warn(Object message, Throwable t) {
+        if (isWarnEnabled()) {
+            mailet.log(message == null ? "NULL" : message.toString(), t);
+        }
+    }
+    
+    
+}
diff --git a/trunk/mailet/src/main/java/org/apache/jsieve/mailet/FileIntoAction.java b/trunk/mailet/src/main/java/org/apache/jsieve/mailet/FileIntoAction.java
new file mode 100644
index 0000000..45c4598
--- /dev/null
+++ b/trunk/mailet/src/main/java/org/apache/jsieve/mailet/FileIntoAction.java
@@ -0,0 +1,142 @@
+/****************************************************************
+ * 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.jsieve.mailet;
+
+import javax.mail.MessagingException;
+import javax.mail.internet.MimeMessage;
+
+import org.apache.commons.logging.Log;
+import org.apache.jsieve.mail.Action;
+import org.apache.jsieve.mail.ActionFileInto;
+import org.apache.mailet.Mail;
+import org.apache.mailet.MailAddress;
+
+/**
+ * Performs the filing of a mail into a specified destination. 
+ * <h4>Thread Safety</h4>
+ * <p>An instance maybe safe accessed concurrently by multiple threads.</p>
+ */
+public class FileIntoAction implements MailAction {
+    
+    private static final char HIERARCHY_DELIMITER = '.';
+
+    public void execute(Action action, Mail mail, ActionContext context) throws MessagingException {
+        if (action instanceof ActionFileInto) {
+            final ActionFileInto fileIntoAction = (ActionFileInto) action;
+            execute(fileIntoAction, mail, context);
+        }
+    }
+
+    /**
+     * <p>
+     * Executes the passed ActionFileInto.
+     * </p>
+     * 
+     * <p>
+     * This implementation accepts any destination with the root of <code>INBOX</code>.
+     * </p>
+     * 
+     * <p>
+     * As the current POP3 server does not support sub-folders, the mail is
+     * stored in the INBOX for the recipient of the mail and the full intended
+     * destination added as a prefix to the message's subject.
+     * </p>
+     * 
+     * <p>
+     * When IMAP support is added to James, it will be possible to support
+     * sub-folders of <code>INBOX</code> fully.
+     * </p>
+     * 
+     * @param anAction
+     * @param aMail
+     * @param context not null
+     * @throws MessagingException
+     */
+    @SuppressWarnings("deprecation")
+    public void execute(ActionFileInto anAction, Mail aMail, final ActionContext context) throws MessagingException
+    {
+        String destinationMailbox = anAction.getDestination();
+        MailAddress recipient;
+        boolean delivered = false;
+        try
+        {
+            recipient = ActionUtils.getSoleRecipient(aMail);
+            MimeMessage localMessage = createMimeMessage(aMail, recipient);
+            
+            if (!(destinationMailbox.length() > 0 
+                    && destinationMailbox.charAt(0) == HIERARCHY_DELIMITER)) {
+                destinationMailbox =  HIERARCHY_DELIMITER + destinationMailbox;
+            }
+            
+            final String mailbox = destinationMailbox.replace(HIERARCHY_DELIMITER, '/');
+            final String host;
+            if (mailbox.charAt(0) == '/') {
+                host = "@localhost";
+            } else {
+                host = "@localhost/";
+            }
+            final String url = "mailbox://" + recipient.getUser() + host + mailbox;
+            //TODO: copying this message so many times seems a waste
+            context.post(url, localMessage);
+            delivered = true;
+        }
+        catch (MessagingException ex)
+        {
+            final Log log = context.getLog();
+            if (log.isDebugEnabled()) {
+                log.debug("Error while storing mail into. "+destinationMailbox, ex);
+            }
+            throw ex;
+        }
+        finally
+        {
+            // Ensure the mail is always ghosted
+            aMail.setState(Mail.GHOST);
+        }
+        if (delivered)
+        {
+            final Log log = context.getLog();
+            if (log.isDebugEnabled()) {
+                log.debug("Filed Message ID: "
+                    + aMail.getMessage().getMessageID()
+                    + " into destination: \""
+                    + destinationMailbox + "\"");
+            }
+        }
+    }
+    
+    private static MimeMessage createMimeMessage(Mail aMail, MailAddress recipient) throws MessagingException {
+        // Adapted from LocalDelivery Mailet
+        // Add qmail's de facto standard Delivered-To header
+        MimeMessage localMessage = new MimeMessage(aMail.getMessage())
+        {
+            protected void updateHeaders() throws MessagingException
+            {
+                if (getMessageID() == null)
+                    super.updateHeaders();
+                else
+                    modified = false;
+            }
+        };
+        localMessage.addHeader("Delivered-To", recipient.toString());
+
+        localMessage.saveChanges();
+        return localMessage;
+    }
+}
diff --git a/trunk/mailet/src/main/java/org/apache/jsieve/mailet/KeepAction.java b/trunk/mailet/src/main/java/org/apache/jsieve/mailet/KeepAction.java
new file mode 100644
index 0000000..bad42b9
--- /dev/null
+++ b/trunk/mailet/src/main/java/org/apache/jsieve/mailet/KeepAction.java
@@ -0,0 +1,65 @@
+/****************************************************************
+ * 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.jsieve.mailet;
+
+import javax.mail.MessagingException;
+
+import org.apache.jsieve.mail.Action;
+import org.apache.jsieve.mail.ActionFileInto;
+import org.apache.jsieve.mail.ActionKeep;
+import org.apache.mailet.Mail;
+
+/**
+ * Performs the filing of a mail into the inbox. 
+ * <h4>Thread Safety</h4>
+ * <p>An instance maybe safe accessed concurrently by multiple threads.</p>
+ */
+public class KeepAction extends FileIntoAction implements MailAction {
+    
+    private static final String INBOX = "INBOX";
+
+    public void execute(Action action, Mail mail, ActionContext context)
+            throws MessagingException {
+        if (action instanceof ActionKeep) {
+            final ActionKeep actionKeep = (ActionKeep) action;
+            execute(actionKeep, mail, context);
+        }
+    }
+
+    /**
+     * <p>
+     * Executes the passed ActionKeep.
+     * </p>
+     * 
+     * <p>
+     * In this implementation, "keep" is equivalent to "fileinto" with a
+     * destination of "INBOX".
+     * </p>
+     * 
+     * @param anAction not null
+     * @param aMail not null
+     * @param context not null
+     * @throws MessagingException
+     */
+    public void execute(ActionKeep anAction, Mail aMail, ActionContext context) throws MessagingException
+    {
+        final ActionFileInto action = new ActionFileInto(INBOX);
+        execute(action, aMail, context);
+    }
+}
diff --git a/trunk/mailet/src/main/java/org/apache/jsieve/mailet/MailAction.java b/trunk/mailet/src/main/java/org/apache/jsieve/mailet/MailAction.java
new file mode 100644
index 0000000..1255dc5
--- /dev/null
+++ b/trunk/mailet/src/main/java/org/apache/jsieve/mailet/MailAction.java
@@ -0,0 +1,40 @@
+/****************************************************************
+ * 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.jsieve.mailet;
+
+import javax.mail.MessagingException;
+
+import org.apache.jsieve.mail.Action;
+import org.apache.mailet.Mail;
+
+/**
+ * Executes a Sieve action.
+ * Implementations may be accessed concurrently by multiple threads.
+ */
+public interface MailAction {
+    
+    /**
+     * Executes the given action.
+     * @param action not null
+     * @param mail not null
+     * @param context not null
+     * @throws MessagingException when action cannot be executed
+     */
+    public void execute(final Action action, final Mail mail, final ActionContext context) throws MessagingException;
+}
diff --git a/trunk/mailet/src/main/java/org/apache/jsieve/mailet/Poster.java b/trunk/mailet/src/main/java/org/apache/jsieve/mailet/Poster.java
new file mode 100644
index 0000000..079cf02
--- /dev/null
+++ b/trunk/mailet/src/main/java/org/apache/jsieve/mailet/Poster.java
@@ -0,0 +1,40 @@
+/****************************************************************
+ * 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.jsieve.mailet;
+
+import javax.mail.MessagingException;
+import javax.mail.internet.MimeMessage;
+
+/**
+ * Experimental interface.
+ */
+public interface Poster {
+    
+    /**
+     * Experimental mail delivery. 
+     * POST verb indicate that mail should be attached to the collection
+     * indicated by the given URI.
+     * 
+     * @param uri indicates the destination to which the mail to added. ATM 
+     * the value should be mailbox://<user>@localhost/<mailbox-path>
+     * @param mail not null
+     */
+    public void post(String uri, MimeMessage mail) throws MessagingException;
+}
diff --git a/trunk/mailet/src/main/java/org/apache/jsieve/mailet/RedirectAction.java b/trunk/mailet/src/main/java/org/apache/jsieve/mailet/RedirectAction.java
new file mode 100644
index 0000000..b68ed83
--- /dev/null
+++ b/trunk/mailet/src/main/java/org/apache/jsieve/mailet/RedirectAction.java
@@ -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.jsieve.mailet;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+import javax.mail.MessagingException;
+import javax.mail.internet.InternetAddress;
+
+import org.apache.commons.logging.Log;
+import org.apache.jsieve.mail.Action;
+import org.apache.jsieve.mail.ActionRedirect;
+import org.apache.mailet.Mail;
+import org.apache.mailet.MailAddress;
+
+/**
+ * Performs the redirection of a mail. 
+ * <h4>Thread Safety</h4>
+ * <p>An instance maybe safe accessed concurrently by multiple threads.</p>
+ */
+public class RedirectAction implements MailAction {
+
+    public void execute(Action action, Mail mail, ActionContext context)
+            throws MessagingException {
+        if (action instanceof ActionRedirect) {
+            final ActionRedirect actionRedirect = (ActionRedirect) action;
+            execute(actionRedirect, mail, context);
+        }
+
+    }
+
+    /**
+     * Method execute executes the passed ActionRedirect.
+     * 
+     * @param anAction not nul
+     * @param aMail not null
+     * @param context not null
+     * @throws MessagingException
+     */
+    public void execute(ActionRedirect anAction, Mail aMail, ActionContext context) throws MessagingException
+    {
+        ActionUtils.detectAndHandleLocalLooping(aMail, context, "redirect");
+        Collection<MailAddress> recipients = new ArrayList<MailAddress>(1);
+        recipients.add(new MailAddress(new InternetAddress(anAction.getAddress())));
+        MailAddress sender = aMail.getSender();
+        context.post(sender, recipients, aMail.getMessage());
+        aMail.setState(Mail.GHOST);
+        Log log = context.getLog();
+        if (log.isDebugEnabled()) {
+            log.debug("Redirected Message ID: "
+                + aMail.getMessage().getMessageID() + " to \""
+                + anAction.getAddress() + "\"");
+        }
+    }
+}
diff --git a/trunk/mailet/src/main/java/org/apache/jsieve/mailet/RejectAction.java b/trunk/mailet/src/main/java/org/apache/jsieve/mailet/RejectAction.java
new file mode 100644
index 0000000..66b1097
--- /dev/null
+++ b/trunk/mailet/src/main/java/org/apache/jsieve/mailet/RejectAction.java
@@ -0,0 +1,145 @@
+/****************************************************************
+ * 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.jsieve.mailet;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Collection;
+
+import javax.mail.Address;
+import javax.mail.MessagingException;
+import javax.mail.internet.InternetAddress;
+import javax.mail.internet.MimeMessage;
+import javax.mail.internet.MimeMultipart;
+
+import org.apache.jsieve.mail.Action;
+import org.apache.jsieve.mail.ActionReject;
+import org.apache.mailet.Mail;
+import org.apache.mailet.MailAddress;
+import org.apache.jsieve.mailet.mdn.ActionModeAutomatic;
+import org.apache.jsieve.mailet.mdn.Disposition;
+import org.apache.jsieve.mailet.mdn.DispositionModifier;
+import org.apache.jsieve.mailet.mdn.MDNFactory;
+import org.apache.jsieve.mailet.mdn.ModifierError;
+import org.apache.jsieve.mailet.mdn.SendingModeAutomatic;
+import org.apache.jsieve.mailet.mdn.TypeDeleted;
+
+/**
+ * Performs the rejection of a mail, with a reply to the sender. 
+ * <h4>Thread Safety</h4>
+ * <p>An instance maybe safe accessed concurrently by multiple threads.</p>
+ */
+public class RejectAction implements MailAction {
+
+    public void execute(Action action, Mail mail, ActionContext context)
+            throws MessagingException {
+        if (action instanceof ActionReject) {
+            final ActionReject actionReject = (ActionReject) action;
+            execute(actionReject, mail, context);
+        }
+
+    }
+
+    /**
+     * <p>
+     * Method execute executes the passed ActionReject. It sends an RFC 2098
+     * compliant reject MDN back to the sender.
+     * </p>
+     * <p>
+     * NOTE: The Mimecontent type should be 'report', but as we do not yet have
+     * a DataHandler for this yet, its currently 'text'!
+     * 
+     * @param anAction not null
+     * @param aMail not null
+     * @param context not null
+     * @throws MessagingException
+     */
+    public void execute(ActionReject anAction, Mail aMail, ActionContext context) throws MessagingException
+    {
+        ActionUtils.detectAndHandleLocalLooping(aMail, context, "reject");
+
+        // Create the MDN part
+        StringBuilder humanText = new StringBuilder(128);
+        humanText.append("This message was refused by the recipient's mail filtering program.");
+        humanText.append("\r\n");
+        humanText.append("The reason given was:");
+        humanText.append("\r\n");
+        humanText.append("\r\n");
+        humanText.append(anAction.getMessage());
+
+        String reporting_UA_name = null;
+        try
+        {
+            reporting_UA_name = InetAddress.getLocalHost()
+                    .getCanonicalHostName();
+        }
+        catch (UnknownHostException ex)
+        {
+            reporting_UA_name = "localhost";
+        }
+
+        String reporting_UA_product = context.getServerInfo();
+
+        String[] originalRecipients = aMail.getMessage().getHeader(
+                "Original-Recipient");
+        String original_recipient = null;
+        if (null != originalRecipients && originalRecipients.length > 0)
+        {
+            original_recipient = originalRecipients[0];
+        }
+
+        MailAddress soleRecipient = ActionUtils.getSoleRecipient(aMail);
+        String final_recipient = soleRecipient.toString();
+
+        String original_message_id = aMail.getMessage().getMessageID();
+
+        DispositionModifier modifiers[] = {new ModifierError()};
+        Disposition disposition = new Disposition(new ActionModeAutomatic(),
+                new SendingModeAutomatic(), new TypeDeleted(), modifiers);
+
+        MimeMultipart multiPart = MDNFactory.create(humanText.toString(),
+                reporting_UA_name, reporting_UA_product, original_recipient,
+                final_recipient, original_message_id, disposition);
+
+        // Send the message
+        MimeMessage reply = (MimeMessage) aMail.getMessage().reply(false);
+        reply.setFrom(soleRecipient.toInternetAddress());
+        reply.setContent(multiPart);
+        reply.saveChanges();
+        Address[] recipientAddresses = reply.getAllRecipients();
+        if (null != recipientAddresses)
+        {
+            Collection<MailAddress> recipients = new ArrayList<MailAddress>(recipientAddresses.length);
+            for (int i = 0; i < recipientAddresses.length; i++)
+            {
+                recipients.add(new MailAddress(
+                        (InternetAddress) recipientAddresses[i]));
+            }
+            context.post(null, recipients, reply);
+        }
+        else
+        {
+            context.getLog().info("Unable to send reject MDN. Could not determine the recipient.");
+        }
+        // Ghost the original mail
+        aMail.setState(Mail.GHOST);
+    }
+
+}
diff --git a/trunk/mailet/src/main/java/org/apache/jsieve/mailet/ResourceLocator.java b/trunk/mailet/src/main/java/org/apache/jsieve/mailet/ResourceLocator.java
new file mode 100644
index 0000000..4c2ad08
--- /dev/null
+++ b/trunk/mailet/src/main/java/org/apache/jsieve/mailet/ResourceLocator.java
@@ -0,0 +1,66 @@
+/****************************************************************
+ * 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.jsieve.mailet;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * <p>Experimental API locates resources. 
+ * Used to load Sieve scripts. The base for relative URLs
+ * should be taken to be the root of the James configuration.
+ * </p><p>
+ * Required schemas:
+ * </p>
+ * <ul>
+ * <li><strong>User sieve scripts</strong> - the relative URL scheme 
+ * <code>//<em>user</em>@<em>host</em>/<em>sieve</em> will be used to
+ * obtain the script
+ * </ul>
+ * <p>
+ * The advantage of using <code>URI</code>s 
+ * and verbs (for example <code>GET</code>, <code>POST</code>)
+ * are their uniformity. The same API can be used to interface radically
+ * different resource types and protocols. This allows concise, minimal,
+ * powerful APIs to be created. Their simplicity is easy to preserved 
+ * across versions. 
+ * </p><p>
+ * The disadvantage is that this free decouple means that there is 
+ * no gaurantee that the implementations decoupled by this interface
+ * actually support the same scheme. Issues will be caught only 
+ * at deployment and not at compile time.
+ * This places a larger burden on the deployer.
+ * </p><p>
+ * Either an understanding or a consistent URL mapping scheme may be 
+ * required. For example, <code>//john.smith@localhost/sieve</code>
+ * may need to be resolved to <code>../apps/james/var/sieve/john.smith@localhost.sieve</code>
+ * when using the file system to store scripts. Note that names <strong>MUST</strong>
+ * be normalised before resolving on a file system.
+ * </p>
+ */
+public interface ResourceLocator {
+    
+    /**
+     * GET verb locates and loads a resource. 
+     * @param uri identifies the Sieve script 
+     * @return not null
+     * @throws IOException when the resource cannot be located
+     */
+    public InputStream get(String uri) throws IOException;
+}
diff --git a/trunk/mailet/src/main/java/org/apache/jsieve/mailet/SieveMailAdapter.java b/trunk/mailet/src/main/java/org/apache/jsieve/mailet/SieveMailAdapter.java
new file mode 100644
index 0000000..116593d
--- /dev/null
+++ b/trunk/mailet/src/main/java/org/apache/jsieve/mailet/SieveMailAdapter.java
@@ -0,0 +1,439 @@
+/****************************************************************
+ * 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.jsieve.mailet;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.mail.Header;
+import javax.mail.MessagingException;
+import javax.mail.internet.MimeMessage;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.james.mime4j.field.address.AddressList;
+import org.apache.james.mime4j.field.address.Mailbox;
+import org.apache.james.mime4j.field.address.MailboxList;
+import org.apache.james.mime4j.field.address.parser.ParseException;
+import org.apache.jsieve.SieveContext;
+import org.apache.jsieve.exception.InternetAddressException;
+import org.apache.jsieve.exception.SieveException;
+import org.apache.jsieve.mail.Action;
+import org.apache.jsieve.mail.AddressImpl;
+import org.apache.jsieve.mail.MailAdapter;
+import org.apache.jsieve.mail.MailUtils;
+import org.apache.jsieve.mail.SieveMailException;
+import org.apache.jsieve.mail.optional.EnvelopeAccessors;
+import org.apache.mailet.Mail;
+import org.apache.mailet.MailAddress;
+import org.apache.mailet.MailetContext;
+/**
+ * <p>
+ * Class <code>SieveMailAdapter</code> implements a <code>MailAdapter</code>
+ * for use in a Mailet environment.
+ * </p>
+ */
+public class SieveMailAdapter implements MailAdapter, EnvelopeAccessors, ActionContext
+{
+    private static final Log LOG = LogFactory.getLog(SieveMailAdapter.class);
+    
+    private Log log = LOG;
+    
+    /**
+     * The Mail being adapted.
+     */
+    private Mail fieldMail;
+    /**
+     * The MailetContext.
+     */
+    private MailetContext fieldMailetContext;
+    /**
+     * List of Actions to perform.
+     */
+    private List<Action> fieldActions;
+    
+    private final ActionDispatcher dispatcher;
+    
+    private final Poster poster;
+
+    private String contentText;
+    
+    /**
+     * Constructor for SieveMailAdapter.
+     * 
+     * @param aMail
+     * @param aMailetContext
+     */
+    public SieveMailAdapter(final Mail aMail, final MailetContext aMailetContext, final ActionDispatcher dispatcher, final Poster poster)
+    {
+        this.poster = poster;
+        this.dispatcher = dispatcher;
+        setMail(aMail);
+        setMailetContext(aMailetContext);
+    }
+    
+  
+    public void setLog(Log log) {
+        this.log = log;
+    }
+
+    /**
+     * Returns the message.
+     * 
+     * @return MimeMessage
+     */
+    protected MimeMessage getMessage() throws MessagingException
+    {
+        return getMail().getMessage();
+    }
+    /**
+     * Returns the List of actions.
+     * 
+     * @return List
+     */
+    public List<Action> getActions()
+    {
+        List<Action> actions = null;
+        if (null == (actions = getActionsBasic()))
+        {
+            updateActions();
+            return getActions();
+        }
+        return actions;
+    }
+    /**
+     * Returns a new List of actions.
+     * 
+     * @return List
+     */
+    protected List<Action> computeActions()
+    {
+        return new ArrayList<Action>();
+    }
+    /**
+     * Returns the List of actions.
+     * 
+     * @return List
+     */
+    private List<Action> getActionsBasic()
+    {
+        return fieldActions;
+    }
+    /**
+     * Adds an Action.
+     * 
+     * @param action The action to set
+     */
+    public void addAction(Action action)
+    {
+        getActions().add(action);
+    }
+    /**
+     * @see org.apache.jsieve.mail.MailAdapter#executeActions()
+     */
+    public void executeActions() throws SieveException
+    {
+        final List<Action> actions = getActions();
+        for (final Action action: actions) {
+            getMailetContext().log("Executing action: " + action.toString());
+            try
+            {
+                dispatcher.execute(action, getMail(), this);
+            }
+            catch (MessagingException e)
+            {
+                throw new SieveException(e);
+            }
+        }
+    }
+    /**
+     * Sets the actions.
+     * 
+     * @param actions The actions to set
+     */
+    protected void setActions(List<Action> actions)
+    {
+        fieldActions = actions;
+    }
+    
+    /**
+     * Updates the actions.
+     */
+    protected void updateActions()
+    {
+        setActions(computeActions());
+    }
+
+    /**
+     * @see org.apache.jsieve.mail.MailAdapter#getHeader(String)
+     */
+    public List<String> getHeader(String name) throws SieveMailException
+    {
+        try
+        {
+            String[] headers = getMessage().getHeader(name);
+            return (headers == null ? new ArrayList<String>(0) : Arrays.asList(headers));
+        }
+        catch (MessagingException ex)
+        {
+            throw new SieveMailException(ex);
+        }
+    }
+    
+    /**
+     * @see org.apache.jsieve.mail.MailAdapter#getHeaderNames()
+     */
+    public List<String> getHeaderNames() throws SieveMailException
+    {
+        Set<String> headerNames = new HashSet<String>();
+        try
+        {
+            Enumeration allHeaders = getMessage().getAllHeaders();
+            while (allHeaders.hasMoreElements())
+            {
+                headerNames.add(((Header) allHeaders.nextElement()).getName());
+            }
+            return new ArrayList<String>(headerNames);
+        }
+        catch (MessagingException ex)
+        {
+            throw new SieveMailException(ex);
+        }
+    }
+    
+    /**
+     * @see org.apache.jsieve.mail.MailAdapter#getMatchingHeader(String)
+     */
+    public List<String> getMatchingHeader(String name) throws SieveMailException
+    {
+        return MailUtils.getMatchingHeader(this, name);
+    }
+    
+    /**
+     * @see org.apache.jsieve.mail.MailAdapter#getSize()
+     */
+    public int getSize() throws SieveMailException
+    {
+        try
+        {
+            return getMessage().getSize();
+        }
+        catch (MessagingException ex)
+        {
+            throw new SieveMailException(ex);
+        }
+    }
+    
+    /**
+     * Method getEnvelopes.
+     * 
+     * @return Map
+     */
+    protected Map<String, String> getEnvelopes()
+    {
+        Map<String, String> envelopes = new HashMap<String, String>(2);
+        if (null != getEnvelopeFrom())
+            envelopes.put("From", getEnvelopeFrom());
+        if (null != getEnvelopeTo())
+            envelopes.put("To", getEnvelopeTo());
+        return envelopes;
+    }
+    /**
+     * @see org.apache.jsieve.mail.optional.EnvelopeAccessors#getEnvelope(String)
+     */
+    public List<String> getEnvelope(String name) throws SieveMailException
+    {
+        List<String> values = new ArrayList<String>(1);
+        String value = getEnvelopes().get(name);
+        if (null != value)
+            values.add(value);
+        return values;
+    }
+    
+    /**
+     * @see org.apache.jsieve.mail.optional.EnvelopeAccessors#getEnvelopeNames()
+     */
+    public List<String> getEnvelopeNames() throws SieveMailException
+    {
+        return new ArrayList<String>(getEnvelopes().keySet());
+    }
+    
+    /**
+     * @see org.apache.jsieve.mail.optional.EnvelopeAccessors#getMatchingEnvelope(String)
+     */
+    public List<String> getMatchingEnvelope(String name) throws SieveMailException
+    {
+        final List<String> matchedEnvelopeValues = new ArrayList<String>(32);
+        for (String envelopeName: getEnvelopeNames()) {
+            if (envelopeName.trim().equalsIgnoreCase(name))
+                matchedEnvelopeValues.addAll(getEnvelope(envelopeName));
+        }
+        return matchedEnvelopeValues;
+    }
+    
+    /**
+     * Returns the from.
+     * 
+     * @return String
+     */
+    public String getEnvelopeFrom()
+    {
+        MailAddress sender = getMail().getSender(); 
+        return (null == sender ? "" : sender.toString());
+    }
+    
+    /**
+     * Returns the sole recipient or null if there isn't one.
+     * 
+     * @return String
+     */
+    public String getEnvelopeTo()
+    {
+        String recipient = null;
+        Iterator recipientIter = getMail().getRecipients().iterator();
+        if (recipientIter.hasNext())
+            recipient = (String) recipientIter.next().toString();
+        return recipient;
+    }
+    
+    /**
+     * Returns the mail.
+     * 
+     * @return Mail
+     */
+    public Mail getMail()
+    {
+        return fieldMail;
+    }
+    
+    /**
+     * Sets the mail.
+     * 
+     * @param mail The mail to set
+     */
+    protected void setMail(Mail mail)
+    {
+        fieldMail = mail;
+        contentText = null;
+    }
+    
+    /**
+     * Returns the mailetContext.
+     * 
+     * @return MailetContext
+     */
+    public MailetContext getMailetContext()
+    {
+        return fieldMailetContext;
+    }
+    
+    /**
+     * Sets the mailetContext.
+     * 
+     * @param mailetContext The mailetContext to set
+     */
+    protected void setMailetContext(MailetContext mailetContext)
+    {
+        fieldMailetContext = mailetContext;
+    }
+    
+    /**
+     * @see java.lang.Object#toString()
+     */
+    public String toString()
+    {
+        String messageID = null;
+        try
+        {
+            messageID = getMail().getMessage().getMessageID();
+        }
+        catch (MessagingException e)
+        {
+            messageID = "<" + e.getMessage() + ">";
+        }
+        return getClass().getName() + " Envelope From: "
+                + (null == getEnvelopeFrom() ? "null" : getEnvelopeFrom())
+                + " Envelope To: "
+                + (null == getEnvelopeTo() ? "null" : getEnvelopeTo())
+                + " Message ID: " + (null == messageID ? "null" : messageID);
+    }
+    
+    public String getContentType() throws SieveMailException {
+        try {
+            return getMessage().getContentType();
+        } catch (MessagingException e) {
+            throw new SieveMailException(e);
+        }
+    }
+    
+    public Address[] parseAddresses(String arg) throws SieveMailException, InternetAddressException {
+        try {
+            final MailboxList list = AddressList.parse(arg).flatten();
+            final int size = list.size();
+            final Address[] results = new Address[size];
+            for (int i=0;i<size;i++) {
+                final Mailbox mailbox = list.get(i);
+                results[i] = new AddressImpl(mailbox.getLocalPart(), mailbox.getDomain());
+            }
+            return null;
+        } catch (ParseException e) {
+            throw new InternetAddressException(e);
+        }
+    }
+
+    public Log getLog() {
+        return log;
+    }
+    
+    public String getServerInfo() {
+        return getMailetContext().getServerInfo();
+    }
+    public void post(String uri, MimeMessage mail) throws MessagingException {
+        poster.post(uri, mail);
+    }
+    
+    public void post(MailAddress sender, Collection recipients, MimeMessage mail) throws MessagingException {
+        getMailetContext().sendMail(sender, recipients, mail);
+    }
+
+
+    public boolean isInBodyText(String phraseCaseInsensitive) throws SieveMailException {
+        try {
+            if (contentText == null) {
+                contentText = getMessage().getContent().toString().toLowerCase();
+            }
+            return contentText.contains(phraseCaseInsensitive);
+        } catch (MessagingException e) {
+            throw new SieveMailException(e);
+        } catch (IOException e) {
+            throw new SieveMailException(e);
+        }
+    }
+
+    public void setContext(SieveContext context) {}
+}
diff --git a/trunk/mailet/src/main/java/org/apache/jsieve/mailet/SieveMailboxMailet.java b/trunk/mailet/src/main/java/org/apache/jsieve/mailet/SieveMailboxMailet.java
new file mode 100644
index 0000000..4ffa52b
--- /dev/null
+++ b/trunk/mailet/src/main/java/org/apache/jsieve/mailet/SieveMailboxMailet.java
@@ -0,0 +1,401 @@
+/****************************************************************
+ * 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.jsieve.mailet;
+
+import java.io.InputStream;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.Vector;
+
+import javax.mail.Header;
+import javax.mail.MessagingException;
+import javax.mail.internet.InternetHeaders;
+import javax.mail.internet.MimeMessage;
+
+import org.apache.commons.logging.Log;
+import org.apache.jsieve.ConfigurationManager;
+import org.apache.jsieve.SieveConfigurationException;
+import org.apache.jsieve.SieveFactory;
+import org.apache.mailet.Mail;
+import org.apache.mailet.MailAddress;
+import org.apache.mailet.MailetConfig;
+import org.apache.mailet.MailetException;
+import org.apache.mailet.base.GenericMailet;
+import org.apache.mailet.base.RFC2822Headers;
+
+/**
+ * <p>Executes a <a href='http://www.rfc-editor.org/rfc/rfc3028.txt'>Sieve</a>
+ * script against incoming mail. The script applied is based on the recipient.</p>
+ * <h4>Init Parameters</h4>
+ * <table>
+ * <thead><tr><th>Name</th><th>Required</th><th>Values</th><th>Role</th></thead>
+ * <tr><td>verbose</td><td>No - defaults to false</td><td>true (ignoring case) to enable, otherwise disable</td>
+ * <td>
+ * Enables verbose logging.
+ * </td></tr>
+ * </table>
+ */
+public class SieveMailboxMailet extends GenericMailet {
+
+    /**
+     * The delivery header
+     */
+    private String deliveryHeader;
+
+    /**
+     * resetReturnPath
+     */
+    private boolean resetReturnPath;
+    /** Experimental */
+    private Poster poster;
+    /** Experimental */
+    private ResourceLocator locator;
+    
+    /** Indicates whether this mailet should log verbosely */
+    private boolean verbose = false;
+    
+    private boolean consume = true;
+    /** Indicates whether this mailet should log minimal information */
+    private boolean quiet = true;
+
+    private SieveFactory factory;
+
+    private ActionDispatcher actionDispatcher;
+
+    private Log log;
+
+    /**
+     * For SDI
+     */
+    public SieveMailboxMailet() {}
+    
+    /**
+     * CDI
+     * @param poster not null
+     */
+    public SieveMailboxMailet(Poster poster, ResourceLocator locator) {
+        this();
+        this.poster = poster;
+        this.locator = locator;
+    }
+
+    
+    public ResourceLocator getLocator() {
+        return locator;
+    }
+
+    /**
+     * For SDI
+     * @param locator not null
+     */
+    public void setLocator(ResourceLocator locator) {
+        this.locator = locator;
+    }
+
+    public Poster getPoster() {
+        return poster;
+    }
+    
+    /**
+     * For SDI
+     * @param poster not null
+     */
+    public void setPoster(Poster poster) {
+        this.poster = poster;
+    }
+
+    /**
+     * Is this mailet GHOSTing all mail it processes?
+     * @return true when mailet consumes all mail, false otherwise
+     */
+    public boolean isConsume() {
+        return consume;
+    }
+
+    /**
+     * Sets whether this mailet should GHOST all mail.
+     * @param consume true when the mailet should consume all mail, 
+     * false otherwise
+     */
+    public void setConsume(boolean consume) {
+        this.consume = consume;
+    }
+
+    /**
+     * Is this mailet logging verbosely?
+     * This property is set by init parameters.
+     * @return true if logging should be verbose, false otherwise
+     */
+    public boolean isVerbose() {
+        return verbose;
+    }
+
+
+    /**
+     * Sets whether logging should be verbose for this mailet.
+     * This property is set by init parameters.
+     * This setting overrides {@link #isQuiet()}.
+     * @param verbose true when logging should be verbose,
+     * false otherwise
+     */
+    public void setVerbose(boolean verbose) {
+        this.verbose = verbose;
+    }
+
+    /**
+     * Is the logging for this mailet set to minimal?
+     * @return true
+     */
+    public boolean isQuiet() {
+        return quiet;
+    }
+
+    /**
+     * Sets the logging for this mailet to minimal.
+     * This is overriden by {@link #setVerbose(boolean)}.
+     * @param quiet true for minimal logging, false otherwise
+     */
+    public void setQuiet(boolean quiet) {
+        this.quiet = quiet;
+    }
+    
+   
+    /**
+     * Is informational logging turned on? 
+     * @return true when minimal logging is off,
+     * false when logging is minimal
+     */
+    public boolean isInfoLoggingOn() {
+        return verbose || !quiet;
+    }
+
+    @Override
+    public void init(MailetConfig config) throws MessagingException {
+        
+        super.init(config);
+
+        try {
+            final ConfigurationManager configurationManager = new ConfigurationManager();
+            final int logLevel;
+            if (verbose) {
+                logLevel = CommonsLoggingAdapter.TRACE;
+            } else if (quiet) {
+                logLevel = CommonsLoggingAdapter.FATAL;
+            } else {
+                logLevel = CommonsLoggingAdapter.WARN;
+            }
+            log = new CommonsLoggingAdapter(this, logLevel);
+            configurationManager.setLog(log);
+            factory = configurationManager.build();
+        } catch (SieveConfigurationException e) {
+            throw new MessagingException("Failed to load standard Sieve configuration.", e);
+        }
+    }
+
+    /**
+     * Delivers a mail to a local mailbox.
+     * 
+     * @param mail
+     *            the mail being processed
+     * 
+     * @throws MessagingException
+     *             if an error occurs while storing the mail
+     */
+    @SuppressWarnings("unchecked")
+    @Override
+    public void service(Mail mail) throws MessagingException {
+        Collection<MailAddress> recipients = mail.getRecipients();
+        Collection<MailAddress> errors = new Vector<MailAddress>();
+
+        MimeMessage message = null;
+        if (deliveryHeader != null || resetReturnPath) {
+            message = mail.getMessage();
+        }
+
+        if (resetReturnPath) {
+            // Set Return-Path and remove all other Return-Path headers from the
+            // message
+            // This only works because there is a placeholder inserted by
+            // MimeMessageWrapper
+            message.setHeader(RFC2822Headers.RETURN_PATH,
+                    (mail.getSender() == null ? "<>" : "<" + mail.getSender()
+                            + ">"));
+        }
+
+        Enumeration headers;
+        InternetHeaders deliveredTo = new InternetHeaders();
+        if (deliveryHeader != null) {
+            // Copy any Delivered-To headers from the message
+            headers = message
+                    .getMatchingHeaders(new String[] { deliveryHeader });
+            while (headers.hasMoreElements()) {
+                Header header = (Header) headers.nextElement();
+                deliveredTo.addHeader(header.getName(), header.getValue());
+            }
+        }
+
+        for (Iterator<MailAddress> i = recipients.iterator(); i.hasNext();) {
+            MailAddress recipient = i.next();
+            try {
+                if (deliveryHeader != null) {
+                    // Add qmail's de facto standard Delivered-To header
+                    message.addHeader(deliveryHeader, recipient.toString());
+                }
+
+                storeMail(mail.getSender(), recipient, mail);
+
+                if (deliveryHeader != null) {
+                    if (i.hasNext()) {
+                        // Remove headers but leave all placeholders
+                        message.removeHeader(deliveryHeader);
+                        headers = deliveredTo.getAllHeaders();
+                        // And restore any original Delivered-To headers
+                        while (headers.hasMoreElements()) {
+                            Header header = (Header) headers.nextElement();
+                            message.addHeader(header.getName(), header
+                                    .getValue());
+                        }
+                    }
+                }
+            } catch (Exception ex) {
+                log("Error while storing mail.", ex);
+                errors.add(recipient);
+            }
+        }
+
+        if (!errors.isEmpty()) {
+            // If there were errors, we redirect the email to the ERROR
+            // processor.
+            // In order for this server to meet the requirements of the SMTP
+            // specification, mails on the ERROR processor must be returned to
+            // the sender. Note that this email doesn't include any details
+            // regarding the details of the failure(s).
+            // In the future we may wish to address this.
+            getMailetContext().sendMail(mail.getSender(), errors,
+                    mail.getMessage(), Mail.ERROR);
+        }
+        if (consume) {
+            // Consume this message
+            mail.setState(Mail.GHOST);
+        }
+    }
+
+    /**
+     * Return a string describing this mailet.
+     * 
+     * @return a string describing this mailet
+     */
+    @Override
+    public String getMailetInfo() {
+        return "Sieve Mailbox Mailet";
+    }
+
+    /**
+     * 
+     * @param sender
+     * @param recipient
+     * @param mail
+     * @throws MessagingException
+     */
+    @SuppressWarnings("deprecation")
+    public void storeMail(MailAddress sender, MailAddress recipient,
+            Mail mail) throws MessagingException {
+        if (recipient == null) {
+            throw new IllegalArgumentException(
+                    "Recipient for mail to be spooled cannot be null.");
+        }
+        if (mail.getMessage() == null) {
+            throw new IllegalArgumentException(
+                    "Mail message to be spooled cannot be null.");
+        }
+        
+        sieveMessage(recipient, mail);
+ 
+    }
+    
+    void sieveMessage(MailAddress recpient, Mail aMail) throws MessagingException {
+    	String username = getUsername(recpient);
+        // Evaluate the script against the mail
+        String relativeUri = "//" + username +"/sieve"; 
+        try
+        {
+            final InputStream ins = locator.get(relativeUri);
+            
+            SieveMailAdapter aMailAdapter = new SieveMailAdapter(aMail,
+                    getMailetContext(), actionDispatcher, poster);
+            aMailAdapter.setLog(log);
+            // This logging operation is potentially costly
+            if (verbose) {
+                log("Evaluating " + aMailAdapter.toString() + "against \""
+                    + relativeUri + "\"");
+            }
+            factory.evaluate(aMailAdapter, factory.parse(ins));
+        }
+        catch (Exception ex)
+        {
+            //
+            // SLIEVE is a mail filtering protocol.
+            // Rejecting the mail because it cannot be filtered
+            // seems very unfriendly.
+            // So just log and store in INBOX.
+            //
+            if (isInfoLoggingOn()) {
+                log("Cannot evaluate Sieve script. Storing mail in user INBOX.", ex);
+            }
+            storeMessageInbox(username, aMail);
+        }
+    }
+    
+    void storeMessageInbox(String username, Mail mail) throws MessagingException {
+        String url = "mailbox://" + username + "/";
+        poster.post(url, mail.getMessage());
+    }
+
+    /**
+     * @see org.apache.mailet.base.GenericMailet#init()
+     */
+    @Override
+    public void init() throws MessagingException {
+        super.init();
+        if (poster == null || locator == null) {
+            throw new MailetException("Not initialised. Please ensure that the mailet container supports either" +
+                    " setter or constructor injection");
+        }
+        
+        this.deliveryHeader = getInitParameter("addDeliveryHeader");
+        this.resetReturnPath = getInitParameter("resetReturnPath", true);
+        this.consume = getInitParameter("consume", true);
+        this.verbose = getInitParameter("verbose", false);
+        this.quiet = getInitParameter("quiet", false);
+        
+        actionDispatcher = new ActionDispatcher();
+    }
+    
+    /**
+     * Return the username to use for sieve processing for the given MailAddress
+     * 
+     * @param m
+     * @return username
+     */
+    protected String getUsername(MailAddress m) {
+    	return m.getLocalPart() + "@localhost";
+    }
+}
diff --git a/trunk/mailet/src/main/java/org/apache/jsieve/mailet/mdn/ActionModeAutomatic.java b/trunk/mailet/src/main/java/org/apache/jsieve/mailet/mdn/ActionModeAutomatic.java
new file mode 100644
index 0000000..64cab9b
--- /dev/null
+++ b/trunk/mailet/src/main/java/org/apache/jsieve/mailet/mdn/ActionModeAutomatic.java
@@ -0,0 +1,47 @@
+/****************************************************************
+ * 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.jsieve.mailet.mdn;
+
+
+/**
+ * Class <code>ActionModeAutomatic</code>
+ */
+public class ActionModeAutomatic extends Object
+        implements
+            DispositionActionMode
+{
+
+    /**
+     * Default Constructor
+     */
+    public ActionModeAutomatic()
+    {
+        super();
+    }
+    
+    /**
+     * @see java.lang.Object#toString()
+     */
+    public String toString()
+    {
+        return "automatic-action";
+    }         
+
+}
diff --git a/trunk/mailet/src/main/java/org/apache/jsieve/mailet/mdn/ActionModeManual.java b/trunk/mailet/src/main/java/org/apache/jsieve/mailet/mdn/ActionModeManual.java
new file mode 100644
index 0000000..79616d8
--- /dev/null
+++ b/trunk/mailet/src/main/java/org/apache/jsieve/mailet/mdn/ActionModeManual.java
@@ -0,0 +1,47 @@
+/****************************************************************
+ * 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.jsieve.mailet.mdn;
+
+
+/**
+ * Class <code>ActionModeManual</code>
+ */
+public class ActionModeManual
+        implements
+            DispositionActionMode
+{
+
+    /**
+     * Default Constructor
+     */
+    public ActionModeManual()
+    {
+        super();
+    }
+    
+    /**
+     * @see java.lang.Object#toString()
+     */
+    public String toString()
+    {
+        return "manual-action";
+    }         
+
+}
diff --git a/trunk/mailet/src/main/java/org/apache/jsieve/mailet/mdn/Disposition.java b/trunk/mailet/src/main/java/org/apache/jsieve/mailet/mdn/Disposition.java
new file mode 100644
index 0000000..5a39e01
--- /dev/null
+++ b/trunk/mailet/src/main/java/org/apache/jsieve/mailet/mdn/Disposition.java
@@ -0,0 +1,176 @@
+/****************************************************************
+ * 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.jsieve.mailet.mdn;
+
+/**
+ * Class <code>Disposition</code> encapsulating
+ * disposition information as defined by RFC 2298.
+ */
+public class Disposition
+{
+    private DispositionActionMode fieldActionMode;
+    private DispositionSendingMode fieldSendingMode;
+    private DispositionType fieldDispositionType;
+    private DispositionModifier[] fieldDispositionModifiers;
+
+    /**
+     * Default Construcor
+     */
+    private Disposition()
+    {
+        super();
+    }
+
+    /**
+     * Constructor.
+     * 
+     * @param actionMode
+     * @param sendingMode
+     * @param type
+     */
+    public Disposition(DispositionActionMode actionMode, DispositionSendingMode sendingMode, DispositionType type)
+    {
+        this();
+        setActionMode(actionMode);
+        setSendingMode(sendingMode);
+        setDispositionType(type);
+    }
+
+    /**
+     * Constructor.
+     * 
+     * @param actionMode
+     * @param sendingMode
+     * @param type
+     * @param modifiers
+     */
+    public Disposition(DispositionActionMode actionMode, DispositionSendingMode sendingMode, DispositionType type,
+            DispositionModifier[] modifiers)
+    {
+        this(actionMode, sendingMode, type);
+        setDispositionModifiers(modifiers);
+    }
+
+    /**
+     * Answer the Disposition Mode.
+     * 
+     * @return Returns the dispostionMode.
+     */
+    protected DispositionActionMode getActionMode()
+    {
+        return fieldActionMode;
+    }
+
+    /**
+     * Set the Disposition Mode.
+     * 
+     * @param dispostionMode The dispostionMode to set.
+     */
+    protected void setActionMode(DispositionActionMode dispostionMode)
+    {
+        fieldActionMode = dispostionMode;
+    }
+
+    /**
+     * Answer the Disposition Modifiers.
+     * 
+     * @return Returns the dispostionModifiers.
+     */
+    protected DispositionModifier[] getDispositionModifiers()
+    {
+        return fieldDispositionModifiers;
+    }
+
+    /**
+     * Set the Disposition Modifiers.
+     * 
+     * @param dispostionModifiers The dispostionModifiers to set.
+     */
+    protected void setDispositionModifiers(DispositionModifier[] dispostionModifiers)
+    {
+        fieldDispositionModifiers = dispostionModifiers;
+    }
+
+    /**
+     * Answer the Disposition Type.
+     * 
+     * @return Returns the dispostionType.
+     */
+    protected DispositionType getDispositionType()
+    {
+        return fieldDispositionType;
+    }
+
+    /**
+     * Set the Disposition Type.
+     * 
+     * @param dispostionType The dispostionType to set.
+     */
+    protected void setDispositionType(DispositionType dispostionType)
+    {
+        fieldDispositionType = dispostionType;
+    }
+
+    /**
+     * @see java.lang.Object#toString()
+     */
+    public String toString()
+    {
+        StringBuilder builder = new StringBuilder(64);
+        builder.append("Disposition: ");
+        builder.append(getActionMode() == null ? "" : getActionMode().toString());
+        builder.append('/');
+        builder.append(getSendingMode() == null ? "" : getSendingMode().toString());
+        builder.append(';');
+        builder.append(getDispositionType() == null ? "" : getDispositionType().toString());
+        if (null != getDispositionModifiers()
+                && getDispositionModifiers().length > 0)
+        {
+            builder.append('/');
+            for (int i = 0; i < getDispositionModifiers().length; i++)
+            {
+                if (i > 0)
+                    builder.append(',');
+                builder.append(getDispositionModifiers()[i]);
+            }
+        }
+        return builder.toString();
+    }
+
+    /**
+     * Answer the Sending Mode.
+     * 
+     * @return Returns the sendingMode.
+     */
+    protected DispositionSendingMode getSendingMode()
+    {
+        return fieldSendingMode;
+    }
+
+    /**
+     * Set the Sending Mode.
+     * 
+     * @param sendingMode The sendingMode to set.
+     */
+    protected void setSendingMode(DispositionSendingMode sendingMode)
+    {
+        fieldSendingMode = sendingMode;
+    }
+}
diff --git a/trunk/mailet/src/main/java/org/apache/jsieve/mailet/mdn/DispositionActionMode.java b/trunk/mailet/src/main/java/org/apache/jsieve/mailet/mdn/DispositionActionMode.java
new file mode 100644
index 0000000..95b883f
--- /dev/null
+++ b/trunk/mailet/src/main/java/org/apache/jsieve/mailet/mdn/DispositionActionMode.java
@@ -0,0 +1,29 @@
+/****************************************************************
+ * 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.jsieve.mailet.mdn;
+
+/**
+ * Interface <code>DispositionActionMode</code> marks a type encapsulating
+ * disposition action mode information as defined by RFC 2298.
+ */
+public interface DispositionActionMode
+{
+
+}
diff --git a/trunk/mailet/src/main/java/org/apache/jsieve/mailet/mdn/DispositionModifier.java b/trunk/mailet/src/main/java/org/apache/jsieve/mailet/mdn/DispositionModifier.java
new file mode 100644
index 0000000..25bf06e
--- /dev/null
+++ b/trunk/mailet/src/main/java/org/apache/jsieve/mailet/mdn/DispositionModifier.java
@@ -0,0 +1,29 @@
+/****************************************************************
+ * 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.jsieve.mailet.mdn;
+
+/**
+ * Interface <code>DispositionModifier</code> marks a type encapsulating
+ * disposition modifier information as defined by RFC 2298.
+ */
+public interface DispositionModifier
+{
+
+}
diff --git a/trunk/mailet/src/main/java/org/apache/jsieve/mailet/mdn/DispositionSendingMode.java b/trunk/mailet/src/main/java/org/apache/jsieve/mailet/mdn/DispositionSendingMode.java
new file mode 100644
index 0000000..5b29b93
--- /dev/null
+++ b/trunk/mailet/src/main/java/org/apache/jsieve/mailet/mdn/DispositionSendingMode.java
@@ -0,0 +1,29 @@
+/****************************************************************
+ * 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.jsieve.mailet.mdn;
+
+/**
+ * Interface <code>DispositionSendingMode</code> marks a type encapsulating
+ * disposition sending mode information as defined by RFC 2298.
+ */
+public interface DispositionSendingMode
+{
+
+}
diff --git a/trunk/mailet/src/main/java/org/apache/jsieve/mailet/mdn/DispositionType.java b/trunk/mailet/src/main/java/org/apache/jsieve/mailet/mdn/DispositionType.java
new file mode 100644
index 0000000..9c17de0
--- /dev/null
+++ b/trunk/mailet/src/main/java/org/apache/jsieve/mailet/mdn/DispositionType.java
@@ -0,0 +1,29 @@
+/****************************************************************
+ * 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.jsieve.mailet.mdn;
+
+/**
+ * Interface <code>DispositionType</code> marks a type encapsulating
+ * disposition type information as defined by RFC 2298.
+ */
+public interface DispositionType
+{
+
+}
diff --git a/trunk/mailet/src/main/java/org/apache/jsieve/mailet/mdn/MDNFactory.java b/trunk/mailet/src/main/java/org/apache/jsieve/mailet/mdn/MDNFactory.java
new file mode 100644
index 0000000..0b514b2
--- /dev/null
+++ b/trunk/mailet/src/main/java/org/apache/jsieve/mailet/mdn/MDNFactory.java
@@ -0,0 +1,115 @@
+/****************************************************************
+ * 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.jsieve.mailet.mdn;
+
+import javax.mail.MessagingException;
+import javax.mail.internet.MimeBodyPart;
+
+import org.apache.mailet.base.mail.MimeMultipartReport;
+
+/**
+ * Class <code>MDNFactory</code> creates MimeMultipartReports containing
+ * Message Delivery Notifications as specified by RFC 2298.
+ */
+public class MDNFactory
+{
+
+    /**
+     * Default Constructor
+     */
+    private MDNFactory()
+    {
+        super();
+    }
+    
+    /**
+     * Answers a MimeMultipartReport containing a
+     * Message Delivery Notification as specified by RFC 2298.
+     * 
+     * @param humanText
+     * @param reporting_UA_name
+     * @param reporting_UA_product
+     * @param original_recipient
+     * @param final_recipient
+     * @param original_message_id
+     * @param disposition
+     * @return MimeMultipartReport
+     * @throws MessagingException
+     */
+    static public MimeMultipartReport create(String humanText,
+            String reporting_UA_name,
+            String reporting_UA_product,
+            String original_recipient,
+            String final_recipient,
+            String original_message_id,
+            Disposition disposition) throws MessagingException
+    {
+        // Create the message parts. According to RFC 2298, there are two
+        // compulsory parts and one optional part...
+        MimeMultipartReport multiPart = new MimeMultipartReport();
+        multiPart.setReportType("disposition-notification");
+        
+        // Part 1: The 'human-readable' part
+        MimeBodyPart humanPart = new MimeBodyPart();
+        humanPart.setText(humanText);
+        multiPart.addBodyPart(humanPart);
+
+        // Part 2: MDN Report Part
+        // 1) reporting-ua-field
+        StringBuilder mdnReport = new StringBuilder(128);
+        mdnReport.append("Reporting-UA: ");
+        mdnReport.append((reporting_UA_name == null ? "" : reporting_UA_name));
+        mdnReport.append("; ");
+        mdnReport.append((reporting_UA_product == null ? "" : reporting_UA_product));
+        mdnReport.append("\r\n");
+        // 2) original-recipient-field
+        if (null != original_recipient)
+        {
+            mdnReport.append("Original-Recipient: ");
+            mdnReport.append("rfc822; ");
+            mdnReport.append(original_recipient);
+            mdnReport.append("\r\n");
+        }
+        // 3) final-recipient-field
+        mdnReport.append("Final-Recepient: ");
+        mdnReport.append("rfc822; ");
+        mdnReport.append((final_recipient == null ? "" : final_recipient));
+        mdnReport.append("\r\n");
+        // 4) original-message-id-field
+        mdnReport.append("Original-Message-ID: ");
+        mdnReport.append((original_message_id == null ? "" : original_message_id));
+        mdnReport.append("\r\n");
+        // 5) disposition-field
+        mdnReport.append(disposition.toString());
+        mdnReport.append("\r\n");
+        MimeBodyPart mdnPart = new MimeBodyPart();
+        mdnPart.setContent(mdnReport.toString(), "message/disposition-notification");
+        multiPart.addBodyPart(mdnPart);
+
+        // Part 3: The optional third part, the original message is omitted.
+        // We don't want to propogate over-sized, virus infected or
+        // other undesirable mail!
+        // There is the option of adding a Text/RFC822-Headers part, which
+        // includes only the RFC 822 headers of the failed message. This is
+        // described in RFC 1892. It would be a useful addition!        
+        return multiPart;
+    }
+
+}
diff --git a/trunk/mailet/src/main/java/org/apache/jsieve/mailet/mdn/ModifierError.java b/trunk/mailet/src/main/java/org/apache/jsieve/mailet/mdn/ModifierError.java
new file mode 100644
index 0000000..c582e5c
--- /dev/null
+++ b/trunk/mailet/src/main/java/org/apache/jsieve/mailet/mdn/ModifierError.java
@@ -0,0 +1,44 @@
+/****************************************************************
+ * 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.jsieve.mailet.mdn;
+
+
+/**
+ * Class <code>ModifierError</code>
+ */
+public class ModifierError implements DispositionModifier
+{
+
+    /**
+     * Default Constructor
+     */
+    public ModifierError()
+    {
+        super();
+    }
+
+    /**
+     * @see java.lang.Object#toString()
+     */
+    public String toString()
+    {
+        return "error";
+    }
+}
diff --git a/trunk/mailet/src/main/java/org/apache/jsieve/mailet/mdn/ModifierExpired.java b/trunk/mailet/src/main/java/org/apache/jsieve/mailet/mdn/ModifierExpired.java
new file mode 100644
index 0000000..880a700
--- /dev/null
+++ b/trunk/mailet/src/main/java/org/apache/jsieve/mailet/mdn/ModifierExpired.java
@@ -0,0 +1,45 @@
+/****************************************************************
+ * 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.jsieve.mailet.mdn;
+
+
+/**
+ * Class <code>ModifierExpired</code>
+ */    
+public class ModifierExpired implements DispositionModifier
+{
+
+    /**
+     * Default Constructor
+     */
+    public ModifierExpired()
+    {
+        super();
+    }
+    
+    /**
+     * @see java.lang.Object#toString()
+     */
+    public String toString()
+    {
+        return "expired";
+    }        
+
+}
diff --git a/trunk/mailet/src/main/java/org/apache/jsieve/mailet/mdn/ModifierFailed.java b/trunk/mailet/src/main/java/org/apache/jsieve/mailet/mdn/ModifierFailed.java
new file mode 100644
index 0000000..741d77e
--- /dev/null
+++ b/trunk/mailet/src/main/java/org/apache/jsieve/mailet/mdn/ModifierFailed.java
@@ -0,0 +1,45 @@
+/****************************************************************
+ * 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.jsieve.mailet.mdn;
+
+
+/**
+ * Class <code>ModifierFailed</code>
+ */    
+public class ModifierFailed implements DispositionModifier
+{
+
+    /**
+     * Default Constructor
+     */
+    public ModifierFailed()
+    {
+        super();
+    }
+    
+    /**
+     * @see java.lang.Object#toString()
+     */
+    public String toString()
+    {
+        return "failed";
+    }        
+
+}
diff --git a/trunk/mailet/src/main/java/org/apache/jsieve/mailet/mdn/ModifierMailboxTerminated.java b/trunk/mailet/src/main/java/org/apache/jsieve/mailet/mdn/ModifierMailboxTerminated.java
new file mode 100644
index 0000000..7d715c5
--- /dev/null
+++ b/trunk/mailet/src/main/java/org/apache/jsieve/mailet/mdn/ModifierMailboxTerminated.java
@@ -0,0 +1,45 @@
+/****************************************************************
+ * 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.jsieve.mailet.mdn;
+
+
+/**
+ * Class <code>ModifierMailboxTerminated</code>
+ */    
+public class ModifierMailboxTerminated implements DispositionModifier
+{
+
+    /**
+     * Default Constructor
+     */
+    public ModifierMailboxTerminated()
+    {
+        super();
+    }
+    
+    /**
+     * @see java.lang.Object#toString()
+     */
+    public String toString()
+    {
+        return "mailbox-terminated";
+    }        
+
+}
diff --git a/trunk/mailet/src/main/java/org/apache/jsieve/mailet/mdn/ModifierSuperseded.java b/trunk/mailet/src/main/java/org/apache/jsieve/mailet/mdn/ModifierSuperseded.java
new file mode 100644
index 0000000..ab8375e
--- /dev/null
+++ b/trunk/mailet/src/main/java/org/apache/jsieve/mailet/mdn/ModifierSuperseded.java
@@ -0,0 +1,45 @@
+/****************************************************************
+ * 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.jsieve.mailet.mdn;
+
+
+/**
+ * Class <code>ModifierSuperseded</code>
+ */    
+public class ModifierSuperseded implements DispositionModifier
+{
+
+    /**
+     * Default Constructor
+     */
+    public ModifierSuperseded()
+    {
+        super();
+    }
+    
+    /**
+     * @see java.lang.Object#toString()
+     */
+    public String toString()
+    {
+        return "superseded";
+    }        
+
+}
diff --git a/trunk/mailet/src/main/java/org/apache/jsieve/mailet/mdn/ModifierWarning.java b/trunk/mailet/src/main/java/org/apache/jsieve/mailet/mdn/ModifierWarning.java
new file mode 100644
index 0000000..dd0f5d1
--- /dev/null
+++ b/trunk/mailet/src/main/java/org/apache/jsieve/mailet/mdn/ModifierWarning.java
@@ -0,0 +1,45 @@
+/****************************************************************
+ * 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.jsieve.mailet.mdn;
+
+
+/**
+ * Class <code>ModifierWarning</code>
+ */    
+public class ModifierWarning implements DispositionModifier
+{
+
+    /**
+     * Default Constructor
+     */
+    public ModifierWarning()
+    {
+        super();
+    }
+    
+    /**
+     * @see java.lang.Object#toString()
+     */
+    public String toString()
+    {
+        return "warning";
+    }        
+
+}
diff --git a/trunk/mailet/src/main/java/org/apache/jsieve/mailet/mdn/SendingModeAutomatic.java b/trunk/mailet/src/main/java/org/apache/jsieve/mailet/mdn/SendingModeAutomatic.java
new file mode 100644
index 0000000..9a178b9
--- /dev/null
+++ b/trunk/mailet/src/main/java/org/apache/jsieve/mailet/mdn/SendingModeAutomatic.java
@@ -0,0 +1,45 @@
+/****************************************************************
+ * 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.jsieve.mailet.mdn;
+
+
+/**
+ * Class <code>SendingModeAutomatic</code>
+ */
+public class SendingModeAutomatic implements DispositionSendingMode
+{
+
+    /**
+     * Default Constructor
+     */
+    public SendingModeAutomatic()
+    {
+        super();
+    }
+    
+    /**
+     * @see java.lang.Object#toString()
+     */
+    public String toString()
+    {
+        return "MDN-sent-automatically";
+    }        
+
+}
diff --git a/trunk/mailet/src/main/java/org/apache/jsieve/mailet/mdn/SendingModeManual.java b/trunk/mailet/src/main/java/org/apache/jsieve/mailet/mdn/SendingModeManual.java
new file mode 100644
index 0000000..f123dc6
--- /dev/null
+++ b/trunk/mailet/src/main/java/org/apache/jsieve/mailet/mdn/SendingModeManual.java
@@ -0,0 +1,45 @@
+/****************************************************************
+ * 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.jsieve.mailet.mdn;
+
+
+/**
+ * Class <code>SendingModeManual</code>
+ */
+public class SendingModeManual implements DispositionSendingMode
+{
+
+    /**
+     * Default Constructor
+     */
+    public SendingModeManual()
+    {
+        super();
+    }
+    
+    /**
+     * @see java.lang.Object#toString()
+     */
+    public String toString()
+    {
+        return "MDN-sent-manually";
+    }         
+
+}
diff --git a/trunk/mailet/src/main/java/org/apache/jsieve/mailet/mdn/TypeDeleted.java b/trunk/mailet/src/main/java/org/apache/jsieve/mailet/mdn/TypeDeleted.java
new file mode 100644
index 0000000..9e0a195
--- /dev/null
+++ b/trunk/mailet/src/main/java/org/apache/jsieve/mailet/mdn/TypeDeleted.java
@@ -0,0 +1,45 @@
+/****************************************************************
+ * 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.jsieve.mailet.mdn;
+
+
+/**
+ * Class <code>TypeDeleted</code>
+ */
+public class TypeDeleted implements DispositionType
+{
+
+    /**
+     * Default Constructor
+     */
+    public TypeDeleted()
+    {
+        super();
+    }
+    
+    /**
+     * @see java.lang.Object#toString()
+     */
+    public String toString()
+    {
+        return "deleted";
+    }         
+
+}
diff --git a/trunk/mailet/src/main/java/org/apache/jsieve/mailet/mdn/TypeDenied.java b/trunk/mailet/src/main/java/org/apache/jsieve/mailet/mdn/TypeDenied.java
new file mode 100644
index 0000000..a08183c
--- /dev/null
+++ b/trunk/mailet/src/main/java/org/apache/jsieve/mailet/mdn/TypeDenied.java
@@ -0,0 +1,44 @@
+/****************************************************************
+ * 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.jsieve.mailet.mdn;
+
+
+/**
+ * Class <code>TypeDenied</code>
+ */    
+public class TypeDenied implements DispositionType
+{
+
+    /**
+     * Default Constructor
+     */
+    public TypeDenied()
+    {
+        super();
+    }
+    
+    /**
+     * @see java.lang.Object#toString()
+     */
+    public String toString()
+    {
+        return "denied";
+    }         
+}
diff --git a/trunk/mailet/src/main/java/org/apache/jsieve/mailet/mdn/TypeDispatched.java b/trunk/mailet/src/main/java/org/apache/jsieve/mailet/mdn/TypeDispatched.java
new file mode 100644
index 0000000..5c41482
--- /dev/null
+++ b/trunk/mailet/src/main/java/org/apache/jsieve/mailet/mdn/TypeDispatched.java
@@ -0,0 +1,43 @@
+/****************************************************************
+ * 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.jsieve.mailet.mdn;
+
+
+/**
+ * Class <code>TypeDispatched</code>
+ */
+public class TypeDispatched implements DispositionType
+{
+    /**
+     * Default Constructor
+     */
+    public TypeDispatched()
+    {
+        super();
+    }
+    
+    /**
+     * @see java.lang.Object#toString()
+     */
+    public String toString()
+    {
+        return "dispatched";
+    }         
+}
diff --git a/trunk/mailet/src/main/java/org/apache/jsieve/mailet/mdn/TypeDisplayed.java b/trunk/mailet/src/main/java/org/apache/jsieve/mailet/mdn/TypeDisplayed.java
new file mode 100644
index 0000000..f5f4032
--- /dev/null
+++ b/trunk/mailet/src/main/java/org/apache/jsieve/mailet/mdn/TypeDisplayed.java
@@ -0,0 +1,44 @@
+/****************************************************************
+ * 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.jsieve.mailet.mdn;
+
+
+/**
+ * Class <code>TypeDisplayed</code>
+ */
+public class TypeDisplayed implements DispositionType
+{
+
+    /**
+     * Default Constructor
+     */
+    public TypeDisplayed()
+    {
+        super();
+    }
+    
+    /**
+     * @see java.lang.Object#toString()
+     */
+    public String toString()
+    {
+        return "displayed";
+    }         
+}
diff --git a/trunk/mailet/src/main/java/org/apache/jsieve/mailet/mdn/TypeFailed.java b/trunk/mailet/src/main/java/org/apache/jsieve/mailet/mdn/TypeFailed.java
new file mode 100644
index 0000000..18f1142
--- /dev/null
+++ b/trunk/mailet/src/main/java/org/apache/jsieve/mailet/mdn/TypeFailed.java
@@ -0,0 +1,44 @@
+/****************************************************************
+ * 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.jsieve.mailet.mdn;
+
+
+/**
+ * Class <code>TypeFailed</code>
+ */    
+public class TypeFailed implements DispositionType
+{
+
+    /**
+     * Default Constructor
+     */
+    public TypeFailed()
+    {
+        super();
+    }
+    
+    /**
+     * @see java.lang.Object#toString()
+     */
+    public String toString()
+    {
+        return "failed";
+    }         
+}
diff --git a/trunk/mailet/src/main/java/org/apache/jsieve/mailet/mdn/TypeProcessed.java b/trunk/mailet/src/main/java/org/apache/jsieve/mailet/mdn/TypeProcessed.java
new file mode 100644
index 0000000..d3b0566
--- /dev/null
+++ b/trunk/mailet/src/main/java/org/apache/jsieve/mailet/mdn/TypeProcessed.java
@@ -0,0 +1,44 @@
+/****************************************************************
+ * 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.jsieve.mailet.mdn;
+
+
+/**
+ * Class <code>TypeProcessed</code>
+ */    
+public class TypeProcessed implements DispositionType
+{
+
+    /**
+     * Default Constructor
+     */
+    public TypeProcessed()
+    {
+        super();
+    }
+    
+    /**
+     * @see java.lang.Object#toString()
+     */
+    public String toString()
+    {
+        return "processed";
+    }         
+}
diff --git a/trunk/mailet/src/main/java/org/apache/jsieve/mailet/mdn/package.html b/trunk/mailet/src/main/java/org/apache/jsieve/mailet/mdn/package.html
new file mode 100644
index 0000000..fd5ae70
--- /dev/null
+++ b/trunk/mailet/src/main/java/org/apache/jsieve/mailet/mdn/package.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<HTML>
+<HEAD>
+<!--
+
+  @(#)package.html
+
+  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.       
+-->
+
+</HEAD>
+<BODY>
+
+<p><abbr title='Message Delivery Notifications'>MDN</abbr> 
+for JavaMail.</p>
+</BODY>
+</HTML>
\ No newline at end of file
diff --git a/trunk/mailet/src/main/java/org/apache/jsieve/mailet/package.html b/trunk/mailet/src/main/java/org/apache/jsieve/mailet/package.html
new file mode 100644
index 0000000..4e499ac
--- /dev/null
+++ b/trunk/mailet/src/main/java/org/apache/jsieve/mailet/package.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<HTML>
+<HEAD>
+<!--
+
+  @(#)package.html
+
+  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.       
+-->
+
+</HEAD>
+<BODY>
+
+<p>Sieve filtering mailet.</p>
+<p>Provides suitable actions and adapters for Sieve use in a <a href='http://james.apache.org/mailet'>Mailet</a> container.</p>
+</BODY>
+</HTML>
\ No newline at end of file
diff --git a/trunk/mailet/src/main/resources/sieveConfig.xml b/trunk/mailet/src/main/resources/sieveConfig.xml
new file mode 100644
index 0000000..c73c06f
--- /dev/null
+++ b/trunk/mailet/src/main/resources/sieveConfig.xml
@@ -0,0 +1,142 @@
+<!--
+  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.                                           
+ -->
+<!-- Sieve configuration. -->
+<sieve>
+    <!-- Declare supported command mappings -->
+    <commandMap>
+        <!-- Condition Commands -->
+        <!--  RFC3082 - Implementations MUST support these: -->
+        <entry> 
+            <name>if</name> 
+            <class>org.apache.jsieve.commands.If</class>
+        </entry>    
+        <entry>
+            <name>else</name>
+            <class>org.apache.jsieve.commands.Else</class>
+        </entry>            
+        <entry>
+            <name>elsif</name>
+            <class>org.apache.jsieve.commands.Elsif</class>
+        </entry>            
+        <entry>
+            <name>require</name>
+            <class>org.apache.jsieve.commands.Require</class>
+        </entry>            
+        <entry>
+            <name>stop</name>
+            <class>org.apache.jsieve.commands.Stop</class>
+        </entry>            
+
+        <!--  Action Commands -->       
+        <!--  RFC3082 - Implementations MUST support these: -->
+        <entry>
+            <name>keep</name>
+            <class>org.apache.jsieve.commands.Keep</class>
+        </entry>            
+        <entry>
+            <name>discard</name>
+            <class>org.apache.jsieve.commands.Discard</class>
+        </entry>            
+        <entry>
+            <name>redirect</name>
+            <class>org.apache.jsieve.commands.Redirect</class>
+        </entry>            
+         <!--  RFC3082 - Implementations SHOULD support these: -->      
+        <entry>
+            <name>reject</name>
+            <class>org.apache.jsieve.commands.optional.Reject</class>
+        </entry>            
+        <entry>
+            <name>fileinto</name>
+            <class>org.apache.jsieve.commands.optional.FileInto</class>
+        </entry>            
+
+        <!-- JUnit Commands for Testing -->       
+        <entry>
+            <name>throwtestexception</name>
+            <class>org.apache.jsieve.junit.commands.ThrowTestException</class>
+        </entry>
+        
+        <!-- Extension Commands -->       
+        <entry>
+            <name>log</name>
+            <class>org.apache.jsieve.commands.extensions.Log</class>
+        </entry>                        
+    </commandMap>
+
+    <!-- Declare supported test mappings -->    
+    <testMap>
+        <!--  RFC3082 - Implementations MUST support these tests: -->
+        <entry>
+            <name>address</name>
+            <class>org.apache.jsieve.tests.Address</class>
+        </entry>            
+        <entry>
+            <name>allof</name>
+            <class>org.apache.jsieve.tests.AllOf</class>
+        </entry>            
+        <entry>
+            <name>anyof</name>
+            <class>org.apache.jsieve.tests.AnyOf</class>
+        </entry>            
+        <entry>
+            <name>exists</name>
+            <class>org.apache.jsieve.tests.Exists</class>
+        </entry>            
+        <entry>
+            <name>false</name>
+            <class>org.apache.jsieve.tests.False</class>
+        </entry>            
+        <entry>
+            <name>header</name>
+            <class>org.apache.jsieve.tests.Header</class>
+        </entry>            
+        <entry>
+            <name>not</name>
+            <class>org.apache.jsieve.tests.Not</class>
+        </entry>            
+        <entry>
+            <name>size</name>
+            <class>org.apache.jsieve.tests.Size</class>
+        </entry>            
+        <entry>
+            <name>true</name>
+            <class>org.apache.jsieve.tests.True</class>
+        </entry>            
+
+        <!--  RFC3082 - Implementations SHOULD support these: -->
+        <entry>
+            <name>envelope</name>
+            <class>org.apache.jsieve.tests.optional.Envelope</class>
+        </entry>            
+    </testMap>
+
+    <!-- Declare supported comparator mappings -->    
+    <comparatorMap>
+        <!--  RFC3082 - Implementations MUST support these: -->
+        <entry>
+            <name>i;octet</name>
+            <class>org.apache.jsieve.comparators.Octet</class>
+        </entry>            
+        <entry>
+            <name>i;ascii-casemap</name>
+            <class>org.apache.jsieve.comparators.AsciiCasemap</class>
+        </entry>                    
+    </comparatorMap>                    
+</sieve>            
diff --git a/trunk/mailet/src/site/resources/images/asf-logo-reduced.gif b/trunk/mailet/src/site/resources/images/asf-logo-reduced.gif
new file mode 100644
index 0000000..93cc102
--- /dev/null
+++ b/trunk/mailet/src/site/resources/images/asf-logo-reduced.gif
Binary files differ
diff --git a/trunk/mailet/src/site/resources/images/james-jsieve-logo.gif b/trunk/mailet/src/site/resources/images/james-jsieve-logo.gif
new file mode 100644
index 0000000..9c7e34f
--- /dev/null
+++ b/trunk/mailet/src/site/resources/images/james-jsieve-logo.gif
Binary files differ
diff --git a/trunk/mailet/src/site/site.xml b/trunk/mailet/src/site/site.xml
new file mode 100644
index 0000000..d1e5b67
--- /dev/null
+++ b/trunk/mailet/src/site/site.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!--

+  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.    

+-->

+<project name="jSieve">
+  <bannerLeft>
+    <name>JAMES jSieve</name>
+    <src>images/james-jsieve-logo.gif</src>
+    <href>http://james.apache.org/</href>
+  </bannerLeft>
+
+  <bannerRight>

+    <name>The Apache Software Foundation</name>

+    <src>images/asf-logo-reduced.gif</src>

+    <href>http://www.apache.org/index.html</href>

+  </bannerRight> 

+
+  <body>
+
+    <menu name="jSieve Mailet">
+      <item name="Overview" href="index.html"/>
+      <item name='Catalog' href='mailet-report.html'/>
+      <item 
+        name="DOAP" 
+        href="doap_apache-jsieve-mailet.rdf" 
+        img='http://www.w3.org/RDF/icons/rdf_metadata_button.32'/>
+    </menu>
+    
+    <menu ref="reports" />
+    
+  </body>
+</project>
diff --git a/trunk/mailet/src/site/xdoc/index.xml b/trunk/mailet/src/site/xdoc/index.xml
new file mode 100644
index 0000000..c3a8ebc
--- /dev/null
+++ b/trunk/mailet/src/site/xdoc/index.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0"?>
+<!--
+  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.  
+-->
+<document>
+
+ <properties>
+  <title>Overview</title>
+  <author email="jsieve-dev@jakarta.apache.org">jSieve Project</author>
+ </properties>
+
+<body>
+<section name="What is jSieve?">
+<p>
+jSieve is a Java implementation of the Sieve mail filtering language defined by 
+<a href='http://www.rfc-editor.org/rfc/rfc3028.txt'>RFC 3028</a>. jSieve is implemented 
+as a language processor that can be plugged into any internet mail application to add 
+Sieve support.
+</p>
+<subsection name='Sieve Mailets'>
+<p>
+This library contains mailets which allow Sieve filters to be easily fitting
+into any enterprise mail server supporting the 
+<a href='http://james.apache.org/mailet' rel='tag'>Mailet API</a>. This library
+supplies Sieve mail filtering support for James.
+</p><p>
+For more details, see the <a href='mailet-report.html'>Catelog</a>.
+</p>
+</subsection>
+</section>
+</body>
+</document>
diff --git a/trunk/main/BUILDING.txt b/trunk/main/BUILDING.txt
new file mode 100644
index 0000000..7c8f4ea
--- /dev/null
+++ b/trunk/main/BUILDING.txt
@@ -0,0 +1,54 @@
+#############################################################################
+# BUILDING JSIEVE WITH MAVEN
+#############################################################################
+
+1) Install maven 2.0.9
+
+2) Add maven to your path and make sure you also have a JAVA_HOME environment
+   variable to point a java 1.4+ virtual machine.
+
+3) Run "mvn package"
+
+
+#############################################################################
+# BUILDING JSIEVE WITH ANT
+#############################################################################
+
+The source trunk for JSIEVE no longer includes Ant, so in order to
+build JSIEVE, you will need to install Ant as well as acquire JSIEVE
+source from subversion or a source tarball.
+
+JSIEVE uses JavaCC during the build process to generate the script parser.
+Unfortunately, though the latest codebase is now available under the BSD
+license there is (at this time) no official open sourced release. So
+to build JSIEVE download the JavaCC 4.0 release from 
+https://javacc.dev.java.net/. Extract the javacc.jar and copy into the
+root of the JSIEVE directory. Rename this to javacc-4.0.jar.
+
+You also have to download activation-1.1.1.jar and mail-1.4.1.jar files to 
+the root folder in order to build/run unit tests.
+
+Steps:
+
+1) Install Ant (v1.6.5 as of the time of this writing)
+
+2) Add Ant to your path.  For me, I do the following:
+   $ tar zxvf apache-ant-1.6.5-bin.tar.gz
+   $ mv apache-ant-1.6.5 /usr/local
+   $ ln -sf /usr/local/apache-ant-1.6.5 /usr/local/ant
+   $ ln -sf /usr/local/ant/bin/ant /usr/local/bin/ant
+
+3) Change any JSIEVE-related build scripts that you might have to call
+    Ant directly, e.g.:  ./build.sh <target> ==> ant <target>
+
+That's it.  Please contact general@james.apache.org if you have any
+problems.
+
+##############################################################################
+# SET UP JSIEVE INSIDE ECLIPSE
+##############################################################################
+
+You can use maven facility to setup an eclipse project:
+mvn eclipse:eclipse
+
+You can also use q4e plugin from eclipse.
\ No newline at end of file
diff --git a/trunk/main/LICENSE.apache b/trunk/main/LICENSE.apache
new file mode 100644
index 0000000..94d8c58
--- /dev/null
+++ b/trunk/main/LICENSE.apache
@@ -0,0 +1,176 @@
+                                 Apache License

+                           Version 2.0, January 2004

+                        http://www.apache.org/licenses/

+

+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

+

+   1. Definitions.

+

+      "License" shall mean the terms and conditions for use, reproduction,

+      and distribution as defined by Sections 1 through 9 of this document.

+

+      "Licensor" shall mean the copyright owner or entity authorized by

+      the copyright owner that is granting the License.

+

+      "Legal Entity" shall mean the union of the acting entity and all

+      other entities that control, are controlled by, or are under common

+      control with that entity. For the purposes of this definition,

+      "control" means (i) the power, direct or indirect, to cause the

+      direction or management of such entity, whether by contract or

+      otherwise, or (ii) ownership of fifty percent (50%) or more of the

+      outstanding shares, or (iii) beneficial ownership of such entity.

+

+      "You" (or "Your") shall mean an individual or Legal Entity

+      exercising permissions granted by this License.

+

+      "Source" form shall mean the preferred form for making modifications,

+      including but not limited to software source code, documentation

+      source, and configuration files.

+

+      "Object" form shall mean any form resulting from mechanical

+      transformation or translation of a Source form, including but

+      not limited to compiled object code, generated documentation,

+      and conversions to other media types.

+

+      "Work" shall mean the work of authorship, whether in Source or

+      Object form, made available under the License, as indicated by a

+      copyright notice that is included in or attached to the work

+      (an example is provided in the Appendix below).

+

+      "Derivative Works" shall mean any work, whether in Source or Object

+      form, that is based on (or derived from) the Work and for which the

+      editorial revisions, annotations, elaborations, or other modifications

+      represent, as a whole, an original work of authorship. For the purposes

+      of this License, Derivative Works shall not include works that remain

+      separable from, or merely link (or bind by name) to the interfaces of,

+      the Work and Derivative Works thereof.

+

+      "Contribution" shall mean any work of authorship, including

+      the original version of the Work and any modifications or additions

+      to that Work or Derivative Works thereof, that is intentionally

+      submitted to Licensor for inclusion in the Work by the copyright owner

+      or by an individual or Legal Entity authorized to submit on behalf of

+      the copyright owner. For the purposes of this definition, "submitted"

+      means any form of electronic, verbal, or written communication sent

+      to the Licensor or its representatives, including but not limited to

+      communication on electronic mailing lists, source code control systems,

+      and issue tracking systems that are managed by, or on behalf of, the

+      Licensor for the purpose of discussing and improving the Work, but

+      excluding communication that is conspicuously marked or otherwise

+      designated in writing by the copyright owner as "Not a Contribution."

+

+      "Contributor" shall mean Licensor and any individual or Legal Entity

+      on behalf of whom a Contribution has been received by Licensor and

+      subsequently incorporated within the Work.

+

+   2. Grant of Copyright License. Subject to the terms and conditions of

+      this License, each Contributor hereby grants to You a perpetual,

+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable

+      copyright license to reproduce, prepare Derivative Works of,

+      publicly display, publicly perform, sublicense, and distribute the

+      Work and such Derivative Works in Source or Object form.

+

+   3. Grant of Patent License. Subject to the terms and conditions of

+      this License, each Contributor hereby grants to You a perpetual,

+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable

+      (except as stated in this section) patent license to make, have made,

+      use, offer to sell, sell, import, and otherwise transfer the Work,

+      where such license applies only to those patent claims licensable

+      by such Contributor that are necessarily infringed by their

+      Contribution(s) alone or by combination of their Contribution(s)

+      with the Work to which such Contribution(s) was submitted. If You

+      institute patent litigation against any entity (including a

+      cross-claim or counterclaim in a lawsuit) alleging that the Work

+      or a Contribution incorporated within the Work constitutes direct

+      or contributory patent infringement, then any patent licenses

+      granted to You under this License for that Work shall terminate

+      as of the date such litigation is filed.

+

+   4. Redistribution. You may reproduce and distribute copies of the

+      Work or Derivative Works thereof in any medium, with or without

+      modifications, and in Source or Object form, provided that You

+      meet the following conditions:

+

+      (a) You must give any other recipients of the Work or

+          Derivative Works a copy of this License; and

+

+      (b) You must cause any modified files to carry prominent notices

+          stating that You changed the files; and

+

+      (c) You must retain, in the Source form of any Derivative Works

+          that You distribute, all copyright, patent, trademark, and

+          attribution notices from the Source form of the Work,

+          excluding those notices that do not pertain to any part of

+          the Derivative Works; and

+

+      (d) If the Work includes a "NOTICE" text file as part of its

+          distribution, then any Derivative Works that You distribute must

+          include a readable copy of the attribution notices contained

+          within such NOTICE file, excluding those notices that do not

+          pertain to any part of the Derivative Works, in at least one

+          of the following places: within a NOTICE text file distributed

+          as part of the Derivative Works; within the Source form or

+          documentation, if provided along with the Derivative Works; or,

+          within a display generated by the Derivative Works, if and

+          wherever such third-party notices normally appear. The contents

+          of the NOTICE file are for informational purposes only and

+          do not modify the License. You may add Your own attribution

+          notices within Derivative Works that You distribute, alongside

+          or as an addendum to the NOTICE text from the Work, provided

+          that such additional attribution notices cannot be construed

+          as modifying the License.

+

+      You may add Your own copyright statement to Your modifications and

+      may provide additional or different license terms and conditions

+      for use, reproduction, or distribution of Your modifications, or

+      for any such Derivative Works as a whole, provided Your use,

+      reproduction, and distribution of the Work otherwise complies with

+      the conditions stated in this License.

+

+   5. Submission of Contributions. Unless You explicitly state otherwise,

+      any Contribution intentionally submitted for inclusion in the Work

+      by You to the Licensor shall be under the terms and conditions of

+      this License, without any additional terms or conditions.

+      Notwithstanding the above, nothing herein shall supersede or modify

+      the terms of any separate license agreement you may have executed

+      with Licensor regarding such Contributions.

+

+   6. Trademarks. This License does not grant permission to use the trade

+      names, trademarks, service marks, or product names of the Licensor,

+      except as required for reasonable and customary use in describing the

+      origin of the Work and reproducing the content of the NOTICE file.

+

+   7. Disclaimer of Warranty. Unless required by applicable law or

+      agreed to in writing, Licensor provides the Work (and each

+      Contributor provides its Contributions) on an "AS IS" BASIS,

+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or

+      implied, including, without limitation, any warranties or conditions

+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A

+      PARTICULAR PURPOSE. You are solely responsible for determining the

+      appropriateness of using or redistributing the Work and assume any

+      risks associated with Your exercise of permissions under this License.

+

+   8. Limitation of Liability. In no event and under no legal theory,

+      whether in tort (including negligence), contract, or otherwise,

+      unless required by applicable law (such as deliberate and grossly

+      negligent acts) or agreed to in writing, shall any Contributor be

+      liable to You for damages, including any direct, indirect, special,

+      incidental, or consequential damages of any character arising as a

+      result of this License or out of the use or inability to use the

+      Work (including but not limited to damages for loss of goodwill,

+      work stoppage, computer failure or malfunction, or any and all

+      other commercial damages or losses), even if such Contributor

+      has been advised of the possibility of such damages.

+

+   9. Accepting Warranty or Additional Liability. While redistributing

+      the Work or Derivative Works thereof, You may choose to offer,

+      and charge a fee for, acceptance of support, warranty, indemnity,

+      or other liability obligations and/or rights consistent with this

+      License. However, in accepting such obligations, You may act only

+      on Your own behalf and on Your sole responsibility, not on behalf

+      of any other Contributor, and only if You agree to indemnify,

+      defend, and hold each Contributor harmless for any liability

+      incurred by, or claims asserted against, such Contributor by reason

+      of your accepting any such warranty or additional liability.

+

+   END OF TERMS AND CONDITIONS	
\ No newline at end of file
diff --git a/trunk/main/NOTICE.base b/trunk/main/NOTICE.base
new file mode 100644
index 0000000..76a0011
--- /dev/null
+++ b/trunk/main/NOTICE.base
@@ -0,0 +1,10 @@
+   =========================================================================

+   ==      NOTICE file for use with the Apache License, Version 2.0,      ==

+   =========================================================================

+

+   Apache James JSieve Library

+   Copyright 2009 The Apache Software Foundation

+

+   This product includes software developed at

+   The Apache Software Foundation (http://www.apache.org/).

+
diff --git a/trunk/main/build.xml b/trunk/main/build.xml
new file mode 100644
index 0000000..5954bb0
--- /dev/null
+++ b/trunk/main/build.xml
@@ -0,0 +1,145 @@
+<?xml version="1.0"?>
+<!-- 
+==========================================================================
+
+ jSieve build file 
+
+
+  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.                                           
+ 
+==============================================================================
+-->
+
+<project name='jsieve-main' default="main" basedir=".">
+    
+    <!--
+    Give user a chance to override without editing this file
+    (and without typing -D each time he compiles it)
+    -->
+    <property file=".ant.properties" />
+    <property file="${user.home}/.ant.properties" />
+    <property file="../include.properties" />
+    <property file="../default.properties" />
+
+    <path id="project.class.path">
+        <pathelement location="${commons-logging.jar}" />
+        <pathelement location="${log4j.jar}" />
+        <pathelement location="${junit.jar}" />
+        <pathelement path="${java.class.path}" />
+        <pathelement path="${build.classes}" />
+    </path>
+
+    <path id="mail.class.path">
+        <pathelement location='${mail.jar}' />
+        <pathelement location='${activation.jar}' />
+    </path>
+
+    <path id="project.test.class.path">
+        <path refid="project.class.path" />
+        <path refid="mail.class.path" />
+    </path>
+    
+    <import file='../build.xml'/>
+    <target name="main" depends="run-tests" description=" - main target"/>
+
+    <target name="prepare" depends="preprocess" description=" - paparations                   [internal]">
+        <CheckMailConditions/>
+    </target>
+    
+    <!--
+    Unless you want to alter the Sieve grammar, expressed by the seive.jjt 
+    file, is built, consider the following processing a black box.
+    Or zen for the more spiritual amongst you.
+    -->                       
+    <target name="preprocess" description=" - generate source from grammar  [internal]">
+        <!--
+        Generate JavaCC source inserting parse tree building actions 
+        and the Java source for the parse classes.
+        -->
+        <mkdir dir="${build.src}/org/apache/jsieve/parser/generated/address" />
+        <java classname="jjtree" fork="yes" failonerror="true" dir="${build.src}">
+            <arg line="${javacc.dir}/sieve/sieve.jjt" />
+            <classpath>
+                <pathelement location="${javacc.jar}" />
+                <pathelement path="${java.class.path}" />
+            </classpath>
+        </java>
+        <java classname="jjtree" fork="yes" failonerror="true" dir="${build.src}">
+            <arg line="${javacc.dir}/address/AddressListParser.jjt" />
+            <classpath>
+                <pathelement location="${javacc.jar}" />
+                <pathelement path="${java.class.path}" />
+            </classpath>
+        </java>
+
+        <!-- Generate Java source from the JavaCC source -->
+        <java classname="javacc" fork="yes" failonerror="true" dir="${build.src}">
+            <arg line="${build.src}/org/apache/jsieve/parser/generated/sieve.jj" />
+            <classpath>
+                <pathelement location="${javacc.jar}" />
+                <pathelement path="${java.class.path}" />
+            </classpath>
+        </java>
+        <java classname="javacc" fork="yes" failonerror="true" dir="${build.src}">
+            <arg line="${build.src}/org/apache/jsieve/parser/generated/address/AddressListParser.jj" />
+            <classpath>
+                <pathelement location="${javacc.jar}" />
+                <pathelement path="${java.class.path}" />
+            </classpath>
+        </java>
+    </target>
+
+    <target name="compile" depends="prepare" description=" - compiles test and main source">
+        <CompileMain>
+            <src path="${build.src}" />
+            <src path="${java.dir}" />
+        </CompileMain>
+        <CompileTests/>
+    </target>
+
+    <target name="jar" depends="compile" description=" - jars classes">
+        <Jar name='' prefix=''/>     
+    
+        <!-- Make jSieve jUnit jar-->
+        <echo message="Making jSieve jUnit Jar (${name}-tests-${version}.jar)" />
+        <jar jarfile="${build.lib}/${name}-tests-${version}.jar" basedir="${build.classes.test}">
+            <include name="org/apache/jsieve/**" />
+            <include name="META-INF/**" />
+            <manifest>
+                <attribute name="Created-By" value='${creator}' />
+                <attribute name="X-Compile-Source-JDK" value='${jdk.source}' />
+                <attribute name="X-Compile-Target-JDK" value='${jdk.target}' />
+            </manifest>
+        </jar>
+
+        <copy todir='${lib.jsieve.dir}' file='${build.lib}/${name}-tests-${version}.jar' />
+    </target>
+
+    <target name="run-tests" depends="jar" description=" - runs all tests">
+        <RunTests/>
+    </target>
+
+    <target name="clean" description=" - cleans build files">
+        <Clean/>
+    </target>
+
+    <target name="usage" description=" - prints help">
+        <Usage/>
+    </target>
+
+</project>
diff --git a/trunk/main/pom.xml b/trunk/main/pom.xml
new file mode 100644
index 0000000..cfd51a8
--- /dev/null
+++ b/trunk/main/pom.xml
@@ -0,0 +1,248 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <!--
+    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.    
+  -->
+  <parent>
+    <artifactId>jsieve-project</artifactId>
+    <groupId>org.apache.james</groupId>
+    <version>0.4</version>
+  </parent>
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>org.apache.james</groupId>
+  <artifactId>apache-jsieve</artifactId>
+  <name>Apache JAMES jSieve</name>
+  <version>0.4</version>
+  <packaging>jar</packaging>
+  <description>
+Apache jSieve is a server side mail filtering system 
+implementing RFC3028. Apache jSieve is developed by the 
+JAMES project.
+  </description>
+  <url>http://james.apache.org/jsieve</url>
+  <inceptionYear>2004</inceptionYear>
+  
+  <properties>
+    <!--
+    The website is committed to subversion. This property can be overriden
+    to upload the site to a local staging location.
+    For example, adding the following to ~/.m2/settings.xml will upload
+    to localhost:
+    
+      <profiles>
+        <profile>
+           <id>main</id>
+           <activation>
+              <activeByDefault>true</activeByDefault>
+           </activation>
+           <properties>
+              <james.www>scp://localhost/www</james.www>
+              <james.www.id>localhost</james.www.id>
+      ...
+    -->
+    <!-- General location for site stage -->
+    <james.www>scp://people.apache.org/www/james.apache.org/</james.www>
+    <!-- Project specific location, allowing specific override -->
+    <james.jsieve.www>${james.www}/jsieve/</james.jsieve.www>
+    <!-- Overridding this value allows single set of loopback settings to be maintained -->
+    <james.www.id>jsieve-website</james.www.id>
+  </properties>
+
+  <distributionManagement>
+    <site>
+      <id>${james.www.id}</id>
+      <url>${james.jsieve.www}/main</url>
+    </site>
+  </distributionManagement>
+
+
+  <issueManagement>
+    <system>JIRA</system>
+    <url>http://issues.apache.org/jira/browse/JSIEVE</url>
+  </issueManagement>
+
+  <scm>
+    <connection>scm:svn:http://svn.apache.org/repos/asf/james/jsieve/tags/jsieve-project-0.4</connection>
+    <developerConnection>scm:svn:https://svn.apache.org/repos/asf/james/jsieve/tags/jsieve-project-0.4</developerConnection>
+    <url>http://svn.apache.org/viewvc/james/jsieve/tags/jsieve-project-0.4</url>
+  </scm>
+  
+  <repositories>
+  	<!-- enable central that is otherwise disabled by the parent pom. -->
+  	<repository>
+  		<id>central</id>
+  		<url>http://repo1.maven.org/maven2</url>
+  		<releases>
+  			<enabled>true</enabled>
+  		</releases>
+  	</repository>
+  </repositories>
+
+  <dependencies>
+    <dependency>
+      <groupId>commons-logging</groupId>
+      <artifactId>commons-logging</artifactId>
+    </dependency>
+
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <scope>test</scope>
+    </dependency>
+    
+    <dependency>
+      <groupId>log4j</groupId>
+      <artifactId>log4j</artifactId>
+      <scope>test</scope>
+    </dependency>
+
+    <dependency>
+      <groupId>javax.mail</groupId>
+      <artifactId>mail</artifactId>
+      <scope>test</scope>
+    </dependency>
+    
+    <dependency>
+      <groupId>javax.activation</groupId>
+      <artifactId>activation</artifactId>
+      <scope>test</scope>
+    </dependency>
+
+  </dependencies>
+
+  <build>
+    <resources>
+      <resource>
+        <directory>src/main/resources</directory>
+      </resource>
+    </resources>
+    
+    <plugins>    
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>javacc-maven-plugin</artifactId>
+        <version>2.5</version>
+        <executions>
+          <execution>
+            <id>generate-jjtree</id>
+            <phase>generate-sources</phase>
+            <goals>
+              <goal>jjtree-javacc</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-jar-plugin</artifactId>
+      </plugin>
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>rat-maven-plugin</artifactId>
+      </plugin>
+      <plugin>
+      	<groupId>org.apache.felix</groupId>
+      	<artifactId>maven-bundle-plugin</artifactId>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-doap-plugin</artifactId>
+      </plugin>
+    </plugins>
+  </build>
+
+  <reporting>
+    <plugins>
+      <plugin>
+        <artifactId>maven-surefire-plugin</artifactId>
+        <version>2.4.3</version>
+      </plugin>
+      <plugin>
+        <artifactId>maven-surefire-report-plugin</artifactId>
+        <version>2.4.3</version>
+      </plugin> 
+      <plugin>
+        <artifactId>maven-javadoc-plugin</artifactId>
+        <version>2.4</version>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-jxr-plugin</artifactId>
+        <version>2.1</version>
+      </plugin>
+      <plugin>
+        <artifactId>maven-pmd-plugin</artifactId>
+        <version>2.4</version>
+        <configuration>
+          <targetJdk>1.5</targetJdk>
+          <rulesets>
+            <ruleset>/rulesets/basic.xml</ruleset>
+            <ruleset>/rulesets/controversial.xml</ruleset>
+          </rulesets>
+          <format>xml</format>
+          <linkXref>true</linkXref>
+          <sourceEncoding>utf-8</sourceEncoding>
+          <minimumTokens>100</minimumTokens>
+        </configuration>
+      </plugin>
+      <plugin>
+        <artifactId>maven-site-plugin</artifactId>
+        <version>2.0-beta-7</version>
+      </plugin>
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>rat-maven-plugin</artifactId>
+        <version>1.0-alpha-3</version>
+        <configuration>
+          <excludes>
+            <exclude>NOTICE.base</exclude>
+            <exclude>LICENSE.apache</exclude>
+            <exclude>release.properties</exclude>
+          </excludes>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>taglist-maven-plugin</artifactId>
+        <version>2.2</version>
+      </plugin>
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>javacc-maven-plugin</artifactId>
+      </plugin>
+    </plugins>
+  </reporting>
+  
+  <mailingLists>
+    <mailingList>
+      <name>Apache James User</name>
+      <subscribe>server-user-subscribe@james.apache.org</subscribe>
+      <unsubscribe>server-user-unsubscribe@james.apache.org</unsubscribe>
+      <post>server-user@james.apache.org</post>
+      <archive>http://mail-archives.apache.org/mod_mbox/james-server-user/</archive>
+    </mailingList>
+    <mailingList>
+      <name>Apache James Developer</name>
+      <subscribe>server-dev-subscribe@james.apache.org</subscribe>
+      <unsubscribe>server-dev-unsubscribe@james.apache.org</unsubscribe>
+      <post>server-dev@james.apache.org</post>
+      <archive>http://mail-archives.apache.org/mod_mbox/james-server-dev/</archive>
+    </mailingList>
+  </mailingLists>
+
+</project>
diff --git a/trunk/main/src/main/appended-resources/supplemental-models.xml b/trunk/main/src/main/appended-resources/supplemental-models.xml
new file mode 100644
index 0000000..bdb831b
--- /dev/null
+++ b/trunk/main/src/main/appended-resources/supplemental-models.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<supplementalDataModels>
+  <!--
+  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.
+  -->
+  <!-- Also added manually in pom.xml appeneded text because test
+       dependencies are not automatically included by the NOTICE 
+       generator -->
+  <supplement>
+    <project>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <name>JUnit</name>
+      <url>http://www.junit.org/</url>
+      <organization>
+      	<name>Kent Beck, Erich Gamma, and David Saff</name>
+      </organization>
+      <licenses>
+        <license>
+          <name>Common Public License Version 1.0</name>
+          <url>http://www.opensource.org/licenses/cpl.php</url>
+        </license>
+      </licenses>
+    </project>
+  </supplement>
+</supplementalDataModels>
\ No newline at end of file
diff --git a/trunk/main/src/main/java/org/apache/jsieve/Argument.java b/trunk/main/src/main/java/org/apache/jsieve/Argument.java
new file mode 100644
index 0000000..023471d
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/Argument.java
@@ -0,0 +1,34 @@
+/****************************************************************
+ * 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.jsieve;
+
+/**
+ * A simple Interface for Arguments.
+ * 
+ * Creation Date: 24-Nov-03
+ */
+public interface Argument {
+    /**
+     * Method getValue answers the value of the receiver's Argument.
+     * 
+     * @return Object
+     */
+    public Object getValue();
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/Arguments.java b/trunk/main/src/main/java/org/apache/jsieve/Arguments.java
new file mode 100644
index 0000000..e3ce2a6
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/Arguments.java
@@ -0,0 +1,154 @@
+/****************************************************************
+ * 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.jsieve;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * <p>
+ * A parsed representation of the RFC3028 BNF...
+ * </p>
+ * 
+ * <code>arguments = *argument [test / test-list]</code>
+ * 
+ * <p>
+ * Note that a test is represented as a test-list with a single element.
+ * </p>
+ * 
+ */
+public class Arguments {
+    /**
+     * A List of the parsed Arguments
+     */
+    private List<Argument> fieldArgumentList;
+
+    /**
+     * The parsed tests
+     */
+    private TestList fieldTestList;
+
+    /**
+     * Constructor for Arguments.
+     */
+    private Arguments() {
+        super();
+    }
+
+    /**
+     * Constructor for Arguments.
+     * 
+     * @param arguments
+     * @param testList
+     */
+    public Arguments(List<Argument> arguments, TestList testList) {
+        this();
+        setArgumentList(arguments);
+        setTestList(testList);
+    }
+
+    /**
+     * Returns the arguments.
+     * 
+     * @return List
+     */
+    public List<Argument> getArgumentList() {
+        return fieldArgumentList;
+    }
+
+    /**
+     * Returns the testList, lazily initialised if required.
+     * 
+     * @return TestList
+     */
+    public TestList getTestList() {
+        TestList testList = null;
+        if (null == (testList = getTestListBasic())) {
+            updateTestList();
+            return getTestList();
+        }
+        return testList;
+    }
+
+    /**
+     * Returns true if there is a TestList and it has Tests. Saves triggering
+     * lazy initialisation.
+     * 
+     * @return boolean
+     */
+    public boolean hasTests() {
+        TestList testList = getTestListBasic();
+        return null != testList && !testList.isEmpty();
+    }
+
+    /**
+     * Returns the testList.
+     * 
+     * @return TestList
+     */
+    private TestList getTestListBasic() {
+        return fieldTestList;
+    }
+
+    /**
+     * Computes the testList.
+     * 
+     * @return TestList
+     */
+    protected TestList computeTestList() {
+        return new TestList(new ArrayList<Test>());
+    }
+
+    /**
+     * Sets the arguments.
+     * 
+     * @param arguments
+     *            The arguments to set
+     */
+    protected void setArgumentList(List<Argument> arguments) {
+        fieldArgumentList = arguments;
+    }
+
+    /**
+     * Sets the testList.
+     * 
+     * @param testList
+     *            The testList to set
+     */
+    protected void setTestList(TestList testList) {
+        fieldTestList = testList;
+    }
+
+    /**
+     * Updates the TestList
+     */
+    protected void updateTestList() {
+        setTestList(computeTestList());
+    }
+
+    /**
+     * @see java.lang.Object#toString()
+     */
+    public String toString() {
+        return "Arguments: " + getArgumentList().toString() + " Tests: "
+                + (hasTests() ? getTestList().toString() : "null");
+    }
+
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/BaseSieveContext.java b/trunk/main/src/main/java/org/apache/jsieve/BaseSieveContext.java
new file mode 100644
index 0000000..3949f1d
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/BaseSieveContext.java
@@ -0,0 +1,136 @@
+/****************************************************************
+ * 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.jsieve;
+
+import org.apache.commons.logging.Log;
+
+/**
+ * Bean based implementation of context.
+ * 
+ */
+public class BaseSieveContext extends SieveContext {
+
+    private ScriptCoordinate coordinate;
+
+    private ConditionManager conditionManager;
+
+    private final CommandStateManager commandStateManager;
+
+    private final CommandManager commandManager;
+
+    private final ComparatorManager comparatorManager;
+
+    private final TestManager testManager;
+
+    private final Log log;
+
+    public BaseSieveContext(final CommandManager commandManager,
+            final ComparatorManager comparatorManager,
+            final TestManager testManager, final Log log) {
+        this.commandStateManager = new CommandStateManager();
+        this.conditionManager = new ConditionManager();
+        this.testManager = testManager;
+        this.commandManager = commandManager;
+        this.comparatorManager = comparatorManager;
+        this.log = log;
+    }
+
+    /**
+     * Gets the script position of the current operation.
+     * 
+     * @return <code>ScriptCoordinate</code>, not null
+     */
+    @Override
+    public ScriptCoordinate getCoordinate() {
+        return coordinate;
+    }
+
+    /**
+     * Sets the script position of the current operation.
+     * 
+     * @param coordinate
+     *            <code>ScriptCoordinate</code>, not null
+     */
+    @Override
+    public void setCoordinate(ScriptCoordinate coordinate) {
+        this.coordinate = coordinate;
+        if (coordinate != null) {
+            coordinate.setLog(getLog());
+        }
+    }
+
+    /**
+     * @see SieveContext#getCommandStateManager()
+     */
+    @Override
+    public CommandStateManager getCommandStateManager() {
+        return commandStateManager;
+    }
+
+    /**
+     * @see SieveContext#getConditionManager()
+     */
+    @Override
+    public ConditionManager getConditionManager() {
+        return conditionManager;
+    }
+
+    /**
+     * @see SieveContext#setConditionManager(ConditionManager)
+     */
+    @Override
+    public void setConditionManager(ConditionManager conditionManager) {
+        this.conditionManager = conditionManager;
+    }
+
+    /**
+     * @see SieveContext#getLog()
+     */
+    @Override
+    public Log getLog() {
+        return log;
+    }
+
+    /**
+     * @see SieveContext#getComparatorManager()
+     */
+    @Override
+    public ComparatorManager getComparatorManager() {
+        return comparatorManager;
+    }
+
+    /**
+     * @see SieveContext#getCommandManager()
+     */
+    @Override
+    public CommandManager getCommandManager() {
+        return commandManager;
+    }
+
+    /**
+     * @see SieveContext#getTestManager()
+     */
+    @Override
+    public TestManager getTestManager() {
+        return testManager;
+    }
+    
+    
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/Block.java b/trunk/main/src/main/java/org/apache/jsieve/Block.java
new file mode 100644
index 0000000..8267c9c
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/Block.java
@@ -0,0 +1,87 @@
+/****************************************************************
+ * 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.jsieve;
+
+import org.apache.jsieve.exception.SieveException;
+import org.apache.jsieve.mail.MailAdapter;
+
+/**
+ * <p>
+ * A parsed representation of the RFC3028 BNF...
+ * </p>
+ * 
+ * <code>block = "{" commands "}"</code>
+ * 
+ */
+public class Block implements Executable {
+    /**
+     * The chilren of the Block
+     */
+    private Commands fieldChildren;
+
+    /**
+     * Constructor for Block.
+     */
+    private Block() {
+        super();
+    }
+
+    /**
+     * Constructor for Block.
+     * 
+     * @param commands
+     */
+    public Block(Commands commands) {
+        this();
+        setChildren(commands);
+    }
+
+    /**
+     * Returns the commands.
+     * 
+     * @return Commands
+     */
+    public Commands getChildren() {
+        return fieldChildren;
+    }
+
+    /**
+     * Sets the commands.
+     * 
+     * @param commands
+     *            The commands to set
+     */
+    protected void setChildren(Commands commands) {
+        fieldChildren = commands;
+    }
+
+    /**
+     * @see org.apache.jsieve.Executable#execute(MailAdapter, SieveContext)
+     */
+    public Object execute(MailAdapter mail, SieveContext context)
+            throws SieveException {
+        return getChildren().execute(mail, context);
+    }
+
+    public String toString() {
+        return "BLOCK: " + getChildren();
+    }
+
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/Command.java b/trunk/main/src/main/java/org/apache/jsieve/Command.java
new file mode 100644
index 0000000..3b6822b
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/Command.java
@@ -0,0 +1,162 @@
+/****************************************************************
+ * 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.jsieve;
+
+import org.apache.commons.logging.Log;
+import org.apache.jsieve.exception.SieveException;
+import org.apache.jsieve.mail.MailAdapter;
+
+/**
+ * <p>
+ * A parsed representation of the RFC3028 BNF...
+ * </p>
+ * 
+ * <code>command = identifier arguments ( ";" / block )</code>
+ */
+public class Command implements Executable {
+
+    /** The name of this Command */
+    private String fieldName;
+
+    /** The Arguments for this Command */
+    private Arguments fieldArguments;
+
+    /** The Block for this Command */
+    private Block fieldBlock;
+
+    /**
+     * Script coordinate for this command. Commands are executed after the
+     * document has been parse. So this must be recorded on construction and
+     * stored for later use.
+     */
+    private ScriptCoordinate coordinate;
+
+    /**
+     * Constructor for Test.
+     */
+    private Command() {
+        super();
+    }
+
+    /**
+     * Constructor for Command.
+     * 
+     * @param name
+     * @param arguments
+     * @param block
+     */
+    public Command(String name, Arguments arguments, Block block,
+            ScriptCoordinate coordinate) {
+        this();
+        this.coordinate = coordinate;
+        setName(name);
+        setArguments(arguments);
+        setBlock(block);
+    }
+
+    /**
+     * Returns the name.
+     * 
+     * @return String
+     */
+    public String getName() {
+        return fieldName;
+    }
+
+    /**
+     * Sets the name.
+     * 
+     * @param name
+     *            The name to set
+     */
+    protected void setName(String name) {
+        fieldName = name;
+    }
+
+    /**
+     * @see java.lang.Object#toString()
+     */
+    public String toString() {
+        return "Command name: "
+                + getName()
+                + " "
+                + ((getArguments() == null) ? "null" : getArguments()
+                        .toString()) + " Block: "
+                + ((getBlock() == null) ? "null" : getBlock().toString());
+    }
+
+    /**
+     * Returns the arguments.
+     * 
+     * @return Arguments
+     */
+    public Arguments getArguments() {
+        return fieldArguments;
+    }
+
+    /**
+     * Returns the block.
+     * 
+     * @return Block
+     */
+    public Block getBlock() {
+        return fieldBlock;
+    }
+
+    /**
+     * Sets the arguments.
+     * 
+     * @param arguments
+     *            The arguments to set
+     */
+    protected void setArguments(Arguments arguments) {
+        fieldArguments = arguments;
+    }
+
+    /**
+     * Sets the block.
+     * 
+     * @param block
+     *            The block to set
+     */
+    protected void setBlock(Block block) {
+        fieldBlock = block;
+    }
+
+    /**
+     * @see org.apache.jsieve.Executable#execute(MailAdapter, SieveContext)
+     */
+    public Object execute(MailAdapter mail, SieveContext context)
+            throws SieveException {
+        Log log = context.getLog();
+        if (log.isDebugEnabled()) {
+            log.debug(toString());
+            coordinate.debugDiagnostics(log);
+        }
+        // commands are executed after the parsing phase
+        // recursively from the top level block
+        // so need to use the coordinate recorded from the parse
+        context.setCoordinate(coordinate);
+        final ExecutableCommand executable = context.getCommandManager().getCommand(getName());
+        final Object result = executable.execute(mail, getArguments(),
+                getBlock(), context);
+        return result;
+    }
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/CommandManager.java b/trunk/main/src/main/java/org/apache/jsieve/CommandManager.java
new file mode 100644
index 0000000..5f5ce8c
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/CommandManager.java
@@ -0,0 +1,52 @@
+/****************************************************************
+ * 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.jsieve;
+
+import org.apache.jsieve.exception.LookupException;
+
+/**
+ * <p>Maps Command names to configured Command implementation classes.</p>
+ * <h4>Thread Safety</h4>
+ * <p>
+ * Implementation dependent. {@link CommandManagerImpl} is a thread safe implementation.
+ * </p>
+ */
+public interface CommandManager {
+
+    /**
+     * <p>Gets an instance of a command by name.</p>
+     * 
+     * @param name -
+     *            The name of the Command
+     * @return command, not null
+     * @throws LookupException
+     */
+    public ExecutableCommand getCommand(String name) throws LookupException;
+
+    /**
+     * Method isSupported answers a boolean indicating if a Command name is
+     * configured.
+     * 
+     * @param name -
+     *            The Command name
+     * @return boolean - True if the Command name is configured.
+     */
+    public boolean isCommandSupported(String name);
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/CommandManagerImpl.java b/trunk/main/src/main/java/org/apache/jsieve/CommandManagerImpl.java
new file mode 100644
index 0000000..42d22cb
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/CommandManagerImpl.java
@@ -0,0 +1,126 @@
+/****************************************************************
+ * 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.jsieve;
+
+import java.util.concurrent.ConcurrentMap;
+
+import org.apache.jsieve.exception.LookupException;
+
+/**
+ * <p>Maps command names to comman implementations.</p>
+ * <h4>Thread Safety</h4>
+ * <p>
+ * Instances may safely be accessed concurrently by multiple threads.
+ * </p>
+ */
+public class CommandManagerImpl implements CommandManager {
+
+    private final ConcurrentMap<String, String> classNameMap;
+
+    /**
+     * Constructor for CommandManager.
+     */
+    public CommandManagerImpl(final ConcurrentMap<String, String> classNameMap) {
+        super();
+        this.classNameMap = classNameMap;
+    }
+
+    /**
+     * <p>
+     * Method lookup answers the class to which a Command name is mapped.
+     * </p>
+     * 
+     * @param name -
+     *            The name of the Command
+     * @return Class - The class of the Command
+     * @throws LookupException
+     */
+    private Class lookup(String name) throws LookupException {
+        Class cmdClass = null;
+        try {
+            cmdClass = getClass().getClassLoader()
+                    .loadClass(getClassName(name));
+        } catch (ClassNotFoundException e) {
+            throw new LookupException("Command named '" + name + "' not found.");
+        }
+        if (!ExecutableCommand.class.isAssignableFrom(cmdClass))
+            throw new LookupException("Class " + cmdClass.getName()
+                    + " must implement " + ExecutableCommand.class.getName());
+        return cmdClass;
+    }
+
+    /**
+     * <p>
+     * Method newInstance answers an instance of the class to which a Command
+     * name is mapped.
+     * </p>
+     * 
+     * @param name -
+     *            The name of the Command
+     * @return Class - The class of the Command
+     * @throws LookupException
+     */
+    public ExecutableCommand getCommand(String name) throws LookupException {
+        try {
+            return (ExecutableCommand) lookup(name).newInstance();
+        } catch (InstantiationException e) {
+            throw new LookupException(e.getMessage());
+        } catch (IllegalAccessException e) {
+            throw new LookupException(e.getMessage());
+        }
+    }
+
+    /**
+     * Method isSupported answers a boolean indicating if a Command name is
+     * configured.
+     * 
+     * @param name -
+     *            The Command name
+     * @return boolean - True if the Command name is configured.
+     */
+    public boolean isCommandSupported(String name) {
+        boolean isSupported = false;
+        try {
+            lookup(name);
+            isSupported = true;
+        } catch (LookupException e) {
+        }
+        return isSupported;
+    }
+
+    /**
+     * <p>
+     * Method getClassName answers the name of the class to which a Command name
+     * is mapped.
+     * </p>
+     * 
+     * @param name -
+     *            The name of the Command
+     * @return String - The name of the class
+     * @throws LookupException
+     */
+    protected String getClassName(String name) throws LookupException {
+        final String className = classNameMap.get(name.toLowerCase());
+        if (null == className)
+            throw new LookupException("Command named '" + name
+                    + "' not mapped.");
+        return className;
+    }
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/CommandStateManager.java b/trunk/main/src/main/java/org/apache/jsieve/CommandStateManager.java
new file mode 100644
index 0000000..e4eda37
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/CommandStateManager.java
@@ -0,0 +1,142 @@
+/****************************************************************
+ * 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.jsieve;
+
+/**
+ * Thread singleton class CommandStateManager records the state of a Sieve
+ * evaluation.
+ */
+public class CommandStateManager {
+
+    /**
+     * The evaluated script is processing Prolog Commands
+     */
+    private boolean fieldInProlog = true;
+
+    /**
+     * The evaluated script has rejected the mail
+     */
+    private boolean fieldRejected = false;
+
+    /**
+     * The evaluated script must keep the mail
+     */
+    private boolean fieldImplicitKeep = true;
+
+    /**
+     * The evaluation has processed Action Commands
+     */
+    private boolean fieldHasActions = false;
+
+    /**
+     * Constructor for CommandStateManager.
+     */
+    public CommandStateManager() {
+        super();
+        initialize();
+    }
+
+    /**
+     * Initialize the receiver.
+     */
+    protected void initialize() {
+        setInProlog(true);
+        setRejected(false);
+        setHasActions(false);
+        setImplicitKeep(true);
+    }
+
+    /**
+     * Returns the hasActions.
+     * 
+     * @return boolean
+     */
+    public boolean isHasActions() {
+        return fieldHasActions;
+    }
+
+    /**
+     * Returns the inProlog.
+     * 
+     * @return boolean
+     */
+    public boolean isInProlog() {
+        return fieldInProlog;
+    }
+
+    /**
+     * Returns the isRejected.
+     * 
+     * @return boolean
+     */
+    public boolean isRejected() {
+        return fieldRejected;
+    }
+
+    /**
+     * Sets the hasActions.
+     * 
+     * @param hasActions
+     *            The hasActions to set
+     */
+    public void setHasActions(boolean hasActions) {
+        fieldHasActions = hasActions;
+    }
+
+    /**
+     * Sets the inProlog.
+     * 
+     * @param inProlog
+     *            The inProlog to set
+     */
+    public void setInProlog(boolean inProlog) {
+        fieldInProlog = inProlog;
+    }
+
+    /**
+     * Sets the isRejected.
+     * 
+     * @param isRejected
+     *            The isRejected to set
+     */
+    public void setRejected(boolean isRejected) {
+        fieldRejected = isRejected;
+    }
+
+    /**
+     * Returns the implicitKeep.
+     * 
+     * @return boolean
+     */
+    public boolean isImplicitKeep() {
+        return fieldImplicitKeep;
+    }
+
+    /**
+     * Sets the implicitKeep.
+     * 
+     * @param implicitKeep
+     *            The implicitKeep to set
+     */
+    public void setImplicitKeep(boolean implicitKeep) {
+        fieldImplicitKeep = implicitKeep;
+    }
+
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/Commands.java b/trunk/main/src/main/java/org/apache/jsieve/Commands.java
new file mode 100644
index 0000000..7390c7a
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/Commands.java
@@ -0,0 +1,92 @@
+/****************************************************************
+ * 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.jsieve;
+
+import java.util.List;
+
+import org.apache.jsieve.exception.SieveException;
+import org.apache.jsieve.mail.MailAdapter;
+
+/**
+ * <p>
+ * A parsed representation of the RFC3028 BNF...
+ * </p>
+ * 
+ * <code>commands = *command</code>
+ * 
+ */
+public class Commands implements Executable {
+    /**
+     * A List of children of the receiver
+     */
+    private List<Command> fieldChildren;
+
+    /**
+     * Constructor for Commands.
+     */
+    private Commands() {
+        super();
+    }
+
+    /**
+     * Constructor for Commands.
+     * 
+     * @param commands
+     */
+    public Commands(List<Command> commands) {
+        this();
+        setChildren(commands);
+    }
+
+    /**
+     * Returns the commands.
+     * 
+     * @return List
+     */
+    public List<Command> getChildren() {
+        return fieldChildren;
+    }
+
+    /**
+     * Sets the commands.
+     * 
+     * @param commands
+     *            The commands to set
+     */
+    protected void setChildren(List<Command> commands) {
+        fieldChildren = commands;
+    }
+
+    /**
+     * @see org.apache.jsieve.Executable#execute(MailAdapter, SieveContext)
+     */
+    public Object execute(MailAdapter mail, SieveContext context)
+            throws SieveException {
+        for (Command command:fieldChildren) {
+            command.execute(mail, context);
+        };
+        return null;
+    }
+
+    public String toString() {
+        return "COMMANDS: " + fieldChildren;
+    }
+
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/ComparatorManager.java b/trunk/main/src/main/java/org/apache/jsieve/ComparatorManager.java
new file mode 100644
index 0000000..067657e
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/ComparatorManager.java
@@ -0,0 +1,59 @@
+/****************************************************************
+ * 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.jsieve;
+
+import org.apache.jsieve.comparators.Comparator;
+import org.apache.jsieve.exception.LookupException;
+
+/**
+ * <p>Maps Comparator names to configured Comparator implementation classes.</p>
+ * <h4>Thread Safety</h4>
+ * <p>
+ * Implementation dependent. {@link ComparatorManagerImpl} is a thread safe implementation.
+ * </p>
+ */
+public interface ComparatorManager {
+    
+    /**
+     * <p>Gets a comparator by name.</p>
+     * 
+     * @param name -
+     *            The (logical) name of the Comparator
+     * @return a comparator, not null
+     * @throws LookupException
+     */
+    public Comparator getComparator(String name) throws LookupException;
+    
+    /**
+     * Is an explicit declaration in a <code>require</code> statement
+     * unnecessary for this comparator?
+     * @param comparatorName not null
+     * @return true when this comparator need not be declared by <core>require</code>,
+     * false when any usage of this comparator must be declared in a <code>require</code> statement
+     */
+    public boolean isImplicitlyDeclared(final String comparatorName);
+    
+    /**
+     * Is the comparator with the given name supported?
+     * @param name not null
+     * @return true when supported, false otherwise
+     */
+    public boolean isSupported(String name);
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/ComparatorManagerImpl.java b/trunk/main/src/main/java/org/apache/jsieve/ComparatorManagerImpl.java
new file mode 100644
index 0000000..b31f46e
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/ComparatorManagerImpl.java
@@ -0,0 +1,165 @@
+/****************************************************************
+ * 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.jsieve;
+
+import static org.apache.jsieve.Constants.COMPARATOR_ASCII_CASEMAP_NAME;
+import static org.apache.jsieve.Constants.COMPARATOR_OCTET_NAME;
+
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.CopyOnWriteArraySet;
+
+import org.apache.jsieve.comparators.Comparator;
+import org.apache.jsieve.exception.LookupException;
+
+/**
+ * <p>Maps Comparator names to configured Comparator implementation classes.</p>
+ * <h4>Thread Safety</h4>
+ * <p>
+ * Instances may safely be accessed concurrently by multiple threads.
+ * </p>
+ */
+public class ComparatorManagerImpl implements ComparatorManager {
+
+    /** 
+     * Constructs a set containing the names of those comparisons for which <code>require</code> 
+     * is not necessary before usage, according to RFC5228.
+     * See <a href='http://tools.ietf.org/html/rfc5228#section-2.7.3'>RFC5228, 2.7.3 Comparators</a>. 
+     */
+    public static CopyOnWriteArraySet<String> standardDefinedComparators() {
+        final CopyOnWriteArraySet<String> results = new CopyOnWriteArraySet<String>();
+        results.add(COMPARATOR_OCTET_NAME);
+        results.add(COMPARATOR_ASCII_CASEMAP_NAME);
+        return results;
+    }
+    
+    private final ConcurrentMap<String, String> classNameMap;
+    /** 
+     * The names of those comparisons for which <code>require</code> is not necessary before usage.
+     * See <a href='http://tools.ietf.org/html/rfc5228#section-2.7.3'>RFC5228, 2.7.3 Comparators</a>. 
+     */
+    private final CopyOnWriteArraySet<String> implicitlyDeclared;
+
+    /**
+     * Constructs a manager with the standard comparators implicitly defined.
+     * @param classNameMap not null
+     */
+    public ComparatorManagerImpl(final ConcurrentMap<String, String> classNameMap) {
+        this(classNameMap, standardDefinedComparators());
+    }
+    
+    /**
+     * Constructor for ComparatorManager.
+     * @param classNameMap indexes names of implementation classes against logical names, not null
+     * @param implicitlyDeclared names of those comparisons for which <code>require</code> is not necessary before usage
+     */
+    public ComparatorManagerImpl(final ConcurrentMap<String, String> classNameMap, final CopyOnWriteArraySet<String> implicitlyDeclared) {
+        super();
+        this.classNameMap = classNameMap;
+        this.implicitlyDeclared = implicitlyDeclared;
+    }
+    
+    /**
+     * Is an explicit declaration in a <code>require</code> statement
+     * unnecessary for this comparator?
+     * @param comparatorName not null
+     * @return true when this comparator need not be declared by <core>require</code>,
+     * false when any usage of this comparator must be declared in a <code>require</code> statement
+     */
+    public boolean isImplicitlyDeclared(final String comparatorName) {
+        return implicitlyDeclared.contains(comparatorName);
+    }
+
+    /**
+     * <p>
+     * Method lookup answers the class to which a Comparator name is mapped.
+     * </p>
+     * 
+     * @param name -
+     *            The name of the Comparator
+     * @return Class - The class of the Comparator
+     * @throws LookupException
+     */
+    public Class lookup(String name) throws LookupException {
+        Class comparatorClass = null;
+        try {
+            comparatorClass = getClass().getClassLoader().loadClass(
+                    getClassName(name));
+        } catch (ClassNotFoundException e) {
+            throw new LookupException("Comparator named '" + name
+                    + "' not found.");
+        }
+        if (!Comparator.class.isAssignableFrom(comparatorClass))
+            throw new LookupException("Class " + comparatorClass.getName()
+                    + " must implement " + Comparator.class.getName());
+        return comparatorClass;
+    }
+
+    /**
+     * <p>
+     * Method newInstance answers an instance of the class to which a Comparator
+     * name is mapped.
+     * </p>
+     * 
+     * @param name -
+     *            The name of the Comparator
+     * @return Class - The class of the Comparator
+     * @throws LookupException
+     */
+    public Comparator getComparator(String name) throws LookupException {
+        try {
+            return (Comparator) lookup(name).newInstance();
+        } catch (InstantiationException e) {
+            throw new LookupException(e.getMessage());
+        } catch (IllegalAccessException e) {
+            throw new LookupException(e.getMessage());
+        }
+    }
+
+    /**
+     * <p>
+     * Method getClassName answers the name of the class to which a Comparator
+     * name is mapped.
+     * </p>
+     * 
+     * @param name -
+     *            The name of the Comparator
+     * @return String - The name of the class
+     * @throws LookupException
+     */
+    private String getClassName(String name) throws LookupException {
+        String className = classNameMap.get(name.toLowerCase());
+        if (null == className)
+            throw new LookupException("Comparator named '" + name
+                    + "' not mapped.");
+        return className;
+    }
+
+    /**
+     * @see ComparatorManager#isSupported(String)
+     */
+    public boolean isSupported(String name) {
+        try {
+            getComparator(name);
+            return true;
+        } catch (LookupException e) {
+            return false;
+        }
+    }
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/ConditionManager.java b/trunk/main/src/main/java/org/apache/jsieve/ConditionManager.java
new file mode 100644
index 0000000..13749a8
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/ConditionManager.java
@@ -0,0 +1,176 @@
+/****************************************************************
+ * 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.jsieve;
+
+/**
+ * Thread singleton class ConditionManager manages Conditional Commands during a
+ * Sieve evaluation.
+ */
+public class ConditionManager {
+
+    /**
+     * Is an Else Condition allowed
+     */
+    private boolean fieldElseAllowed;
+
+    /**
+     * The result of the last Test
+     */
+    private boolean fieldTestResult;
+
+    /**
+     * Constructor for ConditionManager.
+     */
+    public ConditionManager() {
+        super();
+        initialize();
+    }
+
+    /**
+     * Initialize the receiver.
+     */
+    protected void initialize() {
+        setElseAllowed(false);
+        setTestResult(true);
+    }
+
+    /**
+     * Method setIfTestResult enables a following Else Command and records the
+     * test result.
+     * 
+     * @param result
+     */
+    public void setIfTestResult(boolean result) {
+        setElseAllowed(true);
+        setTestResult(result);
+    }
+
+    /**
+     * Method setElsifTestResult enables a following Else Command and records
+     * the test result.
+     * 
+     * @param result
+     */
+    public void setElsifTestResult(boolean result) {
+        setElseAllowed(true);
+        setTestResult(result);
+    }
+
+    /**
+     * Method setElseTestResult disables a following Else Command and records
+     * the test result.
+     * 
+     * @param result
+     */
+    public void setElseTestResult(boolean result) {
+        setElseAllowed(false);
+        setTestResult(result);
+    }
+
+    /**
+     * Method isIfAllowed answers a boolean indicating if an If Command is
+     * allowed.
+     * 
+     * @return boolean
+     */
+    public boolean isIfAllowed() {
+        return true;
+    }
+
+    /**
+     * Method isElsifAllowed answers a boolean indicating if an Elsif Command is
+     * allowed.
+     * 
+     * @return boolean
+     */
+    public boolean isElsifAllowed() {
+        return isElseAllowed();
+    }
+
+    /**
+     * Method isElseAllowed answers a boolean indicating if an Else Command is
+     * allowed.
+     * 
+     * @return boolean
+     */
+    public boolean isElseAllowed() {
+        return fieldElseAllowed;
+    }
+
+    /**
+     * Method isIfRunnable answers a boolean indicating if an If Command is
+     * runnable based upon the current evaluation state.
+     * 
+     * @return boolean
+     */
+    public boolean isIfRunnable() {
+        return true;
+    }
+
+    /**
+     * Method isElsifRunnable answers a boolean indicating if an Elsif Command
+     * is runnable based upon the current evaluation state.
+     * 
+     * @return boolean
+     */
+    public boolean isElsifRunnable() {
+        return isElseRunnable();
+    }
+
+    /**
+     * Method isElseRunnable answers a boolean indicating if an Else Command is
+     * runnable based upon the current evaluation state.
+     * 
+     * @return boolean
+     */
+    public boolean isElseRunnable() {
+        return !isTestResult();
+    }
+
+    /**
+     * Returns the testResult.
+     * 
+     * @return boolean
+     */
+    protected boolean isTestResult() {
+        return fieldTestResult;
+    }
+
+    /**
+     * Sets the elseAllowed.
+     * 
+     * @param elseAllowed
+     *            The elseAllowed to set
+     */
+    protected void setElseAllowed(boolean elseAllowed) {
+        fieldElseAllowed = elseAllowed;
+    }
+
+    /**
+     * Sets the testResult.
+     * 
+     * @param testResult
+     *            The testResult to set
+     */
+    protected void setTestResult(boolean testResult) {
+        fieldTestResult = testResult;
+    }
+
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/ConfigurationManager.java b/trunk/main/src/main/java/org/apache/jsieve/ConfigurationManager.java
new file mode 100644
index 0000000..fbbd056
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/ConfigurationManager.java
@@ -0,0 +1,301 @@
+/****************************************************************
+ * 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.jsieve;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Map;
+import java.util.Properties;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.xml.sax.SAXException;
+
+/**
+ * <p>
+ * <code>ConfigurationManager</code> parses the XML statements
+ * in the Sieve configuration file and translates them to Java objects.
+ * </p>
+ * <p>
+ * The Sieve configuration is read from 3 properties files
+ * </p>
+ * <ul>
+ * <li><code>org/apache/jsieve/commandsmap.properties</code></li>
+ * <li><code>org/apache/jsieve/testsmap.properties</code></li>
+ * <li><code>org/apache/jsieve/comparatorsmap.properties</code></li>
+ * </ul>
+ * <p>They are located by searching the classpath of the current ClassLoader.
+ * </p>
+ * <h4>Thread Safety</h4>
+ * <p>
+ * Each configuration manager instance may be safely accessed by concurrent threads.
+ * </p>
+ * <p>
+ * The managers constructed by 
+ * </p>
+ * <ul>
+ * <li>{@link #getCommandManager()}</li> 
+ * <li>{@link #getComparatorManager()}</li>
+ * <li>{@link #getTestManager()}</li>
+ * </ul>
+ * <p>
+ * and the {@link SieveFactory} built by
+ * </p>
+ * <ul>
+ * <li>{@link #build()}</li>
+ * </ul>
+ * <p>
+ * may be safely shared by multiple threads.
+ * </p>
+ */
+public class ConfigurationManager {
+
+    private static final int DEFAULT_INITIAL_CONCURRENCY_LEVEL = 8;
+
+    private static final String COMMANDSMAP_PROPERTIES = "org/apache/jsieve/commandsmap.properties";
+
+    private static final String TESTSMAP_PROPERTIES = "org/apache/jsieve/testsmap.properties";
+
+    private static final String COMPARATORSMAP_PROPERTIES = "org/apache/jsieve/comparatorsmap.properties";
+
+    /**
+     * A Map of the Command names and their associated class names.
+     */
+    private ConcurrentMap<String, String> fieldCommandMap;
+
+    /**
+     * A Map of the Test names and their associated class names.
+     */
+    private ConcurrentMap<String, String> fieldTestMap;
+
+    /**
+     * A Map of the Comparator names and their associated class names.
+     */
+    private ConcurrentMap<String, String> fieldComparatorMap;
+    
+    /**
+     * The initial size for the {@link ConcurrentHashMap} concurrency level.
+     */
+    private int initialConcurrencyLevel = DEFAULT_INITIAL_CONCURRENCY_LEVEL;
+
+    private static final Log LOG = LogFactory.getLog("org.apache.jsieve");
+
+    private Log log = LOG;
+    
+    /**
+     * Constructor for ConfigurationManager.
+     * 
+     * @throws SieveConfigurationException
+     */
+    public ConfigurationManager() throws SieveConfigurationException {
+        super();
+        try {
+            parse();
+        } catch (SAXException e) {
+            if (log.isErrorEnabled())
+                log.error("Exception processing Configuration: ", e);
+            throw new SieveConfigurationException(e);
+        } catch (IOException e) {
+            if (log.isErrorEnabled())
+                log.error("Exception processing Configuration: ", e);
+            throw new SieveConfigurationException(e);
+        }
+    }
+
+    /**
+     * Gets the current initial size for the {@link ConcurrentHashMap} concurrency level.
+     * @return number of concurrent threads estimated for initial sizing
+     */
+    public int getInitialConcurrencyLevel() {
+        return initialConcurrencyLevel;
+    }
+
+    /**
+     * Sets the current initial size for the {@link ConcurrentHashMap} concurrency level.
+     * @param initialConcurrencyLevel number of concurrent threads estimated for initial sizing
+     */
+    public void setInitialConcurrencyLevel(int initialConcurrencyLevel) {
+        this.initialConcurrencyLevel = initialConcurrencyLevel;
+    }
+
+
+
+    /**
+     * <p>
+     * Method getConfigStream answers an InputStream over the Sieve
+     * configuration file. It is located by searching the classpath of the
+     * current ClassLoader.
+     * </p>
+     * <p>
+     * The context classloader is searched first. If a suitably named resource
+     * is found then this is returned. Otherwise, the classloader used to load
+     * this class is searched for the resource.
+     * </p>
+     * 
+     * @return InputStream
+     * @throws IOException
+     */
+    private InputStream getConfigStream(String configName) throws IOException {
+        InputStream stream = null;
+        // Context classloader is usually right in a JEE evironment
+        final ClassLoader contextClassLoader = Thread.currentThread()
+                .getContextClassLoader();
+        if (contextClassLoader != null) {
+            stream = contextClassLoader.getResourceAsStream(configName);
+        }
+
+        // Sometimes context classloader will not be set conventionally
+        // So, try class classloader
+        if (null == stream) {
+            stream = ConfigurationManager.class.getClassLoader()
+                    .getResourceAsStream(configName);
+        }
+
+        if (null == stream)
+            throw new IOException("Resource \"" + configName + "\" not found");
+        return stream;
+    }
+
+    /**
+     * Method getCommandMap answers a Map of Command names and their associated
+     * class names, lazily initialized if required.
+     * 
+     * @return Map not null
+     */
+    public ConcurrentMap<String, String> getCommandMap() {
+        if (null == fieldCommandMap) {
+            fieldCommandMap = new ConcurrentHashMap<String, String>();
+        }
+        return fieldCommandMap;
+    }
+
+    /**
+     * Method getTestMap answers a Map of Test names and their associated class
+     * names, lazily initialized if required.
+     * 
+     * @return Map not null
+     */
+    public ConcurrentMap<String, String> getTestMap() {
+        if (null == fieldTestMap) {
+            fieldTestMap = new ConcurrentHashMap<String, String>();
+        }
+        return fieldTestMap;
+    }
+
+    /**
+     * Method getComparatorMap answers a Map of Comparator names and their
+     * associated class names, lazily initialized if required.
+     * 
+     * @return Map not null
+     */
+    public ConcurrentMap<String, String> getComparatorMap() {
+        if (null == fieldComparatorMap) {
+            fieldComparatorMap = new ConcurrentHashMap<String, String>();
+        }
+        return fieldComparatorMap;
+    }
+
+    /**
+     * Method parse uses the Digester to parse the XML statements in the Sieve
+     * configuration file into Java objects.
+     * 
+     * @throws SAXException
+     * @throws IOException
+     */
+    private void parse() throws SAXException, IOException {
+        setCommandMap(loadConfiguration(COMMANDSMAP_PROPERTIES));
+        setTestMap(loadConfiguration(TESTSMAP_PROPERTIES));
+        setComparatorMap(loadConfiguration(COMPARATORSMAP_PROPERTIES));
+    }
+
+    private ConcurrentMap<String,String> loadConfiguration(final String name) throws IOException {
+        final Properties properties = loadProperties(name);
+        final ConcurrentMap<String, String> result = 
+            new ConcurrentHashMap<String, String>(properties.size(), 1.0f, initialConcurrencyLevel);
+        for (final Map.Entry<Object, Object> entry: properties.entrySet()) {
+            result.put(entry.getKey().toString(), entry.getValue().toString());
+        }
+        return result;
+    }
+    
+    private Properties loadProperties(final String name) throws IOException {
+        final InputStream is = getConfigStream(name);
+        final Properties p = new Properties();
+        p.load(is);
+        return p;
+    }
+
+    /**
+     * Sets the commandMap.
+     * 
+     * @param commandMap
+     *            The commandMap to set
+     */
+    private void setCommandMap(ConcurrentMap<String, String> commandMap) {
+        fieldCommandMap = commandMap;
+    }
+
+    /**
+     * Sets the testMap.
+     * 
+     * @param testMap
+     *            The testMap to set
+     */
+    private void setTestMap(ConcurrentMap<String, String> testMap) {
+        fieldTestMap = testMap;
+    }
+
+    /**
+     * Sets the comparatorMap.
+     * 
+     * @param comparatorMap
+     *            The comparatorMap to set
+     */
+    private void setComparatorMap(ConcurrentMap<String, String> comparatorMap) {
+        fieldComparatorMap = comparatorMap;
+    }
+
+    public ComparatorManager getComparatorManager() {
+        return new ComparatorManagerImpl(fieldComparatorMap);
+    }
+
+    public CommandManager getCommandManager() {
+        return new CommandManagerImpl(fieldCommandMap);
+    }
+
+    public TestManager getTestManager() {
+        return new TestManagerImpl(fieldTestMap);
+    }
+
+    public Log getLog() {
+        return log;
+    }
+    
+    public void setLog(Log log) {
+        this.log = log;
+    }
+
+    public SieveFactory build() {
+        return new SieveFactory(getCommandManager(), getComparatorManager(),
+                getTestManager(), getLog());
+    }
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/Constants.java b/trunk/main/src/main/java/org/apache/jsieve/Constants.java
new file mode 100644
index 0000000..375dd0c
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/Constants.java
@@ -0,0 +1,35 @@
+/****************************************************************
+ * 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.jsieve;
+
+public class Constants {
+
+    public static final String TAG_COMPARATOR = ":comparator";
+    
+    /** Name of the standard ascii casemap comparator. See <a href='http://tools.ietf.org/html/rfc4790'>RFC4790</a>. */
+    public static final String COMPARATOR_ASCII_CASEMAP_NAME = "i;ascii-casemap";
+    /** Name of the standard octet comparator. See <a href='http://tools.ietf.org/html/rfc4790'>RFC4790</a>. */
+    public static final String COMPARATOR_OCTET_NAME = "i;octet";
+    /** Prefix used to identify extension groups. See <a href='http://tools.ietf.org/html/rfc5228#section-6.1'>RFC5228, 6.1. Capability String</a> */
+    public static final char REQUIRE_EXTENSION_PREFIX = '-';
+    /** Prefix used to identify comparator extensions. See <a href='http://tools.ietf.org/html/rfc5228#section-6.1'>RFC5228, 6.1. Capability String</a> */
+    public static final String COMPARATOR_PREFIX = "comparator" + REQUIRE_EXTENSION_PREFIX;
+    /** Number of characters in {@link #COMPARATOR_PREFIX} */
+    public static final int COMPARATOR_PREFIX_LENGTH = COMPARATOR_PREFIX.length();
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/Executable.java b/trunk/main/src/main/java/org/apache/jsieve/Executable.java
new file mode 100644
index 0000000..1ab46a4
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/Executable.java
@@ -0,0 +1,32 @@
+/****************************************************************
+ * 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.jsieve;
+
+import org.apache.jsieve.exception.SieveException;
+import org.apache.jsieve.mail.MailAdapter;
+
+/**
+ * Interface for an Executable operation.
+ */
+public interface Executable {
+    public Object execute(MailAdapter mail, SieveContext context)
+            throws SieveException;
+
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/ExecutableCommand.java b/trunk/main/src/main/java/org/apache/jsieve/ExecutableCommand.java
new file mode 100644
index 0000000..9d88a92
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/ExecutableCommand.java
@@ -0,0 +1,47 @@
+/****************************************************************
+ * 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.jsieve;
+
+import org.apache.jsieve.exception.SieveException;
+import org.apache.jsieve.mail.MailAdapter;
+
+/**
+ * Interface ExecutableCommand defines the method signatures for Sieve Commands.
+ */
+public interface ExecutableCommand {
+    /**
+     * Method execute executes a Sieve Command.
+     * 
+     * @param mail -
+     *            The mail against which the Command is executed.
+     * @param arguments -
+     *            The Command arguments
+     * @param block -
+     *            An optional Block to be evaluated
+     * @param context
+     *            <code>SieveContext</code> containing contextual information,
+     *            not null
+     * @return Object - The result of evaluating the Command
+     * @throws SieveException
+     */
+    public Object execute(MailAdapter mail, Arguments arguments, Block block,
+            SieveContext context) throws SieveException;
+
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/NumberArgument.java b/trunk/main/src/main/java/org/apache/jsieve/NumberArgument.java
new file mode 100644
index 0000000..a394051
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/NumberArgument.java
@@ -0,0 +1,119 @@
+/****************************************************************
+ * 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.jsieve;
+
+import org.apache.jsieve.parser.generated.Token;
+
+/**
+ * <p>
+ * A parsed representation of the RFC3028 BNF...
+ * </p>
+ * 
+ * <code>1*DIGIT [QUANTIFIER]</code>
+ * 
+ * <p>
+ * Note that the stored value is the absolute value after applying the
+ * quantifier.
+ * </p>
+ * 
+ */
+public class NumberArgument implements Argument {
+
+    /**
+     * The absolute value of the number after applying the quentifier.
+     */
+    private Integer fieldValue;
+
+    /**
+     * Constructor for NumberArgument.
+     */
+    private NumberArgument() {
+        super();
+    }
+
+    /**
+     * Constructor for NumberArgument.
+     * 
+     * @param token
+     */
+    public NumberArgument(Token token) {
+        this();
+        setValue(token);
+    }
+
+    /**
+     * Sets the value of the reciver to an Integer.
+     * 
+     * @param number
+     *            The value to set
+     */
+    protected void setValue(Integer number) {
+        fieldValue = number;
+    }
+
+    /**
+     * @see org.apache.jsieve.Argument#getValue()
+     */
+    public Object getValue() {
+        return fieldValue;
+    }
+
+    /**
+     * Method getInteger answers the value of the receiver as an Integer.
+     * 
+     * @return Integer
+     */
+    public Integer getInteger() {
+        return fieldValue;
+    }
+
+    /**
+     * Sets the value of the receiver from a Token.
+     * 
+     * @param aToken
+     *            The Token from which to extract the value to set
+     */
+    protected void setValue(Token aToken) {
+        int endIndex = aToken.image.length();
+        int magnitude = 1;
+        if (aToken.image.endsWith("K")) {
+            magnitude = 1024;
+            endIndex--;
+        } else if (aToken.image.endsWith("M")) {
+            magnitude = 1048576;
+            endIndex--;
+        } else if (aToken.image.endsWith("G")) {
+            magnitude = 1073741824;
+            endIndex--;
+        }
+
+        setValue(new Integer(Integer.parseInt(aToken.image.substring(0,
+                endIndex))
+                * magnitude));
+    }
+
+    /**
+     * @see java.lang.Object#toString()
+     */
+    public String toString() {
+        return (getValue() == null) ? "null" : getValue().toString();
+    }
+
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/ScriptCoordinate.java b/trunk/main/src/main/java/org/apache/jsieve/ScriptCoordinate.java
new file mode 100644
index 0000000..85b841a
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/ScriptCoordinate.java
@@ -0,0 +1,194 @@
+/****************************************************************
+ * 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.jsieve;
+
+import org.apache.commons.logging.Log;
+import org.apache.jsieve.exception.CommandException;
+import org.apache.jsieve.exception.SyntaxException;
+
+/**
+ * Specifies the positional extent of an element within the script being
+ * executed. In other words, this gives the line and column at which the elment
+ * starts and at which it ends.
+ */
+public final class ScriptCoordinate {
+
+    private final int startLineNumber;
+
+    private final int startColumnNumber;
+
+    private final int endLineNumber;
+
+    private final int endColumnNumber;
+
+    private Log log;
+
+    public ScriptCoordinate(final int startLineNumber,
+            final int startColumnNumber, final int endLineNumber,
+            final int endColumnNumber) {
+        super();
+        this.startLineNumber = startLineNumber;
+        this.startColumnNumber = startColumnNumber;
+        this.endLineNumber = endLineNumber;
+        this.endColumnNumber = endColumnNumber;
+    }
+
+    public Log getLog() {
+        return log;
+    }
+
+    public void setLog(Log logger) {
+        this.log = logger;
+    }
+
+    /**
+     * Gets the number of the column where the elements ends.
+     * 
+     * @return column number
+     */
+    public int getEndColumnNumber() {
+        return endColumnNumber;
+    }
+
+    /**
+     * Gets the number of the line where the element ends.
+     * 
+     * @return line number
+     */
+    public int getEndLineNumber() {
+        return endLineNumber;
+    }
+
+    /**
+     * Gets the number of the column where the element start.
+     * 
+     * @return column number
+     */
+    public int getStartColumnNumber() {
+        return startColumnNumber;
+    }
+
+    /**
+     * Gets the number of the line where the element starts.
+     * 
+     * @return line number
+     */
+    public int getStartLineNumber() {
+        return startLineNumber;
+    }
+
+    /**
+     * Creates a syntax exception based on the given message containing details
+     * of the script position. The message should end with a full stop.
+     * 
+     * @param message
+     *            <code>CharSequence</code> containing the base message, not
+     *            null
+     * @return <code>SyntaxException</code> with details of the script
+     *         position appended to the message, not null
+     */
+    public SyntaxException syntaxException(CharSequence message) {
+        if (log != null) {
+            if (log.isWarnEnabled()) {
+                log.warn(message);
+            }
+            logDiagnosticsInfo(log);
+        }
+        final String fullMessage = addStartLineAndColumn(message);
+        final SyntaxException result = new SyntaxException(fullMessage);
+        return result;
+    }
+
+    /**
+     * Creates a command exception based on the given message containing details
+     * of the script position. The message should end with a full stop.
+     * 
+     * @param message
+     *            <code>CharSequence</code> containing the base message, not
+     *            null
+     * @return <code>CommandException</code> with details of the script
+     *         position appended to the message, not null
+     */
+    public CommandException commandException(CharSequence message) {
+        if (log != null) {
+            if (log.isWarnEnabled()) {
+                log.warn(message);
+            }
+            logDiagnosticsInfo(log);
+        }
+        final String fullMessage = addStartLineAndColumn(message);
+        final CommandException result = new CommandException(fullMessage);
+        return result;
+    }
+
+    /**
+     * Appends a standard position phrase to the given message. This message
+     * should end with a full stop.
+     * 
+     * @param message
+     *            <code>CharSequence</code> message, not null
+     * @return <code>String</code> containing the original message with
+     *         positional phrase appended, not null
+     */
+    public String addStartLineAndColumn(CharSequence message) {
+        final StringBuilder buffer;
+        if (message instanceof StringBuilder) {
+            buffer = (StringBuilder) message;
+        } else {
+            buffer = new StringBuilder(message.toString());
+        }
+        buffer.append(" Line ");
+        buffer.append(startLineNumber);
+        buffer.append(" column ");
+        buffer.append(startColumnNumber);
+        buffer.append(".");
+        return buffer.toString();
+    }
+
+    /**
+     * Logs diagnotic information about the script coordinate.
+     * 
+     * @param logger
+     *            <code>Log</code>, not null
+     */
+    public void logDiagnosticsInfo(Log logger) {
+        if (logger.isInfoEnabled()) {
+            logger.info("Expression starts line " + startLineNumber
+                    + " column " + startColumnNumber);
+            logger.info("Expression ends line " + endLineNumber + " column "
+                    + endColumnNumber);
+        }
+    }
+
+    /**
+     * Logs diagnotic information about the script coordinate.
+     * 
+     * @param logger
+     *            <code>Log</code>, not null
+     */
+    public void debugDiagnostics(Log logger) {
+        if (logger.isDebugEnabled()) {
+            logger.debug("Expression starts line " + startLineNumber
+                    + " column " + startColumnNumber);
+            logger.debug("Expression ends line " + endLineNumber + " column "
+                    + endColumnNumber);
+        }
+    }
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/SieveConfigurationException.java b/trunk/main/src/main/java/org/apache/jsieve/SieveConfigurationException.java
new file mode 100644
index 0000000..46fece8
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/SieveConfigurationException.java
@@ -0,0 +1,67 @@
+/****************************************************************
+ * 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.jsieve;
+
+import org.apache.jsieve.exception.SieveException;
+
+/**
+ * Class <code>SieveConfigurationException</code> indicates an exceptional
+ * condition encountered while evaluating the Sieve configuration.
+ * 
+ */
+@SuppressWarnings("serial")
+public class SieveConfigurationException extends SieveException {
+
+    /**
+     * Constructor for SieveConfigurationException.
+     */
+    public SieveConfigurationException() {
+        super();
+    }
+
+    /**
+     * Constructor for SieveConfigurationException.
+     * 
+     * @param message
+     */
+    public SieveConfigurationException(String message) {
+        super(message);
+    }
+
+    /**
+     * Constructor for SieveConfigurationException.
+     * 
+     * @param message
+     * @param cause
+     */
+    public SieveConfigurationException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    /**
+     * Constructor for SieveConfigurationException.
+     * 
+     * @param cause
+     */
+    public SieveConfigurationException(Throwable cause) {
+        super(cause);
+    }
+
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/SieveContext.java b/trunk/main/src/main/java/org/apache/jsieve/SieveContext.java
new file mode 100644
index 0000000..17a6cff
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/SieveContext.java
@@ -0,0 +1,86 @@
+/****************************************************************
+ * 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.jsieve;
+
+import org.apache.commons.logging.Log;
+
+/**
+ * Context for sieve operations.
+ * 
+ */
+public abstract class SieveContext {
+
+    /**
+     * Gets the script position of the current operation.
+     * 
+     * @return <code>ScriptCoordinate</code>, not null
+     */
+    public abstract ScriptCoordinate getCoordinate();
+
+    /**
+     * Sets the script position of the current operation.
+     * 
+     * @param coordinate
+     *            <code>ScriptCoordinate</code>, not null
+     */
+    public abstract void setCoordinate(ScriptCoordinate coordinate);
+
+    /**
+     * Gets the command state manager.
+     * @return command state manage, not null
+     */
+    public abstract CommandStateManager getCommandStateManager();
+
+    /**
+     * Gets the condition manager.
+     * @return condition manager, not null
+     */
+    public abstract ConditionManager getConditionManager();
+
+    /**
+     * Sets the condition manager.
+     * @param manager not null
+     */
+    public abstract void setConditionManager(final ConditionManager manager);
+
+    /**
+     * Gets the command manager.
+     * @return command manager, not null
+     */
+    public abstract CommandManager getCommandManager();
+    
+    /**
+     * Gets the comparator manager.
+     * @return not null
+     */
+    public abstract ComparatorManager getComparatorManager();
+    
+    /**
+     * Gets the test manager.
+     * @return test manager, not null
+     */
+    public abstract TestManager getTestManager();
+
+    /**
+     * Gets the log.
+     * @return log, not null
+     */
+    public abstract Log getLog();
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/SieveFactory.java b/trunk/main/src/main/java/org/apache/jsieve/SieveFactory.java
new file mode 100644
index 0000000..916fe37
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/SieveFactory.java
@@ -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.jsieve;
+
+import java.io.InputStream;
+
+import org.apache.commons.logging.Log;
+import org.apache.jsieve.exception.SieveException;
+import org.apache.jsieve.exception.StopException;
+import org.apache.jsieve.mail.ActionKeep;
+import org.apache.jsieve.mail.MailAdapter;
+import org.apache.jsieve.parser.generated.Node;
+import org.apache.jsieve.parser.generated.ParseException;
+import org.apache.jsieve.parser.generated.SieveParser;
+import org.apache.jsieve.parser.generated.SieveParserVisitor;
+import org.apache.jsieve.parser.generated.SimpleNode;
+
+/**
+ * <p>
+ * SieveFactory is the primary invocation point for all Sieve
+ * operations. These are...
+ * <dl>
+ * <dt>{@link #parse(InputStream)}</dt>
+ * <dd> Parse a Sieve script into a hierarchy of parsed nodes. A succesful parse
+ * means the script is lexically and gramatically valid according to RFC 3028,
+ * section 8. The result is the start node of the parsed Sieve script. The start
+ * node is resuable. Typically it is stored for reuse in all subsequent
+ * evaluations of the script. </dd>
+ * <dt>{@link #evaluate(MailAdapter, Node)}</dt>
+ * <dd> Evaluate an RFC 822 compliant mail message wrapped in a {@link MailAdapter}
+ * against the parse result referenced by the start node from the Parse
+ * operation above. As evaluation proceeds a List of {@link org.apache.jsieve.mail.Action}s 
+ * is added to the MailAdapter. At the end of evaluation, each Action in the List is executed in
+ * the order they were added. </dd>
+ * <dt>{@link #interpret(MailAdapter, InputStream)}</dt>
+ * <dd>A concatenation of parse and evaluate. Useful for testing, but generally
+ * the parse result should be stored for reuse in subsequent evaluations. </dd>
+ * </dl>
+ * </p>
+ * <h4>Thread Safety</h4>
+ * <p>
+ * An instance can be safely accessed concurrently by multiple threads
+ * provided that the managers used to construct the instance 
+ * (when {@link #SieveFactory(CommandManager, ComparatorManager, TestManager, Log)} 
+ * is called) are thread safe.
+ * </p>
+ */
+public class SieveFactory {
+
+    private final CommandManager commandManager;
+
+    private final ComparatorManager comparatorManager;
+
+    private final TestManager testManager;
+
+    private final Log log;
+
+    /**
+     * Constructor for SieveFactory.
+     */
+    public SieveFactory(final CommandManager commandManager,
+            final ComparatorManager comparatorManager,
+            final TestManager testManager, final Log log) {
+        super();
+        this.commandManager = commandManager;
+        this.comparatorManager = comparatorManager;
+        this.testManager = testManager;
+        this.log = log;
+    }
+
+    /**
+     * Method parse parses a Sieve script into a hierarchy of parsed nodes. A
+     * successful parse means the script is lexically and grammatically valid
+     * according to RFC 3028, section 8. The result is the start node of the
+     * parsed Sieve script. The start node is reusable. Typically it is stored
+     * for reuse in subsequent evaluations of the script.
+     * 
+     * @param inputStream
+     * @return Node
+     * @throws ParseException
+     */
+    public Node parse(InputStream inputStream) throws ParseException {
+        try {
+            final SimpleNode node = new SieveParser(inputStream, "UTF-8")
+                    .start();
+            SieveValidationVisitor visitor = new SieveValidationVisitor(
+                    commandManager, testManager, comparatorManager);
+            node.jjtAccept(visitor, null);
+            return node;
+        } catch (ParseException ex) {
+            if (log.isErrorEnabled())
+                log.error("Parse failed. Reason: " + ex.getMessage());
+            if (log.isDebugEnabled())
+                log.debug("Parse failed.", ex);
+            throw ex;
+        } catch (SieveException ex) {
+            if (log.isErrorEnabled())
+                log.error("Parse failed. Reason: " + ex.getMessage());
+            if (log.isDebugEnabled())
+                log.debug("Parse failed.", ex);
+            throw new ParseException(ex.getMessage());
+        }
+    }
+
+    /**
+     * <p>
+     * Method evaluate evaluates an RFC 822 compliant mail message wrapped in a
+     * MailAdapter by visting each node of the parsed script beginning at the
+     * passed start node. As evaluation proceeds a List of Actions is added to
+     * the MailAdapter.
+     * <p>
+     * 
+     * <p>
+     * At the start of evaluation an 'implicitKeep' state is set. This can be
+     * cancelled by a Command during evaluation. If 'implicitKeep' is still set
+     * at the end of evaluation, a Keep Action is added to the List of Actions.
+     * Finally, each Action in the List is executed in the order they were
+     * added.
+     * </p>
+     * 
+     * @param mail
+     * @param startNode
+     * @throws SieveException
+     */
+    public void evaluate(MailAdapter mail, Node startNode)
+            throws SieveException {
+        final SieveContext context = new BaseSieveContext(commandManager,
+                comparatorManager, testManager, log);
+        try {
+            // Ensure that the context is set on the mail
+            mail.setContext(context);
+            
+            SieveParserVisitor visitor = new SieveParserVisitorImpl(context);
+            try {
+                // Evaluate the Nodes
+                startNode.jjtAccept(visitor, mail);
+    
+            } catch (StopException ex) {
+                // Stop is OK
+            } catch (SieveException ex) {
+                if (log.isErrorEnabled())
+                    log.error("Evaluation failed. Reason: " + ex.getMessage());
+                if (log.isDebugEnabled())
+                    log.debug("Evaluation failed.", ex);
+                throw ex;
+            }
+    
+            // If after evaluating all of the nodes or stopping, implicitKeep is
+            // still
+            // in effect, add a Keep to the list of Actions.
+            if (context.getCommandStateManager().isImplicitKeep())
+                mail.addAction(new ActionKeep());
+    
+            // Execute the List of Actions
+            try {
+                mail.executeActions();
+            } catch (SieveException ex) {
+                if (log.isErrorEnabled())
+                    log.error("Evaluation failed. Reason: " + ex.getMessage());
+                if (log.isDebugEnabled())
+                    log.debug("Evaluation failed.", ex);
+                throw ex;
+            }
+        } finally {
+            // Tidy up by ensuring that a reference to the context is not held by the adapter.
+            // This prevents leaks when the adapter stores the context in a thread local variable.
+            mail.setContext(null);
+        }
+    }
+
+    /**
+     * Method interpret parses a Sieve script and then evaluates the result
+     * against a mail.
+     * 
+     * @param mail
+     * @param inputStream
+     * @throws ParseException
+     * @throws SieveException
+     */
+    public void interpret(MailAdapter mail, InputStream inputStream)
+            throws ParseException, SieveException {
+        evaluate(mail, parse(inputStream));
+    }
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/SieveParserVisitorImpl.java b/trunk/main/src/main/java/org/apache/jsieve/SieveParserVisitorImpl.java
new file mode 100644
index 0000000..67f59f8
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/SieveParserVisitorImpl.java
@@ -0,0 +1,283 @@
+/****************************************************************
+ * 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.jsieve;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.jsieve.exception.SieveException;
+import org.apache.jsieve.mail.MailAdapter;
+import org.apache.jsieve.parser.generated.ASTargument;
+import org.apache.jsieve.parser.generated.ASTarguments;
+import org.apache.jsieve.parser.generated.ASTblock;
+import org.apache.jsieve.parser.generated.ASTcommand;
+import org.apache.jsieve.parser.generated.ASTcommands;
+import org.apache.jsieve.parser.generated.ASTstart;
+import org.apache.jsieve.parser.generated.ASTstring;
+import org.apache.jsieve.parser.generated.ASTstring_list;
+import org.apache.jsieve.parser.generated.ASTtest;
+import org.apache.jsieve.parser.generated.ASTtest_list;
+import org.apache.jsieve.parser.generated.SieveParserVisitor;
+import org.apache.jsieve.parser.generated.SimpleNode;
+
+/**
+ * <p>
+ * Class SieveParserVisitorImpl defines the behaviour for each visited node in
+ * the Sieve grammar. Each method corresponds to a node type and is invoked when
+ * a node of that type is evaluated.
+ * </p>
+ * 
+ * <p>
+ * In essence, this class translates between the nodes operated on by the JavaCC
+ * generated classes and the Sieve classes operated upon by the Commands, Tests
+ * and Comparators. A visit to the start node, ASTstart, triggers evaluation of
+ * all of its descendants.
+ * </p>
+ * 
+ * <p>
+ * See https://javacc.dev.java.net/doc/JJTree.html for indepth information about
+ * Visitor support.
+ * </p>
+ * 
+ * <p>
+ * <strong>Note</strong> that this class is not thread safe. It's use should be
+ * restricted to a single thread for the duration of a visit.
+ * </p>
+ */
+public class SieveParserVisitorImpl implements SieveParserVisitor {
+    private final SieveContext context;
+
+    /**
+     * Constructor for NodeVisitor.
+     */
+    public SieveParserVisitorImpl(final SieveContext context) {
+        super();
+        this.context = context;
+    }
+
+    /**
+     * Method visitChildren adds the children of the node to the passed List.
+     * 
+     * @param node
+     * @param data -
+     *            Assumes a List
+     * @return Object - A List
+     * @throws SieveException
+     */
+    @SuppressWarnings("unchecked")
+    protected Object visitChildren(SimpleNode node, Object data)
+            throws SieveException {
+        List children = new ArrayList(node.jjtGetNumChildren());
+        node.childrenAccept(this, children);
+        ((List) data).addAll(children);
+        return data;
+    }
+
+    /**
+     * @see SieveParserVisitor#visit(ASTargument, Object)
+     */
+    @SuppressWarnings("unchecked")
+    public Object visit(ASTargument node, Object data) throws SieveException {
+        List<String> children = new ArrayList<String>(node.jjtGetNumChildren());
+        Argument argument = null;
+
+        if (null != node.getValue()) {
+            argument = (Argument) node.getValue();
+        } else {
+            argument = new StringListArgument(((List) node.childrenAccept(this,
+                    children)));
+        }
+        ((List) data).add(argument);
+
+        return data;
+    }
+
+    /**
+     * @see SieveParserVisitor#visit(ASTarguments, Object)
+     */
+    @SuppressWarnings("unchecked")
+    public Object visit(ASTarguments node, Object data) throws SieveException {
+        List children = new ArrayList(node.jjtGetNumChildren());
+        children = ((List) node.childrenAccept(this, children));
+
+        // Extract Tests and TestList from the children
+        Iterator childrenIter = children.iterator();
+        TestList testList = null;
+        List<Argument> argList = new ArrayList<Argument>(children.size());
+        while (childrenIter.hasNext()) {
+            Object next = childrenIter.next();
+            if (next instanceof Test)
+                testList = new TestList((Test) next);
+            else if (next instanceof TestList)
+                testList = (TestList) next;
+            else if (next instanceof Argument) {
+                argList.add((Argument)next);
+            } else {
+                context.getLog().error("Expected an 'Argument' but was " + next);
+            }
+        }
+
+        Arguments arguments = new Arguments(argList, testList);
+        ((List) data).add(arguments);
+        return data;
+    }
+
+    /**
+     * @see SieveParserVisitor#visit(ASTblock, Object)
+     */
+    @SuppressWarnings("unchecked")
+    public Object visit(ASTblock node, Object data) throws SieveException {
+        // if (node.jjtGetNumChildren() != 1)
+        // throw new ParseException("Expecting exactly one 1 child");
+        List children = new ArrayList(node.jjtGetNumChildren());
+        Commands commands = (Commands) ((List) node.childrenAccept(this,
+                children)).get(0);
+        Block block = new Block(commands);
+        ((List) data).add(block);
+        return data;
+    }
+
+    /**
+     * @see SieveParserVisitor#visit(ASTcommand, Object)
+     */
+    @SuppressWarnings("unchecked")
+    public Object visit(ASTcommand node, Object data) throws SieveException {
+        List children = new ArrayList(node.jjtGetNumChildren());
+        children = ((List) node.childrenAccept(this, children));
+
+        // Extract the Arguments and Block from the children
+        Iterator childrenIter = children.iterator();
+        Arguments arguments = null;
+        Block block = null;
+        while (childrenIter.hasNext()) {
+            Object next = childrenIter.next();
+            if (next instanceof Arguments)
+                arguments = (Arguments) next;
+            else if (next instanceof Block)
+                block = (Block) next;
+        }
+
+        context.setCoordinate(node.getCoordinate());
+        final ScriptCoordinate coordinate = context.getCoordinate();
+        Command command = new Command(node.getName(), arguments, block,
+                coordinate);
+        ((List) data).add(command);
+        return data;
+    }
+
+    /**
+     * @see SieveParserVisitor#visit(ASTcommands, Object)
+     */
+    @SuppressWarnings("unchecked")
+    public Object visit(ASTcommands node, Object data) throws SieveException {
+        List<Command> children = new ArrayList<Command>(node.jjtGetNumChildren());
+        Commands commands = new Commands(((List) node.childrenAccept(this,
+                children)));
+        ((List) data).add(commands);
+        return data;
+    }
+
+    /**
+     * @see SieveParserVisitor#visit(ASTstart, Object)
+     */
+    public Object visit(ASTstart node, Object data) throws SieveException {
+        // The data object must be the MailAdapter to process
+        if (!(data instanceof MailAdapter))
+            throw new SieveException("Expecting an instance of "
+                    + MailAdapter.class.getName()
+                    + " as data, received an instance of "
+                    + (data == null ? "<null>" : data.getClass().getName())
+                    + ".");
+
+        // Start is an implicit Block
+        // There will be one child, an instance of Commands
+        List children = new ArrayList(node.jjtGetNumChildren());
+        Commands commands = (Commands) ((List) node.childrenAccept(this,
+                children)).get(0);
+        Block block = new Block(commands);
+        context.setCoordinate(node.getCoordinate());
+        // Answer the result of executing the Block
+        return block.execute((MailAdapter) data, context);
+    }
+
+    /**
+     * @see SieveParserVisitor#visit(ASTstring_list, Object)
+     */
+    public Object visit(ASTstring_list node, Object data) throws SieveException {
+        return visitChildren(node, data);
+    }
+
+    /**
+     * @see SieveParserVisitor#visit(ASTstring, Object)
+     */
+    @SuppressWarnings("unchecked")
+    public Object visit(ASTstring node, Object data) {
+        // Strings are always surround by double-quotes
+        final String value = (String) node.getValue();
+        // A String is terminal, add it
+        ((List) data).add(value);
+        return data;
+    }
+
+    /**
+     * @see SieveParserVisitor#visit(ASTtest_list, Object)
+     */
+    @SuppressWarnings("unchecked")
+    public Object visit(ASTtest_list node, Object data) throws SieveException {
+        // return visitChildren(node, data);
+        List<Test> children = new ArrayList<Test>(node.jjtGetNumChildren());
+        TestList testList = new TestList(((List<Test>) node.childrenAccept(this,
+                children)));
+        ((List) data).add(testList);
+        return data;
+    }
+
+    /**
+     * @see SieveParserVisitor#visit(ASTtest, Object)
+     */
+    @SuppressWarnings("unchecked")
+    public Object visit(ASTtest node, Object data) throws SieveException {
+        List children = new ArrayList(node.jjtGetNumChildren());
+        children = ((List) node.childrenAccept(this, children));
+
+        // Extract the Arguments from the children
+        Iterator childrenIter = children.iterator();
+        Arguments arguments = null;
+        while (childrenIter.hasNext()) {
+            Object next = childrenIter.next();
+            if (next instanceof Arguments)
+                arguments = (Arguments) next;
+        }
+
+        context.setCoordinate(node.getCoordinate());
+        Test test = new Test(node.getName(), arguments);
+        ((List) data).add(test);
+        return data;
+    }
+
+    /**
+     * @see SieveParserVisitor#visit(SimpleNode, Object)
+     */
+    public Object visit(SimpleNode node, Object data) throws SieveException {
+        return visitChildren(node, data);
+    }
+
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/SieveValidationVisitor.java b/trunk/main/src/main/java/org/apache/jsieve/SieveValidationVisitor.java
new file mode 100644
index 0000000..baaca57
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/SieveValidationVisitor.java
@@ -0,0 +1,200 @@
+package org.apache.jsieve;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.jsieve.exception.LookupException;
+import org.apache.jsieve.exception.SieveException;
+import org.apache.jsieve.parser.generated.ASTargument;
+import org.apache.jsieve.parser.generated.ASTarguments;
+import org.apache.jsieve.parser.generated.ASTblock;
+import org.apache.jsieve.parser.generated.ASTcommand;
+import org.apache.jsieve.parser.generated.ASTcommands;
+import org.apache.jsieve.parser.generated.ASTstart;
+import org.apache.jsieve.parser.generated.ASTstring;
+import org.apache.jsieve.parser.generated.ASTstring_list;
+import org.apache.jsieve.parser.generated.ASTtest;
+import org.apache.jsieve.parser.generated.ASTtest_list;
+import org.apache.jsieve.parser.generated.SieveParserVisitor;
+import org.apache.jsieve.parser.generated.SimpleNode;
+
+import static org.apache.jsieve.Constants.*;
+
+/****************************************************************
+ * 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.                                           *
+ ****************************************************************/
+
+/**
+ * Validates nodes visited. Some checks are more conveniently carried out what
+ * then tree has already been constructed.
+ */
+public class SieveValidationVisitor implements SieveParserVisitor {
+
+    private final CommandManager commandManager;
+    private final TestManager testManager;
+    private final ComparatorManager comparatorManager;
+
+    private final Set<String> declaredComparators;
+    
+    private boolean requireAllowed = true;
+    /** Is the visitor within a <code>require</code>? */
+    private boolean isInRequire = false;
+    /** Is the next argument expected to be a comparator name? */
+    private boolean nextArgumentIsComparatorName = false;
+    /** Is the visitor within a comparator name argument? */
+    private boolean isInComparatorNameArgument = false;
+    
+    protected SieveValidationVisitor(final CommandManager commandManager,
+            final TestManager testManager, final ComparatorManager comparatorManager) {
+        super();
+        this.commandManager = commandManager;
+        this.testManager = testManager;
+        this.comparatorManager = comparatorManager;
+        declaredComparators = new HashSet<String>();
+    }
+
+    public Object visit(SimpleNode node, Object data) throws SieveException {
+        return visitNode(node, data);
+    }
+
+    private Object visitNode(SimpleNode node, Object data)
+            throws SieveException {
+        List children = new ArrayList(node.jjtGetNumChildren());
+        node.childrenAccept(this, children);
+        return data;
+    }
+
+    public Object visit(ASTstart node, Object data) throws SieveException {
+        return visitNode(node, data);
+    }
+
+    public Object visit(ASTcommands node, Object data) throws SieveException {
+        declaredComparators.clear();
+        return visitNode(node, data);
+    }
+
+    public Object visit(ASTcommand node, Object data) throws SieveException {
+        final String name = node.getName();
+        commandManager.getCommand(name);
+        if ("require".equalsIgnoreCase(name)) {
+            if (requireAllowed) {
+                isInRequire = true;
+            } else {
+                throw new SieveException(
+                        "'require' is only allowed before other commands");
+            }
+        } else {
+            requireAllowed = false;
+            isInRequire = false;
+        }
+        return visitNode(node, data);
+    }
+
+    public Object visit(ASTblock node, Object data) throws SieveException {
+        return visitNode(node, data);
+    }
+
+    public Object visit(ASTarguments node, Object data) throws SieveException {
+        // Reset test for explicitly required comparator types
+        nextArgumentIsComparatorName = false;
+        return visitNode(node, data);
+    }
+
+    public Object visit(ASTargument node, Object data) throws SieveException {
+        final Object value = node.getValue();
+        if (value == null) { 
+            if (nextArgumentIsComparatorName) {
+                // Mark enclosed string for check against required list
+                isInComparatorNameArgument = true;
+            }
+            nextArgumentIsComparatorName = false;
+        } else {
+            if (value instanceof TagArgument) {
+                final TagArgument tag = (TagArgument) value;
+                nextArgumentIsComparatorName = tag.isComparator();
+            } else {
+                nextArgumentIsComparatorName = false;
+            }
+        }
+        final Object result = visitNode(node, data);
+        isInComparatorNameArgument = false;
+        return result;
+    }
+
+    public Object visit(ASTtest node, Object data) throws SieveException {
+        return visitNode(node, data);
+    }
+
+    public Object visit(ASTtest_list node, Object data) throws SieveException {
+        return visitNode(node, data);
+    }
+
+    public Object visit(ASTstring node, Object data) throws SieveException {
+        if (isInRequire) {
+            requirements(node);
+        }
+        if (isInComparatorNameArgument) {
+            comparatorNameArgument(node);
+        }
+        return visitNode(node, data);
+    }
+
+    private void comparatorNameArgument(ASTstring node) throws SieveException {
+        final Object value = node.getValue();
+        if (value != null && value instanceof String) {
+            final String name = (String) value;
+            // Comparators must either be declared (either implicitly or explicitly)
+            if (!comparatorManager.isImplicitlyDeclared(name)) {
+                if (!declaredComparators.contains(name)) {
+                    // TODO: replace with better exception
+                    throw new SieveException("Comparator must be explicitly declared in a require statement.");
+                }
+            }
+        }
+    }
+
+    private void requirements(ASTstring node) throws SieveException {
+        final Object value = node.getValue();
+        if (value != null && value instanceof String) {
+            final String name = (String) value;
+            if (name.startsWith(COMPARATOR_PREFIX)) {
+                final String comparatorName = name.substring(COMPARATOR_PREFIX_LENGTH);
+                if (comparatorManager.isSupported(comparatorName)) {
+                    declaredComparators.add(comparatorName);
+                } else {
+//                  TODO: Replace with more finely grained exception
+                    throw new SieveException("Comparator " + comparatorName + " is not supported");
+                }
+            } else {
+                try {
+                    commandManager.getCommand(name);
+                } catch (LookupException e) {
+                    // TODO: catching is inefficient, should just check
+                    testManager.getTest(name);
+                }
+            }
+        }
+    }
+
+    public Object visit(ASTstring_list node, Object data) throws SieveException {
+        return visitNode(node, data);
+    }
+
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/StringListArgument.java b/trunk/main/src/main/java/org/apache/jsieve/StringListArgument.java
new file mode 100644
index 0000000..8b8d8d8
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/StringListArgument.java
@@ -0,0 +1,112 @@
+/****************************************************************
+ * 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.jsieve;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Class StringListArgument is a parsed representation of the RFC3028 BNF...
+ * </p>
+ * 
+ * <code>string-list = "[" string *("," string) "]" / string</code>
+ */
+public class StringListArgument implements Argument {
+    private List<String> fieldList;
+
+    /**
+     * Constructor for StringListArgument.
+     */
+    private StringListArgument() {
+        super();
+    }
+
+    /**
+     * Constructor for StringListArgument.
+     */
+    public StringListArgument(List<String> stringList) {
+        this();
+        setList(stringList);
+    }
+
+    /**
+     * Returns the list, lazy initialised if required.
+     * 
+     * @return List
+     */
+    public List<String> getList() {
+        List<String> list = null;
+        if (null == (list = getListBasic())) {
+            updateList();
+            return getList();
+        }
+        return list;
+    }
+
+    /**
+     * Returns the list.
+     * 
+     * @return List
+     */
+    private List<String> getListBasic() {
+        return fieldList;
+    }
+
+    /**
+     * Returns a new list.
+     * 
+     * @return List
+     */
+    protected List<String> computeList() {
+        return new ArrayList<String>();
+    }
+
+    /**
+     * Sets the list.
+     * 
+     * @param list
+     *            The list to set
+     */
+    protected void setList(List<String> list) {
+        fieldList = list;
+    }
+
+    /**
+     * Updates the list.
+     */
+    protected void updateList() {
+        setList(computeList());
+    }
+
+    /**
+     * @see org.apache.jsieve.Argument#getValue()
+     */
+    public Object getValue() {
+        return getList();
+    }
+
+    /**
+     * @see java.lang.Object#toString()
+     */
+    public String toString() {
+        return (getValue() == null) ? "null" : getValue().toString();
+    }
+
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/TagArgument.java b/trunk/main/src/main/java/org/apache/jsieve/TagArgument.java
new file mode 100644
index 0000000..e6181c2
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/TagArgument.java
@@ -0,0 +1,118 @@
+/****************************************************************
+ * 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.jsieve;
+
+import static org.apache.jsieve.Constants.TAG_COMPARATOR;
+
+import org.apache.jsieve.parser.generated.Token;
+
+/**
+ * <p>
+ * A parsed representation of an RFC3028 TAG argument...
+ * </p>
+ * 
+ * <code>tag = ":" identifier</code>
+ */
+public class TagArgument implements Argument {
+
+    /**
+     * The Tag
+     */
+    private String fieldTag;
+
+    /**
+     * Constructor for TagArgument.
+     */
+    private TagArgument() {
+        super();
+    }
+
+    /**
+     * Constructor for TagArgument.
+     * 
+     * @param token
+     */
+    public TagArgument(Token token) {
+        this();
+        setTag(token);
+    }
+
+    /**
+     * Method setTag.
+     * 
+     * @param token
+     */
+    protected void setTag(Token token) {
+        setTag(token.image);
+    }
+
+    /**
+     * Returns the tag.
+     * 
+     * @return String
+     */
+    public String getTag() {
+        return fieldTag;
+    }
+
+    /**
+     * Does this argument match the given tag?
+     * @param tag not null
+     * @return true when the tag identifier equals that given,
+     * false otherwise
+     */
+    public boolean is(String tag) {
+       return tag.equals(fieldTag); 
+    }
+    
+    /**
+     * Is this a comparator tag?
+     * @return true when identifier matches {@link Constants#TAG_COMPARATOR},
+     * false otherwise
+     */
+    public boolean isComparator() {
+        return this.is(TAG_COMPARATOR);
+    }
+    
+    /**
+     * Sets the tag.
+     * 
+     * @param tag
+     *            The tag to set
+     */
+    protected void setTag(String tag) {
+        fieldTag = tag;
+    }
+
+    /**
+     * @see org.apache.jsieve.Argument#getValue()
+     */
+    public Object getValue() {
+        return getTag();
+    }
+
+    /**
+     * @see java.lang.Object#toString()
+     */
+    public String toString() {
+        return (getValue() == null) ? "null" : getValue().toString();
+    }
+
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/Test.java b/trunk/main/src/main/java/org/apache/jsieve/Test.java
new file mode 100644
index 0000000..338c34e
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/Test.java
@@ -0,0 +1,134 @@
+/****************************************************************
+ * 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.jsieve;
+
+import org.apache.commons.logging.Log;
+import org.apache.jsieve.exception.LookupException;
+import org.apache.jsieve.exception.SieveException;
+import org.apache.jsieve.mail.MailAdapter;
+import org.apache.jsieve.tests.ExecutableTest;
+
+/**
+ * <p>
+ * A parsed representation of an RFC3028 test argument...
+ * </p>
+ * 
+ * <code>test = identifier arguments</code>
+ */
+public class Test implements Executable {
+
+    /** The name of this Test */
+    private String fieldName;
+
+    /** The arguments for this Test */
+    private Arguments fieldArguments;
+
+    /**
+     * @see org.apache.jsieve.Executable#execute(MailAdapter, SieveContext)
+     */
+    public Object execute(MailAdapter mail, SieveContext context)
+            throws SieveException {
+        return new Boolean(isTestPassed(mail, context));
+    }
+
+    /**
+     * Is this test passed for the given mail?
+     * @param mail not null
+     * @param context not null
+     * @return true when the test passes, false otherwise
+     * @throws LookupException
+     * @throws SieveException
+     */
+    public boolean isTestPassed(MailAdapter mail, SieveContext context) throws LookupException, SieveException {
+        Log log = context.getLog();
+        if (log.isDebugEnabled()) {
+            log.debug(toString());
+        }
+        final String name = getName();
+        final ExecutableTest test = context.getTestManager().getTest(name);
+        return test.execute(mail, getArguments(), context);
+    }
+
+    /**
+     * Constructor for Test.
+     */
+    private Test() {
+        super();
+    }
+
+    /**
+     * Constructor for Test.
+     * 
+     * @param name
+     * @param arguments
+     */
+    public Test(String name, Arguments arguments) {
+        this();
+        setName(name);
+        setArguments(arguments);
+    }
+
+    /**
+     * Returns the arguments.
+     * 
+     * @return Arguments
+     */
+    public Arguments getArguments() {
+        return fieldArguments;
+    }
+
+    /**
+     * Returns the name.
+     * 
+     * @return String
+     */
+    public String getName() {
+        return fieldName;
+    }
+
+    /**
+     * Sets the arguments.
+     * 
+     * @param arguments
+     *            The arguments to set
+     */
+    protected void setArguments(Arguments arguments) {
+        fieldArguments = arguments;
+    }
+
+    /**
+     * Sets the name.
+     * 
+     * @param name
+     *            The name to set
+     */
+    protected void setName(String name) {
+        fieldName = name;
+    }
+
+    /**
+     * @see java.lang.Object#toString()
+     */
+    public String toString() {
+        return "Test name: " + getName() + " "
+                + (getArguments() == null ? "null" : getArguments().toString());
+    }
+
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/TestList.java b/trunk/main/src/main/java/org/apache/jsieve/TestList.java
new file mode 100644
index 0000000..7058816
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/TestList.java
@@ -0,0 +1,152 @@
+/****************************************************************
+ * 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.jsieve;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.jsieve.exception.SieveException;
+import org.apache.jsieve.mail.MailAdapter;
+
+/**
+ * <p>
+ * A parsed representation of an RFC3028 testlist argument...
+ * </p>
+ * 
+ * <code>test-list = "(" test *("," test) ")"</code>
+ */
+public class TestList implements Executable {
+    /**
+     * List of Tests
+     */
+    private List<Test> fieldTests;
+
+    /**
+     * Constructor for TestList.
+     */
+    private TestList() {
+        super();
+    }
+
+    /**
+     * Constructor for TestList.
+     * 
+     * @param children -
+     *            A List of Tests
+     */
+    public TestList(List<Test> children) {
+        this();
+        setTests(children);
+    }
+
+    /**
+     * Constructor for TestList.
+     * 
+     * @param child -
+     *            A Test
+     */
+    public TestList(Test child) {
+        this();
+        List<Test> children = new ArrayList<Test>();
+        children.add(child);
+        setTests(children);
+    }
+
+    /**
+     * @see org.apache.jsieve.Executable#execute(MailAdapter, SieveContext)
+     */
+    public Object execute(MailAdapter mail, SieveContext context)
+            throws SieveException {
+        return new Boolean(allTestsPass(mail, context));
+    }
+
+    /**
+     * Do all tests pass for the given mail?
+     * 
+     * @param mail not null
+     * @param context not null
+     * @return false when any test in the list fails when run against the given mail,
+     * true when no tests fail
+     * @throws SieveException
+     */
+    public boolean allTestsPass(MailAdapter mail, SieveContext context) throws SieveException {
+        boolean result = true;
+        for (Test test:getTests()) {
+            result = test.isTestPassed(mail, context);
+            if (!result) {
+                break;
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Do any tests pass for the given mail?
+     * 
+     * @param mail not null
+     * @param context not null
+     * @return true when any test in this list passes,
+     * false otherwise
+     * @throws SieveException
+     */
+    public boolean anyTestsPass(MailAdapter mail, SieveContext context) throws SieveException {
+        boolean result = false;
+        for (Test test:getTests()) {
+            result = test.isTestPassed(mail, context);
+            if (result) {
+                break;
+            }
+        }
+        return result;
+    }
+    
+    /**
+     * Returns the children.
+     * 
+     * @return List
+     */
+    public List<Test> getTests() {
+        return fieldTests;
+    }
+
+    /**
+     * Sets the children.
+     * 
+     * @param children
+     *            The children to set
+     */
+    protected void setTests(List<Test> children) {
+        fieldTests = children;
+    }
+
+    public String toString() {
+        return "TEST LIST: " + fieldTests;
+    }
+
+    /**
+     * Is this test list empty?
+     * @return true when empty,
+     * false when tests exist
+     */
+    public boolean isEmpty() {
+        return fieldTests.isEmpty();
+    }
+
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/TestManager.java b/trunk/main/src/main/java/org/apache/jsieve/TestManager.java
new file mode 100644
index 0000000..3027bd6
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/TestManager.java
@@ -0,0 +1,43 @@
+/****************************************************************
+ * 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.jsieve;
+
+import org.apache.jsieve.exception.LookupException;
+import org.apache.jsieve.tests.ExecutableTest;
+
+/**
+ * <p>Maps Test names to configured Test implementation classes.</p>
+ * <h4>Thread Safety</h4>
+ * <p>
+ * Implementation dependent. {@link TestManagerImpl} is a thread safe implementation.
+ * </p>
+ */
+public interface TestManager {
+
+    /**
+     * <p>Gets a test instance by name.</p>
+     * 
+     * @param name -
+     *            The name of the Test
+     * @return the test, not null
+     * @throws LookupException
+     */
+    public ExecutableTest getTest(String name) throws LookupException;
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/TestManagerImpl.java b/trunk/main/src/main/java/org/apache/jsieve/TestManagerImpl.java
new file mode 100644
index 0000000..8de211d
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/TestManagerImpl.java
@@ -0,0 +1,109 @@
+/****************************************************************
+ * 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.jsieve;
+
+import java.util.concurrent.ConcurrentMap;
+
+import org.apache.jsieve.exception.LookupException;
+import org.apache.jsieve.tests.ExecutableTest;
+
+/**
+ * <p>Maps Test names to configured Test implementation classes.</p>
+ * <h4>Thread Safety</h4>
+ * <p>
+ * Instances may safely be accessed concurrently by multiple threads.
+ * </p>
+ */
+public class TestManagerImpl implements TestManager {
+
+    private final ConcurrentMap<String, String> classNameMap;
+
+    /**
+     * TestManager is instanciated with getInstance
+     */
+    public TestManagerImpl(final ConcurrentMap<String, String> classNameMap) {
+        super();
+        this.classNameMap = classNameMap;
+    }
+
+    /**
+     * <p>
+     * Method lookup answers the class to which a Test name is mapped.
+     * </p>
+     * 
+     * @param name -
+     *            The name of the Test
+     * @return Class - The class of the Test
+     * @throws LookupException
+     */
+    public Class lookup(String name) throws LookupException {
+        Class testClass = null;
+
+        try {
+            testClass = getClass().getClassLoader().loadClass(
+                    getClassName(name));
+        } catch (ClassNotFoundException e) {
+            throw new LookupException("Test named '" + name + "' not found.");
+        }
+        if (!ExecutableTest.class.isAssignableFrom(testClass))
+            throw new LookupException("Class " + testClass.getName()
+                    + " must implement " + ExecutableTest.class.getName());
+        return testClass;
+    }
+
+    /**
+     * <p>
+     * Method newInstance answers an instance of the class to which a Test name
+     * is mapped.
+     * </p>
+     * 
+     * @param name -
+     *            The name of the Test
+     * @return Class - The class of the Test
+     * @throws LookupException
+     */
+    public ExecutableTest getTest(String name) throws LookupException {
+        try {
+            return (ExecutableTest) lookup(name).newInstance();
+        } catch (InstantiationException e) {
+            throw new LookupException(e.getMessage());
+        } catch (IllegalAccessException e) {
+            throw new LookupException(e.getMessage());
+        }
+    }
+
+    /**
+     * <p>
+     * Method getClassName answers the name of the class to which a Test name is
+     * mapped.
+     * </p>
+     * 
+     * @param name -
+     *            The name of the Test
+     * @return String - The name of the class
+     * @throws LookupException
+     */
+    private String getClassName(String name) throws LookupException {
+        final String className = classNameMap.get(name.toLowerCase());
+        if (null == className)
+            throw new LookupException("Test named '" + name + "' not mapped.");
+        return className;
+    }
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/commands/AbstractActionCommand.java b/trunk/main/src/main/java/org/apache/jsieve/commands/AbstractActionCommand.java
new file mode 100644
index 0000000..a0bde22
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/commands/AbstractActionCommand.java
@@ -0,0 +1,104 @@
+/****************************************************************
+ * 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.jsieve.commands;
+
+import java.util.List;
+
+import org.apache.jsieve.Argument;
+import org.apache.jsieve.Arguments;
+import org.apache.jsieve.SieveContext;
+import org.apache.jsieve.StringListArgument;
+import org.apache.jsieve.exception.CommandException;
+import org.apache.jsieve.exception.SieveException;
+
+/**
+ * Abstract class AbstractActionCommand defines the common state validation and
+ * state update behavior for Action Commands as per RFC 3028, section 8.
+ */
+public abstract class AbstractActionCommand extends AbstractBodyCommand {
+
+    /**
+     * Constructor for AbstractActionCommand.
+     */
+    public AbstractActionCommand() {
+        super();
+    }
+
+    /**
+     * <p>
+     * Method updateState() updates the CommandStateManager to indicate an
+     * Action Command has been processed and to cancel implicit keep.
+     * </p>
+     * 
+     * <p>
+     * And also
+     * </p>
+     * 
+     * @see org.apache.jsieve.commands.AbstractCommand#updateState(SieveContext)
+     */
+    protected void updateState(SieveContext context) {
+        super.updateState(context);
+        context.getCommandStateManager().setHasActions(true);
+        context.getCommandStateManager().setImplicitKeep(false);
+    }
+
+    /**
+     * <p>
+     * Method validateState() validates via the CommandStateManager that an
+     * Action Command is legal at this time.
+     * </p>
+     * 
+     * <p>
+     * Also,
+     * </p>
+     * 
+     * @see org.apache.jsieve.commands.AbstractCommand#validateState(SieveContext)
+     */
+    protected void validateState(SieveContext context) throws CommandException {
+        if (context.getCommandStateManager().isRejected())
+            throw context.getCoordinate().commandException(
+                    "Cannot perform Actions on a rejected message.");
+    }
+
+    /**
+     * This is an utility method for subclasses
+     * 
+     * @see org.apache.jsieve.commands.Redirect
+     * @see org.apache.jsieve.commands.optional.FileInto
+     * @see org.apache.jsieve.commands.optional.Reject
+     */
+    protected void validateSingleStringArguments(Arguments arguments,
+            SieveContext context) throws SieveException {
+        List<Argument> args = arguments.getArgumentList();
+        if (args.size() != 1)
+            throw context.getCoordinate().syntaxException(
+                    "Exactly 1 argument permitted. Found " + args.size());
+
+        Argument argument = args.get(0);
+        if (!(argument instanceof StringListArgument))
+            throw context.getCoordinate().syntaxException(
+                    "Expecting a string-list");
+
+        if (1 != ((StringListArgument) argument).getList().size())
+            throw context.getCoordinate().syntaxException(
+                    "Expecting exactly one argument");
+    }
+
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/commands/AbstractBodyCommand.java b/trunk/main/src/main/java/org/apache/jsieve/commands/AbstractBodyCommand.java
new file mode 100644
index 0000000..905246c
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/commands/AbstractBodyCommand.java
@@ -0,0 +1,53 @@
+/****************************************************************
+ * 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.jsieve.commands;
+
+import org.apache.jsieve.SieveContext;
+
+/**
+ * Abstract class AbstractBodyCommand defines the common state update behavior
+ * for Body Commands. All Commands which are not PrologCommands are Body
+ * Commands.
+ */
+public abstract class AbstractBodyCommand extends AbstractCommand {
+
+    /**
+     * Constructor for AbstractBodyCommand.
+     */
+    public AbstractBodyCommand() {
+        super();
+    }
+
+    /**
+     * <p>
+     * Method updateState() updates the CommandStateManager to indicate a Body
+     * Command has been processed.
+     * </p>
+     * 
+     * <p>
+     * Also,
+     * 
+     * @see org.apache.jsieve.commands.AbstractCommand#updateState(SieveContext)
+     */
+    protected void updateState(final SieveContext context) {
+        context.getCommandStateManager().setInProlog(false);
+    }
+
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/commands/AbstractCommand.java b/trunk/main/src/main/java/org/apache/jsieve/commands/AbstractCommand.java
new file mode 100644
index 0000000..407bf06
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/commands/AbstractCommand.java
@@ -0,0 +1,143 @@
+/****************************************************************
+ * 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.jsieve.commands;
+
+import org.apache.jsieve.Arguments;
+import org.apache.jsieve.Block;
+import org.apache.jsieve.ExecutableCommand;
+import org.apache.jsieve.SieveContext;
+import org.apache.jsieve.exception.CommandException;
+import org.apache.jsieve.exception.SieveException;
+import org.apache.jsieve.mail.MailAdapter;
+
+/**
+ * Abstract class AbstractCommand defines a framework of common behavior for
+ * Sieve Commands.
+ */
+public abstract class AbstractCommand implements ExecutableCommand {
+
+    /**
+     * Constructor for AbstractCommand.
+     */
+    public AbstractCommand() {
+        super();
+    }
+
+    /**
+     * Framework method validateState is invoked before a Sieve Command is
+     * executed to validate its state. Subclass methods are expected to override
+     * or extend this method to perform their own validation as appropriate.
+     * 
+     * @param context
+     *            <code>SieveContext</code> giving contextual information, not
+     *            null
+     * @throws CommandException
+     */
+    protected void validateState(SieveContext context) throws CommandException {
+    }
+
+    /**
+     * Framework method updateState is invoked after a Sieve Command has
+     * executed to update the Sieve state. Subclass methods are expected to
+     * override or extend this method to update state as appropriate.
+     * 
+     * @param context not null
+     */
+    protected void updateState(SieveContext context) {
+    }
+
+    /**
+     * Framework method validateArguments is invoked before a Sieve Command is
+     * executed to validate its arguments. Subclass methods are expected to
+     * override or extend this method to perform their own validation as
+     * appropriate.
+     * 
+     * @param arguments
+     * @param context
+     *            <code>SieveContext</code> giving contextual information, not
+     *            null
+     * @throws SieveException
+     */
+    protected void validateArguments(Arguments arguments, SieveContext context)
+            throws SieveException {
+        if (!arguments.getArgumentList().isEmpty())
+            throw context.getCoordinate().syntaxException(
+                    "Found unexpected arguments");
+    }
+
+    /**
+     * Framework method validateBlock is invoked before a Sieve Command is
+     * executed to validate its Block. Subclass methods are expected to override
+     * or extend this method to perform their own validation as appropriate.
+     * 
+     * @param block
+     * @param context
+     *            <code>ScriptCoordinate</code> giving positional information,
+     *            not null
+     * @throws SieveException
+     */
+    protected void validateBlock(Block block, SieveContext context)
+            throws SieveException {
+        if (null != block)
+            throw context.getCoordinate().syntaxException(
+                    "Found unexpected Block. Missing ';'?");
+    }
+
+    /**
+     * <p>
+     * Method execute executes a basic Sieve Command after first invoking
+     * framework methods to validate that Sieve is in a legal state to invoke
+     * the Command and that the Command arguments are legal. After invocation, a
+     * framework method is invoked to update the state.
+     * </p>
+     * 
+     * <p>
+     * Also,
+     * </p>
+     * 
+     * @see org.apache.jsieve.Executable#execute(MailAdapter, SieveContext)
+     */
+    public Object execute(MailAdapter mail, Arguments arguments, Block block,
+            SieveContext context) throws SieveException {
+        validateState(context);
+        validateArguments(arguments, context);
+        validateBlock(block, context);
+        Object result = executeBasic(mail, arguments, block, context);
+        updateState(context);
+        return result;
+    }
+
+    /**
+     * Abstract method executeBasic invokes a Sieve Command.
+     * 
+     * @param mail
+     * @param arguments
+     * @param block
+     * @param context
+     *            <code>SieveContext</code> giving contextual information, not
+     *            null
+     * @return Object
+     * @throws SieveException
+     */
+    abstract protected Object executeBasic(MailAdapter mail,
+            Arguments arguments, Block block, SieveContext context)
+            throws SieveException;
+
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/commands/AbstractConditionalCommand.java b/trunk/main/src/main/java/org/apache/jsieve/commands/AbstractConditionalCommand.java
new file mode 100644
index 0000000..85727a2
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/commands/AbstractConditionalCommand.java
@@ -0,0 +1,78 @@
+/****************************************************************
+ * 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.jsieve.commands;
+
+import org.apache.jsieve.Block;
+import org.apache.jsieve.ConditionManager;
+import org.apache.jsieve.SieveContext;
+import org.apache.jsieve.exception.SieveException;
+import org.apache.jsieve.mail.MailAdapter;
+
+/**
+ * Abstract class AbstractConditionalCommand defines a framework of common
+ * behavior for conditional Commands (if, elsif, else). Conditional commands use
+ * a ConditionManager to relate and validate Commands within their Blocks.
+ */
+public abstract class AbstractConditionalCommand extends AbstractControlCommand {
+
+    /**
+     * Constructor for AbstractConditionalCommand.
+     */
+    public AbstractConditionalCommand() {
+        super();
+    }
+
+    /**
+     * Method execute executes a Block within the context of a new
+     * ConditionManager.
+     * 
+     * @param mail not null
+     * @param block not null
+     * @param context not null
+     * @return Object 
+     * @throws SieveException
+     */
+    protected Object execute(MailAdapter mail, Block block, SieveContext context)
+            throws SieveException {
+        // Switch to a new ConditionManager
+        ConditionManager oldManager = context.getConditionManager();
+        context.setConditionManager(new ConditionManager());
+
+        try {
+            // Execute the Block
+            Object result = block.execute(mail, context);
+            return result;
+        } finally {
+            // Always restore the old ConditionManager
+            context.setConditionManager(oldManager);
+        }
+    }
+
+    /**
+     * @see org.apache.jsieve.commands.AbstractCommand#validateBlock(Block,
+     *      SieveContext)
+     */
+    protected void validateBlock(Block block, SieveContext context)
+            throws SieveException {
+        if (null == block)
+            throw context.getCoordinate().syntaxException("Expecting a Block.");
+    }
+
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/commands/AbstractControlCommand.java b/trunk/main/src/main/java/org/apache/jsieve/commands/AbstractControlCommand.java
new file mode 100644
index 0000000..ce568b5
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/commands/AbstractControlCommand.java
@@ -0,0 +1,36 @@
+/****************************************************************
+ * 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.jsieve.commands;
+
+/**
+ * Abstract class AbstractControlCommand defines a framework of common behavior
+ * for control Commands as defined in RFC 3028, section 3.
+ */
+
+public abstract class AbstractControlCommand extends AbstractBodyCommand {
+
+    /**
+     * Constructor for AbstractControlCommand.
+     */
+    public AbstractControlCommand() {
+        super();
+    }
+
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/commands/AbstractPrologCommand.java b/trunk/main/src/main/java/org/apache/jsieve/commands/AbstractPrologCommand.java
new file mode 100644
index 0000000..0037c3a
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/commands/AbstractPrologCommand.java
@@ -0,0 +1,61 @@
+/****************************************************************
+ * 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.jsieve.commands;
+
+import org.apache.jsieve.SieveContext;
+import org.apache.jsieve.exception.CommandException;
+
+/**
+ * <p>
+ * Abstract class AbstractPrologCommand defines the common state validation
+ * behavior for Prolog Commands. In RFC 3028 the only Prolog Command is
+ * 'requires', however, the specification may evolve.
+ * </p>
+ */
+public abstract class AbstractPrologCommand extends AbstractCommand {
+
+    /**
+     * Constructor for AbstractPrologCommand.
+     */
+    public AbstractPrologCommand() {
+        super();
+    }
+
+    /**
+     * <p>
+     * Method validateState() ensures, via the CommandStateManager, that a
+     * Prolog Command is permissible.
+     * </p>
+     * 
+     * <p>
+     * Also,
+     * </p>
+     * 
+     * @see org.apache.jsieve.commands.AbstractCommand#validateState(SieveContext)
+     */
+    protected void validateState(SieveContext context) throws CommandException {
+        super.validateState(context);
+
+        if (!(context.getCommandStateManager().isInProlog()))
+            throw context.getCoordinate().commandException(
+                    "Invalid state for a prolog command.");
+    }
+
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/commands/Discard.java b/trunk/main/src/main/java/org/apache/jsieve/commands/Discard.java
new file mode 100644
index 0000000..e55cf9b
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/commands/Discard.java
@@ -0,0 +1,60 @@
+/****************************************************************
+ * 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.jsieve.commands;
+
+import org.apache.jsieve.Arguments;
+import org.apache.jsieve.Block;
+import org.apache.jsieve.SieveContext;
+import org.apache.jsieve.exception.SieveException;
+import org.apache.jsieve.mail.MailAdapter;
+
+/**
+ * Class Discard implements the Discard Command as defined in RFC 3028, section
+ * 4.5.
+ */
+public class Discard extends AbstractActionCommand {
+
+    /**
+     * Constructor for Discard.
+     */
+    public Discard() {
+        super();
+    }
+
+    /**
+     * <p>
+     * Discard silently discards a Mail by cancelling the implicit keep as
+     * specified in RFC 3028, Section 4.5.
+     * </p>
+     * <p>
+     * 
+     * @see org.apache.jsieve.commands.AbstractCommand#executeBasic(MailAdapter,
+     *      Arguments, Block, SieveContext)
+     *      </p>
+     */
+    protected Object executeBasic(MailAdapter mail, Arguments arguments,
+            Block block, SieveContext context) throws SieveException {
+        // Just cancels the implicit keep
+        // See http://tools.ietf.org/html/rfc5228#section-4.4
+        context.getCommandStateManager().setImplicitKeep(false);
+        return null;
+    }
+
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/commands/Else.java b/trunk/main/src/main/java/org/apache/jsieve/commands/Else.java
new file mode 100644
index 0000000..843ab80
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/commands/Else.java
@@ -0,0 +1,73 @@
+/****************************************************************
+ * 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.jsieve.commands;
+
+import org.apache.jsieve.Arguments;
+import org.apache.jsieve.Block;
+import org.apache.jsieve.SieveContext;
+import org.apache.jsieve.exception.SieveException;
+import org.apache.jsieve.mail.MailAdapter;
+
+/**
+ * Class Else implements the Else Command as defined in RFC 3028, section 3.1.
+ */
+public class Else extends AbstractConditionalCommand {
+
+    /**
+     * Constructor for Else.
+     */
+    public Else() {
+        super();
+    }
+
+    /**
+     * <p>
+     * Conditionally eexecute a Block if an Else Condition is runnable.
+     * </p>
+     * <p>
+     * Also,
+     * 
+     * @see org.apache.jsieve.commands.AbstractCommand#executeBasic(MailAdapter,
+     *      Arguments, Block, SieveContext)
+     *      </p>
+     */
+    protected Object executeBasic(MailAdapter mail, Arguments arguments,
+            Block block, SieveContext context) throws SieveException {
+        // Check Syntax
+        if (!context.getConditionManager().isElseAllowed())
+            throw context.getCoordinate().commandException(
+                    "Unexpected Command: \"else\".");
+
+        // Check Runnable
+        if (!context.getConditionManager().isElseRunnable())
+            return Boolean.FALSE;
+
+        // Execute the Block
+        execute(mail, block, context);
+
+        // Update the ConditionManager
+        // 'Else' is always true
+        context.getConditionManager().setElseTestResult(true);
+
+        // Return the result
+        return Boolean.TRUE;
+    }
+
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/commands/Elsif.java b/trunk/main/src/main/java/org/apache/jsieve/commands/Elsif.java
new file mode 100644
index 0000000..250ebb3
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/commands/Elsif.java
@@ -0,0 +1,91 @@
+/****************************************************************
+ * 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.jsieve.commands;
+
+import org.apache.jsieve.Arguments;
+import org.apache.jsieve.Block;
+import org.apache.jsieve.SieveContext;
+import org.apache.jsieve.TestList;
+import org.apache.jsieve.exception.SieveException;
+import org.apache.jsieve.mail.MailAdapter;
+
+/**
+ * Class Elsif implements the Elsif Command as defined in RFC 3028, section 3.1.
+ */
+public class Elsif extends AbstractConditionalCommand {
+
+    /**
+     * Constructor for Elsif.
+     */
+    public Elsif() {
+        super();
+    }
+
+    /**
+     * <p>
+     * Conditionally eexecute a Block if an Elsif Condition is allowed and
+     * runnable.
+     * </p>
+     * <p>
+     * Also,
+     * 
+     * @see org.apache.jsieve.commands.AbstractCommand#executeBasic(MailAdapter,
+     *      Arguments, Block, SieveContext)
+     *      </p>
+     */
+    protected Object executeBasic(MailAdapter mail, Arguments arguments,
+            Block block, SieveContext context) throws SieveException {
+        // Check Syntax
+        if (!context.getConditionManager().isElsifAllowed())
+            throw context.getCoordinate().commandException(
+                    "Unexpected Command: \"elsif\".");
+
+        // Check Runnable
+        if (!context.getConditionManager().isElsifRunnable())
+            return Boolean.FALSE;
+
+        // Run the tests
+        Boolean isTestPassed = (Boolean) arguments.getTestList().execute(mail,
+                context);
+
+        // If the tests answered TRUE, execute the Block
+        if (isTestPassed.booleanValue())
+            execute(mail, block, context);
+
+        // Update the ConditionManager
+        context.getConditionManager().setElsifTestResult(
+                isTestPassed.booleanValue());
+
+        // Return the result
+        return isTestPassed;
+    }
+
+    /**
+     * @see org.apache.jsieve.commands.AbstractCommand#validateArguments(Arguments,
+     *      SieveContext)
+     */
+    protected void validateArguments(Arguments arguments, SieveContext context)
+            throws SieveException {
+        TestList testList = arguments.getTestList();
+        if (null == testList || testList.getTests().isEmpty())
+            throw context.getCoordinate().syntaxException("Expecting a Test");
+    }
+
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/commands/If.java b/trunk/main/src/main/java/org/apache/jsieve/commands/If.java
new file mode 100644
index 0000000..67a68f9
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/commands/If.java
@@ -0,0 +1,86 @@
+/****************************************************************
+ * 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.jsieve.commands;
+
+import org.apache.jsieve.Arguments;
+import org.apache.jsieve.Block;
+import org.apache.jsieve.SieveContext;
+import org.apache.jsieve.exception.SieveException;
+import org.apache.jsieve.mail.MailAdapter;
+
+/**
+ * Class If implements the If Command as defined in RFC 3028, section 3.1.
+ */
+public class If extends AbstractConditionalCommand {
+    /**
+     * Constructor for If.
+     */
+    public If() {
+        super();
+    }
+
+    /**
+     * <p>
+     * Conditionally eexecute a Block if an If Condition is allowed and
+     * runnable.
+     * </p>
+     * <p>
+     * Also,
+     * 
+     * @see org.apache.jsieve.commands.AbstractCommand#executeBasic(MailAdapter,
+     *      Arguments, Block, SieveContext)
+     *      </p>
+     */
+    protected Object executeBasic(MailAdapter mail, Arguments arguments,
+            Block block, SieveContext context) throws SieveException {
+        // Check Syntax
+        if (!context.getConditionManager().isIfAllowed())
+            throw context.getCoordinate().commandException(
+                    "Unexpected Command: \"if\".");
+
+        // Check Runnable
+        if (!context.getConditionManager().isIfRunnable())
+            return Boolean.FALSE;
+
+        // Run the tests
+        final boolean isTestPassed = arguments.getTestList().allTestsPass(mail,context);
+
+        // If the tests answered TRUE, execute the Block
+        if (isTestPassed)
+            execute(mail, block, context);
+
+        // Update the ConditionManager
+        context.getConditionManager().setIfTestResult(isTestPassed);
+
+        // Return the result
+        return isTestPassed;
+    }
+
+    /**
+     * @see org.apache.jsieve.commands.AbstractCommand#validateArguments(Arguments,
+     *      SieveContext)
+     */
+    protected void validateArguments(Arguments arguments, SieveContext context)
+            throws SieveException {
+        if (!arguments.hasTests())
+            throw context.getCoordinate().syntaxException("Expecting a Test");
+    }
+
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/commands/Keep.java b/trunk/main/src/main/java/org/apache/jsieve/commands/Keep.java
new file mode 100644
index 0000000..3e5e544
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/commands/Keep.java
@@ -0,0 +1,58 @@
+/****************************************************************
+ * 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.jsieve.commands;
+
+import org.apache.jsieve.Arguments;
+import org.apache.jsieve.Block;
+import org.apache.jsieve.SieveContext;
+import org.apache.jsieve.exception.SieveException;
+import org.apache.jsieve.mail.ActionKeep;
+import org.apache.jsieve.mail.MailAdapter;
+
+/**
+ * Class Keep implements the Keep Command as defined in RFC 3028, section 4.4.
+ */
+public class Keep extends AbstractActionCommand {
+
+    /**
+     * Constructor for Keep.
+     */
+    public Keep() {
+        super();
+    }
+
+    /**
+     * <p>
+     * Add an ActionKeep to the List of Actions to be performed.
+     * </p>
+     * <p>
+     * Also,
+     * 
+     * @see org.apache.jsieve.commands.AbstractCommand#executeBasic(MailAdapter,
+     *      Arguments, Block, SieveContext)
+     *      </p>
+     */
+    protected Object executeBasic(MailAdapter mail, Arguments arguments,
+            Block block, SieveContext context) throws SieveException {
+        mail.addAction(new ActionKeep());
+        return null;
+    }
+
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/commands/Redirect.java b/trunk/main/src/main/java/org/apache/jsieve/commands/Redirect.java
new file mode 100644
index 0000000..3b8b999
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/commands/Redirect.java
@@ -0,0 +1,74 @@
+/****************************************************************
+ * 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.jsieve.commands;
+
+import org.apache.jsieve.Arguments;
+import org.apache.jsieve.Block;
+import org.apache.jsieve.SieveContext;
+import org.apache.jsieve.StringListArgument;
+import org.apache.jsieve.exception.SieveException;
+import org.apache.jsieve.mail.ActionRedirect;
+import org.apache.jsieve.mail.MailAdapter;
+
+/**
+ * Class Redirect implements the Redirect Command as defined in RFC 3028,
+ * section 4.3.
+ */
+public class Redirect extends AbstractActionCommand {
+
+    /**
+     * Constructor for Redirect.
+     */
+    public Redirect() {
+        super();
+    }
+
+    /**
+     * <p>
+     * Add an ActionRedirect to the List of Actions to be performed passing the
+     * sole StringList argument as the recipient.
+     * </p>
+     * <p>
+     * Also,
+     * 
+     * @see org.apache.jsieve.commands.AbstractCommand#executeBasic(MailAdapter,
+     *      Arguments, Block, SieveContext)
+     *      </p>
+     */
+    protected Object executeBasic(MailAdapter mail, Arguments arguments,
+            Block block, SieveContext context) throws SieveException {
+        String recipient = ((StringListArgument) arguments
+                .getArgumentList().get(0)).getList().get(0);
+
+        mail.addAction(new ActionRedirect(recipient));
+
+        return null;
+    }
+
+    /**
+     * @see org.apache.jsieve.commands.AbstractCommand#validateArguments(Arguments,
+     *      SieveContext)
+     */
+    protected void validateArguments(Arguments arguments, SieveContext context)
+            throws SieveException {
+        validateSingleStringArguments(arguments, context);
+    }
+
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/commands/Require.java b/trunk/main/src/main/java/org/apache/jsieve/commands/Require.java
new file mode 100644
index 0000000..0e224e8
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/commands/Require.java
@@ -0,0 +1,147 @@
+/****************************************************************
+ * 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.jsieve.commands;
+
+import static org.apache.jsieve.Constants.COMPARATOR_PREFIX;
+import static org.apache.jsieve.Constants.COMPARATOR_PREFIX_LENGTH;
+
+import java.util.List;
+
+import org.apache.jsieve.Argument;
+import org.apache.jsieve.Arguments;
+import org.apache.jsieve.Block;
+import org.apache.jsieve.SieveContext;
+import org.apache.jsieve.StringListArgument;
+import org.apache.jsieve.exception.FeatureException;
+import org.apache.jsieve.exception.LookupException;
+import org.apache.jsieve.exception.SieveException;
+import org.apache.jsieve.mail.MailAdapter;
+
+/**
+ * Class Require implements the Require Command as defined in RFC 3028, section
+ * 3.2.
+ */
+public class Require extends AbstractPrologCommand {
+
+    /**
+     * Constructor for Require.
+     */
+    public Require() {
+        super();
+    }
+
+    /**
+     * <p>
+     * Ensure the required feature is configured.
+     * </p>
+     * <p>
+     * Also,
+     * 
+     * @see org.apache.jsieve.commands.AbstractCommand#executeBasic(MailAdapter,
+     *      Arguments, Block, SieveContext)
+     *      </p>
+     */
+    protected Object executeBasic(MailAdapter mail, Arguments arguments,
+            Block block, SieveContext context) throws SieveException {
+        final List<String> stringArgumentList = ((StringListArgument) arguments
+                                .getArgumentList().get(0)).getList();
+        for (String stringArgument: stringArgumentList) {
+            validateFeature(stringArgument, mail, context);
+        }
+        return null;
+    }
+
+    /**
+     * Method validateFeature validates the required feature is configured as
+     * either a Command or a Test.
+     * 
+     * @param name
+     * @param mail
+     * @param context not nul
+     * @throws FeatureException
+     */
+    protected void validateFeature(String name, MailAdapter mail,
+            SieveContext context) throws FeatureException {
+        if (name.startsWith(COMPARATOR_PREFIX)) {
+            final String comparatorName = name.substring(COMPARATOR_PREFIX_LENGTH);
+            if (!context.getComparatorManager().isSupported(comparatorName)) {
+                throw new FeatureException("Comparator \"" + comparatorName
+                        + "\" is not supported.");
+            }
+        } else {
+            // Validate as a Command
+            try {
+                validateCommand(name, context);
+                return;
+            } catch (LookupException e) {
+                // Not a command
+            }
+    
+            // Validate as a Test
+            try {
+                validateTest(name, context);
+            } catch (LookupException e) {
+                throw new FeatureException("Feature \"" + name
+                        + "\" is not supported.");
+            }
+        }
+    }
+
+    /**
+     * Method validateCommand.
+     * 
+     * @param name
+     * @throws LookupException
+     */
+    protected void validateCommand(String name, SieveContext context)
+            throws LookupException {
+        context.getCommandManager().getCommand(name);
+    }
+
+    /**
+     * Method validateTest.
+     * 
+     * @param name not null
+     * @param context not null
+     * @throws LookupException
+     */
+    protected void validateTest(String name, SieveContext context)
+            throws LookupException {
+        context.getTestManager().getTest(name);
+    }
+
+    /**
+     * @see org.apache.jsieve.commands.AbstractCommand#validateArguments(Arguments,
+     *      SieveContext)
+     */
+    protected void validateArguments(Arguments arguments, SieveContext context)
+            throws SieveException {
+        List<Argument> args = arguments.getArgumentList();
+        if (args.size() != 1)
+            throw context.getCoordinate().syntaxException(
+                    new StringBuilder("Exactly 1 argument permitted. Found ").append((int)args.size()));
+
+        Argument argument = args.get(0);
+        if (!(argument instanceof StringListArgument))
+            throw context.getCoordinate().syntaxException(
+                    "Expecting a string-list");
+    }
+
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/commands/Stop.java b/trunk/main/src/main/java/org/apache/jsieve/commands/Stop.java
new file mode 100644
index 0000000..ecef667
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/commands/Stop.java
@@ -0,0 +1,57 @@
+/****************************************************************
+ * 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.jsieve.commands;
+
+import org.apache.jsieve.Arguments;
+import org.apache.jsieve.Block;
+import org.apache.jsieve.SieveContext;
+import org.apache.jsieve.exception.SieveException;
+import org.apache.jsieve.exception.StopException;
+import org.apache.jsieve.mail.MailAdapter;
+
+/**
+ * Class Stop implements the Stop Command as defined in RFC 3028, section 3.3.
+ */
+public class Stop extends AbstractControlCommand {
+
+    /**
+     * Constructor for Require.
+     */
+    public Stop() {
+        super();
+    }
+
+    /**
+     * <p>
+     * Throws a StopException.
+     * </p>
+     * <p>
+     * Also,
+     * 
+     * @see org.apache.jsieve.commands.AbstractCommand#executeBasic(MailAdapter,
+     *      Arguments, Block, SieveContext)
+     *      </p>
+     */
+    protected Object executeBasic(MailAdapter mail, Arguments arguments,
+            Block block, SieveContext context) throws SieveException {
+        throw new StopException("Stop requested");
+    }
+
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/commands/extensions/Log.java b/trunk/main/src/main/java/org/apache/jsieve/commands/extensions/Log.java
new file mode 100644
index 0000000..6081a65
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/commands/extensions/Log.java
@@ -0,0 +1,228 @@
+/****************************************************************
+ * 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.jsieve.commands.extensions;
+
+import static org.apache.jsieve.commands.extensions.LogLevelTags.DEBUG_TAG;
+import static org.apache.jsieve.commands.extensions.LogLevelTags.ERROR_TAG;
+import static org.apache.jsieve.commands.extensions.LogLevelTags.FATAL_TAG;
+import static org.apache.jsieve.commands.extensions.LogLevelTags.INFO_TAG;
+import static org.apache.jsieve.commands.extensions.LogLevelTags.TRACE_TAG;
+import static org.apache.jsieve.commands.extensions.LogLevelTags.WARN_TAG;
+
+import java.util.List;
+import java.util.ListIterator;
+
+import org.apache.jsieve.Argument;
+import org.apache.jsieve.Arguments;
+import org.apache.jsieve.Block;
+import org.apache.jsieve.SieveContext;
+import org.apache.jsieve.StringListArgument;
+import org.apache.jsieve.TagArgument;
+import org.apache.jsieve.commands.AbstractCommand;
+import org.apache.jsieve.exception.SieveException;
+import org.apache.jsieve.exception.SyntaxException;
+import org.apache.jsieve.mail.MailAdapter;
+
+/**
+ * <p>
+ * Class Log is an extension that implements a Command to write messages to the
+ * Sieve Log. The BNF syntax is...
+ * </p>
+ * <code>log [(:fatal / :error / :warn / :info / :debug / :trace)] string</code>
+ * <p>
+ * The default log level is :info.
+ * </p>
+ */
+public class Log extends AbstractCommand {
+    /**
+     * Constructor for Log.
+     */
+    public Log() {
+        super();
+    }
+
+    /**
+     * @see org.apache.jsieve.commands.AbstractCommand#executeBasic(MailAdapter,
+     *      Arguments, Block, SieveContext)
+     */
+    protected Object executeBasic(MailAdapter mail, Arguments arguments,
+            Block block, SieveContext context) throws SieveException {
+        String logLevel = null;
+        String message = null;
+
+        // First MAY be a tag argument of fatal, error, warn, info, debug or
+        // trace.
+        // default is info.
+        final ListIterator<Argument> argumentsIter = arguments.getArgumentList().listIterator();
+        boolean stop = false;
+
+        // Tag processing
+        while (!stop && argumentsIter.hasNext()) {
+            final Argument argument = argumentsIter.next();
+            if (argument instanceof TagArgument) {
+                final String tag = ((TagArgument) argument).getTag();
+
+                // LogLevel?
+                if (null == logLevel
+                        && (tag.equals(FATAL_TAG) || tag.equals(ERROR_TAG)
+                                || tag.equals(WARN_TAG) || tag.equals(INFO_TAG)
+                                || tag.equals(DEBUG_TAG) || tag
+                                .equals(TRACE_TAG)))
+                    logLevel = tag;
+                else
+                    throw context.getCoordinate().syntaxException(
+                            "Found unexpected TagArgument");
+            } else {
+                // Stop when a non-tag argument is encountered
+                argumentsIter.previous();
+                stop = true;
+            }
+        }
+
+        // Next MUST be a String
+        if (argumentsIter.hasNext()) {
+            final Argument argument = argumentsIter.next();
+            if (argument instanceof StringListArgument) {
+                List<String> strings = ((StringListArgument) argument).getList();
+                if (1 == strings.size())
+                    message = strings.get(0);
+            }
+        }
+        if (null == message)
+            throw context.getCoordinate().syntaxException("Expecting a String");
+
+        // Everthing else is an error
+        if (argumentsIter.hasNext())
+            throw context.getCoordinate().syntaxException(
+                    "Found unexpected arguments");
+
+        log(null == logLevel ? ":info" : logLevel, message, context);
+
+        return null;
+    }
+
+    /**
+     * Method log.
+     * 
+     * @param logLevel
+     * @param message
+     * @throws SyntaxException
+     */
+    protected void log(String logLevel, String message, SieveContext context)
+            throws SyntaxException {
+        if (logLevel.equals(INFO_TAG))
+            logInfo(message, context);
+        else if (logLevel.equals(ERROR_TAG))
+            logError(message, context);
+        else if (logLevel.equals(WARN_TAG))
+            logWarn(message, context);
+        else if (logLevel.equals(DEBUG_TAG))
+            logDebug(message, context);
+        else if (logLevel.equals(FATAL_TAG))
+            logFatal(message, context);
+        else if (logLevel.equals(TRACE_TAG))
+            logTrace(message, context);
+        else
+            throw context.getCoordinate().syntaxException(
+                    new StringBuilder("Unsupported logging level: ").append (logLevel));
+    }
+
+    /**
+     * Method logFatal.
+     * 
+     * @param message not null
+     * @param sieveContext not null
+     */
+    protected void logFatal(String message, SieveContext sieveContext) {
+        org.apache.commons.logging.Log log = sieveContext.getLog();
+        if (log.isFatalEnabled())
+            log.fatal(message);
+    }
+
+    /**
+     * Method logWarn.
+     * 
+     * @param message not null
+     * @param context not null
+     */
+    protected void logWarn(String message, SieveContext context) {
+        org.apache.commons.logging.Log log = context.getLog();
+        if (log.isWarnEnabled())
+            log.warn(message);
+    }
+
+    /**
+     * Method logInfo.
+     * 
+     * @param message not null
+     * @param context not null
+     */
+    protected void logInfo(String message, SieveContext context) {
+        org.apache.commons.logging.Log log = context.getLog();
+        if (log.isInfoEnabled())
+            log.info(message);
+    }
+
+    /**
+     * Method logDebug.
+     * 
+     * @param message not null
+     * @param context not null
+     */
+    protected void logDebug(String message, SieveContext context) {
+        org.apache.commons.logging.Log log = context.getLog();
+        if (log.isDebugEnabled())
+            log.debug(message);
+    }
+
+    /**
+     * Method logTrace.
+     * 
+     * @param message not null
+     * @param context not null
+     */
+    protected void logTrace(String message, SieveContext context) {
+        org.apache.commons.logging.Log log = context.getLog();
+        if (log.isTraceEnabled())
+            log.trace(message);
+    }
+
+    /**
+     * Method logError.
+     * 
+     * @param message not null
+     * @param context not null
+     */
+    protected void logError(String message, SieveContext context) {
+        org.apache.commons.logging.Log log = context.getLog();
+        if (log.isErrorEnabled())
+            log.error(message);
+    }
+
+    /**
+     * @see org.apache.jsieve.commands.AbstractCommand#validateArguments(Arguments,
+     *      SieveContext)
+     */
+    protected void validateArguments(Arguments arguments, SieveContext context)
+            throws SieveException {
+        // Validation is performed in executeBasic()
+    }
+
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/commands/extensions/LogLevelTags.java b/trunk/main/src/main/java/org/apache/jsieve/commands/extensions/LogLevelTags.java
new file mode 100644
index 0000000..8579fdd
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/commands/extensions/LogLevelTags.java
@@ -0,0 +1,39 @@
+/****************************************************************
+ * 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.jsieve.commands.extensions;
+
+/**
+ * Interface LogLevelTags defines the String constants for the tags used to
+ * specify the logging level.
+ */
+public interface LogLevelTags {
+    public static final String DEBUG_TAG = ":debug";
+
+    public static final String ERROR_TAG = ":error";
+
+    public static final String FATAL_TAG = ":fatal";
+
+    public static final String INFO_TAG = ":info";
+
+    public static final String TRACE_TAG = ":trace";
+
+    public static final String WARN_TAG = ":warn";
+
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/commands/optional/FileInto.java b/trunk/main/src/main/java/org/apache/jsieve/commands/optional/FileInto.java
new file mode 100644
index 0000000..5d4253f
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/commands/optional/FileInto.java
@@ -0,0 +1,91 @@
+/****************************************************************
+ * 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.jsieve.commands.optional;
+
+import org.apache.jsieve.Arguments;
+import org.apache.jsieve.Block;
+import org.apache.jsieve.SieveContext;
+import org.apache.jsieve.StringListArgument;
+import org.apache.jsieve.commands.AbstractActionCommand;
+import org.apache.jsieve.exception.SieveException;
+import org.apache.jsieve.mail.Action;
+import org.apache.jsieve.mail.ActionFileInto;
+import org.apache.jsieve.mail.MailAdapter;
+
+/**
+ * Class FileInto implements the FileInto Command as defined in RFC 3028,
+ * section 4.2.
+ */
+public class FileInto extends AbstractActionCommand {
+
+    /**
+     * Constructor for Require.
+     */
+    public FileInto() {
+        super();
+    }
+
+    /**
+     * <p>
+     * Add an ActionFileInto to the List of Actions to be performed passing the
+     * sole StringList argument as the destination. RFC 3028 mandates that there
+     * should be only one FileInto per destination. If this is a duplicate, this
+     * Command is silently ignored.
+     * </p>
+     * <p>
+     * Also,
+     * 
+     * @see org.apache.jsieve.commands.AbstractCommand#executeBasic(MailAdapter,
+     *      Arguments, Block, SieveContext)
+     *      </p>
+     */
+    protected Object executeBasic(MailAdapter mail, Arguments arguments,
+            Block block, SieveContext context) throws SieveException {
+        final String destination = ((StringListArgument) arguments
+                .getArgumentList().get(0)).getList().get(0);
+
+        // Only one fileinto per destination allowed, others should be
+        // discarded
+        boolean isDuplicate = false;
+        for (final Action action: mail.getActions()) {
+            isDuplicate = (action instanceof ActionFileInto)
+                    && (((ActionFileInto) action).getDestination()
+                            .equals(destination));
+            if (isDuplicate) {
+                break;
+            }
+        }
+
+        if (!isDuplicate)
+            mail.addAction(new ActionFileInto(destination));
+
+        return null;
+    }
+
+    /**
+     * @see org.apache.jsieve.commands.AbstractCommand#validateArguments(Arguments,
+     *      SieveContext)
+     */
+    protected void validateArguments(Arguments arguments, SieveContext context)
+            throws SieveException {
+        validateSingleStringArguments(arguments, context);
+    }
+
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/commands/optional/Reject.java b/trunk/main/src/main/java/org/apache/jsieve/commands/optional/Reject.java
new file mode 100644
index 0000000..de6913f
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/commands/optional/Reject.java
@@ -0,0 +1,105 @@
+/****************************************************************
+ * 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.jsieve.commands.optional;
+
+import org.apache.jsieve.Arguments;
+import org.apache.jsieve.Block;
+import org.apache.jsieve.SieveContext;
+import org.apache.jsieve.StringListArgument;
+import org.apache.jsieve.commands.AbstractActionCommand;
+import org.apache.jsieve.exception.CommandException;
+import org.apache.jsieve.exception.SieveException;
+import org.apache.jsieve.mail.ActionReject;
+import org.apache.jsieve.mail.MailAdapter;
+
+/**
+ * Class | Interface Enter description here
+ * 
+ * Creation Date: 11-Jan-04
+ * 
+ * @author sbrewin
+ * 
+ * Copyright 2003, Synergy Systems Limited
+ */
+/**
+ * Class Reject implements the Reject Command as defined in RFC 3028, section
+ * 4.1.
+ */
+public class Reject extends AbstractActionCommand {
+
+    /**
+     * Constructor for Reject.
+     */
+    public Reject() {
+        super();
+    }
+
+    /**
+     * <p>
+     * Add an ActionReject to the List of Actions to be performed.
+     * </p>
+     * <p>
+     * Also,
+     * 
+     * @see org.apache.jsieve.commands.AbstractCommand#executeBasic(MailAdapter,
+     *      Arguments, Block, SieveContext)
+     *      </p>
+     */
+    protected Object executeBasic(MailAdapter mail, Arguments arguments,
+            Block block, SieveContext context) throws SieveException {
+        final String message = ((StringListArgument) arguments
+                .getArgumentList().get(0)).getList().get(0);
+
+        mail.addAction(new ActionReject(message));
+        return null;
+    }
+
+    /**
+     * @see org.apache.jsieve.commands.AbstractCommand#validateState(SieveContext)
+     */
+    protected void validateState(final SieveContext context)
+            throws CommandException {
+        super.validateState(context);
+
+        if (context.getCommandStateManager().isHasActions())
+            throw context
+                    .getCoordinate()
+                    .commandException(
+                            "The \"reject\" command is not allowed with other Action Commands");
+    }
+
+    /**
+     * @see org.apache.jsieve.commands.AbstractCommand#updateState(SieveContext)
+     */
+    protected void updateState(SieveContext context) {
+        super.updateState(context);
+        context.getCommandStateManager().setRejected(true);
+    }
+
+    /**
+     * @see org.apache.jsieve.commands.AbstractCommand#validateArguments(Arguments,
+     *      SieveContext)
+     */
+    protected void validateArguments(Arguments arguments, SieveContext context)
+            throws SieveException {
+        validateSingleStringArguments(arguments, context);
+    }
+
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/comparators/AsciiCasemap.java b/trunk/main/src/main/java/org/apache/jsieve/comparators/AsciiCasemap.java
new file mode 100644
index 0000000..16b322e
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/comparators/AsciiCasemap.java
@@ -0,0 +1,64 @@
+/****************************************************************
+ * 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.jsieve.comparators;
+
+import org.apache.jsieve.exception.SievePatternException;
+
+/**
+ * Class AsciiCasemap implements the EQUALITY operation of the i;ascii-casemap
+ * comparator as defined by RFC2244, section 3.4 - "With this function the
+ * values "hello" and "HELLO" have the same ordinal value and are considered
+ * equal".
+ */
+public class AsciiCasemap implements Comparator {
+
+    /**
+     * Constructor for AsciiCasemap.
+     */
+    public AsciiCasemap() {
+        super();
+    }
+
+    /**
+     * @see org.apache.jsieve.comparators.Equals#equals(String, String)
+     */
+    public boolean equals(String string1, String string2) {
+        return ComparatorUtils.equals(string1.toUpperCase(), string2
+                .toUpperCase());
+    }
+
+    /**
+     * @see org.apache.jsieve.comparators.Contains#contains(String, String)
+     */
+    public boolean contains(String container, String content) {
+        return ComparatorUtils.contains(container.toUpperCase(), content
+                .toUpperCase());
+    }
+
+    /**
+     * @see org.apache.jsieve.comparators.Matches#matches(String, String)
+     */
+    public boolean matches(String string, String glob)
+            throws SievePatternException {
+        return ComparatorUtils
+                .matches(string.toUpperCase(), glob.toUpperCase());
+    }
+
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/comparators/AsciiNumeric.java b/trunk/main/src/main/java/org/apache/jsieve/comparators/AsciiNumeric.java
new file mode 100644
index 0000000..b7dca33
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/comparators/AsciiNumeric.java
@@ -0,0 +1,136 @@
+/****************************************************************
+ * 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.jsieve.comparators;
+
+import java.math.BigInteger;
+
+import org.apache.jsieve.exception.FeatureException;
+
+/**
+ * Class AsciiNumeric implements the EQUALITY operation of the i;ascii-numeric
+ * comparator as defined by RFC2244, section 3.4.
+ */
+public class AsciiNumeric implements Comparator {
+
+    /**
+     * Constructor for AsciiNumeric.
+     */
+    public AsciiNumeric() {
+        super();
+    }
+
+    /**
+     * @see org.apache.jsieve.comparators.Equals#equals(String, String)
+     */
+    public boolean equals(String string1, String string2) {
+        final boolean result;
+        if (isPositiveInfinity(string1)) {
+            if (isPositiveInfinity(string2)) {
+                result = true;
+            } else {
+                result = false;
+            }
+        } else {
+            if (isPositiveInfinity(string2)) {
+                result = false;
+            } else {
+                final BigInteger integer1 = toInteger(string1);
+                final BigInteger integer2 = toInteger(string2);
+                result = integer1.equals(integer2);
+            }
+        }
+        return result;
+    }
+    
+    private BigInteger toInteger(final String value) {
+        int i;
+        for (i=0;i<value.length();i++) {
+            final char next = value.charAt(i);
+            if (!isDigit(next)) {
+                break;
+            }
+        }
+        final BigInteger result = new BigInteger(value.substring(0,i));
+        return result;
+    }
+    
+    /**
+     * Does the given string to be handled as positive infinity?
+     * See <a href='http://tools.ietf.org/html/rfc4790#section-9.1.1'>RFC4790</a>
+     * @param value not null
+     * @return true when the value should represent positive infinity,
+     * false otherwise
+     */
+    private boolean isPositiveInfinity(final String value) {
+       final char initialCharacter = value.charAt(0);
+       final boolean result = !isDigit(initialCharacter);
+       return result;
+    }
+
+    /**
+     * Is the given character an ASCII digit?
+     * @param character character to be tested
+     * @return true when the given character is an ASCII digit,
+     * false otherwise 
+     */
+    private boolean isDigit(final char character) {
+        return character>=0x30 && character<=0x39;
+    }
+
+    /**
+     * Method getCompareString answers a <code>String</code> in which all
+     * non-digit characters are translated to the character 0xff.
+     * 
+     * @param string
+     * @return String
+     */
+    protected String computeCompareString(String string) {
+        char[] chars = string.toCharArray();
+        for (int i = chars.length; i < chars.length; i++) {
+            if (!Character.isDigit(chars[i]))
+                chars[i] = 0xff;
+        }
+        return new String(chars);
+    }
+
+    /**
+     * Unsupported, see <a href='http://tools.ietf.org/html/rfc4790#section-9.1.1'>RFC4790</a>.
+     * @see org.apache.jsieve.comparators.Contains#contains(String, String)
+     */
+    public boolean contains(String container, String content) throws FeatureException {
+        // TODO: Consider using finer grained exception
+        throw new FeatureException("Substring match unsupported by ascii-numeric");
+    }
+
+    /**
+     * Unsupported operation.
+     * <a href='http://tools.ietf.org/html/rfc5228#section-2.7.1'>RFC5228</a> limits
+     * support to comparators that support <code>:contains</code>. 
+     * <a href='http://tools.ietf.org/html/rfc4790#section-9.1.1'>RFC4790</a> states
+     * that substring matches are not supported.
+     * @see org.apache.jsieve.comparators.Matches#matches(String, String)
+     */
+    public boolean matches(String string, String glob)
+            throws FeatureException {
+        // TODO: Consider using finer grained exception
+        throw new FeatureException("Substring match unsupported by ascii-numeric");
+    }
+
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/comparators/Comparator.java b/trunk/main/src/main/java/org/apache/jsieve/comparators/Comparator.java
new file mode 100644
index 0000000..48decfc
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/comparators/Comparator.java
@@ -0,0 +1,28 @@
+/****************************************************************
+ * 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.jsieve.comparators;
+
+/**
+ * Interface Comparator defines the method signatures for Sieve comparators. It
+ * consists of Equals, Contains and Matches Comparisons.
+ */
+public interface Comparator extends Equals, Contains, Matches {
+
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/comparators/ComparatorNames.java b/trunk/main/src/main/java/org/apache/jsieve/comparators/ComparatorNames.java
new file mode 100644
index 0000000..7e01eeb
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/comparators/ComparatorNames.java
@@ -0,0 +1,30 @@
+/****************************************************************
+ * 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.jsieve.comparators;
+
+/**
+ * Interface ComparatorNames defines the String constants used to specify a
+ * COMPARATOR name.
+ */
+public interface ComparatorNames {
+    public static final String OCTET_COMPARATOR = "i;octet";
+
+    public static final String ASCII_CASEMAP_COMPARATOR = "i;ascii-casemap";
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/comparators/ComparatorUtils.java b/trunk/main/src/main/java/org/apache/jsieve/comparators/ComparatorUtils.java
new file mode 100644
index 0000000..bf56a6a
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/comparators/ComparatorUtils.java
@@ -0,0 +1,232 @@
+/****************************************************************
+ * 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.jsieve.comparators;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+
+import org.apache.jsieve.SieveContext;
+import org.apache.jsieve.exception.LookupException;
+import org.apache.jsieve.exception.SieveException;
+import org.apache.jsieve.exception.SievePatternException;
+
+import static  org.apache.jsieve.comparators.MatchTypeTags.*;
+
+/**
+ * Class ComparatorUtils implements utility methods used by Comparators.
+ */
+public class ComparatorUtils {
+
+    /**
+     * Constructor for ComparatorUtils.
+     */
+    private ComparatorUtils() {
+        super();
+    }
+
+    /**
+     * Method <code>match</code> answers a boolean indicating if the parameter
+     * <code>matchTarget</code> compares to parameter
+     * <code>matchArgument</code> is a match of <code>matchType</code> using
+     * the comparator <code>comparatorName</code>.
+     * 
+     * @param comparatorName not null
+     * @param matchType not null
+     * @param matchTarget not null
+     * @param matchArgument not null
+     * @param context not null
+     * @return boolean
+     */
+    public static boolean match(String comparatorName, String matchType,
+            String matchTarget, String matchArgument, SieveContext context)
+            throws SieveException {
+        boolean isMatched = false;
+        if (matchType.equals(IS_TAG))
+            isMatched = is(comparatorName, matchTarget, matchArgument, context);
+        else if (matchType.equals(CONTAINS_TAG))
+            isMatched = contains(comparatorName, matchTarget, matchArgument,
+                    context);
+        else if (matchType.equals(MATCHES_TAG))
+            isMatched = matches(comparatorName, matchTarget, matchArgument,
+                    context);
+        return isMatched;
+    }
+
+    /**
+     * <p>
+     * Method <code>matches</code> answers a boolean indicating if the
+     * parameter <code>string</code> matches the glob pattern described by
+     * parameter <code>glob</code>.
+     * 
+     * @param string
+     * @param glob
+     * @return boolean
+     * @throws SievePatternException
+     */
+    static public boolean matches(String string, String glob)
+            throws SievePatternException {
+        try {
+            String regex = sieveToJavaRegex(glob);
+            final Matcher matcher = Pattern.compile(regex).matcher(string);
+            return matcher.matches();
+        } catch (PatternSyntaxException e) {
+            throw new SievePatternException(e.getMessage());
+        }
+    }
+
+    /**
+     * <p>
+     * Method <code>contains</code> answers a boolean indicating if the
+     * parameter <code>container</code> contains the parameter
+     * <code>contents</code>.
+     * </p>
+     * 
+     * @param container
+     * @param contents
+     * @return boolean
+     */
+    static public boolean contains(String container, String contents) {
+        return container.indexOf(contents) > -1;
+    }
+
+    /**
+     * <p>
+     * Method <code>equals</code> answers a boolean indicating if the
+     * parameter <code>string1</code> is equal to the parameter
+     * <code>string2</code>.
+     * </p>
+     * 
+     * @param string1
+     * @param string2
+     * @return boolean
+     */
+    static public boolean equals(String string1, String string2) {
+        return string1.equals(string2);
+    }
+
+    /**
+     * Returns true if the char is a special char for regex
+     */
+    private static boolean isRegexSpecialChar(char ch) {
+        return (ch == '*' || ch == '?' || ch == '+' || ch == '[' || ch == ']'
+                || ch == '(' || ch == ')' || ch == '|' || ch == '^'
+                || ch == '$' || ch == '.' || ch == '{' || ch == '}' || ch == '\\');
+    }
+
+    /**
+     * Returns true if the char is a special char for sieve matching
+     */
+    private static boolean isSieveMatcherSpecialChar(char ch) {
+        return (ch == '*' || ch == '?' || ch == '\\');
+    }
+
+    /**
+     * Converts a Sieve pattern in a java regex pattern
+     */
+    public static String sieveToJavaRegex(String pattern) {
+        int ch;
+        StringBuffer buffer = new StringBuffer(2 * pattern.length());
+        boolean lastCharWasStar = false;
+        for (ch = 0; ch < pattern.length(); ch++) {
+            final char nextChar = pattern.charAt(ch);
+            switch (nextChar) {
+            case '*':
+                //
+                // Java Matcher has issues with repeated stars
+                //
+                if (!lastCharWasStar) {
+                    buffer.append(".*");
+                }
+                break;
+            case '?':
+                buffer.append('.');
+                break;
+            case '\\':
+                buffer.append('\\');
+                if (ch == pattern.length() - 1)
+                    buffer.append('\\');
+                else if (isSieveMatcherSpecialChar(pattern.charAt(ch + 1)))
+                    buffer.append(pattern.charAt(++ch));
+                else
+                    buffer.append('\\');
+                break;
+            default:
+                if (isRegexSpecialChar(nextChar))
+                    buffer.append('\\');
+                buffer.append(nextChar);
+                break;
+            }
+            // Workaround for issue with Java Matcher
+            lastCharWasStar = '*' == nextChar;
+        }
+        return buffer.toString();
+    }
+
+    /**
+     * Method <code>contains<code> answers a boolean indicating if the parameter 
+     * <code>container</code> contains the parameter <code>contents</code> using an
+     * instance of <code>comparatorName</code>.
+     * @param comparatorName not null
+     * @param container not null
+     * @param contents not null
+     * @param context not null
+     * @return boolean
+     */
+    public static boolean contains(String comparatorName, String container,
+            String contents, SieveContext context) throws SieveException {
+        Contains comparatorObj = context.getComparatorManager().getComparator(comparatorName);
+        return comparatorObj.contains(container, contents);
+    }
+
+    /**
+     * Method <code>is<code> answers a boolean indicating if the parameter 
+     * <code>container</code> is equal to the parameter <code>contents</code> using 
+     * an instance of <code>comparatorName</code>.
+     * @param comparatorName
+     * @param string1 not null
+     * @param string2 not null
+     * @param context not null
+     * @return boolean
+     */
+    public static boolean is(String comparatorName, String string1,
+            String string2, SieveContext context) throws LookupException {
+        Equals comparatorObj = context.getComparatorManager().getComparator(comparatorName);
+        return comparatorObj.equals(string1, string2);
+    }
+
+    /**
+     * Method <code>matches</code> answers a boolean indicating if the
+     * parameter
+     * <code>string/code> is matched by the patterm <code>glob</code> using an
+     * instance of <code>comparatorName</code>.
+     * @param comparatorName not null
+     * @param string not null
+     * @param glob not null
+     * @param context not null
+     * @return boolean
+     */
+    public static boolean matches(String comparatorName, String string,
+            String glob, SieveContext context) throws SieveException {
+        Matches comparatorObj = context.getComparatorManager().getComparator(comparatorName);
+        return comparatorObj.matches(string, glob);
+    }
+
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/comparators/Contains.java b/trunk/main/src/main/java/org/apache/jsieve/comparators/Contains.java
new file mode 100644
index 0000000..7d82728
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/comparators/Contains.java
@@ -0,0 +1,42 @@
+/****************************************************************
+ * 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.jsieve.comparators;
+
+import org.apache.jsieve.exception.FeatureException;
+
+
+/**
+ * Interface Contains defines the method signatures for contains comparators.
+ */
+public interface Contains {
+
+    /**
+     * Method contains answers a <code>boolean</code> indicating if parameter
+     * <code>container</code> contains parameter <code>content</code> using
+     * the comparison rules defind by the implementation.
+     * 
+     * @param container
+     * @param content
+     * @return boolean
+     * @throws FeatureException when substring is unsupported
+     */
+    public boolean contains(String container, String content) throws FeatureException;
+
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/comparators/Equals.java b/trunk/main/src/main/java/org/apache/jsieve/comparators/Equals.java
new file mode 100644
index 0000000..a619967
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/comparators/Equals.java
@@ -0,0 +1,36 @@
+/****************************************************************
+ * 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.jsieve.comparators;
+
+/**
+ * Interface Equals defines the method signatures for equals comparators.
+ */
+public interface Equals {
+    /**
+     * Method equals answers a <code>boolean</code> indicating if parameter
+     * <code>string1</code> is equal to parameter <code>string2</code> using
+     * the comparison rules defind by the implementation.
+     * 
+     * @param string1
+     * @param string2
+     * @return boolean
+     */
+    public boolean equals(String string1, String string2);
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/comparators/MatchTypeTags.java b/trunk/main/src/main/java/org/apache/jsieve/comparators/MatchTypeTags.java
new file mode 100644
index 0000000..3b33571
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/comparators/MatchTypeTags.java
@@ -0,0 +1,33 @@
+/****************************************************************
+ * 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.jsieve.comparators;
+
+/**
+ * Interface MatchTypeTags defines the String constants used to specify an
+ * MATCH-TYPE tag.
+ */
+public interface MatchTypeTags {
+    public static final String IS_TAG = ":is";
+
+    public static final String CONTAINS_TAG = ":contains";
+
+    public static final String MATCHES_TAG = ":matches";
+
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/comparators/Matches.java b/trunk/main/src/main/java/org/apache/jsieve/comparators/Matches.java
new file mode 100644
index 0000000..63cd6fa
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/comparators/Matches.java
@@ -0,0 +1,39 @@
+/****************************************************************
+ * 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.jsieve.comparators;
+
+import org.apache.jsieve.exception.SieveException;
+
+/**
+ * Interface Matches defines the method signatures for matches comparators.
+ */
+public interface Matches {
+    /**
+     * Method matches answers a <code>boolean</code> indicating if parameter
+     * <code>string1</code> matches the pattern in parameter <code>glob</code>
+     * using the matching rules defind by the implementation.
+     * 
+     * @param string
+     * @param glob
+     * @return boolean
+     */
+    public boolean matches(String string, String glob) throws SieveException;
+
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/comparators/Octet.java b/trunk/main/src/main/java/org/apache/jsieve/comparators/Octet.java
new file mode 100644
index 0000000..2b6f7a9
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/comparators/Octet.java
@@ -0,0 +1,61 @@
+/****************************************************************
+ * 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.jsieve.comparators;
+
+import org.apache.jsieve.exception.SievePatternException;
+
+/**
+ * Class Octet implements the EQUALITY operation of the i;octet comparator as
+ * defined by RFC2244, section 3.4 - "For the equality function, two strings are
+ * equal if they are the same length and contain the same octets in the same
+ * order. NIL is equal only to itself".
+ */
+public class Octet implements Comparator {
+
+    /**
+     * Constructor for Octet.
+     */
+    public Octet() {
+        super();
+    }
+
+    /**
+     * @see org.apache.jsieve.comparators.Equals#equals(String, String)
+     */
+    public boolean equals(String string1, String string2) {
+        return ComparatorUtils.equals(string1, string2);
+    }
+
+    /**
+     * @see org.apache.jsieve.comparators.Contains#contains(String, String)
+     */
+    public boolean contains(String container, String content) {
+        return ComparatorUtils.contains(container, content);
+    }
+
+    /**
+     * @see org.apache.jsieve.comparators.Matches#matches(String, String)
+     */
+    public boolean matches(String string, String glob)
+            throws SievePatternException {
+        return ComparatorUtils.matches(string, glob);
+    }
+
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/exception/CommandException.java b/trunk/main/src/main/java/org/apache/jsieve/exception/CommandException.java
new file mode 100644
index 0000000..03350c7
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/exception/CommandException.java
@@ -0,0 +1,65 @@
+/****************************************************************
+ * 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.jsieve.exception;
+
+/**
+ * Class <code>CommandException</code> indicates an exceptional condition
+ * encountered while executing a Command.
+ * 
+ */
+@SuppressWarnings("serial")
+public class CommandException extends OperationException {
+
+    /**
+     * Constructor for CommandException.
+     */
+    public CommandException() {
+        super();
+    }
+
+    /**
+     * Constructor for CommandException.
+     * 
+     * @param message
+     */
+    public CommandException(String message) {
+        super(message);
+    }
+
+    /**
+     * Constructor for CommandException.
+     * 
+     * @param message
+     * @param cause
+     */
+    public CommandException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    /**
+     * Constructor for CommandException.
+     * 
+     * @param cause
+     */
+    public CommandException(Throwable cause) {
+        super(cause);
+    }
+
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/exception/FeatureException.java b/trunk/main/src/main/java/org/apache/jsieve/exception/FeatureException.java
new file mode 100644
index 0000000..32c3d2e
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/exception/FeatureException.java
@@ -0,0 +1,64 @@
+/****************************************************************
+ * 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.jsieve.exception;
+
+/**
+ * Class FeatureException indicates an exceptional condition encountered while
+ * evaluating a feature of Sieve.
+ */
+@SuppressWarnings("serial")
+public class FeatureException extends SieveException {
+
+    /**
+     * Constructor for FeatureException.
+     */
+    public FeatureException() {
+        super();
+    }
+
+    /**
+     * Constructor for FeatureException.
+     * 
+     * @param message
+     */
+    public FeatureException(String message) {
+        super(message);
+    }
+
+    /**
+     * Constructor for FeatureException.
+     * 
+     * @param message
+     * @param cause
+     */
+    public FeatureException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    /**
+     * Constructor for FeatureException.
+     * 
+     * @param cause
+     */
+    public FeatureException(Throwable cause) {
+        super(cause);
+    }
+
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/exception/InternetAddressException.java b/trunk/main/src/main/java/org/apache/jsieve/exception/InternetAddressException.java
new file mode 100644
index 0000000..df76207
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/exception/InternetAddressException.java
@@ -0,0 +1,64 @@
+/****************************************************************
+ * 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.jsieve.exception;
+
+/**
+ * Class InternetAddressException indicates an exceptional condition encountered
+ * while evaluating an Internet Address.
+ */
+@SuppressWarnings("serial")
+public class InternetAddressException extends SieveException {
+
+    /**
+     * Constructor for InternetAddressException.
+     */
+    public InternetAddressException() {
+        super();
+    }
+
+    /**
+     * Constructor for InternetAddressException.
+     * 
+     * @param message
+     */
+    public InternetAddressException(String message) {
+        super(message);
+    }
+
+    /**
+     * Constructor for InternetAddressException.
+     * 
+     * @param message
+     * @param cause
+     */
+    public InternetAddressException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    /**
+     * Constructor for InternetAddressException.
+     * 
+     * @param cause
+     */
+    public InternetAddressException(Throwable cause) {
+        super(cause);
+    }
+
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/exception/LookupException.java b/trunk/main/src/main/java/org/apache/jsieve/exception/LookupException.java
new file mode 100644
index 0000000..660e111
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/exception/LookupException.java
@@ -0,0 +1,64 @@
+/****************************************************************
+ * 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.jsieve.exception;
+
+/**
+ * Class LookupException indicates an exceptional condition encountered while
+ * locating a Sieve resource.
+ */
+@SuppressWarnings("serial")
+public class LookupException extends SieveException {
+
+    /**
+     * Constructor for LookupException.
+     */
+    public LookupException() {
+        super();
+    }
+
+    /**
+     * Constructor for LookupException.
+     * 
+     * @param message
+     */
+    public LookupException(String message) {
+        super(message);
+    }
+
+    /**
+     * Constructor for LookupException.
+     * 
+     * @param message
+     * @param cause
+     */
+    public LookupException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    /**
+     * Constructor for LookupException.
+     * 
+     * @param cause
+     */
+    public LookupException(Throwable cause) {
+        super(cause);
+    }
+
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/exception/OperationException.java b/trunk/main/src/main/java/org/apache/jsieve/exception/OperationException.java
new file mode 100644
index 0000000..6ea8a05
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/exception/OperationException.java
@@ -0,0 +1,65 @@
+/****************************************************************
+ * 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.jsieve.exception;
+
+/**
+ * Class <code>OperationException</code> indicates an exceptional condition
+ * encountered while executing an Operation.
+ * 
+ */
+@SuppressWarnings("serial")
+public class OperationException extends SieveException {
+
+    /**
+     * Constructor for OperationException.
+     */
+    public OperationException() {
+        super();
+    }
+
+    /**
+     * Constructor for OperationException.
+     * 
+     * @param message
+     */
+    public OperationException(String message) {
+        super(message);
+    }
+
+    /**
+     * Constructor for OperationException.
+     * 
+     * @param message
+     * @param cause
+     */
+    public OperationException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    /**
+     * Constructor for OperationException.
+     * 
+     * @param cause
+     */
+    public OperationException(Throwable cause) {
+        super(cause);
+    }
+
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/exception/SieveException.java b/trunk/main/src/main/java/org/apache/jsieve/exception/SieveException.java
new file mode 100644
index 0000000..90e8b10
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/exception/SieveException.java
@@ -0,0 +1,64 @@
+/****************************************************************
+ * 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.jsieve.exception;
+
+/**
+ * Class <code>SieveException</code> indicates an exceptional condition
+ * encountered within Sieve.
+ */
+@SuppressWarnings("serial")
+public class SieveException extends Exception {
+
+    /**
+     * Constructor for SieveException.
+     */
+    public SieveException() {
+        super();
+    }
+
+    /**
+     * Constructor for SieveException.
+     * 
+     * @param message
+     */
+    public SieveException(String message) {
+        super(message);
+    }
+
+    /**
+     * Constructor for SieveException.
+     * 
+     * @param message
+     * @param cause
+     */
+    public SieveException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    /**
+     * Constructor for SieveException.
+     * 
+     * @param cause
+     */
+    public SieveException(Throwable cause) {
+        super(cause);
+    }
+
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/exception/SievePatternException.java b/trunk/main/src/main/java/org/apache/jsieve/exception/SievePatternException.java
new file mode 100644
index 0000000..cfb5a0b
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/exception/SievePatternException.java
@@ -0,0 +1,64 @@
+/****************************************************************
+ * 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.jsieve.exception;
+
+/**
+ * Class <code>SievePatternException</code> indicates an exceptional condition
+ * encountered while evaluating a glob expression.
+ */
+@SuppressWarnings("serial")
+public class SievePatternException extends SieveException {
+
+    /**
+     * Constructor for SievePatternException.
+     */
+    public SievePatternException() {
+        super();
+    }
+
+    /**
+     * Constructor for SievePatternException.
+     * 
+     * @param message
+     */
+    public SievePatternException(String message) {
+        super(message);
+    }
+
+    /**
+     * Constructor for SievePatternException.
+     * 
+     * @param message
+     * @param cause
+     */
+    public SievePatternException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    /**
+     * Constructor for SievePatternException.
+     * 
+     * @param cause
+     */
+    public SievePatternException(Throwable cause) {
+        super(cause);
+    }
+
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/exception/StopException.java b/trunk/main/src/main/java/org/apache/jsieve/exception/StopException.java
new file mode 100644
index 0000000..6cd36ed
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/exception/StopException.java
@@ -0,0 +1,65 @@
+/****************************************************************
+ * 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.jsieve.exception;
+
+/**
+ * Class <code>StopException</code> indicates that evaluation should be
+ * terminated. Typically, this exception is thrown when a Stop Command is
+ * encountered.
+ */
+@SuppressWarnings("serial")
+public class StopException extends SieveException {
+
+    /**
+     * Constructor for StopException.
+     */
+    public StopException() {
+        super();
+    }
+
+    /**
+     * Constructor for StopException.
+     * 
+     * @param message
+     */
+    public StopException(String message) {
+        super(message);
+    }
+
+    /**
+     * Constructor for StopException.
+     * 
+     * @param message
+     * @param cause
+     */
+    public StopException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    /**
+     * Constructor for StopException.
+     * 
+     * @param cause
+     */
+    public StopException(Throwable cause) {
+        super(cause);
+    }
+
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/exception/SyntaxException.java b/trunk/main/src/main/java/org/apache/jsieve/exception/SyntaxException.java
new file mode 100644
index 0000000..4ea7a40
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/exception/SyntaxException.java
@@ -0,0 +1,65 @@
+/****************************************************************
+ * 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.jsieve.exception;
+
+/**
+ * Class <code>SyntaxException</code> indicates an exceptional condition
+ * encountered while evaluating the operands of a Sieve operation, such as a
+ * Command, Test or Comparator.
+ */
+@SuppressWarnings("serial")
+public class SyntaxException extends SieveException {
+
+    /**
+     * Constructor for SyntaxException.
+     */
+    public SyntaxException() {
+        super();
+    }
+
+    /**
+     * Constructor for SyntaxException.
+     * 
+     * @param message
+     */
+    public SyntaxException(String message) {
+        super(message);
+    }
+
+    /**
+     * Constructor for SyntaxException.
+     * 
+     * @param message
+     * @param cause
+     */
+    public SyntaxException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    /**
+     * Constructor for SyntaxException.
+     * 
+     * @param cause
+     */
+    public SyntaxException(Throwable cause) {
+        super(cause);
+    }
+
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/exception/TestException.java b/trunk/main/src/main/java/org/apache/jsieve/exception/TestException.java
new file mode 100644
index 0000000..0b113c9
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/exception/TestException.java
@@ -0,0 +1,65 @@
+/****************************************************************
+ * 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.jsieve.exception;
+
+/**
+ * Class <code>TestException</code> indicates an exceptional condition
+ * encountered while executing a Test.
+ * 
+ */
+@SuppressWarnings("serial")
+public class TestException extends OperationException {
+
+    /**
+     * Constructor for TestException.
+     */
+    public TestException() {
+        super();
+    }
+
+    /**
+     * Constructor for TestException.
+     * 
+     * @param message
+     */
+    public TestException(String message) {
+        super(message);
+    }
+
+    /**
+     * Constructor for TestException.
+     * 
+     * @param message
+     * @param cause
+     */
+    public TestException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    /**
+     * Constructor for TestException.
+     * 
+     * @param cause
+     */
+    public TestException(Throwable cause) {
+        super(cause);
+    }
+
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/mail/Action.java b/trunk/main/src/main/java/org/apache/jsieve/mail/Action.java
new file mode 100644
index 0000000..d027a08
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/mail/Action.java
@@ -0,0 +1,28 @@
+/****************************************************************
+ * 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.jsieve.mail;
+
+/**
+ * Interface <code>Action</code> defines the final state of a
+ * <code>MailAdapter</code> instance.
+ */
+public interface Action {
+
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/mail/ActionFileInto.java b/trunk/main/src/main/java/org/apache/jsieve/mail/ActionFileInto.java
new file mode 100644
index 0000000..75ae7b2
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/mail/ActionFileInto.java
@@ -0,0 +1,75 @@
+/****************************************************************
+ * 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.jsieve.mail;
+
+/**
+ * Class ActionFileInto encapsulates the information required to file a mail
+ * into a location. See RFC 3028, Section 4.2.
+ */
+public class ActionFileInto implements Action {
+    /**
+     * A String representation of the location to which the message should be
+     * moved.
+     */
+    private String fieldDestination;
+
+    /**
+     * Constructor for ActionFileInto.
+     */
+    private ActionFileInto() {
+        super();
+    }
+
+    /**
+     * Constructor for ActionFileInto.
+     */
+    public ActionFileInto(String destination) {
+        this();
+        setDestination(destination);
+    }
+
+    /**
+     * Returns the destination.
+     * 
+     * @return String
+     */
+    public String getDestination() {
+        return fieldDestination;
+    }
+
+    /**
+     * @see java.lang.Object#toString()
+     */
+    public String toString() {
+        return "Action: " + getClass().getName() + ", destination: "
+                + getDestination();
+    }
+
+    /**
+     * Sets the destination.
+     * 
+     * @param destination
+     *            The destination to set
+     */
+    protected void setDestination(String destination) {
+        fieldDestination = destination;
+    }
+
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/mail/ActionKeep.java b/trunk/main/src/main/java/org/apache/jsieve/mail/ActionKeep.java
new file mode 100644
index 0000000..0ffab21
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/mail/ActionKeep.java
@@ -0,0 +1,42 @@
+/****************************************************************
+ * 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.jsieve.mail;
+
+/**
+ * Class ActionKeep encapsulates the information required to keep a mail. See
+ * RFC 3028, Section 4.4.
+ */
+public class ActionKeep implements Action {
+
+    /**
+     * Constructor for ActionKeep.
+     */
+    public ActionKeep() {
+        super();
+    }
+
+    /**
+     * @see java.lang.Object#toString()
+     */
+    public String toString() {
+        return "Action: " + getClass().getName();
+    }
+
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/mail/ActionRedirect.java b/trunk/main/src/main/java/org/apache/jsieve/mail/ActionRedirect.java
new file mode 100644
index 0000000..361d83a
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/mail/ActionRedirect.java
@@ -0,0 +1,70 @@
+/****************************************************************
+ * 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.jsieve.mail;
+
+/**
+ * Class ActionRedirect encapsulates the information required to redirect a
+ * mail. See RFC 3028, Section 4.3.
+ */
+public class ActionRedirect implements Action {
+    private String fieldAddress;
+
+    /**
+     * Constructor for ActionRedirect.
+     */
+    private ActionRedirect() {
+        super();
+    }
+
+    /**
+     * Constructor for ActionRedirect.
+     */
+    public ActionRedirect(String address) {
+        this();
+        setAddress(address);
+    }
+
+    /**
+     * @see java.lang.Object#toString()
+     */
+    public String toString() {
+        return "Action: " + getClass().getName() + ", address: " + getAddress();
+    }
+
+    /**
+     * Returns the address.
+     * 
+     * @return String
+     */
+    public String getAddress() {
+        return fieldAddress;
+    }
+
+    /**
+     * Sets the address.
+     * 
+     * @param address
+     *            The address to set
+     */
+    protected void setAddress(String address) {
+        fieldAddress = address;
+    }
+
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/mail/ActionReject.java b/trunk/main/src/main/java/org/apache/jsieve/mail/ActionReject.java
new file mode 100644
index 0000000..5ac440a
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/mail/ActionReject.java
@@ -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.jsieve.mail;
+
+/**
+ * Class ActionReject encapsulates the information required to reject a mail.
+ * See RFC 3028, Section 4.1.
+ */
+public class ActionReject implements Action {
+    private String fieldMessage;
+
+    /**
+     * Constructor for ActionReject.
+     * 
+     * @param aMessage
+     */
+    public ActionReject(String aMessage) {
+        this();
+        setMessage(aMessage);
+    }
+
+    /**
+     * Constructor ActionReject.
+     */
+    private ActionReject() {
+        super();
+    }
+
+    /**
+     * @see java.lang.Object#toString()
+     */
+    public String toString() {
+        return "Action: " + getClass().getName();
+    }
+
+    /**
+     * Returns the message explaining the reason for rejection.
+     * 
+     * @return String
+     */
+    public String getMessage() {
+        return fieldMessage;
+    }
+
+    /**
+     * Sets the message explaining the reason for rejection.
+     * 
+     * @param message
+     *            The message to set
+     */
+    protected void setMessage(String message) {
+        fieldMessage = message;
+    }
+
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/mail/AddressImpl.java b/trunk/main/src/main/java/org/apache/jsieve/mail/AddressImpl.java
new file mode 100644
index 0000000..596a3af
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/mail/AddressImpl.java
@@ -0,0 +1,61 @@
+/****************************************************************
+ * 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.jsieve.mail;
+
+/**
+ * Simple immutable address implementation.
+ */
+public final class AddressImpl implements MailAdapter.Address {
+
+    private final String localPart;
+
+    private final String domain;
+
+    /**
+     * Constructs an address.
+     * 
+     * @param localPart
+     *            the local part of the address
+     * @param domain
+     *            the domain part of the address
+     */
+    public AddressImpl(final String localPart, final String domain) {
+        super();
+        this.localPart = localPart;
+        this.domain = domain;
+    }
+
+    /**
+     * Gets the domain of the address.
+     * 
+     * @return domain
+     */
+    public String getDomain() {
+        return domain;
+    }
+
+    /**
+     * Gets the local part of the address.
+     */
+    public String getLocalPart() {
+        return localPart;
+    }
+
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/mail/MailAdapter.java b/trunk/main/src/main/java/org/apache/jsieve/mail/MailAdapter.java
new file mode 100644
index 0000000..cfe0901
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/mail/MailAdapter.java
@@ -0,0 +1,227 @@
+/****************************************************************
+ * 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.jsieve.mail;
+
+import java.util.List;
+
+import org.apache.jsieve.SieveContext;
+import org.apache.jsieve.exception.InternetAddressException;
+import org.apache.jsieve.exception.SieveException;
+
+/**
+ * <p>
+ * Interface <code>MailAdapter</code> defines the minimum functionality
+ * required of of a class implementing a mail message. This is the functionality
+ * neccesary to implement the Commands and Tests that RFC32028 mandates MUST be
+ * implemented.
+ * </p>
+ * 
+ * <p>
+ * Typically, implementations will wrap an application's pre-existing mail
+ * message implementation. It is expected that implementations will extend the
+ * minimum level of functionality to provide support for Command and Test
+ * extensions that exploit the capabilities of a particular application.
+ * </p>
+ * 
+ * <h4>Implementing parseAddresses</h4>
+ * <p>
+ * <a href='http://james.apache.org/mime4j'>Apache Mime4J</a> is a parser for
+ * <abbr title='Multipurpose Internet Mail Extensions'> <a
+ * href='http://www.faqs.org/rfcs/rfc2045.html'>MIME</a></abbr>. It can easily
+ * be used to parse an address string into addresses. For example:
+ * </p>
+ * <code><pre>
+ *       import org.apache.james.mime4j.field.address.AddressList;
+ *       import org.apache.james.mime4j.field.address.Mailbox;
+ *       import org.apache.james.mime4j.field.address.MailboxList;
+ *       import org.apache.james.mime4j.field.address.parser.ParseException;
+ *       ...
+ *       public Address[] parseAddresses(String arg) throws SieveMailException, InternetAddressException {
+ *           try {
+ *               final MailboxList list = AddressList.parse(arg).flatten();
+ *               final int size = list.size();
+ *               final Address[] results = new Address[size];
+ *               for (int i=0;i&lt;size;i++) {
+ *                   final Mailbox mailbox = list.get(i);
+ *                   results[i] = new AddressImpl(mailbox.getLocalPart(), mailbox.getDomain());
+ *               }
+ *               return null;
+ *           } catch (ParseException e) {
+ *               throw new InternetAddressException(e);
+ *           }
+ *       }
+ * </pre></code>
+ */
+public interface MailAdapter {
+    
+    /**
+     * <p>Sets the context for the current sieve script execution.</p>
+     * <p>Sieve engines <code>MUST</code> set this property before any calls
+     * related to the execution of a script are made.</p>
+     * <p>Implementations intended to be shared between separate threads of
+     * execution <code>MUST</code> ensure that they manage concurrency contexts,
+     * for example by storage in a thread local variable. Engines <code>MUST</code>
+     * - for a script execution - ensure that all calls are made within the
+     * same thread of execution.</p>
+     * @param context the current context, 
+     * or null to clear the contest once the execution of a script has completed.
+     */
+    public void setContext(SieveContext context);
+    
+    /**
+     * Method getActions answers the List of Actions accumulated by the
+     * receiver. Implementations may elect to supply an unmodifiable collection.
+     * 
+     * @return <code>List</code> of {@link Action}'s, not null, possibly
+     *         unmodifiable
+     */
+    public List<Action> getActions();
+    
+    /**
+     * Method getHeader answers a List of all of the headers in the receiver
+     * whose name is equal to the passed name. If no headers are found an empty
+     * List is returned.
+     * 
+     * @param name
+     * @return <code>List</code> not null, possibly empty, possible
+     *         unmodifiable
+     * @throws SieveMailException
+     */
+    public List<String> getHeader(String name) throws SieveMailException;
+
+    /**
+     * <p>
+     * Method getMatchingHeader answers a List of all of the headers in the
+     * receiver with the passed name. If no headers are found an empty List is
+     * returned.
+     * </p>
+     * 
+     * <p>
+     * This method differs from getHeader(String) in that it ignores case and
+     * the whitespace prefixes and suffixes of a header name when performing the
+     * match, as required by RFC 3028. Thus "From", "from ", " From" and " from "
+     * are considered equal.
+     * </p>
+     * 
+     * @param name
+     * @return <code>List</code>, not null possibly empty, possible
+     *         unmodifiable
+     * @throws SieveMailException
+     */
+    public List<String> getMatchingHeader(String name) throws SieveMailException;
+
+    /**
+     * Method getHeaderNames answers a List of all of the headers in the
+     * receiver. No duplicates are allowed.
+     * 
+     * @return <code>List</code>, not null possible empty, possible
+     *         unmodifiable
+     * @throws SieveMailException
+     */
+    public List<String> getHeaderNames() throws SieveMailException;
+
+    /**
+     * Method addAction adds an Action to the List of Actions to be performed by
+     * the receiver.
+     * 
+     * @param action
+     */
+    public void addAction(Action action);
+
+    /**
+     * Method executeActions. Applies the Actions accumulated by the receiver.
+     */
+    public void executeActions() throws SieveException;
+
+    /**
+     * Method getSize answers the receiver's message size in octets.
+     * 
+     * @return int
+     * @throws SieveMailException
+     */
+    int getSize() throws SieveMailException;
+
+    /**
+     * Method getContentType returns string/mime representation of the message
+     * type.
+     * 
+     * @return String
+     * @throws SieveMailException
+     */
+    public String getContentType() throws SieveMailException;
+
+    /**
+     * Is the given phrase found in the body text of this mail?
+     * This search should be case insensitive.
+     * @param phraseCaseInsensitive the phrase to search
+     * @return true when the mail has a textual body and contains the phrase
+     * (case insensitive), false otherwise
+     * @throws SieveMailException when the search cannot be completed
+     */
+    public boolean isInBodyText(final String phraseCaseInsensitive) throws SieveMailException;
+    
+    /**
+     * <p>
+     * Parses the named header value into individual addresses.
+     * </p>
+     * 
+     * <p>
+     * Headers should be matched in a way that ignores case and the whitespace
+     * prefixes and suffixes of a header name when performing the match, as
+     * required by RFC 3028. Thus "From", "from ", " From" and " from " are
+     * considered equal.
+     * </p>
+     * 
+     * @param headerName
+     *            name of the header whose value is to be split
+     * @return addresses listed in the given header not null, possibly empty
+     * @throws InternetAddressException
+     *             when the header value is not an address or list of addresses.
+     *             Implemetations may elect to support only standard headers
+     *             known to containing one or more addresses rather than parsing
+     *             the value content
+     * @throws SieveMailException
+     *             when the header value cannot be read
+     */
+    public Address[] parseAddresses(String headerName)
+            throws SieveMailException, InternetAddressException;
+
+    /**
+     * Contains address data required for SIEVE processing.
+     */
+    public interface Address {
+
+        /**
+         * Gets the local part of the email address. Specified in <a
+         * href='http://james.apache.org/server/rfclist/basic/rfc0822.txt'>RFC822</a>.
+         * 
+         * @return local part, not null
+         */
+        public String getLocalPart();
+
+        /**
+         * Gets the domain part of the email address. Specified in <a
+         * href='http://james.apache.org/server/rfclist/basic/rfc0822.txt'>RFC822</a>.
+         * 
+         * @return domain, not null
+         */
+        public String getDomain();
+    }
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/mail/MailUtils.java b/trunk/main/src/main/java/org/apache/jsieve/mail/MailUtils.java
new file mode 100644
index 0000000..4aea773
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/mail/MailUtils.java
@@ -0,0 +1,64 @@
+/****************************************************************
+ * 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.jsieve.mail;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Class MailUtils implements utility methods that are useful when processing
+ * Sieve mail.
+ */
+public class MailUtils {
+
+    /**
+     * Constructor for MailUtils.
+     */
+    protected MailUtils() {
+        super();
+    }
+
+    /**
+     * <p>
+     * Method getMatchingHeader answers a List of all of the headers in the mail
+     * with the passed name. If no headers are found an empty List is returned.
+     * </p>
+     * 
+     * <p>
+     * This method differs from MailAdapter.getHeader(String) in that it ignores
+     * case and whitespace prefixes and suffixes to a header name when
+     * performing the match, as required by RFC 3028. Thus "From", "from ", "
+     * From" and " from " are considered equal.
+     * </p>
+     * 
+     * @param name
+     * @return List
+     * @throws SieveMailException
+     */
+    static public List<String> getMatchingHeader(MailAdapter mail, String name)
+            throws SieveMailException {;
+        final List<String> matchedHeaderValues = new ArrayList<String>(32);
+        for (String headerName: mail.getHeaderNames()) {
+            if (headerName.trim().equalsIgnoreCase(name))
+                matchedHeaderValues.addAll(mail.getHeader(headerName));
+        }
+        return matchedHeaderValues;
+    }
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/mail/SieveMailException.java b/trunk/main/src/main/java/org/apache/jsieve/mail/SieveMailException.java
new file mode 100644
index 0000000..e6e98d5
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/mail/SieveMailException.java
@@ -0,0 +1,66 @@
+/****************************************************************
+ * 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.jsieve.mail;
+
+import org.apache.jsieve.exception.SieveException;
+
+/**
+ * Class <code>SieveMailException</code> indicates an exceptional condition
+ * encountered while processing Sieve Mail.
+ */
+@SuppressWarnings("serial")
+public class SieveMailException extends SieveException {
+
+    /**
+     * Constructor for SieveMailException.
+     */
+    public SieveMailException() {
+        super();
+    }
+
+    /**
+     * Constructor for SieveMailException.
+     * 
+     * @param message
+     */
+    public SieveMailException(String message) {
+        super(message);
+    }
+
+    /**
+     * Constructor for SieveMailException.
+     * 
+     * @param message
+     * @param cause
+     */
+    public SieveMailException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    /**
+     * Constructor for SieveMailException.
+     * 
+     * @param cause
+     */
+    public SieveMailException(Throwable cause) {
+        super(cause);
+    }
+
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/mail/optional/EnvelopeAccessors.java b/trunk/main/src/main/java/org/apache/jsieve/mail/optional/EnvelopeAccessors.java
new file mode 100644
index 0000000..f772a4d
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/mail/optional/EnvelopeAccessors.java
@@ -0,0 +1,71 @@
+/****************************************************************
+ * 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.jsieve.mail.optional;
+
+import java.util.List;
+
+import org.apache.jsieve.mail.SieveMailException;
+
+/**
+ * Interface EnvelopeAccessors specifies the method signatures required to
+ * support the Envelope Test.
+ */
+public interface EnvelopeAccessors {
+    /**
+     * Method getEnvelope answers a List of all of the envelope values in the
+     * receiver whose name is equal to the passed name. If no values are found
+     * an empty List is returned.
+     * 
+     * @param name
+     * @return List
+     * @throws SieveMailException
+     */
+    public List<String> getEnvelope(String name) throws SieveMailException;
+
+    /**
+     * Method getEnvelopeNames answers a List of the names of the envelope
+     * values in the receiver. No duplicates are allowed.
+     * 
+     * @return List
+     * @throws SieveMailException
+     */
+    public List<String> getEnvelopeNames() throws SieveMailException;
+
+    /**
+     * <p>
+     * Method getMatchingEnvelope answers a List of all of the envelope values
+     * in the receiver with the passed name. If no matching names are found an
+     * empty List is returned.
+     * </p>
+     * 
+     * <p>
+     * This method differs from getEnvelope(String) in that it ignores case and
+     * the whitespace prefixes and suffixes of an envelope value name when
+     * performing the match, as required by RFC 3028. Thus "From", "from ", "
+     * From" and " from " are considered equal.
+     * </p>
+     * 
+     * @param name
+     * @return List
+     * @throws SieveMailException
+     */
+    public List<String> getMatchingEnvelope(String name) throws SieveMailException;
+
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/parser/SieveNode.java b/trunk/main/src/main/java/org/apache/jsieve/parser/SieveNode.java
new file mode 100644
index 0000000..000bd6d
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/parser/SieveNode.java
@@ -0,0 +1,205 @@
+/****************************************************************
+ * 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.jsieve.parser;
+
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.jsieve.ScriptCoordinate;
+import org.apache.jsieve.parser.generated.Token;
+
+/**
+ * Class SieveNode defines aspects all jjTree parse nodes may require.
+ * 
+ * Creation Date: 27-Jan-04
+ */
+public class SieveNode {
+
+    /**
+     * Constructor for SieveNode.
+     */
+    public SieveNode() {
+        super();
+    }
+
+    private Token firstToken;
+
+    private Token lastToken;
+
+    /**
+     * The name associated to this node or null
+     */
+    private String fieldName;
+
+    /**
+     * The value associated to this node or null
+     */
+    private Object fieldValue;
+
+    /**
+     * Returns the name.
+     * 
+     * @return String
+     */
+    public String getName() {
+        return fieldName;
+    }
+
+    /**
+     * Returns the value.
+     * 
+     * @return Object
+     */
+    public Object getValue() {
+        return fieldValue;
+    }
+
+    /**
+     * Sets the name.
+     * 
+     * @param name
+     *            The name to set
+     */
+    public void setName(String name) {
+        fieldName = name;
+    }
+
+    /**
+     * Sets the value.
+     * 
+     * @param value
+     *            The value to set
+     */
+    public void setValue(Object value) {
+        fieldValue = value;
+    }
+
+    /**
+     * Gets the first token comprising this node.
+     * 
+     * @return <code>Token</code>, not null
+     */
+    public Token getFirstToken() {
+        return firstToken;
+    }
+
+    /**
+     * Sets the first token comprising this node.
+     * 
+     * @param firstToken
+     *            <code>Token</code>, not null
+     */
+    public void setFirstToken(Token firstToken) {
+        this.firstToken = firstToken;
+    }
+
+    /**
+     * Gets the last token comprising this node.
+     * 
+     * @return <code>Token</code>, not null
+     */
+    public Token getLastToken() {
+        return lastToken;
+    }
+
+    /**
+     * Sets the last token comprising this node.
+     * 
+     * @param lastToken
+     *            <code>Token</code>, not null
+     */
+    public void setLastToken(Token lastToken) {
+        this.lastToken = lastToken;
+    }
+
+    /**
+     * Gets the position of this node in the script.
+     * 
+     * @return <code>ScriptCoordinate</code> containing the position of this
+     *         node, not null
+     */
+    public ScriptCoordinate getCoordinate() {
+        final int lastColumn = lastToken.endColumn;
+        final int lastList = lastToken.endLine;
+        final int firstColumn = firstToken.beginColumn;
+        final int firstLine = firstToken.beginLine;
+        final ScriptCoordinate scriptCoordinate = new ScriptCoordinate(
+                firstLine, firstColumn, lastList, lastColumn);
+        return scriptCoordinate;
+    }
+
+    /**
+     * Get any comments between this node and the previous one.
+     * Each comment is returned without whitespace trimming.
+     * Comments are returned in the order of occurance in the script.
+     * @return collection of strings, not null
+     */
+    public List<String> getPrecedingComments() {
+        final LinkedList<String> results = new LinkedList<String>();
+        if (firstToken != null) {
+            Token special = firstToken.specialToken;
+            while (special != null) {
+                final String comment = parseComment(special);
+                results.addFirst(comment);
+                special = special.specialToken;
+            }
+        }
+        return results;
+    }
+
+    private String parseComment(Token special) {
+        final String image = special.image;
+        final String comment;
+        if ('#' == image.charAt(0)) {
+            final int leftHandCharactersToIgnore;
+            if ('\r' == image.charAt(image.length()-2)) {
+                leftHandCharactersToIgnore = 2;
+            } else {
+                leftHandCharactersToIgnore = 1;
+            }
+            comment = image.substring(1, image.length()-leftHandCharactersToIgnore);
+        } else {
+            comment = image.substring(2, image.length()-2);
+        }
+        return comment;
+    }
+    
+    /**
+     * Get the last comment before this node and after the last node.
+     * Each comment is returned without whitespace trimming.
+     * Comments are returned in the order of occurance in the script.
+     * @return the comment without whitespace trimming,
+     * or null if there is no comment between this and the last node
+     */
+    public String getLastComment() {
+        final String result;
+        if (firstToken == null) {
+            result = null;
+        } else {
+            Token special = firstToken.specialToken;
+            if (special == null) {
+                result = null;
+            } else {
+                result = parseComment(special);
+            } 
+        }
+        return result;
+    }
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/parser/address/AddressNode.java b/trunk/main/src/main/java/org/apache/jsieve/parser/address/AddressNode.java
new file mode 100644
index 0000000..2b7ef10
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/parser/address/AddressNode.java
@@ -0,0 +1,30 @@
+/****************************************************************
+ * 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.jsieve.parser.address;
+
+import org.apache.jsieve.parser.generated.address.Token;
+
+public class AddressNode {
+
+    public Token firstToken = null;
+
+    public Token lastToken = null;
+
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/parser/address/BaseAddressListVisitor.java b/trunk/main/src/main/java/org/apache/jsieve/parser/address/BaseAddressListVisitor.java
new file mode 100644
index 0000000..65d6cc1
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/parser/address/BaseAddressListVisitor.java
@@ -0,0 +1,89 @@
+/****************************************************************
+ * 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.jsieve.parser.address;
+
+import org.apache.jsieve.parser.generated.address.ASTaddr_spec;
+import org.apache.jsieve.parser.generated.address.ASTaddress;
+import org.apache.jsieve.parser.generated.address.ASTaddress_list;
+import org.apache.jsieve.parser.generated.address.ASTangle_addr;
+import org.apache.jsieve.parser.generated.address.ASTdomain;
+import org.apache.jsieve.parser.generated.address.ASTgroup_body;
+import org.apache.jsieve.parser.generated.address.ASTlocal_part;
+import org.apache.jsieve.parser.generated.address.ASTmailbox;
+import org.apache.jsieve.parser.generated.address.ASTname_addr;
+import org.apache.jsieve.parser.generated.address.ASTphrase;
+import org.apache.jsieve.parser.generated.address.ASTroute;
+import org.apache.jsieve.parser.generated.address.AddressListParserVisitor;
+import org.apache.jsieve.parser.generated.address.SimpleNode;
+
+/**
+ * Do nothing implementation suitablae for subclassing.
+ */
+public abstract class BaseAddressListVisitor implements
+        AddressListParserVisitor {
+
+    public Object visit(SimpleNode node, Object data) {
+        return node.childrenAccept(this, data);
+    }
+
+    public Object visit(ASTaddress_list node, Object data) {
+        return node.childrenAccept(this, data);
+    }
+
+    public Object visit(ASTaddress node, Object data) {
+        return node.childrenAccept(this, data);
+    }
+
+    public Object visit(ASTmailbox node, Object data) {
+        return node.childrenAccept(this, data);
+    }
+
+    public Object visit(ASTname_addr node, Object data) {
+        return node.childrenAccept(this, data);
+    }
+
+    public Object visit(ASTgroup_body node, Object data) {
+        return node.childrenAccept(this, data);
+    }
+
+    public Object visit(ASTangle_addr node, Object data) {
+        return node.childrenAccept(this, data);
+    }
+
+    public Object visit(ASTroute node, Object data) {
+        return node.childrenAccept(this, data);
+    }
+
+    public Object visit(ASTphrase node, Object data) {
+        return node.childrenAccept(this, data);
+    }
+
+    public Object visit(ASTaddr_spec node, Object data) {
+        return node.childrenAccept(this, data);
+    }
+
+    public Object visit(ASTlocal_part node, Object data) {
+        return node.childrenAccept(this, data);
+    }
+
+    public Object visit(ASTdomain node, Object data) {
+        return node.childrenAccept(this, data);
+    }
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/parser/address/SieveAddressBuilder.java b/trunk/main/src/main/java/org/apache/jsieve/parser/address/SieveAddressBuilder.java
new file mode 100644
index 0000000..284a136
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/parser/address/SieveAddressBuilder.java
@@ -0,0 +1,172 @@
+/****************************************************************
+ * 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.jsieve.parser.address;
+
+import java.io.Reader;
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+
+import org.apache.jsieve.mail.MailAdapter;
+import org.apache.jsieve.mail.MailAdapter.Address;
+import org.apache.jsieve.parser.generated.address.ASTaddr_spec;
+import org.apache.jsieve.parser.generated.address.ASTaddress_list;
+import org.apache.jsieve.parser.generated.address.ASTdomain;
+import org.apache.jsieve.parser.generated.address.ASTlocal_part;
+import org.apache.jsieve.parser.generated.address.AddressListParser;
+import org.apache.jsieve.parser.generated.address.ParseException;
+import org.apache.jsieve.parser.generated.address.Token;
+
+/**
+ * Builds <code>MailAdapter.Address</code> from address lists. Note that
+ * implementators of {@link MailAdapter} are recommended to use a fully featured
+ * and maintained parser such as <a href='http://james.apache.org/mime4j'>Apache
+ * Mime4J</a>. This implementation is based on Mime4J code but is intended only
+ * for internal and demonstration purposes. It is not actively maintained.
+ */
+public class SieveAddressBuilder {
+
+    private static final Address[] EMPTY_ADDRESSES = {};
+
+    private final Collection<Address> addresses;
+
+    private final Worker worker;
+
+    public SieveAddressBuilder() {
+        addresses = Collections.synchronizedCollection(new ArrayList<Address>());
+        worker = new Worker();
+    }
+
+    /**
+     * Clears the addresses currently accumulated.
+     */
+    public void reset() {
+        addresses.clear();
+    }
+
+    /**
+     * Adds addresses in the given list.
+     * 
+     * @param addressList
+     *            RFC822 address list
+     * @throws ParseException
+     */
+    public void addAddresses(String addressList) throws ParseException {
+        final StringReader reader = new StringReader(addressList);
+        worker.addAddressses(reader, addresses);
+    }
+
+    /**
+     * Gets addresses currently accumulated by calls to
+     * {@link #addAddresses(String)} since the last call to {@link #reset}.
+     * 
+     * @return addresses, not null
+     */
+    public Address[] getAddresses() {
+        final Address[] results = (Address[]) addresses.toArray(EMPTY_ADDRESSES);
+        return results;
+    }
+
+    /**
+     * Performs the actual work. Factored into an inner class so that the build
+     * interface is clean.
+     */
+    private final class Worker extends BaseAddressListVisitor {
+
+        public void addAddressses(final Reader reader, final Collection results)
+                throws ParseException {
+            AddressListParser parser = new AddressListParser(reader);
+            ASTaddress_list root = parser.parse();
+            root.childrenAccept(this, results);
+        }
+
+        @SuppressWarnings("unchecked")
+        public Object visit(ASTaddr_spec node, Object data) {
+            final AddressBean address = new AddressBean();
+            node.childrenAccept(this, address);
+            if (data instanceof Collection) {
+                final Collection collection = (Collection) data;
+                collection.add(address);
+            }
+            return data;
+        }
+
+        public Object visit(final ASTdomain node, final Object data) {
+            if (data instanceof AddressBean) {
+                final AddressBean address = (AddressBean) data;
+                final String domain = contents(node);
+                address.setDomain(domain);
+            }
+            return data;
+        }
+
+        public Object visit(ASTlocal_part node, Object data) {
+            if (data instanceof AddressBean) {
+                final AddressBean address = (AddressBean) data;
+                final String localPart = contents(node);
+                address.setLocalPart(localPart);
+            }
+            return data;
+        }
+
+        private String contents(AddressNode node) {
+            StringBuffer buffer = new StringBuffer(32);
+            Token last = node.lastToken;
+            Token next = node.firstToken;
+            while (next != last) {
+                buffer.append(next.image);
+                next = next.next;
+            }
+            buffer.append(last.image);
+            return buffer.toString();
+        }
+    }
+
+    /**
+     * Bean based address implementation.
+     */
+    private final class AddressBean implements Address {
+        private String localPart;
+
+        private String domain;
+
+        public AddressBean() {
+            localPart = "";
+            domain = "";
+        }
+
+        public String getDomain() {
+            return domain;
+        }
+
+        public void setDomain(String domain) {
+            this.domain = domain;
+        }
+
+        public String getLocalPart() {
+            return localPart;
+        }
+
+        public void setLocalPart(String localPart) {
+            this.localPart = localPart;
+        }
+    }
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/tests/AbstractCompatatorTest.java b/trunk/main/src/main/java/org/apache/jsieve/tests/AbstractCompatatorTest.java
new file mode 100644
index 0000000..9d46c53
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/tests/AbstractCompatatorTest.java
@@ -0,0 +1,234 @@
+/****************************************************************
+ * 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.jsieve.tests;
+
+import static org.apache.jsieve.comparators.ComparatorNames.ASCII_CASEMAP_COMPARATOR;
+import static org.apache.jsieve.comparators.MatchTypeTags.CONTAINS_TAG;
+import static org.apache.jsieve.comparators.MatchTypeTags.IS_TAG;
+import static org.apache.jsieve.comparators.MatchTypeTags.MATCHES_TAG;
+import static org.apache.jsieve.tests.AddressPartTags.ALL_TAG;
+import static org.apache.jsieve.tests.AddressPartTags.DOMAIN_TAG;
+import static org.apache.jsieve.tests.AddressPartTags.LOCALPART_TAG;
+import static org.apache.jsieve.tests.ComparatorTags.COMPARATOR_TAG;
+
+import java.util.List;
+import java.util.ListIterator;
+
+import org.apache.jsieve.Argument;
+import org.apache.jsieve.Arguments;
+import org.apache.jsieve.SieveContext;
+import org.apache.jsieve.StringListArgument;
+import org.apache.jsieve.TagArgument;
+import org.apache.jsieve.exception.SieveException;
+import org.apache.jsieve.exception.SyntaxException;
+import org.apache.jsieve.mail.MailAdapter;
+import org.apache.jsieve.mail.SieveMailException;
+
+
+public abstract class AbstractCompatatorTest extends AbstractTest {
+
+    public AbstractCompatatorTest() {
+        super();
+    }
+
+    /**
+     * <p>
+     * From RFC 3028, Section 5.1...
+     * </p>
+     * <code>  
+     * Syntax: address [ADDRESS-PART] [COMPARATOR] [MATCH-TYPE]
+     *         &lt;header-list: string-list&gt; &lt;key-list: string-list&gt;
+     * </code>
+     * <p>
+     * Note that the spec. then goes on to give an example where the order of
+     * the optional parts is different, so I am assuming that the order of the
+     * optional parts is optional too!
+     * </p>
+     * 
+     * @see org.apache.jsieve.tests.AbstractTest#executeBasic(MailAdapter,
+     *      Arguments, SieveContext)
+     */
+    protected boolean executeBasic(MailAdapter mail, Arguments arguments,
+            SieveContext context) throws SieveException {
+        String addressPart = null;
+        String comparator = null;
+        String matchType = null;
+        List<String> headerNames = null;
+        List<String> keys = null;
+
+        ListIterator<Argument> argumentsIter = arguments.getArgumentList().listIterator();
+        boolean stop = false;
+
+        // Tag processing
+        while (!stop && argumentsIter.hasNext()) {
+            Argument argument = argumentsIter.next();
+            if (argument instanceof TagArgument) {
+                String tag = ((TagArgument) argument).getTag();
+
+                // [ADDRESS-PART]?
+                if (null == addressPart
+                        && (tag.equals(LOCALPART_TAG) || tag.equals(DOMAIN_TAG) || tag
+                                .equals(ALL_TAG)))
+                    addressPart = tag;
+                // [COMPARATOR]?
+                else if (null == comparator && tag.equals(COMPARATOR_TAG)) {
+                    // The next argument must be a stringlist
+                    if (argumentsIter.hasNext()) {
+                        argument = argumentsIter.next();
+                        if (argument instanceof StringListArgument) {
+                            List stringList = ((StringListArgument) argument)
+                                    .getList();
+                            if (stringList.size() != 1)
+                                throw new SyntaxException(
+                                        "Expecting exactly one String");
+                            comparator = (String) stringList.get(0);
+                        } else
+                            throw new SyntaxException("Expecting a StringList");
+                    }
+                }
+                // [MATCH-TYPE]?
+                else if (null == matchType
+                        && (tag.equals(IS_TAG) || tag.equals(CONTAINS_TAG) || tag
+                                .equals(MATCHES_TAG)))
+                    matchType = tag;
+                else
+                    throw context.getCoordinate().syntaxException(
+                            "Found unexpected TagArgument");
+            } else {
+                // Stop when a non-tag argument is encountered
+                argumentsIter.previous();
+                stop = true;
+            }
+        }
+
+        // The next argument MUST be a string-list of header names
+        if (argumentsIter.hasNext()) {
+            Argument argument = argumentsIter.next();
+            if (argument instanceof StringListArgument)
+                headerNames = ((StringListArgument) argument).getList();
+        }
+        if (null == headerNames)
+            throw context.getCoordinate().syntaxException(
+                    "Expecting a StringList of header names");
+
+        // The next argument MUST be a string-list of keys
+        if (argumentsIter.hasNext()) {
+            Argument argument = argumentsIter.next();
+            if (argument instanceof StringListArgument)
+                keys = ((StringListArgument) argument).getList();
+        } else if (null == keys)
+            throw context.getCoordinate().syntaxException(
+                    "Expecting a StringList of keys");
+
+        if (argumentsIter.hasNext())
+            throw context.getCoordinate().syntaxException(
+                    "Found unexpected arguments");
+
+        return match(mail, (addressPart == null ? ALL_TAG : addressPart),
+                (comparator == null ? ASCII_CASEMAP_COMPARATOR : comparator),
+                (matchType == null ? IS_TAG : matchType), headerNames, keys,
+                context);
+    }
+
+    /**
+     * Method match.
+     * 
+     * @param mail
+     * @param addressPart
+     * @param comparator
+     * @param matchType
+     * @param headerNames
+     * @param keys
+     * @param context not null
+     * @return boolean
+     * @throws SieveMailException
+     */
+    protected boolean match(MailAdapter mail, String addressPart,
+            String comparator, String matchType, List<String> headerNames, List<String> keys,
+            SieveContext context) throws SieveException {
+        // Iterate over the header names looking for a match
+        boolean isMatched = false;
+        for (final String headerName: headerNames) {
+            isMatched = match(mail, addressPart, comparator, matchType,
+                    headerName, keys, context); 
+            if (isMatched) {
+                break;
+            }
+        }
+        return isMatched;
+    }
+
+    /**
+     * Method match.
+     * 
+     * @param mail
+     * @param addressPart
+     * @param comparator
+     * @param matchType
+     * @param headerName
+     * @param keys
+     * @param context not null
+     * @return boolean
+     * @throws SieveMailException
+     */
+    protected boolean match(MailAdapter mail, String addressPart,
+            String comparator, String matchType, String headerName, List<String> keys,
+            SieveContext context) throws SieveException {
+        // Iterate over the keys looking for a match
+        boolean isMatched = false;
+        for (final String key:keys) {
+            isMatched = match(mail, addressPart, comparator, matchType,
+                    headerName, key, context);
+            if (isMatched) {
+                break;
+            }
+        }
+        return isMatched;
+    }
+
+    /**
+     * Method match.
+     * 
+     * @param mail
+     * @param addressPart
+     * @param comparator
+     * @param matchType
+     * @param headerName
+     * @param key
+     * @param context not null
+     * @return boolean
+     * @throws SieveMailException
+     */
+    protected abstract boolean match(MailAdapter mail, String addressPart,
+            String comparator, String matchType, String headerName, String key,
+            SieveContext context) throws SieveException;
+
+    /**
+     * @see org.apache.jsieve.tests.AbstractTest#validateArguments(Arguments,
+     *      SieveContext)
+     */
+    protected void validateArguments(Arguments arguments, SieveContext context)
+            throws SieveException {
+        if (arguments.hasTests())
+            throw context.getCoordinate().syntaxException(
+                    "Found unexpected tests");
+    }
+
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/tests/AbstractTest.java b/trunk/main/src/main/java/org/apache/jsieve/tests/AbstractTest.java
new file mode 100644
index 0000000..653a97c
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/tests/AbstractTest.java
@@ -0,0 +1,100 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.jsieve.tests;
+
+import org.apache.commons.logging.Log;
+import org.apache.jsieve.Arguments;
+import org.apache.jsieve.SieveContext;
+import org.apache.jsieve.exception.SieveException;
+import org.apache.jsieve.exception.SyntaxException;
+import org.apache.jsieve.mail.MailAdapter;
+
+/**
+ * Abstract class AbstractTest defines a framework of common behavior for Sieve
+ * Tests.
+ */
+public abstract class AbstractTest implements ExecutableTest {
+
+    /**
+     * Constructor for AbstractTest.
+     */
+    public AbstractTest() {
+        super();
+    }
+
+    /**
+     * <p>
+     * Method execute executes a basic Sieve Test after first invoking framework
+     * methods to validate the Command arguments.
+     * </p>
+     * 
+     * <p>
+     * Also,
+     * 
+     * @see org.apache.jsieve.tests.ExecutableTest#execute(MailAdapter,
+     *      Arguments, SieveContext)
+     */
+    public boolean execute(MailAdapter mail, Arguments arguments,
+            SieveContext context) throws SieveException {
+        validateArguments(arguments, context);
+        return executeBasic(mail, arguments, context);
+    }
+
+    /**
+     * Abstract method executeBasic invokes a Sieve Test.
+     * 
+     * @param mail
+     * @param arguments
+     * @param context
+     *            <code>SieveContext</code> giving contextual information, not
+     *            null
+     * @return boolean
+     * @throws SieveException
+     */
+    protected abstract boolean executeBasic(MailAdapter mail,
+            Arguments arguments, SieveContext context) throws SieveException;
+
+    /**
+     * Framework method validateArguments is invoked before a Sieve Test is
+     * executed to validate its arguments. Subclass methods are expected to
+     * override or extend this method to perform their own validation as
+     * appropriate.
+     * 
+     * @param arguments
+     * @param context
+     *            <code>SieveContext</code> giving comntextual information,
+     *            not null
+     * @throws SieveException
+     */
+    protected void validateArguments(Arguments arguments, SieveContext context)
+            throws SieveException {
+        if (!arguments.getArgumentList().isEmpty()) {
+            final Log logger = context.getLog();
+            if (logger.isWarnEnabled()) {
+                logger.warn("Unexpected arguments for " + getClass().getName());
+            }
+            context.getCoordinate().logDiagnosticsInfo(logger);
+            logger.debug(arguments);
+            final String message = context.getCoordinate()
+                    .addStartLineAndColumn("Found unexpected arguments.");
+            throw new SyntaxException(message);
+        }
+    }
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/tests/Address.java b/trunk/main/src/main/java/org/apache/jsieve/tests/Address.java
new file mode 100644
index 0000000..1ec68a0
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/tests/Address.java
@@ -0,0 +1,92 @@
+/****************************************************************
+ * 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.jsieve.tests;
+
+import static org.apache.jsieve.tests.AddressPartTags.DOMAIN_TAG;
+import static org.apache.jsieve.tests.AddressPartTags.LOCALPART_TAG;
+
+import org.apache.jsieve.SieveContext;
+import org.apache.jsieve.comparators.ComparatorUtils;
+import org.apache.jsieve.exception.InternetAddressException;
+import org.apache.jsieve.exception.SieveException;
+import org.apache.jsieve.mail.MailAdapter;
+import org.apache.jsieve.mail.SieveMailException;
+
+/**
+ * Class Address implements the Addresss Test as defined in RFC 3028, section
+ * 5.1.
+ */
+public class Address extends AbstractCompatatorTest {
+    /**
+     * Constructor for Address.
+     */
+    public Address() {
+        super();
+    }
+
+    protected boolean match(MailAdapter mail, String addressPart,
+            String comparator, String matchType, String headerName, String key,
+            SieveContext context) throws SieveException {
+        final MailAdapter.Address[] addresses = getMatchingValues(mail,
+                headerName);
+        final int length = addresses.length;
+        int i = 0;
+        boolean isMatched = false;
+        while (!isMatched && i < length) {
+            isMatched = match(addressPart, comparator, matchType,
+                    addresses[i++], key, context);
+        }
+        return isMatched;
+    }
+
+    private MailAdapter.Address[] getMatchingValues(MailAdapter mail,
+            String valueName) throws SieveMailException,
+            InternetAddressException {
+        return mail.parseAddresses(valueName);
+    }
+
+    protected boolean match(String addressPart, String comparator,
+            String matchType, MailAdapter.Address address, String key,
+            SieveContext context) throws SieveException {
+        final String localPart = address.getLocalPart();
+        final String domain = address.getDomain();
+
+        // Extract the part of the address we are matching on
+        final String matchAddress;
+        if (addressPart.equals(":all"))
+            matchAddress = localPart + "@" + domain;
+        else {
+            matchAddress = (addressPart.equals(LOCALPART_TAG) ? localPart
+                    : domain.toLowerCase());
+        }
+
+        // domain matches MUST ignore case, others should not
+        String matchKey = null;
+        if (addressPart.equals(DOMAIN_TAG)) {
+            matchKey = key.toLowerCase();
+        } else {
+            matchKey = key;
+        }
+
+        // Match using the specified comparator
+        return ComparatorUtils.match(comparator, matchType, matchAddress,
+                matchKey, context);
+    }
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/tests/AddressPartTags.java b/trunk/main/src/main/java/org/apache/jsieve/tests/AddressPartTags.java
new file mode 100644
index 0000000..5f9cfec
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/tests/AddressPartTags.java
@@ -0,0 +1,33 @@
+/****************************************************************
+ * 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.jsieve.tests;
+
+/**
+ * Interface AddressPartTags defines the String constants used to specify an
+ * ADDRESS-PART tag.
+ */
+public interface AddressPartTags {
+    public static final String LOCALPART_TAG = ":localpart";
+
+    public static final String DOMAIN_TAG = ":domain";
+
+    public static final String ALL_TAG = ":all";
+
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/tests/AllOf.java b/trunk/main/src/main/java/org/apache/jsieve/tests/AllOf.java
new file mode 100644
index 0000000..8a8510a
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/tests/AllOf.java
@@ -0,0 +1,48 @@
+/****************************************************************
+ * 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.jsieve.tests;
+
+import org.apache.jsieve.Arguments;
+import org.apache.jsieve.SieveContext;
+import org.apache.jsieve.exception.SieveException;
+import org.apache.jsieve.mail.MailAdapter;
+
+/**
+ * Class AllOf implements the AllOf Test as defined in RFC 3028, section 5.2.
+ */
+public class AllOf extends AbstractTest {
+
+    /**
+     * Constructor for AnyOf.
+     */
+    public AllOf() {
+        super();
+    }
+
+    /**
+     * @see org.apache.jsieve.tests.AbstractTest#executeBasic(MailAdapter,
+     *      Arguments, SieveContext)
+     */
+    protected boolean executeBasic(MailAdapter mail, Arguments arguments,
+            SieveContext context) throws SieveException {
+        return arguments.getTestList().allTestsPass(mail, context);
+    }
+
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/tests/AnyOf.java b/trunk/main/src/main/java/org/apache/jsieve/tests/AnyOf.java
new file mode 100644
index 0000000..539b277
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/tests/AnyOf.java
@@ -0,0 +1,48 @@
+/****************************************************************
+ * 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.jsieve.tests;
+
+import org.apache.jsieve.Arguments;
+import org.apache.jsieve.SieveContext;
+import org.apache.jsieve.exception.SieveException;
+import org.apache.jsieve.mail.MailAdapter;
+
+/**
+ * Class AnyOf implements the AnyOf Test as defined in RFC 3028, section 5.3.
+ */
+public class AnyOf extends AbstractTest {
+
+    /**
+     * Constructor for AnyOf.
+     */
+    public AnyOf() {
+        super();
+    }
+
+    /**
+     * @see org.apache.jsieve.tests.AbstractTest#executeBasic(MailAdapter,
+     *      Arguments, SieveContext)
+     */
+    protected boolean executeBasic(MailAdapter mail, Arguments arguments,
+            SieveContext context) throws SieveException {
+        return arguments.getTestList().anyTestsPass(mail, context);
+    }
+
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/tests/ComparatorTags.java b/trunk/main/src/main/java/org/apache/jsieve/tests/ComparatorTags.java
new file mode 100644
index 0000000..1fe5dad
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/tests/ComparatorTags.java
@@ -0,0 +1,28 @@
+/****************************************************************
+ * 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.jsieve.tests;
+
+/**
+ * Interface ComparatorTags defines the String constants used to specify a
+ * COMPARATOR tag.
+ */
+public interface ComparatorTags {
+    public static final String COMPARATOR_TAG = ":comparator";
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/tests/ExecutableTest.java b/trunk/main/src/main/java/org/apache/jsieve/tests/ExecutableTest.java
new file mode 100644
index 0000000..6e4b84e
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/tests/ExecutableTest.java
@@ -0,0 +1,46 @@
+/****************************************************************
+ * 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.jsieve.tests;
+
+import org.apache.jsieve.Arguments;
+import org.apache.jsieve.SieveContext;
+import org.apache.jsieve.exception.SieveException;
+import org.apache.jsieve.mail.MailAdapter;
+
+/**
+ * Interface ExecutableTest defines the method signatures for a Sieve Test.
+ */
+public interface ExecutableTest {
+    /**
+     * Method execute executes a Test and answers a boolean indicating if the
+     * test was passed.
+     * 
+     * @param mail
+     * @param arguments
+     * @param context
+     *            <code>SieveContext</code> giving contextual information, not
+     *            null
+     * @return boolean
+     * @throws SieveException
+     */
+    public boolean execute(MailAdapter mail, Arguments arguments,
+            SieveContext context) throws SieveException;
+
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/tests/Exists.java b/trunk/main/src/main/java/org/apache/jsieve/tests/Exists.java
new file mode 100644
index 0000000..a138b52
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/tests/Exists.java
@@ -0,0 +1,84 @@
+/****************************************************************
+ * 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.jsieve.tests;
+
+import java.util.List;
+
+import org.apache.jsieve.Argument;
+import org.apache.jsieve.Arguments;
+import org.apache.jsieve.SieveContext;
+import org.apache.jsieve.StringListArgument;
+import org.apache.jsieve.exception.SieveException;
+import org.apache.jsieve.mail.MailAdapter;
+
+/**
+ * Class Exists implements the Exists Test as defined in RFC 3028, section 5.5.
+ */
+public class Exists extends AbstractTest {
+
+    /**
+     * Constructor for Exists.
+     */
+    public Exists() {
+        super();
+    }
+
+    /**
+     * @see org.apache.jsieve.tests.AbstractTest#executeBasic(MailAdapter,
+     *      Arguments, SieveContext)
+     */
+    protected boolean executeBasic(MailAdapter mail, Arguments arguments,
+            SieveContext context) throws SieveException {
+
+        final List<String> argumentList = ((StringListArgument) arguments
+                                .getArgumentList().get(0)).getList();
+        
+        boolean found = true;
+        for (final String arg:argumentList) {
+            List<String> headers = mail.getMatchingHeader(arg);
+            found = found && !headers.isEmpty();
+            if (!found) {
+                break;
+            }
+        }
+        return found;
+    }
+
+    /**
+     * @see org.apache.jsieve.tests.AbstractTest#validateArguments(Arguments,
+     *      SieveContext)
+     */
+    protected void validateArguments(Arguments arguments, SieveContext context)
+            throws SieveException {
+        List<Argument> argumentsList = arguments.getArgumentList();
+        if (1 != argumentsList.size())
+            throw context.getCoordinate().syntaxException(
+                    "Expecting exactly one argument");
+
+        if (!(argumentsList.get(0) instanceof StringListArgument))
+            throw context.getCoordinate().syntaxException(
+                    "Expecting a StringList");
+
+        if (arguments.hasTests())
+            throw context.getCoordinate().syntaxException(
+                    "Found unexpected tests");
+    }
+
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/tests/False.java b/trunk/main/src/main/java/org/apache/jsieve/tests/False.java
new file mode 100644
index 0000000..8191a51
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/tests/False.java
@@ -0,0 +1,47 @@
+/****************************************************************
+ * 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.jsieve.tests;
+
+import org.apache.jsieve.Arguments;
+import org.apache.jsieve.SieveContext;
+import org.apache.jsieve.mail.MailAdapter;
+
+/**
+ * Class False implements the False Test as defined in RFC 3028, section 5.6.
+ */
+public class False extends AbstractTest {
+
+    /**
+     * Constructor for False.
+     */
+    public False() {
+        super();
+    }
+
+    /**
+     * @see org.apache.jsieve.tests.AbstractTest#executeBasic(MailAdapter,
+     *      Arguments, SieveContext)
+     */
+    protected boolean executeBasic(MailAdapter mail, Arguments arguments,
+            SieveContext context) {
+        return false;
+    }
+
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/tests/Header.java b/trunk/main/src/main/java/org/apache/jsieve/tests/Header.java
new file mode 100644
index 0000000..014f2c7
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/tests/Header.java
@@ -0,0 +1,247 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.jsieve.tests;
+
+import static org.apache.jsieve.comparators.ComparatorNames.ASCII_CASEMAP_COMPARATOR;
+import static org.apache.jsieve.comparators.MatchTypeTags.CONTAINS_TAG;
+import static org.apache.jsieve.comparators.MatchTypeTags.IS_TAG;
+import static org.apache.jsieve.comparators.MatchTypeTags.MATCHES_TAG;
+import static org.apache.jsieve.tests.ComparatorTags.COMPARATOR_TAG;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+
+import org.apache.jsieve.Argument;
+import org.apache.jsieve.Arguments;
+import org.apache.jsieve.SieveContext;
+import org.apache.jsieve.StringListArgument;
+import org.apache.jsieve.TagArgument;
+import org.apache.jsieve.comparators.ComparatorUtils;
+import org.apache.jsieve.exception.SieveException;
+import org.apache.jsieve.mail.MailAdapter;
+
+/**
+ * Class Header implements the Header Test as defined in RFC 3028, section 5.7.
+ */
+public class Header extends AbstractTest {
+
+    /**
+     * Constructor for Header.
+     */
+    public Header() {
+        super();
+    }
+
+    /**
+     * <p>
+     * From RFC 3028, Section 5.7...
+     * </p>
+     * <code>  
+     * Syntax: header [COMPARATOR] [MATCH-TYPE]
+     *       &lt;header-names: string-list&gt; &lt;key-list: string-list&gt;
+     * </code>
+     * <p>
+     * Note that the spec. then goes on to give an example where the order of
+     * the optional parts is different, so I guess that the order is optional
+     * too!
+     * </p>
+     * 
+     * @see org.apache.jsieve.tests.AbstractTest#executeBasic(MailAdapter,
+     *      Arguments, SieveContext)
+     */
+    protected boolean executeBasic(MailAdapter mail, Arguments arguments,
+            SieveContext context) throws SieveException {
+        String comparator = null;
+        String matchType = null;
+        List<String> headerNames = null;
+        List<String> keys = null;
+
+        ListIterator<Argument> argumentsIter = arguments.getArgumentList().listIterator();
+        boolean stop = false;
+
+        // Tag processing
+        while (!stop && argumentsIter.hasNext()) {
+            Argument argument = argumentsIter.next();
+            if (argument instanceof TagArgument) {
+                final String tag = ((TagArgument) argument).getTag();
+
+                if (null == comparator && tag.equals(COMPARATOR_TAG)) {
+                    // The next argument must be a stringlist
+                    if (argumentsIter.hasNext()) {
+                        argument = argumentsIter.next();
+                        if (argument instanceof StringListArgument) {
+                            List<String> stringList = ((StringListArgument) argument)
+                                    .getList();
+                            if (stringList.size() != 1)
+                                throw context.getCoordinate().syntaxException(
+                                        "Expecting exactly one String");
+                            comparator = stringList.get(0);
+                        } else
+                            throw context.getCoordinate().syntaxException(
+                                    "Expecting a StringList");
+                    }
+                }
+                // [MATCH-TYPE]?
+                else if (null == matchType
+                        && (tag.equals(IS_TAG) || tag.equals(CONTAINS_TAG) || tag
+                                .equals(MATCHES_TAG)))
+                    matchType = tag;
+                else
+                    throw context.getCoordinate().syntaxException(
+                            "Found unexpected TagArgument: \"" + tag + "\"");
+            } else {
+                // Stop when a non-tag argument is encountered
+                argumentsIter.previous();
+                stop = true;
+            }
+        }
+
+        // The next argument MUST be a string-list of header names
+        if (argumentsIter.hasNext()) {
+            final Argument argument = argumentsIter.next();
+            if (argument instanceof StringListArgument)
+                headerNames = ((StringListArgument) argument).getList();
+        }
+        if (null == headerNames)
+            throw context.getCoordinate().syntaxException(
+                    "Expecting a StringListof header names");
+
+        // The next argument MUST be a string-list of keys
+        if (argumentsIter.hasNext()) {
+            final Argument argument = argumentsIter.next();
+            if (argument instanceof StringListArgument)
+                keys = ((StringListArgument) argument).getList();
+        }
+        if (null == keys)
+            throw context.getCoordinate().syntaxException(
+                    "Expecting a StringList of keys");
+
+        if (argumentsIter.hasNext())
+            throw context.getCoordinate().syntaxException(
+                    "Found unexpected arguments");
+
+        return match(mail, (comparator == null ? ASCII_CASEMAP_COMPARATOR
+                : comparator), (matchType == null ? IS_TAG : matchType),
+                headerNames, keys, context);
+    }
+
+    /**
+     * Method match.
+     * 
+     * @param mail
+     * @param comparator
+     * @param matchType
+     * @param headerNames
+     * @param keys
+     * @param context not null
+     * @return boolean
+     * @throws SieveException
+     */
+    protected boolean match(MailAdapter mail, String comparator,
+            String matchType, List<String> headerNames, List<String> keys, SieveContext context)
+            throws SieveException {
+        // Iterate over the header names looking for a match
+        boolean isMatched = false;
+        Iterator headerNamesIter = headerNames.iterator();
+        while (!isMatched && headerNamesIter.hasNext()) {
+            isMatched = match(comparator, matchType, mail
+                    .getMatchingHeader((String) headerNamesIter.next()), keys,
+                    context);
+        }
+        return isMatched;
+    }
+
+    /**
+     * Method match.
+     * 
+     * @param comparator
+     * @param matchType
+     * @param headerValues
+     * @param keys
+     * @param context not null
+     * @return boolean
+     * @throws SieveException
+     */
+    protected boolean match(String comparator, String matchType,
+            List<String> headerValues, List<String> keys, SieveContext context)
+            throws SieveException {
+        // Special case for empty values
+        // If the matchType is :contains
+        // add the headerValue of a null string
+        // else
+        // not matched
+        if (headerValues.isEmpty())
+            if (matchType.equals(CONTAINS_TAG)) {
+                // header values may be immutable
+                headerValues = new ArrayList<String>(headerValues);
+                headerValues.add("");
+            } else {
+                return false;
+            }
+        // Iterate over the header values looking for a match
+        boolean isMatched = false;
+        Iterator headerValuesIter = headerValues.iterator();
+        while (!isMatched && headerValuesIter.hasNext()) {
+            isMatched = match(comparator, matchType, (String) headerValuesIter
+                    .next(), keys, context);
+        }
+        return isMatched;
+    }
+
+    /**
+     * Method match.
+     * 
+     * @param comparator
+     * @param matchType
+     * @param headerValue
+     * @param keys
+     * @param context not null
+     * @return boolean
+     * @throws SieveException
+     */
+    protected boolean match(String comparator, String matchType,
+            String headerValue, List<String> keys, SieveContext context)
+            throws SieveException {
+        // Iterate over the keys looking for a match
+        boolean isMatched = false;
+        for (final String key: keys) {
+            isMatched = ComparatorUtils.match(comparator, matchType,
+                    headerValue, key, context);
+            if (isMatched) {
+                break;
+            }
+        }
+        return isMatched;
+    }
+
+    /**
+     * @see org.apache.jsieve.tests.AbstractTest#validateArguments(Arguments,
+     *      SieveContext)
+     */
+    protected void validateArguments(Arguments arguments, SieveContext context)
+            throws SieveException {
+        if (arguments.hasTests())
+            throw context.getCoordinate().syntaxException(
+                    "Found unexpected tests");
+    }
+
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/tests/Not.java b/trunk/main/src/main/java/org/apache/jsieve/tests/Not.java
new file mode 100644
index 0000000..6c78813
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/tests/Not.java
@@ -0,0 +1,59 @@
+/****************************************************************
+ * 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.jsieve.tests;
+
+import java.util.List;
+
+import org.apache.jsieve.Arguments;
+import org.apache.jsieve.SieveContext;
+import org.apache.jsieve.Test;
+import org.apache.jsieve.exception.SieveException;
+import org.apache.jsieve.mail.MailAdapter;
+
+/**
+ * Class Not implements the Not Test as defined in RFC 3028, section 5.8.
+ */
+public class Not extends AbstractTest {
+
+    /**
+     * Constructor for Not.
+     */
+    public Not() {
+        super();
+    }
+
+    /**
+     * @see org.apache.jsieve.tests.AbstractTest#executeBasic(MailAdapter,
+     *      Arguments, SieveContext)
+     */
+    protected boolean executeBasic(MailAdapter mail, Arguments arguments,
+            SieveContext context) throws SieveException {
+        boolean result = true;
+        List<Test> tests = arguments.getTestList().getTests();
+        if (tests.size() != 1)
+            throw context.getCoordinate().syntaxException(
+                    new StringBuilder("Exactly 1 test permitted. Found ").append(tests.size()));
+        for (Test test: tests) {
+            result = result && test.isTestPassed(mail, context);
+        }
+        return !result;
+    }
+
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/tests/Size.java b/trunk/main/src/main/java/org/apache/jsieve/tests/Size.java
new file mode 100644
index 0000000..a819211
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/tests/Size.java
@@ -0,0 +1,148 @@
+/****************************************************************
+ * 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.jsieve.tests;
+
+import java.util.ListIterator;
+
+import org.apache.jsieve.Argument;
+import org.apache.jsieve.Arguments;
+import org.apache.jsieve.NumberArgument;
+import org.apache.jsieve.SieveContext;
+import org.apache.jsieve.TagArgument;
+import org.apache.jsieve.exception.SieveException;
+import org.apache.jsieve.exception.SyntaxException;
+import org.apache.jsieve.mail.MailAdapter;
+import org.apache.jsieve.mail.SieveMailException;
+
+/**
+ * Class Size implements the Size Test as defined in RFC 3028, section 5.9.
+ */
+public class Size extends AbstractTest {
+
+    /**
+     * Constructor for Size.
+     */
+    public Size() {
+        super();
+    }
+
+    /**
+     * @see org.apache.jsieve.tests.AbstractTest#executeBasic(MailAdapter,
+     *      Arguments, SieveContext)
+     *      <p>
+     *      From RFC 3028, Section 5.9...
+     *      </p>
+     *      <code>  
+     *    Syntax: size &lt;&quote;:over"&quote; / &quote;:under&quote;&gt; &lt;limit: number&gt;
+     * </code>
+     */
+    protected boolean executeBasic(MailAdapter mail, Arguments arguments,
+            SieveContext context) throws SyntaxException, SieveMailException {
+        String comparator = null;
+        Integer size = null;
+        ListIterator<Argument> argumentsIter = arguments.getArgumentList().listIterator();
+
+        // First argument MUST be a tag of ":under" or ":over"
+        if (argumentsIter.hasNext()) {
+            Argument argument = argumentsIter.next();
+            if (argument instanceof TagArgument) {
+                final String tag = ((TagArgument) argument).getTag();
+                if (tag.equals(":under") || tag.equals(":over"))
+                    comparator = tag;
+                else
+                    throw context.getCoordinate().syntaxException(
+                            new StringBuilder("Found unexpected TagArgument: \"").append(tag).append("\""));
+            }
+        }
+        if (null == comparator)
+            throw context.getCoordinate().syntaxException("Expecting a Tag");
+
+        // Second argument MUST be a number
+        if (argumentsIter.hasNext()) {
+            final Argument argument = argumentsIter.next();
+            if (argument instanceof NumberArgument)
+                size = ((NumberArgument) argument).getInteger();
+        }
+        if (null == size)
+            throw context.getCoordinate().syntaxException("Expecting a Number");
+
+        // There MUST NOT be any further arguments
+        if (argumentsIter.hasNext())
+            throw context.getCoordinate().syntaxException(
+                    "Found unexpected argument(s)");
+
+        return test(mail, comparator, size.intValue());
+    }
+
+    /**
+     * Method test.
+     * 
+     * @param mail
+     * @param comparator
+     * @param size
+     * @return boolean
+     * @throws SieveMailException
+     */
+    protected boolean test(MailAdapter mail, String comparator, int size)
+            throws SieveMailException {
+        boolean isTestPassed = false;
+        if (comparator.equals(":over"))
+            isTestPassed = testOver(mail, size);
+        else if (comparator.equals(":under"))
+            isTestPassed = testUnder(mail, size);
+        return isTestPassed;
+    }
+
+    /**
+     * Method testUnder.
+     * 
+     * @param mail
+     * @param size
+     * @return boolean
+     * @throws SieveMailException
+     */
+    protected boolean testUnder(MailAdapter mail, int size)
+            throws SieveMailException {
+        return mail.getSize() < size;
+    }
+
+    /**
+     * Method testOver.
+     * 
+     * @param mail
+     * @param size
+     * @return boolean
+     * @throws SieveMailException
+     */
+    protected boolean testOver(MailAdapter mail, int size)
+            throws SieveMailException {
+        return mail.getSize() > size;
+    }
+
+    /**
+     * @see org.apache.jsieve.tests.AbstractTest#validateArguments(Arguments,
+     *      SieveContext)
+     */
+    protected void validateArguments(Arguments arguments, SieveContext context)
+            throws SieveException {
+        // All done in executeBasic()
+    }
+
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/tests/True.java b/trunk/main/src/main/java/org/apache/jsieve/tests/True.java
new file mode 100644
index 0000000..0842b53
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/tests/True.java
@@ -0,0 +1,47 @@
+/****************************************************************
+ * 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.jsieve.tests;
+
+import org.apache.jsieve.Arguments;
+import org.apache.jsieve.SieveContext;
+import org.apache.jsieve.mail.MailAdapter;
+
+/**
+ * Class True implements the True Test as defined in RFC 3028, section 5.10.
+ */
+public class True extends AbstractTest {
+
+    /**
+     * Constructor for True.
+     */
+    public True() {
+        super();
+    }
+
+    /**
+     * @see org.apache.jsieve.tests.AbstractTest#executeBasic(MailAdapter,
+     *      Arguments, SieveContext)
+     */
+    protected boolean executeBasic(MailAdapter mail, Arguments arguments,
+            SieveContext context) {
+        return true;
+    }
+
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/tests/optional/Body.java b/trunk/main/src/main/java/org/apache/jsieve/tests/optional/Body.java
new file mode 100644
index 0000000..2899cc7
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/tests/optional/Body.java
@@ -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.jsieve.tests.optional;
+
+import java.util.List;
+
+import org.apache.jsieve.Argument;
+import org.apache.jsieve.Arguments;
+import org.apache.jsieve.SieveContext;
+import org.apache.jsieve.StringListArgument;
+import org.apache.jsieve.TagArgument;
+import org.apache.jsieve.exception.SieveException;
+import org.apache.jsieve.exception.SyntaxException;
+import org.apache.jsieve.mail.MailAdapter;
+import org.apache.jsieve.mail.SieveMailException;
+import org.apache.jsieve.tests.AbstractTest;
+
+/**
+ * Implementation of body extension defined in
+ * <a href='http://tools.ietf.org/html/rfc5173'>RFC5173</a>.
+ */
+public class Body extends AbstractTest {
+    private StringListArgument strings;
+
+    public Body() {
+        super();
+        strings = null;
+    }
+
+    // TODO: Check how complete this is of the body specification
+    // Validate (sorta); we're only implementing part of the spec
+    protected void validateArguments(Arguments args, SieveContext ctx)
+            throws SieveException {
+
+        final List<Argument> arglist = args.getArgumentList();
+        if (arglist.size() != 2) {
+            throw new SyntaxException(
+                    "Currently body-test can only two arguments");
+        }
+
+        // TODO: FIXME: As this is a limited implementation force the use of
+        // ':contains'.
+        Argument arg = arglist.get(0);
+        if (!(arg instanceof TagArgument)) {
+            throw new SyntaxException("Body expects a :contains tag");
+        }
+
+        if (!((TagArgument) arg).getTag().equals(":contains")) {
+            throw new SyntaxException("Body expects a :contains tag");
+        }
+
+        // Get list of strings to search for
+        arg = arglist.get(1);
+        if (!(arg instanceof StringListArgument)) {
+            throw new SyntaxException("Body expects a list of strings");
+        }
+        strings = (StringListArgument) args.getArgumentList().get(1);
+    }
+
+    // This implement body tests of the form
+    // "body :contains ['string' 'string' ....]"
+    protected boolean executeBasic(MailAdapter mail, Arguments args,
+            SieveContext ctx) throws SieveException {
+        // Attempt to fetch content as a string. If we can't do this it's
+        // not a message we can handle.
+        if (mail.getContentType().indexOf("text/") != 0) {
+            throw new SieveMailException("Message is not of type 'text'");
+        }
+
+        // Compare each test string with body, ignoring case
+        for (final String phrase:strings.getList()) {
+            if (mail.isInBodyText(phrase)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+}
diff --git a/trunk/main/src/main/java/org/apache/jsieve/tests/optional/Envelope.java b/trunk/main/src/main/java/org/apache/jsieve/tests/optional/Envelope.java
new file mode 100644
index 0000000..4b9d238
--- /dev/null
+++ b/trunk/main/src/main/java/org/apache/jsieve/tests/optional/Envelope.java
@@ -0,0 +1,120 @@
+/****************************************************************
+ * 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.jsieve.tests.optional;
+
+
+import static org.apache.jsieve.tests.AddressPartTags.DOMAIN_TAG;
+import static org.apache.jsieve.tests.AddressPartTags.LOCALPART_TAG;
+
+import java.util.List;
+
+import org.apache.jsieve.SieveContext;
+import org.apache.jsieve.comparators.ComparatorUtils;
+import org.apache.jsieve.exception.SieveException;
+import org.apache.jsieve.mail.MailAdapter;
+import org.apache.jsieve.mail.SieveMailException;
+import org.apache.jsieve.mail.optional.EnvelopeAccessors;
+import org.apache.jsieve.tests.AbstractCompatatorTest;
+
+/**
+ * Class Envelope implements the optional Envelope Test as defined in RFC 3028,
+ * section 5.4.
+ */
+public class Envelope extends AbstractCompatatorTest {
+
+    /**
+     * Constructor for EnvelopeAccessors.
+     */
+    public Envelope() {
+        super();
+    }
+
+    protected List<String> getMatchingValues(MailAdapter mail, String valueName)
+            throws SieveMailException {
+        return ((EnvelopeAccessors) mail).getMatchingEnvelope(valueName);
+    }
+
+    /**
+     * Method match.
+     * 
+     * @param addressPart
+     * @param comparator
+     * @param matchType
+     * @param headerValue
+     * @param key
+     * @param context not null
+     * @return boolean
+     * @throws SieveMailException
+     */
+    protected boolean match(String addressPart, String comparator,
+            String matchType, String headerValue, String key,
+            SieveContext context) throws SieveException {
+
+        // Extract the part of the address we are matching on
+        String matchAddress = null;
+        if (addressPart.equals(":all"))
+            matchAddress = headerValue;
+        else {
+            int localStart = 0;
+            int localEnd = 0;
+            int domainStart = 0;
+            int domainEnd = headerValue.length();
+            int splitIndex = headerValue.indexOf('@');
+            // If there is no domain part (-1), treat it as an empty String
+            if (splitIndex == -1) {
+                localEnd = domainEnd;
+                domainStart = domainEnd;
+            } else {
+                localEnd = splitIndex;
+                domainStart = splitIndex + 1;
+            }
+            matchAddress = (addressPart.equals(LOCALPART_TAG) ? headerValue
+                    .substring(localStart, localEnd) : headerValue.substring(
+                    domainStart, domainEnd));
+        }
+
+        // domain matches MUST ignore case, others should not
+        String matchKey = null;
+        if (addressPart.equals(DOMAIN_TAG)) {
+            matchKey = key.toLowerCase();
+            matchAddress = matchAddress.toLowerCase();
+        } else
+            matchKey = key;
+
+        // Match using the specified comparator
+        return ComparatorUtils.match(comparator, matchType, matchAddress,
+                matchKey, context);
+    }
+
+    protected boolean match(MailAdapter mail, String addressPart,
+            String comparator, String matchType, String headerName, String key,
+            SieveContext context) throws SieveException {
+        final List<String> headerValues = getMatchingValues(mail, headerName);
+        boolean isMatched = false;
+        for (final String value:headerValues) {
+            isMatched = match(addressPart, comparator, matchType, value, key, context);
+            if (isMatched) {
+                break;
+            }
+        }
+        return isMatched;
+    }
+
+}
diff --git a/trunk/main/src/main/jjtree/address/AddressListParser.jjt b/trunk/main/src/main/jjtree/address/AddressListParser.jjt
new file mode 100644
index 0000000..b3ccf0c
--- /dev/null
+++ b/trunk/main/src/main/jjtree/address/AddressListParser.jjt
@@ -0,0 +1,318 @@
+/****************************************************************
+ * 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.                                           *
+ ****************************************************************/
+
+
+/**
+ * RFC2822 address list parser.
+ *
+ * Created 9/17/2004
+ * by Joe Cheng <code@joecheng.com>
+ */
+
+options {
+    STATIC=false;
+    LOOKAHEAD=1;
+    VISITOR=true;
+    MULTI=true;
+    NODE_SCOPE_HOOK=true;
+    NODE_EXTENDS="org.apache.jsieve.parser.address.AddressNode"; 
+    OUTPUT_DIRECTORY="./org/apache/jsieve/parser/generated/address";
+    //DEBUG_PARSER=true;
+    //DEBUG_TOKEN_MANAGER=true;
+    JDK_VERSION="1.5";
+}
+
+PARSER_BEGIN(AddressListParser)
+/****************************************************************
+ * 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.jsieve.parser.generated.address;
+
+public class AddressListParser {    
+
+    public ASTaddress_list parse() throws ParseException {
+        try {
+            parseAll();
+            return (ASTaddress_list)jjtree.rootNode();
+        } catch (TokenMgrError tme) {
+            throw new ParseException(tme.getMessage());
+        }
+    }
+    
+    
+    void jjtreeOpenNodeScope(Node n) {
+        ((SimpleNode)n).firstToken = getToken(1);
+    }
+    
+    void jjtreeCloseNodeScope(Node n) {
+        ((SimpleNode)n).lastToken = getToken(0);
+    }
+}
+
+PARSER_END(AddressListParser)
+
+void parseLine() #void :
+{}
+{
+    address_list() ["\r"] "\n"
+}
+
+void parseAll() #void :
+{}
+{
+    address_list() <EOF>
+}
+
+void address_list() :
+{}
+{
+    [ address() ]
+    (
+        ","
+        [ address() ]
+    )*
+}
+
+void address() :
+{}
+{
+    LOOKAHEAD(2147483647)
+    addr_spec()
+|   angle_addr()
+|   ( phrase() (group_body() | angle_addr()) )
+}
+
+void mailbox() :
+{}
+{
+    LOOKAHEAD(2147483647)
+    addr_spec()
+|   angle_addr()
+|   name_addr()
+}
+
+void name_addr() :
+{}
+{
+    phrase() angle_addr()
+}
+
+void group_body() :
+{}
+{
+    ":"
+    [ mailbox() ]
+    (
+        ","
+        [ mailbox() ]
+    )*
+    ";"
+}
+
+void angle_addr() :
+{}
+{
+    "<" [ route() ] addr_spec() ">"
+}
+
+void route() :
+{}
+{
+    "@" domain() ( (",")* "@" domain() )* ":"
+}
+
+void phrase() :
+{}
+{
+(   <DOTATOM>
+|   <QUOTEDSTRING>
+)+
+}
+
+void addr_spec() :
+{}
+{
+    ( local_part() "@" domain() )
+}
+
+void local_part() :
+{ Token t; }
+{
+    ( t=<DOTATOM> | t=<QUOTEDSTRING> )
+    (   [t="."]
+        {
+            if ( t.kind == AddressListParserConstants.QUOTEDSTRING || t.image.charAt(t.image.length() - 1) != '.')
+                throw new ParseException("Words in local part must be separated by '.'");
+        }
+        (   t=<DOTATOM> | t=<QUOTEDSTRING> )
+    )*
+}
+
+void domain() :
+{ Token t; }
+{
+    (   t=<DOTATOM>
+        (   [t="."]
+            {
+                if (t.image.charAt(t.image.length() - 1) != '.')
+                    throw new ParseException("Atoms in domain names must be separated by '.'");
+            }
+            t=<DOTATOM>
+        )*
+    )
+|   <DOMAINLITERAL>
+}
+
+SPECIAL_TOKEN :
+{
+    < WS: ( [" ", "\t"] )+ >
+}
+
+TOKEN :
+{
+    < #ALPHA: ["a" - "z", "A" - "Z"] >
+|   < #DIGIT: ["0" - "9"] >
+|   < #ATEXT: ( <ALPHA> | <DIGIT>
+              | "!" | "#" | "$" | "%"
+              | "&" | "'" | "*" | "+"
+              | "-" | "/" | "=" | "?"
+              | "^" | "_" | "`" | "{"
+              | "|" | "}" | "~"
+              )>
+|   < DOTATOM: <ATEXT> ( <ATEXT> | "." )* >
+}
+
+TOKEN_MGR_DECLS :
+{
+    // Keeps track of how many levels of comment nesting
+    // we've encountered.  This is only used when the 2nd
+    // level is reached, for example ((this)), not (this).
+    // This is because the outermost level must be treated
+    // specially anyway, because the outermost ")" has a 
+    // different token type than inner ")" instances.
+    static int commentNest;
+}
+
+MORE :
+{
+    // domain literal
+    "[" : INDOMAINLITERAL
+}
+
+<INDOMAINLITERAL>
+MORE :
+{
+    < <QUOTEDPAIR>> { image.deleteCharAt(image.length() - 2); }
+|   < ~["[", "]", "\\"] >
+}
+
+<INDOMAINLITERAL>
+TOKEN :
+{
+    < DOMAINLITERAL: "]" > { matchedToken.image = image.toString(); }: DEFAULT
+}
+
+MORE :
+{
+    // starts a comment
+    "(" : INCOMMENT
+}
+
+<INCOMMENT>
+SKIP :
+{
+    // ends a comment
+    < COMMENT: ")" > : DEFAULT
+    // if this is ever changed to not be a SKIP, need
+    // to make sure matchedToken.token = token.toString()
+    // is called.
+}
+
+<INCOMMENT>
+MORE :
+{
+    < <QUOTEDPAIR>> { image.deleteCharAt(image.length() - 2); }
+|   "(" { commentNest = 1; } : NESTED_COMMENT
+|   < <ANY>>
+}
+
+<NESTED_COMMENT>
+MORE :
+{
+    < <QUOTEDPAIR>> { image.deleteCharAt(image.length() - 2); }
+|   "(" { ++commentNest; }
+|   ")" { --commentNest; if (commentNest == 0) SwitchTo(INCOMMENT); }
+|   < <ANY>>
+}
+
+
+// QUOTED STRINGS
+
+MORE :
+{
+    "\"" { image.deleteCharAt(image.length() - 1); } : INQUOTEDSTRING
+}
+
+<INQUOTEDSTRING>
+MORE :
+{
+    < <QUOTEDPAIR>> { image.deleteCharAt(image.length() - 2); }
+|   < (~["\"", "\\"])+ >
+}
+
+<INQUOTEDSTRING>
+TOKEN :
+{
+    < QUOTEDSTRING: "\"" > { matchedToken.image = image.substring(0, image.length() - 1); } : DEFAULT
+}
+
+// GLOBALS
+
+<*>
+TOKEN :
+{
+    < #QUOTEDPAIR: "\\" <ANY> >
+|   < #ANY: ~[] >
+}
+
+// ERROR!
+/*
+
+<*>
+TOKEN :
+{
+    < UNEXPECTED_CHAR: <ANY> >
+}
+
+*/
diff --git a/trunk/main/src/main/jjtree/sieve/sieve.jjt b/trunk/main/src/main/jjtree/sieve/sieve.jjt
new file mode 100644
index 0000000..7616f70
--- /dev/null
+++ b/trunk/main/src/main/jjtree/sieve/sieve.jjt
@@ -0,0 +1,308 @@
+/****************************************************************
+ * 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.                                           *
+ ****************************************************************/
+
+options {
+  MULTI=true;
+  VISITOR=true;
+  VISITOR_EXCEPTION="org.apache.jsieve.exception.SieveException";
+  NODE_PACKAGE="org.apache.jsieve.parser.generated";
+  NODE_DEFAULT_VOID=false;
+  STATIC = false;
+  JAVA_UNICODE_ESCAPE = true;  
+  DEBUG_PARSER = false;
+  OUTPUT_DIRECTORY="./org/apache/jsieve/parser/generated";
+  NODE_EXTENDS="org.apache.jsieve.parser.SieveNode";
+  NODE_SCOPE_HOOK=true;
+  JDK_VERSION="1.5";
+}
+
+PARSER_BEGIN(SieveParser)
+
+package org.apache.jsieve.parser.generated;
+
+import org.apache.jsieve.*;
+import org.apache.jsieve.parser.*;
+
+public class SieveParser { 
+
+  public void jjtreeOpenNodeScope(Node n) {
+    ((SieveNode) n).setFirstToken(getToken(1));
+  }
+  
+  public void jjtreeCloseNodeScope(Node n) {
+    ((SieveNode) n).setLastToken(getToken(0));
+  }
+}
+
+PARSER_END(SieveParser)
+
+/*****************************************
+ * THE SIEVE LANGUAGE TOKENS STARTS HERE *
+ *****************************************/
+
+SKIP : /* WHITE SPACE */
+{
+  " "
+| "\t"
+| "\n"
+| "\r"
+| "\f"
+}
+
+SPECIAL_TOKEN : /* COMMENTS */
+{
+  <HASH_COMMENT: "#" (~["\n","\r"])* ("\n"|"\r"|"\r\n")>
+| 
+  <BRACKET_COMMENT: "/*" (~["*"])* "*" ("*" | (~["*","/"] (~["*"])* "*"))* "/">
+}
+
+TOKEN : /* Can't make this one special */
+{
+  <EOF_HASH_COMMENT: "#" (~["\n","\r"])*>
+}              
+
+
+// identifier = (ALPHA / "_") *(ALPHA DIGIT "_")
+TOKEN : /* IDENTIFIER */
+{
+   <IDENTIFIER: (<ALPHA>|"_") (<ALPHA>|<DIGIT>|"_")*>
+|
+  < #ALPHA:
+      [
+       "\u0041"-"\u005a",
+       "\u0061"-"\u007a"
+      ]
+  >
+|
+  < #DIGIT:
+      [
+       "\u0030"-"\u0039"
+      ]
+  >  
+}
+
+// tag = ":" identifier
+TOKEN : /* TAG */
+{
+   <TAG: ":" <IDENTIFIER> >
+}
+
+TOKEN : /* LITERALS */
+{
+// number = 1*DIGIT [QUANTIFIER]
+// QUANTIFIER = "K" / "M" / "G"
+   < NUMBER: 
+       <DECIMAL_LITERAL> (<QUANTIFIER>)?
+   >
+|
+  < #DECIMAL_LITERAL: (["0"-"9"])+ >
+|
+  < #QUANTIFIER: ["K","M","G"] >    
+|
+// quoted-string = DQUOTE *CHAR DQUOTE
+// in general, \ CHAR inside a string maps to CHAR
+// so \" maps to " and \\ maps to \
+// note that newlines and other characters are all allowed strings
+  < QUOTED_STRING:
+      "\""
+      (~["\""]|"\\\"")*
+      "\""
+  >
+|
+// multi-line = "text:" *(SP / HTAB) (hash-comment / CRLF)
+//               *(multi-line-literal / multi-line-dotstuff)
+//               "." CRLF
+// Hmm. What we need to do is treat (CRLF / LF / CR) as <NEWLINE> throughout
+  < MULTI_LINE:
+      <MULTI_LINE_START>
+      (<MULTI_LINE_LITERAL> |<MULTI_LINE_DOTSTUFF>)*
+      <MULTI_LINE_END>
+  >
+|
+  <#MULTI_LINE_START: 
+      ("text:")
+      ([" ", "\t"])*
+      (<HASH_COMMENT>|<NEWLINE>)
+  >
+|
+  <#MULTI_LINE_END:
+      ("." <NEWLINE>)
+  >              
+|
+  <#NEWLINE:
+      ("\n"|"\r"|"\r\n")
+  >
+|                           
+// multi-line-literal  = [CHAR-NOT-DOT *CHAR_NOT_NEWLINE] NEWLINE 
+  < #MULTI_LINE_LITERAL:
+      (<CHAR_NOT_DOT> (<CHAR_NOT_NEWLINE>)*)?
+      <NEWLINE>
+  >  
+|
+  < #CHAR_NOT_DOT: 
+      (~["."])
+  >
+|
+  < #CHAR_NOT_NEWLINE: 
+      (~["\n"] | ("\r" ~["\n"]))
+  >   
+|
+// multi-line-dotstuff = "." 1*CHAR-NOT-CRLF CRLF
+// A line containing only "." ends the multi-line.
+// Remove a leading '.' if followed by another '.'.
+  < #MULTI_LINE_DOTSTUFF:
+    "."
+    (<CHAR_NOT_NEWLINE>)+
+    <NEWLINE>
+  >  
+}
+
+/******************************************
+ * THE SIEVE LANGUAGE GRAMMAR STARTS HERE *
+ ******************************************/
+
+// start = commands   
+SimpleNode start() :
+{  }
+{
+  commands()
+  (<EOF_HASH_COMMENT>)? // Allow a Hash comment immediately prior to EOF
+  <EOF>
+  { return jjtThis; }  
+}
+
+//   commands = *command
+void commands() :
+{ }
+{
+  (command())*
+}
+
+//   command = identifier arguments ( ";" / block )
+void command() :
+{ Token identifier = null; }
+{
+  (identifier = <IDENTIFIER>) arguments() (";" | block())
+  {
+    jjtThis.setName(identifier.image);
+  }  
+}
+
+//   block = "{" commands "}"
+void block() :
+{ }
+{
+   "{" commands() "}"
+}
+
+//   arguments = *argument [test / test-list]
+void arguments() :
+{ }
+{
+  (argument())* (test() | test_list())?
+}
+
+//    argument = string-list / number / tag
+void argument() :
+{ Token string_list = null, number = null, tag = null; }
+{
+  (string_list() | number = <NUMBER> | tag = <TAG>)
+  {
+    Argument value = null; 
+    if (null != number)
+       value = new NumberArgument(number);
+    else if (null != tag)
+       value = new TagArgument(tag);
+    jjtThis.setValue(value);       
+  }
+}
+
+//   test = identifier arguments
+void test() :
+{ Token identifier = null; }
+{
+  (identifier = <IDENTIFIER> arguments())
+  {
+    jjtThis.setName(identifier.image);
+  }   
+}
+
+//   test-list = "(" test *("," test) ")"
+void test_list() :
+{ }
+{
+  "(" test() ("," test())* ")"
+}
+
+//   string = quoted-string / multi-line
+void string() :
+{ Token quoted_string = null, multi_line = null;}
+{
+  (quoted_string = <QUOTED_STRING> | multi_line = <MULTI_LINE>)
+  {
+    final StringBuilder builder;
+    if (null != quoted_string) {
+      builder=new StringBuilder(quoted_string.image);
+      // unquote
+      builder.deleteCharAt(builder.length() - 1);
+    } else if (null != multi_line) {
+      builder=new StringBuilder(multi_line.image);
+      // remove prefixing 'text' and whitespace
+      while (builder.length()>0 && builder.charAt(0) != '\n') {
+      	 builder.deleteCharAt(0);
+      }
+      // remove suffixing newline-dot-newline
+      builder.deleteCharAt(builder.length() - 1);
+      builder.deleteCharAt(builder.length() - 1);
+      builder.deleteCharAt(builder.length() - 1);
+      
+      int nextStuffedDot = builder.indexOf("\n..");
+      while (nextStuffedDot >= 0) {
+      	 builder.deleteCharAt(nextStuffedDot+1);
+      	 nextStuffedDot = builder.indexOf("\n..", nextStuffedDot + 2);
+      } 
+    } else {
+      builder=null;  
+    }
+    if (builder != null) {
+        // Unescape
+        builder.deleteCharAt(0);
+        int i = 0;
+        while (i < builder.length()) {
+            if ('\\' == builder.charAt(i)) {
+                builder.deleteCharAt(i);
+            }
+            i++;
+        }
+        jjtThis.setValue(builder.toString());
+    }
+  }
+}
+
+//   string-list = "[" string *("," string) "]" / string         ;; if
+//   there is only a single string, the brackets are optional
+void string_list() :
+{ }
+{
+  ("[" string() ("," string())* "]") | string()
+}
+
+
+   
+ 
diff --git a/trunk/main/src/main/resources/org/apache/jsieve/commandsmap.properties b/trunk/main/src/main/resources/org/apache/jsieve/commandsmap.properties
new file mode 100644
index 0000000..8a3c564
--- /dev/null
+++ b/trunk/main/src/main/resources/org/apache/jsieve/commandsmap.properties
@@ -0,0 +1,33 @@
+################################################################
+# 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.                                           #
+################################################################
+
+if=org.apache.jsieve.commands.If
+else=org.apache.jsieve.commands.Else
+elsif=org.apache.jsieve.commands.Elsif
+require=org.apache.jsieve.commands.Require
+stop=org.apache.jsieve.commands.Stop
+# RFC3082 - Implementations MUST support these
+keep=org.apache.jsieve.commands.Keep
+discard=org.apache.jsieve.commands.Discard
+redirect=org.apache.jsieve.commands.Redirect
+# RFC3082 - Implementations SHOULD support these
+reject=org.apache.jsieve.commands.optional.Reject
+fileinto=org.apache.jsieve.commands.optional.FileInto
+# Extension Commands
+log=org.apache.jsieve.commands.extensions.Log
diff --git a/trunk/main/src/main/resources/org/apache/jsieve/comparatorsmap.properties b/trunk/main/src/main/resources/org/apache/jsieve/comparatorsmap.properties
new file mode 100644
index 0000000..0d065de
--- /dev/null
+++ b/trunk/main/src/main/resources/org/apache/jsieve/comparatorsmap.properties
@@ -0,0 +1,24 @@
+################################################################
+# 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.                                           #
+################################################################
+
+# RFC3082 - Implementations MUST support these
+i;octet=org.apache.jsieve.comparators.Octet
+i;ascii-casemap=org.apache.jsieve.comparators.AsciiCasemap
+# Optional
+i;ascii-numeric=org.apache.jsieve.comparators.AsciiNumeric
\ No newline at end of file
diff --git a/trunk/main/src/main/resources/org/apache/jsieve/testsmap.properties b/trunk/main/src/main/resources/org/apache/jsieve/testsmap.properties
new file mode 100644
index 0000000..1b6ee4e
--- /dev/null
+++ b/trunk/main/src/main/resources/org/apache/jsieve/testsmap.properties
@@ -0,0 +1,33 @@
+################################################################
+# 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.                                           #
+################################################################
+
+# RFC3082 - Implementations MUST support these tests
+address=org.apache.jsieve.tests.Address
+allof=org.apache.jsieve.tests.AllOf
+anyof=org.apache.jsieve.tests.AnyOf
+exists=org.apache.jsieve.tests.Exists
+false=org.apache.jsieve.tests.False
+header=org.apache.jsieve.tests.Header
+not=org.apache.jsieve.tests.Not
+size=org.apache.jsieve.tests.Size
+true=org.apache.jsieve.tests.True
+# RFC3082 - Implementations SHOULD support these
+envelope=org.apache.jsieve.tests.optional.Envelope
+# Limited implementation
+body=org.apache.jsieve.tests.optional.Body
diff --git a/trunk/main/src/site/resources/images/asf-logo-reduced.gif b/trunk/main/src/site/resources/images/asf-logo-reduced.gif
new file mode 100644
index 0000000..93cc102
--- /dev/null
+++ b/trunk/main/src/site/resources/images/asf-logo-reduced.gif
Binary files differ
diff --git a/trunk/main/src/site/resources/images/james-jsieve-logo.gif b/trunk/main/src/site/resources/images/james-jsieve-logo.gif
new file mode 100644
index 0000000..9c7e34f
--- /dev/null
+++ b/trunk/main/src/site/resources/images/james-jsieve-logo.gif
Binary files differ
diff --git a/trunk/main/src/site/site.xml b/trunk/main/src/site/site.xml
new file mode 100644
index 0000000..58ae900
--- /dev/null
+++ b/trunk/main/src/site/site.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!--

+  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.    

+-->

+<project name="jSieve">
+  <bannerLeft>
+    <name>JAMES jSieve</name>
+    <src>images/james-jsieve-logo.gif</src>
+    <href>http://james.apache.org/</href>
+  </bannerLeft>
+
+  <bannerRight>

+    <name>The Apache Software Foundation</name>

+    <src>images/asf-logo-reduced.gif</src>

+    <href>http://www.apache.org/index.html</href>

+  </bannerRight> 

+
+  <body>
+
+    <menu name="jSieve Engine">
+      <item name="Overview" href="index.html"/>

+      <item name="Sieve Features" href="features.html"/>

+      <item name="Getting Started" href="start.html"/>
+      <item 
+        name="DOAP" 
+        href="doap_apache-jsieve.rdf" 
+        img='http://www.w3.org/RDF/icons/rdf_metadata_button.32'/>
+    </menu>
+    
+    <menu ref="reports" />
+    
+  </body>
+</project>
diff --git a/trunk/main/src/site/xdoc/features.xml b/trunk/main/src/site/xdoc/features.xml
new file mode 100644
index 0000000..9ccadd2
--- /dev/null
+++ b/trunk/main/src/site/xdoc/features.xml
@@ -0,0 +1,121 @@
+<?xml version="1.0"?>
+<!--
+  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.  
+-->
+<document>
+
+ <properties>
+  <title>Overview</title>
+  <author email="jsieve-dev@jakarta.apache.org">jSieve Project</author>
+ </properties>
+
+<body>
+<section name='jSieve Features'>
+<subsection name='Sieve Features Supported By jSieve'>
+<p>
+jSieve is limited only by the platform on which it runs.
+</p>
+<table>
+<tr><th>Limits</th><th/></tr>
+<tr><td>Maximum Number Of Actions</td><td>No intrinsic limit</td></tr>
+<tr><td>Maximum Number Of Nested Blocks</td><td>No intrinsic limit</td></tr>
+<tr><td>Maximum Number Of Nested Test Lists</td><td>No intrinsic limit</td></tr>
+</table>
+<p>
+jSieve supports the standard control commands specified in RFC 3028.
+</p>
+<table>
+<tr>
+<th>Control Command</th><th>Specification</th><th>Supported</th>
+</tr>
+<tr>
+<td>If</td><td>RFC 3028 (REQUIRED)</td><td>yes</td>
+</tr>
+<td>Require</td><td>RFC 3028 (REQUIRED)</td><td>yes</td>
+<tr>
+<td>Stop</td><td>RFC 3028 (REQUIRED)</td><td>yes</td>
+</tr>
+</table>
+<p>
+jSieve supports all action commands (both required and optional) 
+specified in RFC 3028. 
+</p><p>
+<strong>Note</strong> that jSieve uses a pluggable 
+<code>MailAdapter</code> as an interface to the mail server.
+Not all implementations may fully support all actions.
+</p>  
+<table>
+<tr>
+<th>Action Command</th><th>Specification</th><th>Supported</th>
+</tr><tr>
+<td>reject</td><td>RFC 3028 (OPTIONAL)</td><td>yes</td>
+</tr><tr>
+<td>fileinto</td><td>RFC 3028 (OPTIONAL)</td><td>yes</td>
+</tr><tr>
+<td>redirect</td><td>RFC 3028 (REQUIRED)</td><td>yes</td>
+</tr><tr>
+<td>keep</td><td>RFC 3028 (REQUIRED)</td><td>yes</td>
+</tr><tr>
+<td>discard</td><td>RFC 3028 (REQUIRED)</td><td>yes</td>
+</tr>
+</table>
+<p>
+jSieve supports all tests commands specified in RFC 3028.
+</p>
+<table>
+<tr>
+<th>Test Command</th><th>Specification</th><th>Supported</th>
+</tr><tr>
+<td>address</td><td>RFC 3028 (REQUIRED)</td><td>yes</td>
+</tr><tr>
+<td>allof</td><td>RFC 3028 (REQUIRED)</td><td>yes</td>
+</tr><tr>
+<td>anyof</td><td>RFC 3028 (REQUIRED)</td><td>yes</td>
+</tr><tr>
+<td>envelope</td><td>RFC 3028 (OPTIONAL)</td><td>yes</td>
+</tr><tr>
+<td>exists</td><td>RFC 3028 (REQUIRED)</td><td>yes</td>
+</tr><tr>
+<td>false</td><td>RFC 3028 (REQUIRED)</td><td>yes</td>
+</tr><tr>
+<td>header</td><td>RFC 3028 (REQUIRED)</td><td>yes</td>
+</tr><tr>
+<td>not</td><td>RFC 3028 (REQUIRED)</td><td>yes</td>
+</tr><tr>
+<td>size</td><td>RFC 3028 (REQUIRED)</td><td>yes</td>
+</tr><tr>
+<td>true</td><td>RFC 3028 (REQUIRED)</td><td>yes</td>
+</tr><tr>
+<td>body</td><td><a href='http://tools.ietf.org/html/draft-ietf-sieve-body-00'>SIEVE body extension</a></td><td>partial</td>
+</tr>
+</table>
+</subsection>
+<subsection name='jSieve Extension Commands'>
+<p>
+jSieve allows extension commands to be developed
+and plugged in at run time.
+The following extension commands ship with jSieve:
+</p>
+<table>
+<tr><th>Name</th><th>Function</th></tr>
+<tr><td>Log</td><td>Logs messages</td></tr>
+</table>
+</subsection>
+</section>
+</body>
+</document>
diff --git a/trunk/main/src/site/xdoc/index.xml b/trunk/main/src/site/xdoc/index.xml
new file mode 100644
index 0000000..e28d413
--- /dev/null
+++ b/trunk/main/src/site/xdoc/index.xml
@@ -0,0 +1,64 @@
+<?xml version="1.0"?>
+<!--
+  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.  
+-->
+<document>
+
+ <properties>
+  <title>Overview</title>
+  <author email="jsieve-dev@jakarta.apache.org">jSieve Project</author>
+ </properties>
+
+<body>
+<section name="What is jSieve?">
+<p>
+jSieve is a Java implementation of the Sieve mail filtering language defined by 
+<a href='http://www.rfc-editor.org/rfc/rfc3028.txt'>RFC 3028</a>. jSieve is implemented 
+as a language processor that can be plugged into any internet mail application to add 
+Sieve support.
+</p>
+<p>
+jSieve is a subproject of <a href='http://james.apache.org'>Apache JAMES</a>.
+All who are interested in developing jSieve and JAMES will be warmly
+welcomed on the <a href='mail-lists.html'>mailing lists</a>.
+</p>
+<subsection name='What is Sieve?'>
+<p>
+Sieve is an extensible mail filtering language. It's limited expressiveness (no loops
+or variables, no tests with side effects) allows user created scripts to be run 
+safely on email servers. Sieve is targeted at the final delivery phase 
+(where an incoming email is transferred to a user's mailbox).
+</p>
+<p>
+Sieve scripts are composed of commands. Control commands manage the execution of the script.
+Test commands define side-effect free criteria. 
+Action commands are mail operations to be performed.
+</p>
+
+</subsection>
+</section>
+<section name="News">
+<subsection name="2008">
+  <h4>Aug/2008 - jSieve-0.2 released</h4>
+  <p>The Apache JAMES team is happy to announce the availability of 
+  <a href='http://james.apache.org/download.cgi#jsieve'>Apache jSieve 0.2</a>. 
+  This first public release is a major milestone for JSieve.</p>
+</subsection>
+</section>
+</body>
+</document>
diff --git a/trunk/main/src/site/xdoc/start.xml b/trunk/main/src/site/xdoc/start.xml
new file mode 100644
index 0000000..4d9deea
--- /dev/null
+++ b/trunk/main/src/site/xdoc/start.xml
@@ -0,0 +1,119 @@
+<?xml version="1.0"?>
+<!--
+  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.  
+-->
+<document>
+
+ <properties>
+  <title>Overview</title>
+  <author email="jsieve-dev@jakarta.apache.org">jSieve Project</author>
+ </properties>
+
+<body>
+<section name="Getting Started">
+<p>Until these pages are fleshed out, the best way to understand jSieve is read the 
+<a href='apidocs/index.html'>Javadocs</a> 
+and look at the <a href='xref/index.html'>source code</a>. 
+The jUnit tests illustrate many aspects
+of the implementation.
+</p>
+<subsection name='Using jSieve'>
+<subsection name='Standalone'>
+<p>
+<a href='xref/org/apache/jsieve/SieveFactory.html'>
+<code>org.apache.jsieve.SieveFactory</code></a> is the primary invocation
+point for all Sieve operations. The API is clean and simple.
+See the 
+<a href='apidocs/index.html'>javadocs</a> for more details.
+</p>
+</subsection>
+<subsection name='In JAMES'>
+<p>
+The <code>SieveToMultiMailbox</code> mailet is included in JAMES.
+It is integrated with the message delivery spool and provides per-user 
+Sieve scripting using jSieve. 
+</p>
+</subsection>
+</subsection>
+<subsection name='Implementing A Mail Adapter'>
+<p>
+<a href='xref/org/apache/jsieve/mail/MailAdapter.html'>
+<code>org.apache.jsieve.mail.MailAdapter</code></a> is the interface API used
+by jSieve to interact with a mail server. 
+</p><p>
+The mail adapter wraps an email
+and supplies information (required by the script) about the email. It is 
+responsible for accumulating <code>Action</code>s during the parsing of a
+script and for executing them once the parsing is complete.
+</p><p>
+See the 
+<a href='apidocs/index.html'>javadocs</a> for more details and this
+<a href='xref/org/apache/jsieve/samples/james/SieveMailAdapter.html'>sample</a>.
+</p>
+</subsection>
+<subsection name='Creating A Custom Extension Command'>
+<p>
+<a href='xref/org/apache/jsieve/commands/extensions/Log.html'>
+<code>org.apache.jsieve.commands.extensions.Log</code></a> is an example
+of a custom extension command. It is recommended that custom commands
+extend <a href='xref/org/apache/jsieve/commands/AbstractCommand.html'>
+<code>AbstractCommand</code></a>. See the 
+<a href='apidocs/index.html'>javadocs</a> for more details.
+</p><p>
+Custom commands need to be registered with 
+<a href='xref/org/apache/jsieve/ConfigurationManager.html'>
+<code>ConfigurationManager</code></a> before they can be used. This
+may be done programmatically but the recommended method is by altering
+the <code>org/apache/jsieve/commandsmap.properties</code>, <code>org/apache/jsieve/testsmap.properties</code>
+and <code>org/apache/jsieve/comparatorsmap.properties</code> resource files. 
+</p>
+</subsection>
+<subsection name='Building jSieve'>
+<p>
+jSieve uses <a href='http://ant.apache.org'>Ant</a>. <code>ant -projecthelp</code>
+describes appropriate targets. <code>ant</code> runs the default target.
+</p>
+</subsection>
+</section>
+
+<section name="Comments, Questions and Issues">
+<p>jSieve is a sub-project of Apache James. Please direct your comments and questions
+to the relevant James list.
+</p>
+<p>To report issues, such as bugs, go to the
+<a href="https://issues.apache.org/jira/browse/JSIEVE">jSieve Issue Tracker</a>.
+As jSieve comes with a fairly extensive suite of jUnit Tests, it would be most 
+helpful for bug reports to be accompanied by an illustrative jUnit test case.
+</p>
+<section name='Frequently Asked Question'>
+    <section name='Why Do Tests Using Non-ASCII Characters Fail?'>
+        <p>
+<code>SIEVE</code> specifies that <a href='http://ietf.org/rfc/rfc2278.txt'>UTF-8</a> 
+encoding is used for scripts. This format is an international standard and has wide 
+support but not all platforms use this encoding by default. 
+        </p>
+        <p>
+By default, JSieve expects that scripts are encoding using <code>UTF-8</code>.
+Either set the encoding programmatically or ensure that the script is encoded using
+<code>UTF-8</code>.
+        </p>
+    </section>
+</section>
+</section>
+</body>
+</document>
diff --git a/trunk/main/src/test/java/org/apache/jsieve/AddressParseTest.java b/trunk/main/src/test/java/org/apache/jsieve/AddressParseTest.java
new file mode 100644
index 0000000..c4c4527
--- /dev/null
+++ b/trunk/main/src/test/java/org/apache/jsieve/AddressParseTest.java
@@ -0,0 +1,77 @@
+/****************************************************************
+ * 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.jsieve;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.logging.LogFactory;
+import org.apache.jsieve.BaseSieveContext;
+import org.apache.jsieve.ConfigurationManager;
+import org.apache.jsieve.utils.JUnitUtils;
+import org.apache.jsieve.utils.SieveMailAdapter;
+
+public class AddressParseTest extends TestCase {
+
+    private static final String MULTIPLE_ADDRESS_VALUES = "coyote@desert.example.org, bugs@example.org,  elmer@hunters.example.org";
+
+    private static final String SOLO_ADDRESS_VALUES = "coyote@desert.example.org";
+
+    BaseSieveContext context;
+
+    SieveMailAdapter mail;
+
+    OpenedAddress address;
+
+    protected void setUp() throws Exception {
+        super.setUp();
+        ConfigurationManager configurationManager = new ConfigurationManager();
+        context = new BaseSieveContext(
+                configurationManager.getCommandManager(), configurationManager
+                        .getComparatorManager(), configurationManager
+                        .getTestManager(), LogFactory
+                        .getLog(AddressParseTest.class));
+        mail = (SieveMailAdapter) JUnitUtils.createMail();
+        address = new OpenedAddress();
+    }
+
+    public void testSingleAddress() throws Exception {
+        mail.getMessage().addHeader("From", SOLO_ADDRESS_VALUES);
+        assertTrue(address.match(mail, ":all", "i;ascii-casemap", ":is",
+                "from", "coyote@desert.example.org", context));
+        assertFalse(address.match(mail, ":all", "i;ascii-casemap", ":is",
+                "from", "elmer@hunters.example.org", context));
+        assertFalse(address.match(mail, ":all", "i;ascii-casemap", ":is",
+                "from", "bugs@example.org", context));
+        assertFalse(address.match(mail, ":all", "i;ascii-casemap", ":is",
+                "from", "roadrunner@example.org", context));
+    }
+
+    public void testMultipleAddresses() throws Exception {
+        mail.getMessage().addHeader("From", MULTIPLE_ADDRESS_VALUES);
+        assertTrue(address.match(mail, ":all", "i;ascii-casemap", ":is",
+                "from", "coyote@desert.example.org", context));
+        assertTrue(address.match(mail, ":all", "i;ascii-casemap", ":is",
+                "from", "elmer@hunters.example.org", context));
+        assertTrue(address.match(mail, ":all", "i;ascii-casemap", ":is",
+                "from", "bugs@example.org", context));
+        assertFalse(address.match(mail, ":all", "i;ascii-casemap", ":is",
+                "from", "roadrunner@example.org", context));
+    }
+}
diff --git a/trunk/main/src/test/java/org/apache/jsieve/AddressTest.java b/trunk/main/src/test/java/org/apache/jsieve/AddressTest.java
new file mode 100644
index 0000000..5387fcd
--- /dev/null
+++ b/trunk/main/src/test/java/org/apache/jsieve/AddressTest.java
@@ -0,0 +1,615 @@
+/****************************************************************
+ * 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.jsieve;
+
+import javax.mail.MessagingException;
+
+import junit.framework.TestCase;
+
+import org.apache.jsieve.commands.ThrowTestException;
+import org.apache.jsieve.exception.SieveException;
+import org.apache.jsieve.parser.generated.ParseException;
+import org.apache.jsieve.utils.JUnitUtils;
+import org.apache.jsieve.utils.SieveMailAdapter;
+
+/**
+ * Class AddressTest
+ */
+public class AddressTest extends TestCase {
+
+    /**
+     * Test for Test 'address'
+     */
+    public void testIfAddressAllIsTrue() {
+        boolean isTestPassed = false;
+        String script = "if address :all :is \"From\" \"user@domain\" {throwTestException;}";
+        try {
+            SieveMailAdapter mail = (SieveMailAdapter) JUnitUtils.createMail();
+            mail.getMessage().addHeader("From", "user@domain");
+            JUnitUtils.interpret(mail, script);
+        } catch (MessagingException e) {
+        } catch (ThrowTestException.TestException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'address'
+     */
+    public void testCaseInsensitiveHeaderName() {
+        boolean isTestPassed = false;
+        String script = "if address :all :is \"from\" \"user@domain\" {throwTestException;}";
+        try {
+            SieveMailAdapter mail = (SieveMailAdapter) JUnitUtils.createMail();
+            mail.getMessage().addHeader("From", "user@domain");
+            JUnitUtils.interpret(mail, script);
+        } catch (MessagingException e) {
+        } catch (ThrowTestException.TestException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'address'
+     */
+    public void testTreatmentOfEmbededSpacesInHeaderName() {
+        boolean isTestPassed = false;
+        String script = "if address :all :is \"From\" \"user@domain\" {throwTestException;}";
+        try {
+            SieveMailAdapter mail = (SieveMailAdapter) JUnitUtils.createMail();
+            mail.getMessage().addHeader("From ", "user@domain");
+            JUnitUtils.interpret(mail, script);
+        } catch (MessagingException e) {
+        } catch (ThrowTestException.TestException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'address'
+     */
+    public void testOctetComparatorTrue() {
+        boolean isTestPassed = false;
+        String script = "if address :comparator \"i;octet\" :all :is \"From\" \"uSeR@dOmAiN\" {throwTestException;}";
+        try {
+            SieveMailAdapter mail = (SieveMailAdapter) JUnitUtils.createMail();
+            mail.getMessage().addHeader("From ", "uSeR@dOmAiN");
+            JUnitUtils.interpret(mail, script);
+        } catch (MessagingException e) {
+        } catch (ThrowTestException.TestException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'address'
+     */
+    public void testOctetComparatorFalse() {
+        boolean isTestPassed = false;
+        String script = "if address :comparator \"i;octet\" :all :is \"From\" \"uSeR@dOmAiN\" {throwTestException;}";
+        try {
+            SieveMailAdapter mail = (SieveMailAdapter) JUnitUtils.createMail();
+            mail.getMessage().addHeader("From ", "user@domain");
+            JUnitUtils.interpret(mail, script);
+            isTestPassed = true;
+        } catch (MessagingException e) {
+        } catch (ThrowTestException.TestException e) {
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'address'
+     */
+    public void testAsciiComparatorTrue() {
+        boolean isTestPassed = false;
+        String script = "if address :comparator \"i;ascii-casemap\" :all :is \"From\" \"uSeR@dOmAiN\" {throwTestException;}";
+        try {
+            SieveMailAdapter mail = (SieveMailAdapter) JUnitUtils.createMail();
+            mail.getMessage().addHeader("From ", "user@domain");
+            JUnitUtils.interpret(mail, script);
+        } catch (MessagingException e) {
+        } catch (ThrowTestException.TestException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'address'
+     */
+    public void testAsciiComparatorFalse() {
+        boolean isTestPassed = false;
+        String script = "if address :comparator \"i;ascii-casemap\" :all :is \"From\" \"uSeR@dOmAiN\" {throwTestException;}";
+        try {
+            SieveMailAdapter mail = (SieveMailAdapter) JUnitUtils.createMail();
+            mail.getMessage().addHeader("From ", "user@domain1");
+            JUnitUtils.interpret(mail, script);
+            isTestPassed = true;
+        } catch (MessagingException e) {
+        } catch (ThrowTestException.TestException e) {
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'address'
+     */
+    public void testIfAddressAllIsMultiTrue1() {
+        boolean isTestPassed = false;
+        String script = "if address :all :is [\"From\", \"To\"] \"user@domain\" {throwTestException;}";
+        try {
+            SieveMailAdapter mail = (SieveMailAdapter) JUnitUtils.createMail();
+            mail.getMessage().addHeader("From", "user@domain");
+            mail.getMessage().addHeader("To", "user@domain");
+            JUnitUtils.interpret(mail, script);
+        } catch (MessagingException e) {
+        } catch (ThrowTestException.TestException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'address'
+     */
+    public void testIfAddressAllIsMultiTrue2() {
+        boolean isTestPassed = false;
+        String script = "if address :all :is [\"From\", \"To\"] [\"user@domain\", \"tweety@pie\"] {throwTestException;}";
+        try {
+            SieveMailAdapter mail = (SieveMailAdapter) JUnitUtils.createMail();
+            mail.getMessage().addHeader("From", "user@domain");
+            mail.getMessage().addHeader("To", "user@domain");
+            JUnitUtils.interpret(mail, script);
+        } catch (MessagingException e) {
+        } catch (ThrowTestException.TestException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'address'
+     */
+    public void testIfAddressAllIsMultiTrue3() {
+        boolean isTestPassed = false;
+        String script = "if address :all :is [\"From\", \"To\"] [\"user@domain\", \"tweety@pie\"] {throwTestException;}";
+        try {
+            SieveMailAdapter mail = (SieveMailAdapter) JUnitUtils.createMail();
+            mail.getMessage().addHeader("From", "user@domain");
+            mail.getMessage().addHeader("To", "tweety@pie");
+            JUnitUtils.interpret(mail, script);
+        } catch (MessagingException e) {
+        } catch (ThrowTestException.TestException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'address'
+     */
+    public void testIfAddressAllIsMultiTrue4() {
+        boolean isTestPassed = false;
+        String script = "if address :all :is [\"From\", \"To\"] [\"user@domain\", \"tweety@pie\"] {throwTestException;}";
+        try {
+            SieveMailAdapter mail = (SieveMailAdapter) JUnitUtils.createMail();
+            mail.getMessage().addHeader("From", "tweety@pie");
+            mail.getMessage().addHeader("To", "tweety@pie");
+            JUnitUtils.interpret(mail, script);
+        } catch (MessagingException e) {
+        } catch (ThrowTestException.TestException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'address'
+     */
+    public void testIfAddressAllMatchesTrue() {
+        boolean isTestPassed = false;
+        String script = "if address :all :matches \"From\" \"*@domain\" {throwTestException;}";
+        try {
+            SieveMailAdapter mail = (SieveMailAdapter) JUnitUtils.createMail();
+            mail.getMessage().addHeader("From", "user@domain");
+            JUnitUtils.interpret(mail, script);
+        } catch (MessagingException e) {
+        } catch (ThrowTestException.TestException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'address'
+     */
+    public void testIfAddressAllContainsTrue() {
+        boolean isTestPassed = false;
+        String script = "if address :all :contains \"From\" \"r@dom\" {throwTestException;}";
+        try {
+            SieveMailAdapter mail = (SieveMailAdapter) JUnitUtils.createMail();
+            mail.getMessage().addHeader("From", "user@domain");
+            JUnitUtils.interpret(mail, script);
+        } catch (MessagingException e) {
+        } catch (ThrowTestException.TestException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'address'
+     */
+    public void testIfAddressLocalpartIsTrue() {
+        boolean isTestPassed = false;
+        String script = "if address :localpart :is \"From\" \"user\" {throwTestException;}";
+        try {
+            SieveMailAdapter mail = (SieveMailAdapter) JUnitUtils.createMail();
+            mail.getMessage().addHeader("From", "user@domain");
+            JUnitUtils.interpret(mail, script);
+        } catch (MessagingException e) {
+        } catch (ThrowTestException.TestException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'address'
+     */
+    public void testIfAddressLocalpartMatchesTrue() {
+        boolean isTestPassed = false;
+        String script = "if address :localpart :matches \"From\" \"*er\" {throwTestException;}";
+        try {
+            SieveMailAdapter mail = (SieveMailAdapter) JUnitUtils.createMail();
+            mail.getMessage().addHeader("From", "user@domain");
+            JUnitUtils.interpret(mail, script);
+        } catch (MessagingException e) {
+        } catch (ThrowTestException.TestException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'address'
+     */
+    public void testIfAddressLocalpartContainsTrue() {
+        boolean isTestPassed = false;
+        String script = "if address :localpart :contains \"From\" \"r\" {throwTestException;}";
+        try {
+            SieveMailAdapter mail = (SieveMailAdapter) JUnitUtils.createMail();
+            mail.getMessage().addHeader("From", "user@domain");
+            JUnitUtils.interpret(mail, script);
+        } catch (MessagingException e) {
+        } catch (ThrowTestException.TestException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'address'
+     */
+    public void testIfAddressDomainIsTrue() {
+        boolean isTestPassed = false;
+        String script = "if address :domain :is \"From\" \"domain\" {throwTestException;}";
+
+        try {
+            SieveMailAdapter mail = (SieveMailAdapter) JUnitUtils.createMail();
+            mail.getMessage().addHeader("From", "user@domain");
+            JUnitUtils.interpret(mail, script);
+        } catch (MessagingException e) {
+        } catch (ThrowTestException.TestException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'address'
+     */
+    public void testIfAddressDomainMatchesTrue() {
+        boolean isTestPassed = false;
+        String script = "if address :domain :matches \"From\" \"*main\" {throwTestException;}";
+        try {
+            SieveMailAdapter mail = (SieveMailAdapter) JUnitUtils.createMail();
+            mail.getMessage().addHeader("From", "user@domain");
+            JUnitUtils.interpret(mail, script);
+        } catch (MessagingException e) {
+        } catch (ThrowTestException.TestException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'address'
+     */
+    public void testIfAddressDomainContainsTrue() {
+        boolean isTestPassed = false;
+        String script = "if address :domain :contains \"From\" \"dom\" {throwTestException;}";
+        try {
+            SieveMailAdapter mail = (SieveMailAdapter) JUnitUtils.createMail();
+            mail.getMessage().addHeader("From", "user@domain");
+            JUnitUtils.interpret(mail, script);
+        } catch (MessagingException e) {
+        } catch (ThrowTestException.TestException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'address'
+     */
+    public void testIfAddressAllIsFalse() {
+        boolean isTestPassed = false;
+        String script = "if address :all :is \"From\" \"user@domain\" {throwTestException;}";
+        try {
+            SieveMailAdapter mail = (SieveMailAdapter) JUnitUtils.createMail();
+            mail.getMessage().addHeader("From", "tweety@pie");
+            JUnitUtils.interpret(mail, script);
+            isTestPassed = true;
+        } catch (MessagingException e) {
+        } catch (ThrowTestException.TestException e) {
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'address'
+     */
+    public void testIfAddressAllMatchesFalse() {
+        boolean isTestPassed = false;
+        String script = "if address :all :matches \"From\" \"(.*)@domain\" {throwTestException;}";
+        try {
+            SieveMailAdapter mail = (SieveMailAdapter) JUnitUtils.createMail();
+            mail.getMessage().addHeader("From", "tweety@pie");
+            JUnitUtils.interpret(mail, script);
+            isTestPassed = true;
+        } catch (MessagingException e) {
+        } catch (ThrowTestException.TestException e) {
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'address'
+     */
+    public void testIfAddressAllContainsFalse() {
+        boolean isTestPassed = false;
+        String script = "if address :all :contains \"From\" \"r@dom\" {throwTestException;}";
+        try {
+            SieveMailAdapter mail = (SieveMailAdapter) JUnitUtils.createMail();
+            mail.getMessage().addHeader("From", "tweety@pie");
+            JUnitUtils.interpret(mail, script);
+            isTestPassed = true;
+        } catch (MessagingException e) {
+        } catch (ThrowTestException.TestException e) {
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'address'
+     */
+    public void testIfAddressLocalpartIsFalse() {
+        boolean isTestPassed = false;
+        String script = "if address :localpart :is \"From\" \"user\" {throwTestException;}";
+        try {
+            SieveMailAdapter mail = (SieveMailAdapter) JUnitUtils.createMail();
+            mail.getMessage().addHeader("From", "tweety@pie");
+            JUnitUtils.interpret(mail, script);
+            isTestPassed = true;
+        } catch (MessagingException e) {
+        } catch (ThrowTestException.TestException e) {
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'address'
+     */
+    public void testIfAddressLocalpartMatchesFalse() {
+        boolean isTestPassed = false;
+        String script = "if address :localpart :matches \"From\" \"(.*)er\" {throwTestException;}";
+        try {
+            SieveMailAdapter mail = (SieveMailAdapter) JUnitUtils.createMail();
+            mail.getMessage().addHeader("From", "tweety@pie");
+            JUnitUtils.interpret(mail, script);
+            isTestPassed = true;
+        } catch (MessagingException e) {
+        } catch (ThrowTestException.TestException e) {
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'address'
+     */
+    public void testIfAddressLocalpartContainsFalse() {
+        boolean isTestPassed = false;
+        String script = "if address :localpart :contains \"From\" \"r\" {throwTestException;}";
+        try {
+            SieveMailAdapter mail = (SieveMailAdapter) JUnitUtils.createMail();
+            mail.getMessage().addHeader("From", "tweety@pie");
+            JUnitUtils.interpret(mail, script);
+            isTestPassed = true;
+        } catch (MessagingException e) {
+        } catch (ThrowTestException.TestException e) {
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'address'
+     */
+    public void testIfAddressDomainIsFalse() {
+        boolean isTestPassed = false;
+        String script = "if address :domain :is \"From\" \"domain\" {throwTestException;}";
+
+        try {
+            SieveMailAdapter mail = (SieveMailAdapter) JUnitUtils.createMail();
+            mail.getMessage().addHeader("From", "tweety@pie");
+            JUnitUtils.interpret(mail, script);
+            isTestPassed = true;
+        } catch (MessagingException e) {
+        } catch (ThrowTestException.TestException e) {
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'address'
+     */
+    public void testIfAddressDomainMatchesFalse() {
+        boolean isTestPassed = false;
+        String script = "if address :domain :matches \"From\" \"(.*)main\" {throwTestException;}";
+        try {
+            SieveMailAdapter mail = (SieveMailAdapter) JUnitUtils.createMail();
+            mail.getMessage().addHeader("From", "tweety@pie");
+            JUnitUtils.interpret(mail, script);
+            isTestPassed = true;
+        } catch (MessagingException e) {
+        } catch (ThrowTestException.TestException e) {
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'address'
+     */
+    public void testIfAddressDomainContainsFalse() {
+        boolean isTestPassed = false;
+        String script = "if address :domain :contains \"From\" \"dom\" {throwTestException;}";
+        try {
+            SieveMailAdapter mail = (SieveMailAdapter) JUnitUtils.createMail();
+            mail.getMessage().addHeader("From", "tweety@pie");
+            JUnitUtils.interpret(mail, script);
+            isTestPassed = true;
+        } catch (MessagingException e) {
+        } catch (ThrowTestException.TestException e) {
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'address'
+     */
+    public void testIfAddressAllIsMultiFalse1() {
+        boolean isTestPassed = false;
+        String script = "if address :all :is [\"From\", \"To\"] \"user@domain\" {throwTestException;}";
+        try {
+            SieveMailAdapter mail = (SieveMailAdapter) JUnitUtils.createMail();
+            mail.getMessage().addHeader("From", "bugs@bunny");
+            mail.getMessage().addHeader("To", "bugs@bunny");
+            JUnitUtils.interpret(mail, script);
+            isTestPassed = true;
+        } catch (MessagingException e) {
+        } catch (ThrowTestException.TestException e) {
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'address'
+     */
+    public void testIfAddressAllIsMultiFalse2() {
+        boolean isTestPassed = false;
+        String script = "if address :all :is [\"From\", \"To\"] [\"user@domain\", \"tweety@pie\"] {throwTestException;}";
+        try {
+            SieveMailAdapter mail = (SieveMailAdapter) JUnitUtils.createMail();
+            mail.getMessage().addHeader("From", "bugs@bunny");
+            mail.getMessage().addHeader("To", "bugs@bunny");
+            JUnitUtils.interpret(mail, script);
+            isTestPassed = true;
+        } catch (MessagingException e) {
+        } catch (ThrowTestException.TestException e) {
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+}
diff --git a/trunk/main/src/test/java/org/apache/jsieve/AllOfTest.java b/trunk/main/src/test/java/org/apache/jsieve/AllOfTest.java
new file mode 100644
index 0000000..2920f1c
--- /dev/null
+++ b/trunk/main/src/test/java/org/apache/jsieve/AllOfTest.java
@@ -0,0 +1,120 @@
+/****************************************************************
+ * 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.jsieve;
+
+import junit.framework.TestCase;
+
+import org.apache.jsieve.commands.ThrowTestException;
+import org.apache.jsieve.exception.SieveException;
+import org.apache.jsieve.exception.SyntaxException;
+import org.apache.jsieve.parser.generated.ParseException;
+import org.apache.jsieve.utils.JUnitUtils;
+
+/**
+ * Class AllOfTest
+ */
+public class AllOfTest extends TestCase {
+
+    /**
+     * Test for Test 'allof'
+     */
+    public void testIfAllOfFalseTrue() {
+        boolean isTestPassed = false;
+        String script = "if allof (false, true) {stop;} throwTestException;";
+
+        try {
+            JUnitUtils.interpret(JUnitUtils.createMail(), script);
+        } catch (ThrowTestException.TestException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'allof'
+     */
+    public void testIfAllOfTrueTrue() {
+        boolean isTestPassed = false;
+        String script = "if allof (true, true) {throwTestException;}";
+
+        try {
+            JUnitUtils.interpret(JUnitUtils.createMail(), script);
+        } catch (ThrowTestException.TestException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'allof'
+     */
+    public void testIfAllOfFalseFalse() {
+        boolean isTestPassed = false;
+        String script = "if allof (false, false) {stop;} throwTestException;";
+
+        try {
+            JUnitUtils.interpret(JUnitUtils.createMail(), script);
+        } catch (ThrowTestException.TestException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'allof'
+     */
+    public void testIfAllOfTrueFalse() {
+        boolean isTestPassed = false;
+        String script = "if allof (true, false) {stop;} throwTestException;";
+
+        try {
+            JUnitUtils.interpret(JUnitUtils.createMail(), script);
+        } catch (ThrowTestException.TestException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'anyof' with invalid argument
+     */
+    public void testInvalidArgument() {
+        boolean isTestPassed = false;
+        String script = "if anyof 1 {throwTestException;}";
+
+        try {
+            JUnitUtils.interpret(JUnitUtils.createMail(), script);
+        } catch (SyntaxException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+}
diff --git a/trunk/main/src/test/java/org/apache/jsieve/AllTests.java b/trunk/main/src/test/java/org/apache/jsieve/AllTests.java
new file mode 100644
index 0000000..da1b8ee
--- /dev/null
+++ b/trunk/main/src/test/java/org/apache/jsieve/AllTests.java
@@ -0,0 +1,59 @@
+/****************************************************************
+ * 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.jsieve;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Class AllTests
+ */
+public class AllTests {
+
+    public static void main(String[] args) {
+        junit.swingui.TestRunner.run(AllTests.class);
+    }
+
+    public static Test suite() {
+        TestSuite suite = new TestSuite("Test for org.apache.jsieve.junit");
+        // $JUnit-BEGIN$
+        suite.addTest(new TestSuite(ConfigurationManagerTest.class));
+        suite.addTest(new TestSuite(ConditionTest.class));
+        suite.addTest(new TestSuite(RequireTest.class));
+        suite.addTest(new TestSuite(StopTest.class));
+        suite.addTest(new TestSuite(KeepTest.class));
+        suite.addTest(new TestSuite(DiscardTest.class));
+        suite.addTest(new TestSuite(FileIntoTest.class));
+        suite.addTest(new TestSuite(RejectTest.class));
+        suite.addTest(new TestSuite(TrueTest.class));
+        suite.addTest(new TestSuite(FalseTest.class));
+        suite.addTest(new TestSuite(NotTest.class));
+        suite.addTest(new TestSuite(AnyOfTest.class));
+        suite.addTest(new TestSuite(AllOfTest.class));
+        suite.addTest(new TestSuite(ExistsTest.class));
+        suite.addTest(new TestSuite(AddressTest.class));
+        suite.addTest(new TestSuite(HeaderTest.class));
+        suite.addTest(new TestSuite(SizeTest.class));
+        suite.addTest(new TestSuite(EnvelopeTest.class));
+        suite.addTest(new TestSuite(LogTest.class));
+        // $JUnit-END$
+        return suite;
+    }
+}
diff --git a/trunk/main/src/test/java/org/apache/jsieve/AnyOfTest.java b/trunk/main/src/test/java/org/apache/jsieve/AnyOfTest.java
new file mode 100644
index 0000000..74c53cd
--- /dev/null
+++ b/trunk/main/src/test/java/org/apache/jsieve/AnyOfTest.java
@@ -0,0 +1,120 @@
+/****************************************************************
+ * 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.jsieve;
+
+import junit.framework.TestCase;
+
+import org.apache.jsieve.commands.ThrowTestException;
+import org.apache.jsieve.exception.SieveException;
+import org.apache.jsieve.exception.SyntaxException;
+import org.apache.jsieve.parser.generated.ParseException;
+import org.apache.jsieve.utils.JUnitUtils;
+
+/**
+ * Class AnyOfTest
+ */
+public class AnyOfTest extends TestCase {
+
+    /**
+     * Test for Test 'anyof'
+     */
+    public void testIfAnyOfFalseTrue() {
+        boolean isTestPassed = false;
+        String script = "if anyof (false, true) {throwTestException;}";
+
+        try {
+            JUnitUtils.interpret(JUnitUtils.createMail(), script);
+        } catch (ThrowTestException.TestException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'anyof'
+     */
+    public void testIfAnyOfTrueTrue() {
+        boolean isTestPassed = false;
+        String script = "if anyof (true, true) {throwTestException;}";
+
+        try {
+            JUnitUtils.interpret(JUnitUtils.createMail(), script);
+        } catch (ThrowTestException.TestException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'anyof'
+     */
+    public void testIfAnyOfFalseFalse() {
+        boolean isTestPassed = false;
+        String script = "if anyof (false, false) {stop;} throwTestException;";
+
+        try {
+            JUnitUtils.interpret(JUnitUtils.createMail(), script);
+        } catch (ThrowTestException.TestException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'anyof'
+     */
+    public void testIfAnyOfTrueFalse() {
+        boolean isTestPassed = false;
+        String script = "if anyof (true, false) {throwTestException;}";
+
+        try {
+            JUnitUtils.interpret(JUnitUtils.createMail(), script);
+        } catch (ThrowTestException.TestException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'anyof' with invalid argument
+     */
+    public void testInvalidArgument() {
+        boolean isTestPassed = false;
+        String script = "if anyof 1 {throwTestException;}";
+
+        try {
+            JUnitUtils.interpret(JUnitUtils.createMail(), script);
+        } catch (SyntaxException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+}
diff --git a/trunk/main/src/test/java/org/apache/jsieve/BodyTest.java b/trunk/main/src/test/java/org/apache/jsieve/BodyTest.java
new file mode 100644
index 0000000..ad26e05
--- /dev/null
+++ b/trunk/main/src/test/java/org/apache/jsieve/BodyTest.java
@@ -0,0 +1,131 @@
+/****************************************************************
+ * 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.jsieve;
+
+import javax.mail.MessagingException;
+import javax.mail.internet.MimeMultipart;
+
+import junit.framework.TestCase;
+
+import org.apache.jsieve.commands.ThrowTestException;
+import org.apache.jsieve.exception.SieveException;
+import org.apache.jsieve.parser.generated.ParseException;
+import org.apache.jsieve.utils.JUnitUtils;
+import org.apache.jsieve.utils.SieveMailAdapter;
+
+/**
+ * Class BodyTest
+ */
+public class BodyTest extends TestCase {
+
+    protected SieveMailAdapter textMail() throws MessagingException {
+        SieveMailAdapter mail = (SieveMailAdapter) JUnitUtils.createMail();
+        mail.getMessage().setContent("Wibble\n\n" + "Wibble\n", "text/plain");
+        return mail;
+    }
+
+    protected SieveMailAdapter nonTextMail() throws MessagingException,
+            SieveException {
+        SieveMailAdapter mail = (SieveMailAdapter) JUnitUtils.createMail();
+        // FIXME: This doesn't work
+        mail.getMessage().setContent(new MimeMultipart("image/png"));
+        return mail;
+    }
+
+    /**
+     * Test for Test 'header'
+     */
+    public void testBasic() {
+        boolean isTestPassed = false;
+        String script = "if body :contains [\"Wibble\"] {throwTestException;}";
+        try {
+            JUnitUtils.interpret(textMail(), script);
+        } catch (MessagingException e) {
+        } catch (ThrowTestException.TestException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'body'
+     */
+    public void testBodyCaseInsensitivity() {
+        boolean isTestPassed = false;
+        String script = "if body :contains [\"wibble\"] {throwTestException;}";
+        try {
+            JUnitUtils.interpret(textMail(), script);
+        } catch (MessagingException e) {
+        } catch (ThrowTestException.TestException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'body'
+     */
+    public void testBodyNoContains() {
+        boolean isTestPassed = false;
+        String script = "if body [\"wibble\"] {throwTestException;}";
+        try {
+            JUnitUtils.interpret(textMail(), script);
+        } catch (MessagingException e) {
+        } catch (ThrowTestException.TestException e) {
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+            isTestPassed = true;
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'body'
+     */
+    // FIXME: I can't find a method of forcing the mime type, so this test
+    // always fails ...
+    // public void testBodyNonText()
+    // {
+    // boolean isTestPassed = false;
+    // String script = "if body :contains [\"wibble\"] {throwTestException;}";
+    // try
+    // {
+    // JUnitUtils.interpret(nonTextMail(), script);
+    // }
+    // catch (MessagingException e)
+    // {
+    // }
+    // catch (ThrowTestException.TestException e)
+    // {
+    // }
+    // catch (ParseException e)
+    // {
+    // }
+    // catch (SieveException e)
+    // {
+    // isTestPassed = true;
+    // }
+    // assertTrue(isTestPassed);
+    // }
+}
diff --git a/trunk/main/src/test/java/org/apache/jsieve/ConditionTest.java b/trunk/main/src/test/java/org/apache/jsieve/ConditionTest.java
new file mode 100644
index 0000000..1d3c7ef
--- /dev/null
+++ b/trunk/main/src/test/java/org/apache/jsieve/ConditionTest.java
@@ -0,0 +1,326 @@
+/****************************************************************
+ * 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.jsieve;
+
+import junit.framework.TestCase;
+
+import org.apache.jsieve.commands.ThrowTestException;
+import org.apache.jsieve.exception.CommandException;
+import org.apache.jsieve.exception.SieveException;
+import org.apache.jsieve.exception.SyntaxException;
+import org.apache.jsieve.parser.generated.ParseException;
+import org.apache.jsieve.utils.JUnitUtils;
+
+/**
+ * Class <code>ConditionTest</code> tests the conditional commands if, elsif
+ * and else.
+ */
+public class ConditionTest extends TestCase {
+
+    /**
+     * Test for Command 'if' with an argument of 'true'
+     */
+    public void testIfTrue() {
+        boolean isTestPassed = false;
+        String script = "if true {throwTestException;}";
+
+        try {
+            JUnitUtils.interpret(JUnitUtils.createMail(), script);
+        } catch (ThrowTestException.TestException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Command 'if' with an argument of 'false'
+     */
+    public void testIfFalse() {
+        boolean isTestPassed = false;
+        String script = "if false {stop;} throwTestException;";
+
+        try {
+            JUnitUtils.interpret(JUnitUtils.createMail(), script);
+        } catch (ThrowTestException.TestException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Command 'elsif' with an argument of 'true'
+     */
+    public void testElsifTrue() {
+        boolean isTestPassed = false;
+        String script = "if false {stop;} elsif true {throwTestException;}";
+
+        try {
+            JUnitUtils.interpret(JUnitUtils.createMail(), script);
+        } catch (ThrowTestException.TestException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Command 'elsif' with an argument of 'false'
+     */
+    public void testElsifFalse() {
+        boolean isTestPassed = false;
+        String script = "if false {stop;} elsif false {stop;} throwTestException;";
+
+        try {
+            JUnitUtils.interpret(JUnitUtils.createMail(), script);
+        } catch (ThrowTestException.TestException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for nested Command 'elsif' with an argument of 'true'
+     */
+    public void testElsifFalseElsifTrue() {
+        boolean isTestPassed = false;
+        String script = "if false {stop;} elsif false {stop;} elsif true {throwTestException;}";
+
+        try {
+            JUnitUtils.interpret(JUnitUtils.createMail(), script);
+        } catch (ThrowTestException.TestException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Command 'else' after 'elseif'
+     */
+    public void testElsifFalseElse() {
+        boolean isTestPassed = false;
+        String script = "if false {stop;} elsif false {stop;} else {throwTestException;}";
+
+        try {
+            JUnitUtils.interpret(JUnitUtils.createMail(), script);
+        } catch (ThrowTestException.TestException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Command 'else'
+     */
+    public void testElse() {
+        boolean isTestPassed = false;
+        String script = "if false {stop;} else {throwTestException;}";
+
+        try {
+            JUnitUtils.interpret(JUnitUtils.createMail(), script);
+        } catch (ThrowTestException.TestException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Command 'else' out of sequence
+     */
+    public void testOutOfSequenceElse() {
+        boolean isTestPassed = false;
+        String script = "else {stop;}";
+
+        try {
+            JUnitUtils.interpret(JUnitUtils.createMail(), script);
+        } catch (CommandException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Command 'elsif' out of sequence
+     */
+    public void testOutOfSequenceElsif() {
+        boolean isTestPassed = false;
+        String script = "elsif {stop;}";
+
+        try {
+            JUnitUtils.interpret(JUnitUtils.createMail(), script);
+        } catch (SyntaxException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Command 'if' without a corresponding Block
+     */
+    public void testIfMissingBlock() {
+        boolean isTestPassed = false;
+        String script = "if true stop;";
+
+        try {
+            JUnitUtils.interpret(JUnitUtils.createMail(), script);
+        } catch (SyntaxException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Command 'if' without a test
+     */
+    public void testIfMissingTest() {
+        boolean isTestPassed = false;
+        String script = "if {stop;}";
+
+        try {
+            JUnitUtils.interpret(JUnitUtils.createMail(), script);
+        } catch (SyntaxException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Command 'if' without a test
+     */
+    public void testElsifMissingTest() {
+        boolean isTestPassed = false;
+        String script = "if false {stop;} elsif {stop;}";
+
+        try {
+            JUnitUtils.interpret(JUnitUtils.createMail(), script);
+        } catch (SyntaxException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Command 'elsif' without a corresponding Block
+     */
+    public void testElsifMissingBlock() {
+        boolean isTestPassed = false;
+        String script = "if false {stop;} elsif true stop;";
+
+        try {
+            JUnitUtils.interpret(JUnitUtils.createMail(), script);
+        } catch (SyntaxException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Command 'else' without a corresponding Block
+     */
+    public void testElseMissingBlock() {
+        boolean isTestPassed = false;
+        String script = "if false {stop;} else stop;";
+
+        try {
+            JUnitUtils.interpret(JUnitUtils.createMail(), script);
+        } catch (SyntaxException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Command 'if' nested in a block
+     */
+    public void testNestedIf() {
+        boolean isTestPassed = false;
+        String script = "if true {if true {throwTestException;}}";
+
+        try {
+            JUnitUtils.interpret(JUnitUtils.createMail(), script);
+        } catch (ThrowTestException.TestException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Command 'else' out of sequence nested in a block
+     */
+    public void testNestedOutOfSequenceElse() {
+        boolean isTestPassed = false;
+        String script = "if true {else {stop;}}";
+
+        try {
+            JUnitUtils.interpret(JUnitUtils.createMail(), script);
+        } catch (CommandException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Command 'elsif' out of sequence nested in a block
+     */
+    public void testNestedOutOfSequenceElsif() {
+        boolean isTestPassed = false;
+        String script = "if true {elsif true {stop;}}";
+
+        try {
+            JUnitUtils.interpret(JUnitUtils.createMail(), script);
+        } catch (CommandException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+}
diff --git a/trunk/main/src/test/java/org/apache/jsieve/ConfigurationManagerTest.java b/trunk/main/src/test/java/org/apache/jsieve/ConfigurationManagerTest.java
new file mode 100644
index 0000000..4cb47ab
--- /dev/null
+++ b/trunk/main/src/test/java/org/apache/jsieve/ConfigurationManagerTest.java
@@ -0,0 +1,147 @@
+/****************************************************************
+ * 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.jsieve;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import junit.framework.TestCase;
+
+import org.apache.jsieve.ConfigurationManager;
+import org.apache.jsieve.SieveConfigurationException;
+
+/**
+ * Class ConfigurationManagerTest
+ */
+public class ConfigurationManagerTest extends TestCase {
+
+    /**
+     * Test the CommandMap maps 'MUST' and 'SHOULD' be supported commands to the
+     * correct classes.
+     */
+    public void testCommandMap() {
+        Map<String, String> map = new HashMap<String, String>();
+        // Condition Commands
+        // RFC3082 - Implementations MUST support these:
+        map.put("if", "org.apache.jsieve.commands.If");
+        map.put("else", "org.apache.jsieve.commands.Else");
+        map.put("elsif", "org.apache.jsieve.commands.Elsif");
+        map.put("require", "org.apache.jsieve.commands.Require");
+        map.put("stop", "org.apache.jsieve.commands.Stop");
+
+        // Action Commands
+        // RFC3082 - Implementations MUST support these:
+        map.put("keep", "org.apache.jsieve.commands.Keep");
+        map.put("discard", "org.apache.jsieve.commands.Discard");
+        map.put("redirect", "org.apache.jsieve.commands.Redirect");
+        // RFC3082 - Implementations SHOULD support these:
+        map.put("reject", "org.apache.jsieve.commands.optional.Reject");
+        map.put("fileinto", "org.apache.jsieve.commands.optional.FileInto");
+
+        boolean isTestPassed = false;
+        try {
+            Map commandMap = new ConfigurationManager().getCommandMap();
+
+            Iterator mapIter = map.entrySet().iterator();
+            while (mapIter.hasNext()) {
+                Map.Entry entry = (Map.Entry) mapIter.next();
+                assertTrue("Key: " + entry.getKey(), commandMap
+                        .containsKey(entry.getKey()));
+                assertTrue("Value: " + entry.getValue(), commandMap.get(
+                        entry.getKey()).equals(entry.getValue()));
+            }
+            isTestPassed = true;
+        } catch (SieveConfigurationException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test the TestMap maps 'MUST' and 'SHOULD' be supported tests to the
+     * correct classes.
+     */
+    public void testTestMap() {
+        Map<String, String> map = new HashMap<String, String>();
+
+        // RFC3082 - Implementations MUST support these tests:
+        map.put("address", "org.apache.jsieve.tests.Address");
+        map.put("allof", "org.apache.jsieve.tests.AllOf");
+        map.put("anyof", "org.apache.jsieve.tests.AnyOf");
+        map.put("exists", "org.apache.jsieve.tests.Exists");
+        map.put("false", "org.apache.jsieve.tests.False");
+        map.put("header", "org.apache.jsieve.tests.Header");
+        map.put("not", "org.apache.jsieve.tests.Not");
+        map.put("size", "org.apache.jsieve.tests.Size");
+        map.put("true", "org.apache.jsieve.tests.True");
+
+        // RFC3082 - Implementations SHOULD support the "envelope" test.
+        map.put("envelope", "org.apache.jsieve.tests.optional.Envelope");
+
+        boolean isTestPassed = false;
+        try {
+            Map testMap = new ConfigurationManager().getTestMap();
+
+            Iterator mapIter = map.entrySet().iterator();
+            while (mapIter.hasNext()) {
+                Map.Entry entry = (Map.Entry) mapIter.next();
+                assertTrue("Key: " + entry.getKey(), testMap.containsKey(entry
+                        .getKey()));
+                assertTrue("Value: " + entry.getValue(), testMap.get(
+                        entry.getKey()).equals(entry.getValue()));
+            }
+            isTestPassed = true;
+        } catch (SieveConfigurationException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test the CommparatorMap maps 'MUST' and 'SHOULD' be supported comparators
+     * to the correct classes.
+     */
+    public void testComparatorMap() {
+        Map<String, String> map = new HashMap<String, String>();
+
+        // RFC3082 - Required Comparators
+        map.put("i;octet", "org.apache.jsieve.comparators.Octet");
+        map
+                .put("i;ascii-casemap",
+                        "org.apache.jsieve.comparators.AsciiCasemap");
+
+        boolean isTestPassed = false;
+        try {
+            Map comparatorMap = new ConfigurationManager().getComparatorMap();
+
+            Iterator mapIter = map.entrySet().iterator();
+            while (mapIter.hasNext()) {
+                Map.Entry entry = (Map.Entry) mapIter.next();
+                assertTrue("Key: " + entry.getKey(), comparatorMap
+                        .containsKey(entry.getKey()));
+                assertTrue("Value: " + entry.getValue(), comparatorMap.get(
+                        entry.getKey()).equals(entry.getValue()));
+            }
+            isTestPassed = true;
+        } catch (SieveConfigurationException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+}
diff --git a/trunk/main/src/test/java/org/apache/jsieve/DiscardTest.java b/trunk/main/src/test/java/org/apache/jsieve/DiscardTest.java
new file mode 100644
index 0000000..36d6775
--- /dev/null
+++ b/trunk/main/src/test/java/org/apache/jsieve/DiscardTest.java
@@ -0,0 +1,87 @@
+/****************************************************************
+ * 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.jsieve;
+
+import junit.framework.TestCase;
+
+import org.apache.jsieve.exception.SieveException;
+import org.apache.jsieve.exception.SyntaxException;
+import org.apache.jsieve.mail.MailAdapter;
+import org.apache.jsieve.parser.generated.ParseException;
+import org.apache.jsieve.utils.JUnitUtils;
+
+/**
+ * Class DiscardTest
+ */
+public class DiscardTest extends TestCase {
+
+    /**
+     * Test for Command 'discard' with invalid arguments
+     */
+    public void testInvalidArguments() {
+        boolean isTestPassed = false;
+        String script = "discard 1 ;";
+
+        try {
+            JUnitUtils.interpret(JUnitUtils.createMail(), script);
+        } catch (SyntaxException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Command 'discard' with an invalid block
+     */
+    public void testInvalidBlock() {
+        boolean isTestPassed = false;
+        String script = "discard 1 {throwTestException;}";
+
+        try {
+            JUnitUtils.interpret(JUnitUtils.createMail(), script);
+        } catch (SyntaxException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /*
+     * Test for Command 'discard'
+     */
+    public void testDiscard() {
+        boolean isTestPassed = false;
+        String script = "discard;";
+
+        try {
+            MailAdapter mail = JUnitUtils.createMail();
+            JUnitUtils.interpret(mail, script);
+            assertTrue(mail.getActions().isEmpty());
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+}
diff --git a/trunk/main/src/test/java/org/apache/jsieve/EnvelopeTest.java b/trunk/main/src/test/java/org/apache/jsieve/EnvelopeTest.java
new file mode 100644
index 0000000..c0ed219
--- /dev/null
+++ b/trunk/main/src/test/java/org/apache/jsieve/EnvelopeTest.java
@@ -0,0 +1,565 @@
+/****************************************************************
+ * 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.jsieve;
+
+import junit.framework.TestCase;
+
+import org.apache.jsieve.commands.ThrowTestException;
+import org.apache.jsieve.exception.SieveException;
+import org.apache.jsieve.parser.generated.ParseException;
+import org.apache.jsieve.utils.JUnitUtils;
+import org.apache.jsieve.utils.SieveEnvelopeMailAdapter;
+
+/**
+ * Class EnvelopeTest
+ */
+public class EnvelopeTest extends TestCase {
+
+    /**
+     * Test for Test 'envelope'
+     */
+    public void testIfEnvelopeAllIsTrue() {
+        boolean isTestPassed = false;
+        String script = "if envelope :all :is \"From\" \"user@domain\" {throwTestException;}";
+        try {
+            SieveEnvelopeMailAdapter mail = JUnitUtils.createEnvelopeMail();
+            mail.setEnvelopeFrom("user@domain");
+            JUnitUtils.interpret(mail, script);
+        } catch (ThrowTestException.TestException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'envelope'
+     */
+    public void testCaseInsensitiveEnvelopeName() {
+        boolean isTestPassed = false;
+        String script = "if envelope :all :is \"from\" \"user@domain\" {throwTestException;}";
+        try {
+            SieveEnvelopeMailAdapter mail = JUnitUtils.createEnvelopeMail();
+            mail.setEnvelopeFrom("user@domain");
+            JUnitUtils.interpret(mail, script);
+        } catch (ThrowTestException.TestException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'envelope'
+     */
+    public void testOctetComparatorTrue() {
+        boolean isTestPassed = false;
+        String script = "if envelope :comparator \"i;octet\" :all :is \"From\" \"uSeR@dOmAiN\" {throwTestException;}";
+        try {
+            SieveEnvelopeMailAdapter mail = JUnitUtils.createEnvelopeMail();
+            mail.setEnvelopeFrom("uSeR@dOmAiN");
+            JUnitUtils.interpret(mail, script);
+        } catch (ThrowTestException.TestException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'envelope'
+     */
+    public void testOctetComparatorFalse() {
+        boolean isTestPassed = false;
+        String script = "if envelope :comparator \"i;octet\" :all :is \"From\" \"uSeR@dOmAiN\" {throwTestException;}";
+        try {
+            SieveEnvelopeMailAdapter mail = JUnitUtils.createEnvelopeMail();
+            mail.setEnvelopeFrom("user@domain");
+            JUnitUtils.interpret(mail, script);
+            isTestPassed = true;
+        } catch (ThrowTestException.TestException e) {
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'envelope'
+     */
+    public void testAsciiComparatorTrue() {
+        boolean isTestPassed = false;
+        String script = "if envelope :comparator \"i;ascii-casemap\" :all :is \"From\" \"uSeR@dOmAiN\" {throwTestException;}";
+        try {
+            SieveEnvelopeMailAdapter mail = JUnitUtils.createEnvelopeMail();
+            mail.setEnvelopeFrom("user@domain");
+            JUnitUtils.interpret(mail, script);
+        } catch (ThrowTestException.TestException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'envelope'
+     */
+    public void testAsciiComparatorFalse() {
+        boolean isTestPassed = false;
+        String script = "if envelope :comparator \"i;ascii-casemap\" :all :is \"From\" \"uSeR@dOmAiN\" {throwTestException;}";
+        try {
+            SieveEnvelopeMailAdapter mail = JUnitUtils.createEnvelopeMail();
+            mail.setEnvelopeFrom("user@domain1");
+            JUnitUtils.interpret(mail, script);
+            isTestPassed = true;
+        } catch (ThrowTestException.TestException e) {
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'envelope'
+     */
+    public void testIfEnvelopeAllIsMultiTrue1() {
+        boolean isTestPassed = false;
+        String script = "if envelope :all :is [\"From\", \"To\"] \"user@domain\" {throwTestException;}";
+        try {
+            SieveEnvelopeMailAdapter mail = JUnitUtils.createEnvelopeMail();
+            mail.setEnvelopeFrom("user@domain");
+            mail.setEnvelopeTo("user@domain");
+            JUnitUtils.interpret(mail, script);
+        } catch (ThrowTestException.TestException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'envelope'
+     */
+    public void testIfEnvelopeAllIsMultiTrue2() {
+        boolean isTestPassed = false;
+        String script = "if envelope :all :is [\"From\", \"To\"] [\"user@domain\", \"tweety@pie\"] {throwTestException;}";
+        try {
+            SieveEnvelopeMailAdapter mail = JUnitUtils.createEnvelopeMail();
+            mail.setEnvelopeFrom("user@domain");
+            mail.setEnvelopeTo("user@domain");
+            JUnitUtils.interpret(mail, script);
+        } catch (ThrowTestException.TestException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'envelope'
+     */
+    public void testIfEnvelopeAllIsMultiTrue3() {
+        boolean isTestPassed = false;
+        String script = "if envelope :all :is [\"From\", \"To\"] [\"user@domain\", \"tweety@pie\"] {throwTestException;}";
+        try {
+            SieveEnvelopeMailAdapter mail = JUnitUtils.createEnvelopeMail();
+            mail.setEnvelopeFrom("user@domain");
+            mail.setEnvelopeTo("tweety@pie");
+            JUnitUtils.interpret(mail, script);
+        } catch (ThrowTestException.TestException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'envelope'
+     */
+    public void testIfEnvelopeAllIsMultiTrue4() {
+        boolean isTestPassed = false;
+        String script = "if envelope :all :is [\"From\", \"To\"] [\"user@domain\", \"tweety@pie\"] {throwTestException;}";
+        try {
+            SieveEnvelopeMailAdapter mail = JUnitUtils.createEnvelopeMail();
+            mail.setEnvelopeFrom("tweety@pie");
+            mail.setEnvelopeTo("tweety@pie");
+            JUnitUtils.interpret(mail, script);
+        } catch (ThrowTestException.TestException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'envelope'
+     */
+    public void testIfEnvelopeAllMatchesTrue() {
+        boolean isTestPassed = false;
+        String script = "if envelope :all :matches \"From\" \"*@domain\" {throwTestException;}";
+        try {
+            SieveEnvelopeMailAdapter mail = JUnitUtils.createEnvelopeMail();
+            mail.setEnvelopeFrom("user@domain");
+            JUnitUtils.interpret(mail, script);
+        } catch (ThrowTestException.TestException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'envelope'
+     */
+    public void testIfEnvelopeAllContainsTrue() {
+        boolean isTestPassed = false;
+        String script = "if envelope :all :contains \"From\" \"r@dom\" {throwTestException;}";
+        try {
+            SieveEnvelopeMailAdapter mail = JUnitUtils.createEnvelopeMail();
+            mail.setEnvelopeFrom("user@domain");
+            JUnitUtils.interpret(mail, script);
+        } catch (ThrowTestException.TestException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'envelope'
+     */
+    public void testIfEnvelopeLocalpartIsTrue() {
+        boolean isTestPassed = false;
+        String script = "if envelope :localpart :is \"From\" \"user\" {throwTestException;}";
+        try {
+            SieveEnvelopeMailAdapter mail = JUnitUtils.createEnvelopeMail();
+            mail.setEnvelopeFrom("user@domain");
+            JUnitUtils.interpret(mail, script);
+        } catch (ThrowTestException.TestException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'envelope'
+     */
+    public void testIfEnvelopeLocalpartMatchesTrue() {
+        boolean isTestPassed = false;
+        String script = "if envelope :localpart :matches \"From\" \"*er\" {throwTestException;}";
+        try {
+            SieveEnvelopeMailAdapter mail = JUnitUtils.createEnvelopeMail();
+            mail.setEnvelopeFrom("user@domain");
+            JUnitUtils.interpret(mail, script);
+        } catch (ThrowTestException.TestException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'envelope'
+     */
+    public void testIfEnvelopeLocalpartContainsTrue() {
+        boolean isTestPassed = false;
+        String script = "if envelope :localpart :contains \"From\" \"r\" {throwTestException;}";
+        try {
+            SieveEnvelopeMailAdapter mail = JUnitUtils.createEnvelopeMail();
+            mail.setEnvelopeFrom("user@domain");
+            JUnitUtils.interpret(mail, script);
+        } catch (ThrowTestException.TestException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'envelope'
+     */
+    public void testIfEnvelopeDomainIsTrue() {
+        boolean isTestPassed = false;
+        String script = "if envelope :domain :is \"From\" \"domain\" {throwTestException;}";
+
+        try {
+            SieveEnvelopeMailAdapter mail = JUnitUtils.createEnvelopeMail();
+            mail.setEnvelopeFrom("user@domain");
+            JUnitUtils.interpret(mail, script);
+        } catch (ThrowTestException.TestException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'envelope'
+     */
+    public void testIfEnvelopeDomainMatchesTrue() {
+        boolean isTestPassed = false;
+        String script = "if envelope :domain :matches \"From\" \"*main\" {throwTestException;}";
+        try {
+            SieveEnvelopeMailAdapter mail = JUnitUtils.createEnvelopeMail();
+            mail.setEnvelopeFrom("user@domain");
+            JUnitUtils.interpret(mail, script);
+        } catch (ThrowTestException.TestException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'envelope'
+     */
+    public void testIfEnvelopeDomainContainsTrue() {
+        boolean isTestPassed = false;
+        String script = "if envelope :domain :contains \"From\" \"dom\" {throwTestException;}";
+        try {
+            SieveEnvelopeMailAdapter mail = JUnitUtils.createEnvelopeMail();
+            mail.setEnvelopeFrom("user@domain");
+            JUnitUtils.interpret(mail, script);
+        } catch (ThrowTestException.TestException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'envelope'
+     */
+    public void testIfEnvelopeAllIsFalse() {
+        boolean isTestPassed = false;
+        String script = "if envelope :all :is \"From\" \"user@domain\" {throwTestException;}";
+        try {
+            SieveEnvelopeMailAdapter mail = JUnitUtils.createEnvelopeMail();
+            mail.setEnvelopeFrom("tweety@pie");
+            JUnitUtils.interpret(mail, script);
+            isTestPassed = true;
+        } catch (ThrowTestException.TestException e) {
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'envelope'
+     */
+    public void testIfEnvelopeAllMatchesFalse() {
+        boolean isTestPassed = false;
+        String script = "if envelope :all :matches \"From\" \"(.*)@domain\" {throwTestException;}";
+        try {
+            SieveEnvelopeMailAdapter mail = JUnitUtils.createEnvelopeMail();
+            mail.setEnvelopeFrom("bugs@bunny");
+            JUnitUtils.interpret(mail, script);
+            isTestPassed = true;
+        } catch (ThrowTestException.TestException e) {
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'envelope'
+     */
+    public void testIfEnvelopeAllContainsFalse() {
+        boolean isTestPassed = false;
+        String script = "if envelope :all :contains \"From\" \"r@dom\" {throwTestException;}";
+        try {
+            SieveEnvelopeMailAdapter mail = JUnitUtils.createEnvelopeMail();
+            mail.setEnvelopeFrom("tweety@pie");
+            JUnitUtils.interpret(mail, script);
+            isTestPassed = true;
+        } catch (ThrowTestException.TestException e) {
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'envelope'
+     */
+    public void testIfEnvelopeLocalpartIsFalse() {
+        boolean isTestPassed = false;
+        String script = "if envelope :localpart :is \"From\" \"user\" {throwTestException;}";
+        try {
+            SieveEnvelopeMailAdapter mail = JUnitUtils.createEnvelopeMail();
+            mail.setEnvelopeFrom("tweety@pie");
+            JUnitUtils.interpret(mail, script);
+            isTestPassed = true;
+        } catch (ThrowTestException.TestException e) {
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'envelope'
+     */
+    public void testIfEnvelopeLocalpartMatchesFalse() {
+        boolean isTestPassed = false;
+        String script = "if envelope :localpart :matches \"From\" \"(.*)er\" {throwTestException;}";
+        try {
+            SieveEnvelopeMailAdapter mail = JUnitUtils.createEnvelopeMail();
+            mail.setEnvelopeFrom("tweety@pie");
+            JUnitUtils.interpret(mail, script);
+            isTestPassed = true;
+        } catch (ThrowTestException.TestException e) {
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'envelope'
+     */
+    public void testIfEnvelopeLocalpartContainsFalse() {
+        boolean isTestPassed = false;
+        String script = "if envelope :localpart :contains \"From\" \"r\" {throwTestException;}";
+        try {
+            SieveEnvelopeMailAdapter mail = JUnitUtils.createEnvelopeMail();
+            mail.setEnvelopeFrom("tweety@pie");
+            JUnitUtils.interpret(mail, script);
+            isTestPassed = true;
+        } catch (ThrowTestException.TestException e) {
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'envelope'
+     */
+    public void testIfEnvelopeDomainIsFalse() {
+        boolean isTestPassed = false;
+        String script = "if envelope :domain :is \"From\" \"domain\" {throwTestException;}";
+
+        try {
+            SieveEnvelopeMailAdapter mail = JUnitUtils.createEnvelopeMail();
+            mail.setEnvelopeFrom("tweety@pie");
+            JUnitUtils.interpret(mail, script);
+            isTestPassed = true;
+        } catch (ThrowTestException.TestException e) {
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'envelope'
+     */
+    public void testIfEnvelopeDomainMatchesFalse() {
+        boolean isTestPassed = false;
+        String script = "if envelope :domain :matches \"From\" \"(.*)main\" {throwTestException;}";
+        try {
+            SieveEnvelopeMailAdapter mail = JUnitUtils.createEnvelopeMail();
+            mail.setEnvelopeFrom("tweety@pie");
+            JUnitUtils.interpret(mail, script);
+            isTestPassed = true;
+        } catch (ThrowTestException.TestException e) {
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'envelope'
+     */
+    public void testIfEnvelopeDomainContainsFalse() {
+        boolean isTestPassed = false;
+        String script = "if envelope :domain :contains \"From\" \"dom\" {throwTestException;}";
+        try {
+            SieveEnvelopeMailAdapter mail = JUnitUtils.createEnvelopeMail();
+            mail.setEnvelopeFrom("tweety@pie");
+            JUnitUtils.interpret(mail, script);
+            isTestPassed = true;
+        } catch (ThrowTestException.TestException e) {
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'envelope'
+     */
+    public void testIfEnvelopeAllIsMultiFalse1() {
+        boolean isTestPassed = false;
+        String script = "if envelope :all :is [\"From\", \"To\"] \"user@domain\" {throwTestException;}";
+        try {
+            SieveEnvelopeMailAdapter mail = JUnitUtils.createEnvelopeMail();
+            mail.setEnvelopeFrom("bugs@bunny");
+            mail.setEnvelopeTo("bugs@bunny");
+            JUnitUtils.interpret(mail, script);
+            isTestPassed = true;
+        } catch (ThrowTestException.TestException e) {
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'envelope'
+     */
+    public void testIfEnvelopeAllIsMultiFalse2() {
+        boolean isTestPassed = false;
+        String script = "if envelope :all :is [\"From\", \"To\"] [\"user@domain\", \"tweety@pie\"] {throwTestException;}";
+        try {
+            SieveEnvelopeMailAdapter mail = JUnitUtils.createEnvelopeMail();
+            mail.setEnvelopeFrom("bugs@bunny");
+            mail.setEnvelopeTo("bugs@bunny");
+            JUnitUtils.interpret(mail, script);
+            isTestPassed = true;
+        } catch (ThrowTestException.TestException e) {
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+}
diff --git a/trunk/main/src/test/java/org/apache/jsieve/ExistsTest.java b/trunk/main/src/test/java/org/apache/jsieve/ExistsTest.java
new file mode 100644
index 0000000..3eff04a
--- /dev/null
+++ b/trunk/main/src/test/java/org/apache/jsieve/ExistsTest.java
@@ -0,0 +1,188 @@
+/****************************************************************
+ * 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.jsieve;
+
+import javax.mail.MessagingException;
+
+import junit.framework.TestCase;
+
+import org.apache.jsieve.commands.ThrowTestException;
+import org.apache.jsieve.exception.SieveException;
+import org.apache.jsieve.exception.SyntaxException;
+import org.apache.jsieve.parser.generated.ParseException;
+import org.apache.jsieve.utils.JUnitUtils;
+import org.apache.jsieve.utils.SieveMailAdapter;
+
+/**
+ * Class ExistsTest
+ */
+public class ExistsTest extends TestCase {
+
+    /**
+     * Test for Test 'exists'
+     */
+    public void testExistsTrue() {
+        boolean isTestPassed = false;
+        String script = "if exists \"From\" {throwTestException;}";
+
+        try {
+            SieveMailAdapter mail = (SieveMailAdapter) JUnitUtils.createMail();
+            mail.getMessage().addHeader("From", "tweety@pie");
+            JUnitUtils.interpret(mail, script);
+        } catch (MessagingException e) {
+        } catch (ThrowTestException.TestException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'exists'
+     */
+    public void testCaseInsensitivity() {
+        boolean isTestPassed = false;
+        String script = "if exists \"From\" {throwTestException;}";
+
+        try {
+            SieveMailAdapter mail = (SieveMailAdapter) JUnitUtils.createMail();
+            mail.getMessage().addHeader("from", "tweety@pie");
+            JUnitUtils.interpret(mail, script);
+        } catch (MessagingException e) {
+        } catch (ThrowTestException.TestException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'exists'
+     */
+    public void testExistsTrueTrue() {
+        boolean isTestPassed = false;
+        String script = "if exists [\"From\", \"X-Files\"] {throwTestException;}";
+
+        try {
+            SieveMailAdapter mail = (SieveMailAdapter) JUnitUtils.createMail();
+            mail.getMessage().addHeader("From", "tweety@pie");
+            mail.getMessage().addHeader("X-Files", "spooks@everywhere");
+            JUnitUtils.interpret(mail, script);
+        } catch (MessagingException e) {
+        } catch (ThrowTestException.TestException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'exists'
+     */
+    public void testExistsTrueFalse() {
+        boolean isTestPassed = false;
+        String script = "if exists [\"From\", \"X-Files\"] {stop;} throwTestException;";
+        try {
+            SieveMailAdapter mail = (SieveMailAdapter) JUnitUtils.createMail();
+            mail.getMessage().addHeader("From", "tweety@pie");
+            JUnitUtils.interpret(mail, script);
+        } catch (MessagingException e) {
+        } catch (ThrowTestException.TestException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'exists'
+     */
+    public void testExistsFalse() {
+        boolean isTestPassed = false;
+        String script = "if exists \"From\" {stop;} throwTestException;";
+
+        try {
+            SieveMailAdapter mail = (SieveMailAdapter) JUnitUtils.createMail();
+            JUnitUtils.interpret(mail, script);
+        } catch (ThrowTestException.TestException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'exists'
+     */
+    public void testExistsFalseFalse() {
+        boolean isTestPassed = false;
+        String script = "if exists [\"From\", \"X-Files\"] {stop;} throwTestException;";
+
+        try {
+            SieveMailAdapter mail = (SieveMailAdapter) JUnitUtils.createMail();
+            JUnitUtils.interpret(mail, script);
+        } catch (ThrowTestException.TestException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'exists' with invalid numeric argument
+     */
+    public void testInvalidNumericArgument() {
+        boolean isTestPassed = false;
+        String script = "if exists 1 {throwTestException;}";
+
+        try {
+            JUnitUtils.interpret(JUnitUtils.createMail(), script);
+        } catch (SyntaxException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'exists' with invalid test argument
+     */
+    public void testInvalidTestArgument() {
+        boolean isTestPassed = false;
+        String script = "if exists not {throwTestException;}";
+
+        try {
+            JUnitUtils.interpret(JUnitUtils.createMail(), script);
+        } catch (SyntaxException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+}
diff --git a/trunk/main/src/test/java/org/apache/jsieve/FalseTest.java b/trunk/main/src/test/java/org/apache/jsieve/FalseTest.java
new file mode 100644
index 0000000..86c92de
--- /dev/null
+++ b/trunk/main/src/test/java/org/apache/jsieve/FalseTest.java
@@ -0,0 +1,69 @@
+/****************************************************************
+ * 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.jsieve;
+
+import junit.framework.TestCase;
+
+import org.apache.jsieve.commands.ThrowTestException;
+import org.apache.jsieve.exception.SieveException;
+import org.apache.jsieve.exception.SyntaxException;
+import org.apache.jsieve.parser.generated.ParseException;
+import org.apache.jsieve.utils.JUnitUtils;
+
+/**
+ * Class FalseTest
+ */
+public class FalseTest extends TestCase {
+
+    /**
+     * Test for Test 'true'
+     */
+    public void testIfFalse() {
+        boolean isTestPassed = false;
+        String script = "if false {stop;} throwTestException;";
+
+        try {
+            JUnitUtils.interpret(JUnitUtils.createMail(), script);
+        } catch (ThrowTestException.TestException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'true' with invalid argument
+     */
+    public void testInvalidArgument() {
+        boolean isTestPassed = false;
+        String script = "if false 1 {throwTestException;}";
+
+        try {
+            JUnitUtils.interpret(JUnitUtils.createMail(), script);
+        } catch (SyntaxException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+}
diff --git a/trunk/main/src/test/java/org/apache/jsieve/FileIntoTest.java b/trunk/main/src/test/java/org/apache/jsieve/FileIntoTest.java
new file mode 100644
index 0000000..f39d3f4
--- /dev/null
+++ b/trunk/main/src/test/java/org/apache/jsieve/FileIntoTest.java
@@ -0,0 +1,133 @@
+/****************************************************************
+ * 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.jsieve;
+
+import junit.framework.TestCase;
+
+import org.apache.jsieve.exception.SieveException;
+import org.apache.jsieve.exception.SyntaxException;
+import org.apache.jsieve.mail.ActionFileInto;
+import org.apache.jsieve.mail.MailAdapter;
+import org.apache.jsieve.parser.generated.ParseException;
+import org.apache.jsieve.utils.JUnitUtils;
+
+/**
+ * Class FileIntoTest
+ */
+public class FileIntoTest extends TestCase {
+
+    /**
+     * Test for Command 'fileinto'
+     */
+    public void testFileInto() {
+        boolean isTestPassed = false;
+        String script = "fileinto \"INBOX.test1\"; fileinto \"INBOX.test2\";";
+
+        try {
+            MailAdapter mail = JUnitUtils.createMail();
+            JUnitUtils.interpret(mail, script);
+            assertTrue(mail.getActions().size() == 2);
+            assertTrue(mail.getActions().get(0) instanceof ActionFileInto);
+            assertTrue(((ActionFileInto) mail.getActions().get(0))
+                    .getDestination().equals("INBOX.test1"));
+            assertTrue(mail.getActions().get(1) instanceof ActionFileInto);
+            assertTrue(((ActionFileInto) mail.getActions().get(1))
+                    .getDestination().equals("INBOX.test2"));
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Command 'fileinto' with duplicate destinations. Only one
+     * ActionFileInto should result.
+     */
+    public void testDuplicateFileInto() {
+        boolean isTestPassed = false;
+        String script = "fileinto \"INBOX.test1\"; fileinto \"INBOX.test1\";";
+
+        try {
+            MailAdapter mail = JUnitUtils.createMail();
+            JUnitUtils.interpret(mail, script);
+            assertTrue(mail.getActions().size() == 1);
+            assertTrue(mail.getActions().get(0) instanceof ActionFileInto);
+            assertTrue(((ActionFileInto) mail.getActions().get(0))
+                    .getDestination().equals("INBOX.test1"));
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Command 'fileinto' with an invalid argument type
+     */
+    public void testInvalidArgumentType() {
+        boolean isTestPassed = false;
+        String script = "fileinto 1 ;";
+
+        try {
+            JUnitUtils.interpret(JUnitUtils.createMail(), script);
+        } catch (SyntaxException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Command 'fileinto' with an invalid argument number
+     */
+    public void testInvalidArgumentNumber() {
+        boolean isTestPassed = false;
+        String script = "fileinto [\"INBOX.test\", \"elsewhere\"];";
+
+        try {
+            JUnitUtils.interpret(JUnitUtils.createMail(), script);
+        } catch (SyntaxException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Command 'fileinto' with an invalid block
+     */
+    public void testInvalidBlock() {
+        boolean isTestPassed = false;
+        String script = "fileinto 1 {throwTestException;}";
+
+        try {
+            JUnitUtils.interpret(JUnitUtils.createMail(), script);
+        } catch (SyntaxException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+}
diff --git a/trunk/main/src/test/java/org/apache/jsieve/HeaderTest.java b/trunk/main/src/test/java/org/apache/jsieve/HeaderTest.java
new file mode 100644
index 0000000..97f07d3
--- /dev/null
+++ b/trunk/main/src/test/java/org/apache/jsieve/HeaderTest.java
@@ -0,0 +1,367 @@
+/****************************************************************
+ * 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.jsieve;
+
+import javax.mail.MessagingException;
+
+import junit.framework.TestCase;
+
+import org.apache.jsieve.commands.ThrowTestException;
+import org.apache.jsieve.exception.SieveException;
+import org.apache.jsieve.parser.generated.ParseException;
+import org.apache.jsieve.utils.JUnitUtils;
+import org.apache.jsieve.utils.SieveMailAdapter;
+
+/**
+ * Class HeaderTest
+ */
+public class HeaderTest extends TestCase {
+
+    /**
+     * Test for Test 'header'
+     */
+    public void testHeaderIsTrue() {
+        boolean isTestPassed = false;
+        String script = "if header :is \"X-Caffeine\" \"C8H10N4O2\" {throwTestException;}";
+        try {
+            SieveMailAdapter mail = (SieveMailAdapter) JUnitUtils.createMail();
+            mail.getMessage().addHeader("X-Caffeine", "C8H10N4O2");
+            JUnitUtils.interpret(mail, script);
+        } catch (MessagingException e) {
+        } catch (ThrowTestException.TestException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'header'
+     */
+    public void testHeaderCaseInsensitivity() {
+        boolean isTestPassed = false;
+        String script = "if header :is \"X-Caffeine\" \"C8H10N4O2\" {throwTestException;}";
+        try {
+            SieveMailAdapter mail = (SieveMailAdapter) JUnitUtils.createMail();
+            mail.getMessage().addHeader("x-caffeine", "C8H10N4O2");
+            JUnitUtils.interpret(mail, script);
+        } catch (MessagingException e) {
+        } catch (ThrowTestException.TestException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'header'
+     */
+    public void testHeaderIsTrueMulti1() {
+        boolean isTestPassed = false;
+        String script = "if header :is [\"X-Decaf\", \"X-Caffeine\"] \"C8H10N4O2\" {throwTestException;}";
+        try {
+            SieveMailAdapter mail = (SieveMailAdapter) JUnitUtils.createMail();
+            mail.getMessage().addHeader("X-Caffeine", "C8H10N4O2");
+            JUnitUtils.interpret(mail, script);
+        } catch (MessagingException e) {
+        } catch (ThrowTestException.TestException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'header'
+     */
+    public void testHeaderIsFalseMulti1() {
+        boolean isTestPassed = false;
+        String script = "if header :is [\"X-Decaf\", \"X-Caffeine\"] \"C8H10N4O2\" {throwTestException;}";
+        try {
+            SieveMailAdapter mail = (SieveMailAdapter) JUnitUtils.createMail();
+            mail.getMessage().addHeader("X-Caffeine", "stuff");
+            mail.getMessage().addHeader("X-Decaf", "more stuff");
+            JUnitUtils.interpret(mail, script);
+            isTestPassed = true;
+        } catch (MessagingException e) {
+        } catch (ThrowTestException.TestException e) {
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'header'
+     */
+    public void testHeaderIsTrueMulti2() {
+        boolean isTestPassed = false;
+        String script = "if header :is \"X-Caffeine\" [\"absent\", \"C8H10N4O2\"] {throwTestException;}";
+        try {
+            SieveMailAdapter mail = (SieveMailAdapter) JUnitUtils.createMail();
+            mail.getMessage().addHeader("X-Caffeine", "C8H10N4O2");
+            JUnitUtils.interpret(mail, script);
+        } catch (MessagingException e) {
+        } catch (ThrowTestException.TestException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'header'
+     */
+    public void testHeaderIsTrueMulti3() {
+        boolean isTestPassed = false;
+        String script = "if header :is [\"X-Decaf\", \"X-Caffeine\"] [\"absent\", \"C8H10N4O2\"] {throwTestException;}";
+        try {
+            SieveMailAdapter mail = (SieveMailAdapter) JUnitUtils.createMail();
+            mail.getMessage().addHeader("X-Caffeine", "stuff");
+            mail.getMessage().addHeader("X-Decaf", "C8H10N4O2");
+            JUnitUtils.interpret(mail, script);
+        } catch (MessagingException e) {
+        } catch (ThrowTestException.TestException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'header'
+     */
+    public void testHeaderIsFalseValue() {
+        boolean isTestPassed = false;
+        String script = "if header :is \"X-Caffeine\" \"C8H10N4O2\" {throwTestException;}";
+        try {
+            SieveMailAdapter mail = (SieveMailAdapter) JUnitUtils.createMail();
+            mail.getMessage().addHeader("X-Caffeine", "C8H10N4O");
+            JUnitUtils.interpret(mail, script);
+            isTestPassed = true;
+        } catch (MessagingException e) {
+        } catch (ThrowTestException.TestException e) {
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'header'
+     */
+    public void testHeaderIsFalseHeader() {
+        boolean isTestPassed = false;
+        String script = "if header :is \"X-Caffeine\" \"C8H10N4O2\" {throwTestException;}";
+        try {
+            SieveMailAdapter mail = (SieveMailAdapter) JUnitUtils.createMail();
+            mail.getMessage().addHeader("X-Caffein", "C8H10N4O2");
+            JUnitUtils.interpret(mail, script);
+            isTestPassed = true;
+        } catch (MessagingException e) {
+        } catch (ThrowTestException.TestException e) {
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'header'
+     */
+    public void testHeaderContainsTrue() {
+        boolean isTestPassed = false;
+        String script = "if header :contains \"X-Caffeine\" \"C8H10\" {throwTestException;}";
+        try {
+            SieveMailAdapter mail = (SieveMailAdapter) JUnitUtils.createMail();
+            mail.getMessage().addHeader("X-Caffeine", "C8H10N4O2");
+            JUnitUtils.interpret(mail, script);
+        } catch (MessagingException e) {
+        } catch (ThrowTestException.TestException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'header'
+     */
+    public void testHeaderContainsFalse() {
+        boolean isTestPassed = false;
+        String script = "if header :is \"X-Caffeine\" \"C8H10N4O2\" {throwTestException;}";
+        try {
+            SieveMailAdapter mail = (SieveMailAdapter) JUnitUtils.createMail();
+            mail.getMessage().addHeader("X-Caffeine", "izzy");
+            JUnitUtils.interpret(mail, script);
+            isTestPassed = true;
+        } catch (MessagingException e) {
+        } catch (ThrowTestException.TestException e) {
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'header'
+     */
+    public void testHeaderContainsNullTrue() {
+        boolean isTestPassed = false;
+        String script = "if header :contains \"X-Caffeine\" \"\" {throwTestException;}";
+        try {
+            SieveMailAdapter mail = (SieveMailAdapter) JUnitUtils.createMail();
+            mail.getMessage().addHeader("X-Caffeine", null);
+            JUnitUtils.interpret(mail, script);
+        } catch (MessagingException e) {
+        } catch (ThrowTestException.TestException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'header'
+     */
+    public void testHeaderIsNullFalse() {
+        boolean isTestPassed = false;
+        String script = "if header :is \"X-Caffeine\" \"\" {throwTestException;}";
+        try {
+            SieveMailAdapter mail = (SieveMailAdapter) JUnitUtils.createMail();
+            mail.getMessage().addHeader("X-Caffeine", null);
+            JUnitUtils.interpret(mail, script);
+            isTestPassed = true;
+        } catch (MessagingException e) {
+        } catch (ThrowTestException.TestException e) {
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'header'
+     */
+    public void testHeaderMatchesTrue() {
+        boolean isTestPassed = false;
+        String script = "if header :matches \"X-Caffeine\" \"*10N?O2\" {throwTestException;}";
+        try {
+            SieveMailAdapter mail = (SieveMailAdapter) JUnitUtils.createMail();
+            mail.getMessage().addHeader("X-Caffeine", "C8H10N4O2");
+            JUnitUtils.interpret(mail, script);
+        } catch (MessagingException e) {
+        } catch (ThrowTestException.TestException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'header'
+     */
+    public void testHeaderMatchesFalse() {
+        boolean isTestPassed = false;
+        String script = "if header :matches \"X-Caffeine\" \"*10N?O2\" {throwTestException;}";
+        try {
+            SieveMailAdapter mail = (SieveMailAdapter) JUnitUtils.createMail();
+            mail.getMessage().addHeader("X-Caffeine", "C8H10N4O3");
+            JUnitUtils.interpret(mail, script);
+            isTestPassed = true;
+        } catch (MessagingException e) {
+        } catch (ThrowTestException.TestException e) {
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for square brackets in matching headers When the "[" is in the first
+     * char of the pattern it does not matches.
+     * 
+     * See http://issues.apache.org/jira/browse/JSIEVE-19
+     */
+    public void testSquareBracketsInMatch() {
+        boolean isTestPassed = false;
+        String script = "if header :matches \"X-Caffeine\" \"[test*\" {throwTestException;}";
+        try {
+            SieveMailAdapter mail = (SieveMailAdapter) JUnitUtils.createMail();
+            mail.getMessage().addHeader("X-Caffeine", "[test] my subject");
+            JUnitUtils.interpret(mail, script);
+        } catch (MessagingException e) {
+        } catch (ThrowTestException.TestException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for special char escaping: \\? is a ? and \\* is an *
+     */
+    public void testSpecialCharsEscapingInMatch() {
+        boolean isTestPassed = false;
+        String script = "if header :matches \"X-Caffeine\" \"my,\\\\,?,\\?,\\\\?,*,\\*,\\\\*,pattern\" {throwTestException;}";
+        try {
+            SieveMailAdapter mail = (SieveMailAdapter) JUnitUtils.createMail();
+            mail.getMessage().addHeader("X-Caffeine",
+                    "my,\\,x,x,?,foo,bar,*,pattern");
+            JUnitUtils.interpret(mail, script);
+        } catch (MessagingException e) {
+        } catch (ThrowTestException.TestException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for special char escaping: \\? is a ? and \\* is an *
+     */
+    public void testSpecialCharsEscapingInMatchFalse() {
+        boolean isTestPassed = false;
+        String script = "if header :matches \"X-Caffeine\" \"my,?,\\?,\\\\?,*,\\*,\\\\*,pattern\" {throwTestException;}";
+        try {
+            SieveMailAdapter mail = (SieveMailAdapter) JUnitUtils.createMail();
+            mail.getMessage().addHeader("X-Caffeine",
+                    "my,x,x,q,foo,bar,*,pattern");
+            JUnitUtils.interpret(mail, script);
+            isTestPassed = true;
+        } catch (MessagingException e) {
+        } catch (ThrowTestException.TestException e) {
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+}
diff --git a/trunk/main/src/test/java/org/apache/jsieve/KeepTest.java b/trunk/main/src/test/java/org/apache/jsieve/KeepTest.java
new file mode 100644
index 0000000..3c759f1
--- /dev/null
+++ b/trunk/main/src/test/java/org/apache/jsieve/KeepTest.java
@@ -0,0 +1,89 @@
+/****************************************************************
+ * 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.jsieve;
+
+import junit.framework.TestCase;
+
+import org.apache.jsieve.exception.SieveException;
+import org.apache.jsieve.exception.SyntaxException;
+import org.apache.jsieve.mail.ActionKeep;
+import org.apache.jsieve.mail.MailAdapter;
+import org.apache.jsieve.parser.generated.ParseException;
+import org.apache.jsieve.utils.JUnitUtils;
+
+/**
+ * Class KeepTest
+ */
+public class KeepTest extends TestCase {
+
+    /**
+     * Test for Command 'keep' with invalid arguments
+     */
+    public void testInvalidArguments() {
+        boolean isTestPassed = false;
+        String script = "keep 1 ;";
+
+        try {
+            JUnitUtils.interpret(JUnitUtils.createMail(), script);
+        } catch (SyntaxException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Command 'keep' with an invalid block
+     */
+    public void testInvalidBlock() {
+        boolean isTestPassed = false;
+        String script = "keep 1 {throwTestException;}";
+
+        try {
+            JUnitUtils.interpret(JUnitUtils.createMail(), script);
+        } catch (SyntaxException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Command 'keep'
+     */
+    public void testKeep() {
+        boolean isTestPassed = false;
+        String script = "keep;";
+
+        try {
+            MailAdapter mail = JUnitUtils.createMail();
+            JUnitUtils.interpret(mail, script);
+            assertTrue(mail.getActions().size() == 1);
+            assertTrue(mail.getActions().get(0) instanceof ActionKeep);
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+}
diff --git a/trunk/main/src/test/java/org/apache/jsieve/LiteralEscapeTest.java b/trunk/main/src/test/java/org/apache/jsieve/LiteralEscapeTest.java
new file mode 100644
index 0000000..d61529e
--- /dev/null
+++ b/trunk/main/src/test/java/org/apache/jsieve/LiteralEscapeTest.java
@@ -0,0 +1,55 @@
+/****************************************************************
+ * 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.jsieve;
+
+import org.apache.jsieve.utils.JUnitUtils;
+import org.apache.jsieve.utils.SieveMailAdapter;
+
+import junit.framework.TestCase;
+
+public class LiteralEscapeTest extends TestCase {
+
+    private final String SCRIPT = "require [\"fileinto\", \"reject\"];" +
+    "" +
+    "# test" +
+    "if allof (header :contains \"to\" \"\\\\\") {" +
+    "    keep;" +
+    "    stop;" +
+    "}" +
+    "" +
+    "# test2" +
+    "if anyof (header :contains \"subject\" \"foo\") {" +
+    "    keep;" +
+    "    stop;" +
+    "}"; 
+    
+    protected void setUp() throws Exception {
+        super.setUp();
+    }
+
+    protected void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    public void testBackSlash() throws Exception {
+        SieveMailAdapter mail = (SieveMailAdapter) JUnitUtils.createMail();
+        mail.getMessage().addHeader("to", "tweety@pie");
+        JUnitUtils.interpret(mail, SCRIPT);
+    }
+}
diff --git a/trunk/main/src/test/java/org/apache/jsieve/LogTest.java b/trunk/main/src/test/java/org/apache/jsieve/LogTest.java
new file mode 100644
index 0000000..ec59f95
--- /dev/null
+++ b/trunk/main/src/test/java/org/apache/jsieve/LogTest.java
@@ -0,0 +1,145 @@
+/****************************************************************
+ * 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.jsieve;
+
+import junit.framework.TestCase;
+
+import org.apache.jsieve.exception.SieveException;
+import org.apache.jsieve.parser.generated.ParseException;
+import org.apache.jsieve.utils.JUnitUtils;
+
+/**
+ * Class LogTest
+ */
+public class LogTest extends TestCase {
+
+    /**
+     * Test for Command 'log'.
+     */
+    public void testLogDebug() {
+        boolean isTestPassed = false;
+        String script = "log :debug \"Log a debug message.\";";
+
+        try {
+            JUnitUtils.interpret(JUnitUtils.createMail(), script);
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Command 'log'.
+     */
+    public void testLogError() {
+        boolean isTestPassed = false;
+        String script = "log :error \"Log an error message.\";";
+
+        try {
+            JUnitUtils.interpret(JUnitUtils.createMail(), script);
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Command 'log'.
+     */
+    public void testLogFatal() {
+        boolean isTestPassed = false;
+        String script = "log :fatal \"Log a fatal message.\";";
+
+        try {
+            JUnitUtils.interpret(JUnitUtils.createMail(), script);
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Command 'log'.
+     */
+    public void testLogInfo() {
+        boolean isTestPassed = false;
+        String script = "log :info \"Log an info message.\";";
+
+        try {
+            JUnitUtils.interpret(JUnitUtils.createMail(), script);
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Command 'log'.
+     */
+    public void testLogTrace() {
+        boolean isTestPassed = false;
+        String script = "log :trace \"Log a trace message.\";";
+
+        try {
+            JUnitUtils.interpret(JUnitUtils.createMail(), script);
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Command 'log'.
+     */
+    public void testLogWarn() {
+        boolean isTestPassed = false;
+        String script = "log :warn \"Log a warning message.\";";
+
+        try {
+            JUnitUtils.interpret(JUnitUtils.createMail(), script);
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Command 'log'.
+     */
+    public void testLogDefault() {
+        boolean isTestPassed = false;
+        String script = "log \"Log a default message.\";";
+
+        try {
+            JUnitUtils.interpret(JUnitUtils.createMail(), script);
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+}
diff --git a/trunk/main/src/test/java/org/apache/jsieve/MultilineTextTest.java b/trunk/main/src/test/java/org/apache/jsieve/MultilineTextTest.java
new file mode 100644
index 0000000..10947a8
--- /dev/null
+++ b/trunk/main/src/test/java/org/apache/jsieve/MultilineTextTest.java
@@ -0,0 +1,124 @@
+/****************************************************************
+ * 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.jsieve;
+
+import junit.framework.TestCase;
+
+import org.apache.jsieve.exception.SieveException;
+import org.apache.jsieve.mail.ActionReject;
+import org.apache.jsieve.mail.MailAdapter;
+import org.apache.jsieve.parser.generated.ParseException;
+import org.apache.jsieve.utils.JUnitUtils;
+
+/**
+ * Class RejectTest
+ */
+public class MultilineTextTest extends TestCase {
+    
+    /**
+     * Tests that a multiline message is correctly passed
+     */
+    public void testRejectMultilineMessage() throws Exception {
+        String message = "This is not a love song";
+        String script = "reject text:\n" + message + "\n.\n;";
+        ActionReject rejection = runRejectScript(script);        
+        assertEquals(message, rejection.getMessage());
+    }
+    
+    /**
+     * Tests that a multiline message is correctly passed when whitespace is inserted
+     * between the command and the content.
+     */
+    public void testRejectMultilineMessageWithWhitespace() throws Exception {
+        String message = "This is not a love song";
+        String script = "reject text: \t \t \n" + message + "\n.\n;";
+        ActionReject rejection = runRejectScript(script);        
+        assertEquals(message, rejection.getMessage());
+    }    
+    
+    /**
+     * Tests that a multiline message is correctly passed when dots within a line
+     * between the command and the content.
+     */
+    public void testRejectMultilineMessageWithDotsMidline() throws Exception {
+        String message = "This is not.....a love song";
+        String script = "reject text:\n" + message + "\n.\n;";
+        ActionReject rejection = runRejectScript(script);        
+        assertEquals(message, rejection.getMessage());
+    }    
+    
+    /**
+     * Tests that a multiline message with dot stuffing is correctly decoded.
+     */
+    public void testRejectMultilineMessageWithDotStuffing() throws Exception {
+        String lineOne = "This is not\n";
+        String lineTwo = ".A Love Story";
+        String script = "reject text:\n" + lineOne + '.' + lineTwo + "\n.\n;";
+        ActionReject rejection = runRejectScript(script);        
+        assertEquals(lineOne + lineTwo, rejection.getMessage());
+    }
+    
+    /**
+     * Tests that a multiline message with missed dot stuffing is correctly decoded.
+     */
+    public void testRejectMultilineMessageWithMissedDotStuffing() throws Exception {
+        String lineOne = "This is not\n";
+        String lineTwo = ".A Love Story";
+        String script = "reject text:\n" + lineOne + lineTwo + "\n.\n;";
+        ActionReject rejection = runRejectScript(script);        
+        assertEquals(lineOne + lineTwo, rejection.getMessage());
+    }
+    
+    /**
+     * Tests that a multiline message with many dots stuffed is correctly decoded.
+     */
+    public void testNumberOfStuffedDotsInMultilineMessage() throws Exception {
+        String lineOne = "This is line 1.\n";
+        String lineTwo = "This is line 2.\n";
+        String lineThree = "........ This is line 3.\n";
+        String script = "reject text:\n" + lineOne + lineTwo + '.' + lineThree + "\n.\n;";
+        ActionReject rejection = runRejectScript(script);        
+        assertEquals(lineOne + lineTwo + lineThree, rejection.getMessage());
+    }
+    
+    /**
+     * Tests that a multiline message with many dots stuffed is correctly decoded.
+     */
+    public void testConsecutiveDotStuffedLineInMultilineMessage() throws Exception {
+        String lineOne = "This is line 1.\n";
+        String lineTwo = "This is line 2.\n";
+        String lineThree = "........ This is line 3.\n";
+        String lineFour = ".\n";
+        String lineFive = ".\n";
+        String script = "reject text:\n" + lineOne + lineTwo + '.' + lineThree + '.' + lineFour + '.' + lineFive + "\n.\n;";
+        ActionReject rejection = runRejectScript(script);        
+        assertEquals(lineOne + lineTwo + lineThree + lineFour + lineFive, rejection.getMessage());
+    }
+    
+    private ActionReject runRejectScript(String script) throws SieveException, ParseException {
+        MailAdapter mail = JUnitUtils.createMail();
+        JUnitUtils.interpret(mail, script);
+        assertTrue(mail.getActions().size() == 1);
+        Object action = mail.getActions().get(0);
+        assertTrue(action instanceof ActionReject);
+        ActionReject rejection = (ActionReject) action;
+        return rejection;
+    }
+}
diff --git a/trunk/main/src/test/java/org/apache/jsieve/MultipleRequireTest.java b/trunk/main/src/test/java/org/apache/jsieve/MultipleRequireTest.java
new file mode 100644
index 0000000..ade427b
--- /dev/null
+++ b/trunk/main/src/test/java/org/apache/jsieve/MultipleRequireTest.java
@@ -0,0 +1,50 @@
+/****************************************************************
+ * 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.jsieve;
+
+import junit.framework.TestCase;
+
+import org.apache.jsieve.mail.MailAdapter;
+import org.apache.jsieve.utils.JUnitUtils;
+
+public class MultipleRequireTest extends TestCase {
+
+    private static final String MINIMAL_SIEVE = "require [\"fileinto\", \"reject\"];\n";
+
+    private static final String MULTIPLE_REQUIRED_SIEVE = "# \n"
+            + "# Start with some comments\n" + "# Whatever\n" + "#\n" + "\n"
+            + MINIMAL_SIEVE + "\n" + "#\n" + "# Lets have some more comments\n"
+            + "#\n" + "\n";
+
+    MailAdapter mail;
+
+    protected void setUp() throws Exception {
+        super.setUp();
+        mail = JUnitUtils.createMail();
+    }
+
+    public void testMinimalScriptMultipleRequiredParsing() throws Exception {
+        JUnitUtils.interpret(mail, MINIMAL_SIEVE);
+    }
+
+    public void testScriptMultipleRequiredParsing() throws Exception {
+        JUnitUtils.interpret(mail, MULTIPLE_REQUIRED_SIEVE);
+    }
+}
diff --git a/trunk/main/src/test/java/org/apache/jsieve/MultipleToTest.java b/trunk/main/src/test/java/org/apache/jsieve/MultipleToTest.java
new file mode 100644
index 0000000..6532eea
--- /dev/null
+++ b/trunk/main/src/test/java/org/apache/jsieve/MultipleToTest.java
@@ -0,0 +1,84 @@
+/****************************************************************
+ * 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.jsieve;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.util.List;
+
+import junit.framework.TestCase;
+
+import org.apache.jsieve.util.check.ScriptChecker;
+
+public class MultipleToTest extends TestCase {
+
+    private static final String SOLO_TO_EMAIL = "Date: Sun, 1 Apr 2007 1100:00:00 +0100 (BST)\r\f"
+            + "From: roadrunner@acme.example.com\r\f"
+            + "To: coyote@desert.example.org\r\f"
+            + "Subject: Who's The Fool?\r\f" + "\r\f" + "Beep-Beep\r\f";
+
+    private static final String MULTIPLE_TO_EMAIL = "Date: Sun, 1 Apr 2007 1100:00:00 +0100 (BST)\r\f"
+            + "From: roadrunner@acme.example.com\r\f"
+            + "To: coyote@desert.example.org, bugs@example.org, "
+            + "    elmer@hunters.example.org,\r\f"
+            + "Subject: Who's The Fool?\r\f" + "\r\f" + "Beep-Beep\r\f";
+
+    private static final String FILTER_SCRIPT = "require \"fileinto\";\r\f"
+            + "if address :is :all \"to\" \"coyote@desert.example.org\" {\r\f"
+            + "  fileinto \"coyote\";\r\f}\r\f"
+            + "if address :is :all \"to\" \"bugs@example.org\" {\r\f"
+            + "  fileinto \"bugs\";\r\f}\r\f"
+            + "if address :is :all \"to\" \"roadrunneracme.@example.org\" {\r\f"
+            + "  fileinto \"rr\";\r\f}\r\f"
+            + "if address :is :all \"to\" \"elmer@hunters.example.org\" {\r\f"
+            + "  fileinto \"elmer\";\r\f}\r\f";
+
+    public void testSingleTo() throws Exception {
+        ScriptChecker checker = new ScriptChecker();
+        ScriptChecker.Results results = checker.check(toStream(SOLO_TO_EMAIL),
+                toStream(FILTER_SCRIPT));
+        if (results.getException() != null) {
+            fail(results.getException().toString());
+        }
+        final List actionsExecuted = results.getActionsExecuted();
+        assertEquals(1, actionsExecuted.size());
+        assertTrue(results.isActionFileInto("coyote", 0));
+    }
+
+    public void testMultipleTo() throws Exception {
+        ScriptChecker checker = new ScriptChecker();
+        ScriptChecker.Results results = checker.check(
+                toStream(MULTIPLE_TO_EMAIL), toStream(FILTER_SCRIPT));
+        if (results.getException() != null) {
+            fail(results.getException().toString());
+        }
+        final List actionsExecuted = results.getActionsExecuted();
+        assertEquals(3, actionsExecuted.size());
+        assertTrue(results.isActionFileInto("coyote", 0));
+        assertTrue(results.isActionFileInto("bugs", 1));
+        assertTrue(results.isActionFileInto("elmer", 2));
+    }
+
+    private InputStream toStream(String in) throws Exception {
+        byte[] bytes = in.getBytes("US-ASCII");
+        ByteArrayInputStream result = new ByteArrayInputStream(bytes);
+        return result;
+    }
+}
diff --git a/trunk/main/src/test/java/org/apache/jsieve/NotTest.java b/trunk/main/src/test/java/org/apache/jsieve/NotTest.java
new file mode 100644
index 0000000..3e82ae3
--- /dev/null
+++ b/trunk/main/src/test/java/org/apache/jsieve/NotTest.java
@@ -0,0 +1,69 @@
+/****************************************************************
+ * 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.jsieve;
+
+import junit.framework.TestCase;
+
+import org.apache.jsieve.commands.ThrowTestException;
+import org.apache.jsieve.exception.SieveException;
+import org.apache.jsieve.exception.SyntaxException;
+import org.apache.jsieve.parser.generated.ParseException;
+import org.apache.jsieve.utils.JUnitUtils;
+
+/**
+ * Class NotTest
+ */
+public class NotTest extends TestCase {
+
+    /**
+     * Test for Test 'not'
+     */
+    public void testIfNotFalse() {
+        boolean isTestPassed = false;
+        String script = "if not false {throwTestException;}";
+
+        try {
+            JUnitUtils.interpret(JUnitUtils.createMail(), script);
+        } catch (ThrowTestException.TestException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'true' with invalid argument
+     */
+    public void testInvalidArgument() {
+        boolean isTestPassed = false;
+        String script = "if not 1 {throwTestException;}";
+
+        try {
+            JUnitUtils.interpret(JUnitUtils.createMail(), script);
+        } catch (SyntaxException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+}
diff --git a/trunk/main/src/test/java/org/apache/jsieve/OpenedAddress.java b/trunk/main/src/test/java/org/apache/jsieve/OpenedAddress.java
new file mode 100644
index 0000000..4c42c67
--- /dev/null
+++ b/trunk/main/src/test/java/org/apache/jsieve/OpenedAddress.java
@@ -0,0 +1,36 @@
+/****************************************************************
+ * 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.jsieve;
+
+import org.apache.jsieve.SieveContext;
+import org.apache.jsieve.exception.SieveException;
+import org.apache.jsieve.mail.MailAdapter;
+import org.apache.jsieve.tests.Address;
+
+final class OpenedAddress extends Address {
+
+    protected boolean match(MailAdapter mail, String addressPart,
+            String comparator, String matchType, String headerName, String key,
+            SieveContext context) throws SieveException {
+        return super.match(mail, addressPart, comparator, matchType,
+                headerName, key, context);
+    }
+
+}
diff --git a/trunk/main/src/test/java/org/apache/jsieve/QuotingTest.java b/trunk/main/src/test/java/org/apache/jsieve/QuotingTest.java
new file mode 100644
index 0000000..5250545
--- /dev/null
+++ b/trunk/main/src/test/java/org/apache/jsieve/QuotingTest.java
@@ -0,0 +1,43 @@
+/****************************************************************
+ * 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.jsieve;
+
+import junit.framework.TestCase;
+
+import org.apache.jsieve.commands.ThrowTestException;
+import org.apache.jsieve.utils.JUnitUtils;
+import org.apache.jsieve.utils.SieveMailAdapter;
+
+public class QuotingTest extends TestCase {
+
+    public void testQuoteInQuotedString() throws Exception {
+        String script = "if header :is \"X-Test\" \"Before\\\"After\" {throwTestException;}";
+
+        final SieveMailAdapter mail = (SieveMailAdapter) JUnitUtils
+                .createMail();
+        mail.getMessage().addHeader("X-Test", "Before\"After");
+        try {
+            JUnitUtils.interpret(mail, script);
+            fail("Expected header to be matched");
+        } catch (ThrowTestException.TestException e) {
+            // expected
+        }
+    }
+}
diff --git a/trunk/main/src/test/java/org/apache/jsieve/RejectTest.java b/trunk/main/src/test/java/org/apache/jsieve/RejectTest.java
new file mode 100644
index 0000000..2198478
--- /dev/null
+++ b/trunk/main/src/test/java/org/apache/jsieve/RejectTest.java
@@ -0,0 +1,183 @@
+/****************************************************************
+ * 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.jsieve;
+
+import junit.framework.TestCase;
+
+import org.apache.jsieve.exception.CommandException;
+import org.apache.jsieve.exception.SieveException;
+import org.apache.jsieve.exception.SyntaxException;
+import org.apache.jsieve.mail.ActionReject;
+import org.apache.jsieve.mail.MailAdapter;
+import org.apache.jsieve.parser.generated.ParseException;
+import org.apache.jsieve.utils.JUnitUtils;
+
+/**
+ * Class RejectTest
+ */
+public class RejectTest extends TestCase {
+
+    /**
+     * Test for Command 'reject' with invalid arguments
+     */
+    public void testInvalidArguments() {
+        boolean isTestPassed = false;
+        String script = "reject 1 ;";
+
+        try {
+            JUnitUtils.interpret(JUnitUtils.createMail(), script);
+        } catch (SyntaxException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Command 'reject' with an invalid block
+     */
+    public void testInvalidBlock() {
+        boolean isTestPassed = false;
+        String script = "reject \"Spam not consumed here!\" {throwTestException;}";
+
+        try {
+            JUnitUtils.interpret(JUnitUtils.createMail(), script);
+        } catch (SyntaxException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Command 'reject'
+     */
+    public void testReject() {
+        boolean isTestPassed = false;
+        String script = "reject \"Spam not consumed here!\";";
+
+        try {
+            MailAdapter mail = JUnitUtils.createMail();
+            JUnitUtils.interpret(mail, script);
+            assertTrue(mail.getActions().size() == 1);
+            assertTrue(mail.getActions().get(0) instanceof ActionReject);
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Tests that the message is correctly passed
+     */
+    public void testRejectMessage() throws Exception {
+        String message = "Spam not consumed here!";
+        String script = "reject \"" + message + "\";";
+        ActionReject rejection = runRejectScript(script);        
+        assertEquals(message, rejection.getMessage());
+    }
+
+    private ActionReject runRejectScript(String script) throws SieveException, ParseException {
+        MailAdapter mail = JUnitUtils.createMail();
+        JUnitUtils.interpret(mail, script);
+        assertTrue(mail.getActions().size() == 1);
+        Object action = mail.getActions().get(0);
+        assertTrue(action instanceof ActionReject);
+        ActionReject rejection = (ActionReject) action;
+        return rejection;
+    }
+    
+    /**
+     * Test for Command 'reject'
+     */
+    public void testRejectMissingMessage() {
+        boolean isTestPassed = false;
+        String script = "reject;";
+
+        try {
+            MailAdapter mail = JUnitUtils.createMail();
+            JUnitUtils.interpret(mail, script);
+            assertTrue(mail.getActions().size() == 1);
+            assertTrue(mail.getActions().get(0) instanceof ActionReject);
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+            isTestPassed = true;
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for duplicate Command 'reject'
+     */
+    public void testDuplicateReject() {
+        boolean isTestPassed = false;
+        String script = "reject \"Spam not consumed here!\"; reject \"Spam not consumed here!\";";
+
+        try {
+            MailAdapter mail = JUnitUtils.createMail();
+            JUnitUtils.interpret(mail, script);
+        } catch (CommandException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Command 'reject' preceded by another command
+     */
+    public void testRejectAndAPrecedingCommand() {
+        boolean isTestPassed = false;
+        String script = "keep; reject \"Spam not consumed here!\";";
+
+        try {
+            MailAdapter mail = JUnitUtils.createMail();
+            JUnitUtils.interpret(mail, script);
+        } catch (CommandException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Command 'reject' followed by another command
+     */
+    public void testRejectAndAFollowingCommand() {
+        boolean isTestPassed = false;
+        String script = "reject \"Spam not consumed here!\"; keep;";
+
+        try {
+            MailAdapter mail = JUnitUtils.createMail();
+            JUnitUtils.interpret(mail, script);
+        } catch (CommandException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+}
diff --git a/trunk/main/src/test/java/org/apache/jsieve/RequireMissingTest.java b/trunk/main/src/test/java/org/apache/jsieve/RequireMissingTest.java
new file mode 100644
index 0000000..57ce954
--- /dev/null
+++ b/trunk/main/src/test/java/org/apache/jsieve/RequireMissingTest.java
@@ -0,0 +1,99 @@
+/****************************************************************
+ * 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.jsieve;
+
+import junit.framework.TestCase;
+
+import org.apache.jsieve.parser.generated.ParseException;
+import org.apache.jsieve.utils.JUnitUtils;
+
+/**
+ * Class AddressTest
+ */
+public class RequireMissingTest extends TestCase {
+
+    /**
+     * Tests that unsupported requires are caught before script execution.
+     */
+    public void testUnsupportedRequireNoBrackets() throws Exception {
+        String script = "require \"whatever\"; if address :contains [\"To\", \"From\"] \"Fish!\"{ fileinto \"aFolder\"; }";
+        try {
+            JUnitUtils.parse(script);
+            fail("Expect exception to be throw during parse since command is unsupported");
+        } catch (ParseException e) {
+            // expected
+        }
+    }
+
+    /**
+     * Tests that unsupported requires are caught before script execution.
+     */
+    public void testUnsupportedRequireMultiple() throws Exception {
+        String script = "require [\"fileinto\",\"whatever\"]; if address :contains [\"To\", \"From\"] \"Fish!\"{ fileinto \"aFolder\"; }";
+        try {
+            JUnitUtils.parse(script);
+            fail("Expect exception to be throw during parse since command is unsupported");
+        } catch (ParseException e) {
+            // expected
+        }
+    }
+
+    /**
+     * Tests that unsupported requires are caught before script execution.
+     */
+    public void testUnsupportedRequire() throws Exception {
+        String script = "require [\"whatever\"]; if address :contains [\"To\", \"From\"] \"Fish!\"{ fileinto \"aFolder\"; }";
+        try {
+            JUnitUtils.parse(script);
+            fail("Expect exception to be throw during parse since command is unsupported");
+        } catch (ParseException e) {
+            // expected
+        }
+    }
+
+    /**
+     * Tests 2.10.5 Extensions and Optional Features: If an extension is not
+     * enabled with "required" they must treat it as if they do not support it
+     * at all.
+     */
+    public void testMissingRequire() throws Exception {
+        String script = "if address :contains [\"To\", \"From\"] \"Fish!\"{ bogus \"aFolder\"; }";
+        try {
+            JUnitUtils.parse(script);
+            fail("Expect exception to be throw during parse since command is missing");
+        } catch (ParseException e) {
+            // expected
+        }
+    }
+
+    /**
+     * Tests 3.2 Control Structure Require: Require MUST NOT be used after any
+     * other command.
+     */
+    public void testRequireAfterOtherCommand() throws Exception {
+        String script = "if address :contains [\"To\", \"From\"] \"Fish!\"{ fileinto \"aFolder\"; } require [\"whatever\"]; ";
+        try {
+            JUnitUtils.parse(script);
+            fail("Expect exception to be throw during parse");
+        } catch (ParseException e) {
+            // expected
+        }
+    }
+}
diff --git a/trunk/main/src/test/java/org/apache/jsieve/RequireTest.java b/trunk/main/src/test/java/org/apache/jsieve/RequireTest.java
new file mode 100644
index 0000000..92d1135
--- /dev/null
+++ b/trunk/main/src/test/java/org/apache/jsieve/RequireTest.java
@@ -0,0 +1,210 @@
+/****************************************************************
+ * 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.jsieve;
+
+import junit.framework.TestCase;
+
+import org.apache.jsieve.exception.SieveException;
+import org.apache.jsieve.exception.SyntaxException;
+import org.apache.jsieve.parser.generated.ParseException;
+import org.apache.jsieve.utils.JUnitUtils;
+
+/**
+ * Class RequireTest
+ */
+public class RequireTest extends TestCase {
+
+    /**
+     * Test for Command 'require' with a single command that is present
+     */
+    public void testSingleCommandSatisfied() {
+        boolean isTestPassed = false;
+        String script = "require \"if\";";
+
+        try {
+            JUnitUtils.interpret(JUnitUtils.createMail(), script);
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Command 'require' with a single test that is present
+     */
+    public void testSingleTestSatisfied() throws Exception {
+        String script = "require \"true\";";
+        JUnitUtils.interpret(JUnitUtils.createMail(), script);
+    }
+
+    /**
+     * Test for Command 'require' with multiple commands that are present
+     */
+    public void testMultipleCommandSatisfied() throws Exception {
+        String script = "require [\"if\", \"elsif\", \"else\"];";
+        JUnitUtils.interpret(JUnitUtils.createMail(), script);
+    }
+
+    /**
+     * Test for Command 'require' with multiple tests that are present
+     */
+    public void testMultipleTestSatisfied() throws Exception {
+        String script = "require [\"true\", \"false\", \"not\"];";
+        JUnitUtils.interpret(JUnitUtils.createMail(), script);
+    }
+
+    /**
+     * Test for Command 'require' with a single command that is absent
+     */
+    public void testSingleCommandUnsatisfied() throws Exception {
+        boolean isTestPassed = false;
+        String script = "require \"absent\";";
+
+        try {
+            JUnitUtils.interpret(JUnitUtils.createMail(), script);
+        } catch (ParseException e) {
+            isTestPassed = true;
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Command 'require' with a single test that is absent
+     */
+    public void testSingleTestUnsatisfied() throws Exception {
+        boolean isTestPassed = false;
+        String script = "require \"absent\";";
+
+        try {
+            JUnitUtils.interpret(JUnitUtils.createMail(), script);
+        } catch (ParseException e) {
+            isTestPassed = true;
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Command 'require' for missing argument
+     */
+    public void testMissingArgument() throws Exception {
+        boolean isTestPassed = false;
+        String script = "require;";
+
+        try {
+            JUnitUtils.interpret(JUnitUtils.createMail(), script);
+        } catch (SyntaxException e) {
+            isTestPassed = true;
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Command 'require' for extra argument
+     */
+    public void testExtraArgument() throws Exception {
+        boolean isTestPassed = false;
+        String script = "require \"if\" 1;";
+
+        try {
+            JUnitUtils.interpret(JUnitUtils.createMail(), script);
+        } catch (SyntaxException e) {
+            isTestPassed = true;
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Command 'require' rejecting Blocks
+     */
+    public void testRejectBlock() throws Exception {
+        boolean isTestPassed = false;
+        String script = "require \"if\" {stop;}";
+
+        try {
+            JUnitUtils.interpret(JUnitUtils.createMail(), script);
+        } catch (SyntaxException e) {
+            isTestPassed = true;
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Command 'require' after a Command
+     */
+    public void testInterveningCommand() throws Exception {
+        boolean isTestPassed = false;
+        String script = "fileinto \"someplace\"; require \"fileinto\";";
+
+        try {
+            JUnitUtils.interpret(JUnitUtils.createMail(), script);
+        } catch (ParseException e) {
+            isTestPassed = true;
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Command 'require' rejecting invalid arguments
+     */
+    public void testRejectInvalidArgument() throws Exception {
+        boolean isTestPassed = false;
+        String script = "require 1 ;";
+
+        try {
+            JUnitUtils.interpret(JUnitUtils.createMail(), script);
+        } catch (SyntaxException e) {
+            isTestPassed = true;
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Command 'require' with a multiple commands of which one is
+     * absent
+     */
+    public void testMultipleCommandsUnsatisfied() throws Exception {
+        boolean isTestPassed = false;
+        String script = "require [\"if\", \"elsif\", \"absent\"];";
+
+        try {
+            JUnitUtils.interpret(JUnitUtils.createMail(), script);
+        } catch (ParseException e) {
+            isTestPassed = true;
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Command 'require' with a multiple tests of which one is absent
+     */
+    public void testMultipleTestsUnsatisfied() throws Exception {
+        boolean isTestPassed = false;
+        String script = "require [\"true\", \"false\", \"absent\"];";
+
+        try {
+            JUnitUtils.interpret(JUnitUtils.createMail(), script);
+        } catch (ParseException e) {
+            isTestPassed = true;
+        }
+        assertTrue(isTestPassed);
+    }
+
+}
diff --git a/trunk/main/src/test/java/org/apache/jsieve/SieveParserVisitorImplQuoteTest.java b/trunk/main/src/test/java/org/apache/jsieve/SieveParserVisitorImplQuoteTest.java
new file mode 100644
index 0000000..e13831a
--- /dev/null
+++ b/trunk/main/src/test/java/org/apache/jsieve/SieveParserVisitorImplQuoteTest.java
@@ -0,0 +1,109 @@
+/****************************************************************
+ * 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.jsieve;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.logging.LogFactory;
+import org.apache.jsieve.parser.generated.ASTstring;
+import org.apache.jsieve.parser.generated.Node;
+import org.apache.jsieve.utils.JUnitUtils;
+
+public class SieveParserVisitorImplQuoteTest extends TestCase {
+
+    
+    SieveParserVisitorImpl visitor;
+
+    List data;
+
+    ASTstring node;
+
+    protected void setUp() throws Exception {
+        super.setUp();
+        final ConfigurationManager configurationManager = new ConfigurationManager();
+        visitor = new SieveParserVisitorImpl(new BaseSieveContext(
+                configurationManager.getCommandManager(), configurationManager
+                        .getComparatorManager(), configurationManager
+                        .getTestManager(), LogFactory
+                        .getLog(SieveParserVisitorImplQuoteTest.class)));
+        data = new ArrayList();
+
+    }
+
+    private ASTstring stringNode(String value) throws Exception {
+        Node node = JUnitUtils.parse("fileinto " + value + ";");
+        return (ASTstring) node.jjtGetChild(0).jjtGetChild(0).jjtGetChild(0).jjtGetChild(0).jjtGetChild(0).jjtGetChild(0);
+    }
+    
+    public void testVisitASTstringObjectQuoted() throws Exception {
+        node = stringNode("\"value\"");
+        visitor.visit(node, data);
+        assertEquals("Data value added after quotes stripped", 1, data.size());
+        assertEquals("Data value added after quotes stripped", "value", data
+                .get(0));
+    }
+
+    public void testVisitASTstringObjectQuoteInQuoted() throws Exception {
+        
+        node = stringNode("\"val\\\"ue\"");
+        visitor.visit(node, data);
+        assertEquals("Data value added after quotes stripped", 1, data.size());
+        assertEquals("Data value added after quotes stripped", "val\"ue", data
+                .get(0));
+    }
+
+    public void testVisitASTstringObjectDoubleSlashQuoted() throws Exception {
+
+        node = stringNode("\"val\\\\ue\"");
+        visitor.visit(node, data);
+        assertEquals("Data value added after quotes stripped", 1, data.size());
+        assertEquals("Data value added after quotes stripped", "val\\ue", data
+                .get(0));
+    }
+
+    public void testVisitASTstringObjectSlashQuoted() throws Exception {
+
+        node = stringNode("\"value\"");
+        visitor.visit(node, data);
+        assertEquals("Data value added after quotes stripped", 1, data.size());
+        assertEquals("Data value added after quotes stripped", "value", data
+                .get(0));
+    }
+
+    public void testVisitASTstringEmptyQuoted() throws Exception {
+
+        node = stringNode("\"\"");
+        visitor.visit(node, data);
+        assertEquals("Data value added after quotes stripped", 1, data.size());
+        assertEquals("Data value added after quotes stripped", "", data.get(0));
+    }
+
+    public void testVisitASTstringObjectMultiSlashQuoted() throws Exception {
+
+        node = stringNode("\"v\\\\al\\\\u\\e\\\\\"");
+        visitor.visit(node, data);
+        assertEquals("Data value added after quotes stripped", 1, data.size());
+        assertEquals("Data value added after quotes stripped", "v\\al\\ue\\",
+                data.get(0));
+    }
+}
diff --git a/trunk/main/src/test/java/org/apache/jsieve/SizeTest.java b/trunk/main/src/test/java/org/apache/jsieve/SizeTest.java
new file mode 100644
index 0000000..cc18bb6
--- /dev/null
+++ b/trunk/main/src/test/java/org/apache/jsieve/SizeTest.java
@@ -0,0 +1,200 @@
+/****************************************************************
+ * 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.jsieve;
+
+import javax.mail.MessagingException;
+
+import junit.framework.TestCase;
+
+import org.apache.jsieve.commands.ThrowTestException;
+import org.apache.jsieve.exception.SieveException;
+import org.apache.jsieve.mail.SieveMailException;
+import org.apache.jsieve.parser.generated.ParseException;
+import org.apache.jsieve.utils.JUnitUtils;
+import org.apache.jsieve.utils.SieveMailAdapter;
+
+/**
+ * Class SizeTest
+ */
+public class SizeTest extends TestCase {
+
+    /**
+     * Test for Test 'size'
+     */
+    public void testSizeIsOverTrue() {
+        boolean isTestPassed = false;
+        SieveMailAdapter mail = null;
+        int size = 0;
+        try {
+            mail = (SieveMailAdapter) JUnitUtils.createMail();
+            mail.getMessage().setText("Hi!");
+            mail.getMessage().saveChanges();
+            // Need to copy the mail to get JavaMail to report the message size
+            // correctly (saveChanges() only saves the headers!)
+            mail = (SieveMailAdapter) JUnitUtils.copyMail(mail);
+            size = mail.getSize();
+        } catch (SieveMailException e) {
+        } catch (MessagingException e) {
+        }
+
+        String script = "if size :over " + new Integer(size - 1).toString()
+                + " {throwTestException;}";
+        try {
+
+            JUnitUtils.interpret(mail, script);
+        }
+
+        catch (ThrowTestException.TestException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'size'
+     */
+    public void testSizeIsOverFalse() {
+        boolean isTestPassed = false;
+        SieveMailAdapter mail = null;
+        int size = 0;
+        try {
+            mail = (SieveMailAdapter) JUnitUtils.createMail();
+            mail.getMessage().setText("Hi!");
+            mail.getMessage().saveChanges();
+            // Need to copy the mail to get JavaMail to report the message size
+            // correctly (saveChanges() only saves the headers!)
+            mail = (SieveMailAdapter) JUnitUtils.copyMail(mail);
+            size = mail.getSize();
+        } catch (SieveMailException e) {
+        } catch (MessagingException e) {
+        }
+
+        String script = "if size :over " + new Integer(size).toString()
+                + " {throwTestException;}";
+        try {
+
+            JUnitUtils.interpret(mail, script);
+            isTestPassed = true;
+        }
+
+        catch (ThrowTestException.TestException e) {
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'size'
+     */
+    public void testSizeIsUnderTrue() {
+        boolean isTestPassed = false;
+        SieveMailAdapter mail = null;
+        int size = 0;
+        try {
+            mail = (SieveMailAdapter) JUnitUtils.createMail();
+            mail.getMessage().setText("Hi!");
+            mail.getMessage().saveChanges();
+            // Need to copy the mail to get JavaMail to report the message size
+            // correctly (saveChanges() only saves the headers!)
+            mail = (SieveMailAdapter) JUnitUtils.copyMail(mail);
+            size = mail.getSize();
+        } catch (SieveMailException e) {
+        } catch (MessagingException e) {
+        }
+
+        String script = "if size :under " + new Integer(size + 1).toString()
+                + " {throwTestException;}";
+        try {
+
+            JUnitUtils.interpret(mail, script);
+        }
+
+        catch (ThrowTestException.TestException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'size'
+     */
+    public void testSizeIsUnderFalse() {
+        boolean isTestPassed = false;
+        SieveMailAdapter mail = null;
+        int size = 0;
+        try {
+            mail = (SieveMailAdapter) JUnitUtils.createMail();
+            mail.getMessage().setText("Hi!");
+            mail.getMessage().saveChanges();
+            // Need to copy the mail to get JavaMail to report the message size
+            // correctly (saveChanges() only saves the headers!)
+            mail = (SieveMailAdapter) JUnitUtils.copyMail(mail);
+            size = mail.getSize();
+        } catch (SieveMailException e) {
+        } catch (MessagingException e) {
+        }
+
+        String script = "if size :over " + new Integer(size).toString()
+                + " {throwTestException;}";
+        try {
+
+            JUnitUtils.interpret(mail, script);
+            isTestPassed = true;
+        }
+
+        catch (ThrowTestException.TestException e) {
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+    
+    /**
+     * Test for Test 'size' with quantifier
+     */
+    public void testSizeIsWithQuantifier() throws Exception {
+        boolean isTestPassed = false;
+        SieveMailAdapter mail = (SieveMailAdapter) JUnitUtils.createMail();
+        mail.getMessage().setText("Hi!");
+        mail.getMessage().saveChanges();
+        // Need to copy the mail to get JavaMail to report the message size
+        // correctly (saveChanges() only saves the headers!)
+        mail = (SieveMailAdapter) JUnitUtils.copyMail(mail);
+
+        String script = "if size :over 1G {throwTestException;}";
+        try {
+
+            JUnitUtils.interpret(mail, script);
+            isTestPassed = true;
+        }
+
+        catch (ThrowTestException.TestException e) {
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+}
diff --git a/trunk/main/src/test/java/org/apache/jsieve/StopTest.java b/trunk/main/src/test/java/org/apache/jsieve/StopTest.java
new file mode 100644
index 0000000..df2bcf6
--- /dev/null
+++ b/trunk/main/src/test/java/org/apache/jsieve/StopTest.java
@@ -0,0 +1,91 @@
+/****************************************************************
+ * 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.jsieve;
+
+import junit.framework.TestCase;
+
+import org.apache.jsieve.commands.ThrowTestException;
+import org.apache.jsieve.exception.SieveException;
+import org.apache.jsieve.exception.SyntaxException;
+import org.apache.jsieve.mail.ActionKeep;
+import org.apache.jsieve.mail.MailAdapter;
+import org.apache.jsieve.parser.generated.ParseException;
+import org.apache.jsieve.utils.JUnitUtils;
+
+/**
+ * Class StopTest
+ */
+public class StopTest extends TestCase {
+
+    /**
+     * Test for Command 'stop'. This has an implicit Keep.
+     */
+    public void testStop() {
+        boolean isTestPassed = false;
+        String script = "stop; throwTestException;";
+
+        try {
+            MailAdapter mail = JUnitUtils.createMail();
+            JUnitUtils.interpret(mail, script);
+            assertTrue(mail.getActions().size() == 1);
+            assertTrue(mail.getActions().get(0) instanceof ActionKeep);
+            isTestPassed = true;
+        } catch (ThrowTestException.TestException e) {
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Command 'stop' with invalid arguments
+     */
+    public void testInvalidArguments() {
+        boolean isTestPassed = false;
+        String script = "stop 1 ;";
+
+        try {
+            JUnitUtils.interpret(JUnitUtils.createMail(), script);
+        } catch (SyntaxException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Command 'stop' with an invalid block
+     */
+    public void testInvalidBlock() {
+        boolean isTestPassed = false;
+        String script = "stop 1 {throwTestException;}";
+
+        try {
+            JUnitUtils.interpret(JUnitUtils.createMail(), script);
+        } catch (SyntaxException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+}
diff --git a/trunk/main/src/test/java/org/apache/jsieve/TrueTest.java b/trunk/main/src/test/java/org/apache/jsieve/TrueTest.java
new file mode 100644
index 0000000..7bfd769
--- /dev/null
+++ b/trunk/main/src/test/java/org/apache/jsieve/TrueTest.java
@@ -0,0 +1,69 @@
+/****************************************************************
+ * 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.jsieve;
+
+import junit.framework.TestCase;
+
+import org.apache.jsieve.commands.ThrowTestException;
+import org.apache.jsieve.exception.SieveException;
+import org.apache.jsieve.exception.SyntaxException;
+import org.apache.jsieve.parser.generated.ParseException;
+import org.apache.jsieve.utils.JUnitUtils;
+
+/**
+ * Class TrueTest
+ */
+public class TrueTest extends TestCase {
+
+    /**
+     * Test for Test 'true'
+     */
+    public void testIfTrue() {
+        boolean isTestPassed = false;
+        String script = "if true {throwTestException;}";
+
+        try {
+            JUnitUtils.interpret(JUnitUtils.createMail(), script);
+        } catch (ThrowTestException.TestException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+    /**
+     * Test for Test 'true' with invalid argument
+     */
+    public void testInvalidArgument() {
+        boolean isTestPassed = false;
+        String script = "if true 1 {throwTestException;}";
+
+        try {
+            JUnitUtils.interpret(JUnitUtils.createMail(), script);
+        } catch (SyntaxException e) {
+            isTestPassed = true;
+        } catch (ParseException e) {
+        } catch (SieveException e) {
+        }
+        assertTrue(isTestPassed);
+    }
+
+}
diff --git a/trunk/main/src/test/java/org/apache/jsieve/WierdAddressTest.java b/trunk/main/src/test/java/org/apache/jsieve/WierdAddressTest.java
new file mode 100644
index 0000000..a6540dd
--- /dev/null
+++ b/trunk/main/src/test/java/org/apache/jsieve/WierdAddressTest.java
@@ -0,0 +1,41 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.jsieve;
+
+import junit.framework.TestCase;
+
+import org.apache.jsieve.utils.JUnitUtils;
+import org.apache.jsieve.utils.SieveMailAdapter;
+
+public class WierdAddressTest extends TestCase {
+
+    /**
+     * My god - it's full of stars
+     */
+    public void testShouldParseAddressWithLotsOfStars() throws Exception {
+        String script = "if anyof (header :matches \"from\"" +
+            "\"**********************************************address@yahoo.com\" ," +
+            "header :is \"subject\" \"5\" )" +
+            "{ fileinto \"Whatever\"; stop;}";
+        SieveMailAdapter mail = (SieveMailAdapter) JUnitUtils.createMail();
+        mail.getMessage().addHeader("From", "user@domain");
+        JUnitUtils.interpret(mail, script);
+    }
+}
diff --git a/trunk/main/src/test/java/org/apache/jsieve/commands/ThrowTestException.java b/trunk/main/src/test/java/org/apache/jsieve/commands/ThrowTestException.java
new file mode 100644
index 0000000..808a24e
--- /dev/null
+++ b/trunk/main/src/test/java/org/apache/jsieve/commands/ThrowTestException.java
@@ -0,0 +1,92 @@
+/****************************************************************
+ * 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.jsieve.commands;
+
+import org.apache.jsieve.Arguments;
+import org.apache.jsieve.Block;
+import org.apache.jsieve.ExecutableCommand;
+import org.apache.jsieve.SieveContext;
+import org.apache.jsieve.exception.SieveException;
+import org.apache.jsieve.mail.MailAdapter;
+
+/**
+ * Class ThrowTestException implements a Sieve Command to throw a TestException.
+ */
+public class ThrowTestException implements ExecutableCommand {
+
+    /**
+     * Class TestException
+     */
+    public class TestException extends SieveException {
+
+        /**
+         * Constructor for TestException.
+         */
+        public TestException() {
+            super();
+        }
+
+        /**
+         * Constructor for TestException.
+         * 
+         * @param message
+         */
+        public TestException(String message) {
+            super(message);
+        }
+
+        /**
+         * Constructor for TestException.
+         * 
+         * @param message
+         * @param cause
+         */
+        public TestException(String message, Throwable cause) {
+            super(message, cause);
+        }
+
+        /**
+         * Constructor for TestException.
+         * 
+         * @param cause
+         */
+        public TestException(Throwable cause) {
+            super(cause);
+        }
+
+    }
+
+    /**
+     * Constructor for ThrowTestException.
+     */
+    public ThrowTestException() {
+        super();
+    }
+
+    /**
+     * @see org.apache.jsieve.ExecutableCommand#execute(MailAdapter, Arguments,
+     *      Block, SieveContext)
+     */
+    public Object execute(MailAdapter mail, Arguments arguments, Block block,
+            SieveContext context) throws SieveException {
+        throw new TestException();
+    }
+
+}
diff --git a/trunk/main/src/test/java/org/apache/jsieve/commands/optional/FileIntoTest.java b/trunk/main/src/test/java/org/apache/jsieve/commands/optional/FileIntoTest.java
new file mode 100644
index 0000000..7d700bf
--- /dev/null
+++ b/trunk/main/src/test/java/org/apache/jsieve/commands/optional/FileIntoTest.java
@@ -0,0 +1,79 @@
+/****************************************************************
+ * 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.jsieve.commands.optional;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.logging.LogFactory;
+import org.apache.jsieve.Argument;
+import org.apache.jsieve.Arguments;
+import org.apache.jsieve.BaseSieveContext;
+import org.apache.jsieve.ConfigurationManager;
+import org.apache.jsieve.ScriptCoordinate;
+import org.apache.jsieve.SieveContext;
+import org.apache.jsieve.StringListArgument;
+import org.apache.jsieve.TestList;
+import org.apache.jsieve.mail.ActionFileInto;
+import org.apache.jsieve.util.check.ScriptCheckMailAdapter;
+
+public class FileIntoTest extends TestCase {
+
+    FileInto subject;
+    
+    ScriptCheckMailAdapter mockAdapter;
+    Arguments dummyArguments;
+    SieveContext dummyContext;
+    
+    @SuppressWarnings("unchecked")
+    protected void setUp() throws Exception {
+        super.setUp();
+        mockAdapter = new ScriptCheckMailAdapter();
+        List<String> stringList = new ArrayList<String>();
+        stringList.add("Whatever");
+        List<Argument> argumentList = new ArrayList<Argument>();
+        argumentList.add(new StringListArgument(stringList));
+        dummyArguments = new Arguments(argumentList, new TestList(Collections.EMPTY_LIST));
+        ConfigurationManager configurationManager = new ConfigurationManager();
+        dummyContext = new BaseSieveContext(
+                configurationManager.getCommandManager(), configurationManager
+                        .getComparatorManager(), configurationManager
+                        .getTestManager(), LogFactory
+                        .getLog(this.getClass()));
+        dummyContext.setCoordinate(new ScriptCoordinate(0, 0, 0, 0));
+        subject = new FileInto();
+    }
+
+    protected void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    public void testFileIntoShouldNotAllowMultipleFileIntoActions() throws Exception {
+        subject.execute(mockAdapter, dummyArguments, null, dummyContext);
+        assertEquals(1, mockAdapter.getActions().size());
+        assertTrue(mockAdapter.getActions().get(0) instanceof ActionFileInto);
+        
+        subject.execute(mockAdapter, dummyArguments, null, dummyContext);
+        assertEquals(1, mockAdapter.getActions().size());
+        assertTrue(mockAdapter.getActions().get(0) instanceof ActionFileInto);
+    }
+}
diff --git a/trunk/main/src/test/java/org/apache/jsieve/comparator/ComparatorUtilsTest.java b/trunk/main/src/test/java/org/apache/jsieve/comparator/ComparatorUtilsTest.java
new file mode 100644
index 0000000..b1e679f
--- /dev/null
+++ b/trunk/main/src/test/java/org/apache/jsieve/comparator/ComparatorUtilsTest.java
@@ -0,0 +1,74 @@
+/****************************************************************
+ * 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.jsieve.comparator;
+
+import junit.framework.TestCase;
+
+import org.apache.jsieve.comparators.ComparatorUtils;
+import org.apache.jsieve.exception.SievePatternException;
+
+public class ComparatorUtilsTest extends TestCase {
+
+    public void testMatchesStringString() throws SievePatternException {
+        String sievematch = "[test] ?\\\\?\\?*\\\\*\\*\\";
+        assertTrue(ComparatorUtils.matches("[test] a\\x?foo\\bar*\\",
+                sievematch));
+        assertFalse(ComparatorUtils.matches("[test] ab\\x?foo\\bar*\\",
+                sievematch));
+        assertFalse(ComparatorUtils.matches("[test]a\\x?foo\\bar*\\",
+                sievematch));
+        assertFalse(ComparatorUtils.matches("[tst] a\\x?foo\\bar*\\",
+                sievematch));
+        assertFalse(ComparatorUtils.matches("[test] a\\\\x?foo\\bar*\\",
+                sievematch));
+        assertFalse(ComparatorUtils.matches("[test] a\\?foo\\bar*\\",
+                sievematch));
+        assertFalse(ComparatorUtils.matches("[test] a\\xafoo\\bar*\\",
+                sievematch));
+        assertTrue(ComparatorUtils.matches("[test] a\\x?\\bar*\\", sievematch));
+        assertTrue(ComparatorUtils.matches("[test] a\\x?foo\\\\bar*\\",
+                sievematch));
+        assertFalse(ComparatorUtils
+                .matches("[test] a\\x?foobar*\\", sievematch));
+        assertFalse(ComparatorUtils.matches("[test] a\\x?foo\\bar.\\",
+                sievematch));
+        assertFalse(ComparatorUtils.matches("[test] a\\x?foo\\bar*\\\\",
+                sievematch));
+        assertFalse(ComparatorUtils
+                .matches("[test] a\\x?foo\\bar*", sievematch));
+    }
+
+    /**
+     * The ":matches" version specifies a wildcard match using the characters
+     * "*" and "?". "*" matches zero or more characters, and "?" matches a
+     * single character. "?" and "*" may be escaped as "\\?" and "\\*" in
+     * strings to match against themselves. The first backslash escapes the
+     * second backslash; together, they escape the "*". This is awkward, but it
+     * is commonplace in several programming languages that use globs and
+     * regular expressions.
+     */
+    public void testSieveToJavaRegex() {
+        String sievematch = "[test] ?\\\\?\\?*\\\\*\\*\\";
+        String res = ComparatorUtils.sieveToJavaRegex(sievematch);
+        String expected = "\\[test\\] .\\\\.\\?.*\\\\.*\\*\\\\";
+        assertEquals(expected, res);
+    }
+
+}
diff --git a/trunk/main/src/test/java/org/apache/jsieve/comparators/AsciiNumericTest.java b/trunk/main/src/test/java/org/apache/jsieve/comparators/AsciiNumericTest.java
new file mode 100644
index 0000000..0d29dd5
--- /dev/null
+++ b/trunk/main/src/test/java/org/apache/jsieve/comparators/AsciiNumericTest.java
@@ -0,0 +1,95 @@
+/****************************************************************
+ * 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.jsieve.comparators;
+
+import junit.framework.TestCase;
+
+import org.apache.jsieve.exception.FeatureException;
+import org.apache.jsieve.parser.generated.ParseException;
+import org.apache.jsieve.utils.JUnitUtils;
+
+public class AsciiNumericTest extends TestCase {
+    
+    AsciiNumeric subject;
+
+    protected void setUp() throws Exception {
+        super.setUp();
+        subject = new AsciiNumeric();
+    }
+
+    protected void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    public void testVerificationFailsWhenAsciiNumericIsNotRequired() throws Exception {
+        String script = "if header :contains :comparator \"i;ascii-numeric\" \"Subject\" \"69\" {stop;}";
+        try {
+            JUnitUtils.interpret(JUnitUtils.createMail(), script);
+            fail("Comparator must be declared in require statement");
+        } catch (ParseException e) {
+            // Expected
+        }
+    }
+    
+    public void testVerificationPassesWhenAsciiNumericIsRequired() throws Exception {
+        String script = "require [\"comparator-i;ascii-numeric\"]; if header :is :comparator \"i;ascii-numeric\" \"Subject\" \"69\" {stop;}";
+        JUnitUtils.interpret(JUnitUtils.createMail(), script);
+    }
+    
+    public void testBasicNumbericEquality() throws Exception {
+        assertFalse(subject.equals("1", "2"));
+        assertTrue(subject.equals("1", "1"));
+    }
+    
+    public void testEqualityShouldIgnoreTrailingCharacters() throws Exception {
+        assertTrue(subject.equals("01", "1A"));
+        assertTrue(subject.equals("1", "00000000000000001A"));
+        assertTrue(subject.equals("234S", "234YTGSDBBSD"));
+    }
+    
+    public void testEqualityShouldIgnoreLeadingZeros() throws Exception {
+        assertTrue(subject.equals("01", "1"));
+        assertTrue(subject.equals("000001", "1"));
+        assertFalse(subject.equals("000001", "10"));
+    }
+    
+    public void testStingsThatDoNotStartWithADigitRepresentPositiveInfinityWhenUsedInEquality() throws Exception {
+        assertFalse(subject.equals("1", "A4"));
+        assertFalse(subject.equals("x", "4"));
+        assertTrue(subject.equals("GT", "A4"));
+    }
+    
+    public void testSubstringIsNotSupported() throws Exception {
+        try {
+            subject.contains("234234", "34");
+            fail("Substring is unsupported");
+        } catch (FeatureException e) {
+            // Expected
+        }
+    }
+    
+    public void testMatchNotSupported() throws Exception {
+        try {
+            subject.matches("234234", "34");
+            fail("Substring is unsupported");
+        } catch (FeatureException e) {
+            // Expected
+        }
+    }
+}
diff --git a/trunk/main/src/test/java/org/apache/jsieve/comparators/RequireComparatorTest.java b/trunk/main/src/test/java/org/apache/jsieve/comparators/RequireComparatorTest.java
new file mode 100644
index 0000000..777873d
--- /dev/null
+++ b/trunk/main/src/test/java/org/apache/jsieve/comparators/RequireComparatorTest.java
@@ -0,0 +1,56 @@
+/****************************************************************
+ * 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.jsieve.comparators;
+
+import junit.framework.TestCase;
+
+import org.apache.jsieve.parser.generated.ParseException;
+import org.apache.jsieve.utils.JUnitUtils;
+
+public class RequireComparatorTest extends TestCase {
+
+    protected void setUp() throws Exception {
+        super.setUp();
+    }
+
+    protected void tearDown() throws Exception {
+        super.tearDown();
+    }
+    
+    public void testAsciiCasemapShouldBeImplicitlyDeclared() throws Exception {
+        String script = "if header :contains :comparator \"i;ascii-casemap\" \"Subject\" \"69\" {stop;}";
+        JUnitUtils.interpret(JUnitUtils.createMail(), script);
+    }
+    
+    public void testOctetShouldBeImplicitlyDeclared() throws Exception {
+        String script = "if header :contains :comparator \"i;octet\" \"Subject\" \"69\" {stop;}";
+        JUnitUtils.interpret(JUnitUtils.createMail(), script);
+    }
+    
+    public void testBogusComparatorShouldFailAtParseTime() throws Exception {
+        String script = "if header :contains :comparator \"i;bogus\" \"Subject\" \"69\" {stop;}";
+        try {
+            JUnitUtils.interpret(JUnitUtils.createMail(), script);
+            fail("Bogus comparator should fail");
+        } catch (ParseException e) {
+            // TODO: catch more finely grained exception
+            // Expected
+        }
+    }
+}
diff --git a/trunk/main/src/test/java/org/apache/jsieve/javaxmail/MockMimeMessage.java b/trunk/main/src/test/java/org/apache/jsieve/javaxmail/MockMimeMessage.java
new file mode 100644
index 0000000..7827083
--- /dev/null
+++ b/trunk/main/src/test/java/org/apache/jsieve/javaxmail/MockMimeMessage.java
@@ -0,0 +1,605 @@
+/****************************************************************
+ * 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.jsieve.javaxmail;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import javax.activation.DataHandler;
+import javax.mail.Address;
+import javax.mail.Flags;
+import javax.mail.Folder;
+import javax.mail.Header;
+import javax.mail.Message;
+import javax.mail.MessagingException;
+import javax.mail.Multipart;
+import javax.mail.Session;
+import javax.mail.internet.InternetAddress;
+import javax.mail.internet.InternetHeaders;
+import javax.mail.internet.MimeMessage;
+import javax.mail.search.SearchTerm;
+
+/**
+ * Forked from JAMES server.
+ */
+public class MockMimeMessage extends MimeMessage {
+
+    private final List m_fromAddresses = new ArrayList();
+
+    private Address m_senderAddress;
+
+    private final List m_toRecepients = new ArrayList();
+
+    private final List m_ccRecepients = new ArrayList();
+
+    private final List m_bccRecepients = new ArrayList();
+
+    private final List m_replyToAddresses = new ArrayList();
+
+    private String m_subject;
+
+    private int m_iMessageNumber;
+
+    private boolean m_bIsExpunged;
+
+    private Object m_content;
+
+    private Date m_sentDate;
+
+    private String[] m_contentLanguage;
+
+    private String m_fileName;
+
+    private DataHandler m_dataHandler;
+
+    private HashMap m_contentHeaders = new HashMap();
+
+    private Flags m_setFlags = new Flags();
+
+    private boolean m_doMatch;
+
+    private int m_size = -1;
+
+    public MockMimeMessage() throws MessagingException {
+        super((Session) null);
+    }
+
+    public MockMimeMessage(int messageNumber) throws MessagingException {
+        super((Session) null);
+        m_iMessageNumber = messageNumber;
+    }
+
+    public MockMimeMessage(MimeMessage mimeMessage) throws MessagingException {
+        super(mimeMessage); // trivial implementation
+    }
+
+    public Address[] getFrom() throws MessagingException {
+        return (Address[]) m_fromAddresses.toArray();
+    }
+
+    public void setFrom(Address address) throws MessagingException {
+        m_fromAddresses.clear();
+        m_fromAddresses.add(address);
+    }
+
+    public void setFrom() throws MessagingException {
+        m_fromAddresses.clear();
+        m_fromAddresses.add(InternetAddress.getLocalAddress(null));
+    }
+
+    public void addFrom(Address[] addresses) throws MessagingException {
+        m_fromAddresses.add(addresses);
+    }
+
+    public Address getSender() throws MessagingException {
+        return m_senderAddress;
+    }
+
+    public void setSender(Address address) throws MessagingException {
+        m_senderAddress = address;
+    }
+
+    public Address[] getRecipients(Message.RecipientType recipientType)
+            throws MessagingException {
+        List recipientsList = getRecipientsList(recipientType);
+        List recipientAddresses = new ArrayList();
+        for (Iterator iterator = recipientsList.iterator(); iterator.hasNext();) {
+            String recipient = (String) iterator.next();
+            recipientAddresses.add(new InternetAddress(recipient));
+        }
+        return (Address[]) (recipientAddresses.toArray(new Address[] {}));
+    }
+
+    private List getRecipientsList(Message.RecipientType recipientType) {
+        if (Message.RecipientType.TO.equals(recipientType))
+            return m_toRecepients;
+        if (Message.RecipientType.CC.equals(recipientType))
+            return m_ccRecepients;
+        if (Message.RecipientType.BCC.equals(recipientType))
+            return m_bccRecepients;
+        return null;
+    }
+
+    public Address[] getAllRecipients() throws MessagingException {
+        List allRecipients = new ArrayList();
+        allRecipients.addAll(m_toRecepients);
+        allRecipients.addAll(m_ccRecepients);
+        allRecipients.addAll(m_bccRecepients);
+        return (Address[]) allRecipients.toArray();
+    }
+
+    public void setRecipients(Message.RecipientType recipientType,
+            Address[] addresses) throws MessagingException {
+        getRecipientsList(recipientType).addAll(Arrays.asList(addresses));
+    }
+
+    public void setRecipients(Message.RecipientType recipientType,
+            String recipient) throws MessagingException {
+        getRecipientsList(recipientType).add(recipient);
+    }
+
+    public void addRecipients(Message.RecipientType recipientType,
+            Address[] addresses) throws MessagingException {
+        getRecipientsList(recipientType).addAll(Arrays.asList(addresses));
+    }
+
+    public void addRecipients(Message.RecipientType recipientType,
+            String recipient) throws MessagingException {
+        getRecipientsList(recipientType).add(recipient);
+    }
+
+    public Address[] getReplyTo() throws MessagingException {
+        return (Address[]) m_replyToAddresses.toArray();
+    }
+
+    public void setReplyTo(Address[] addresses) throws MessagingException {
+        m_replyToAddresses.addAll(Arrays.asList(addresses));
+    }
+
+    public String getSubject() throws MessagingException {
+        return m_subject;
+    }
+
+    public void setSubject(String subject) throws MessagingException {
+        m_subject = subject;
+    }
+
+    public void setSubject(String subject, String charset)
+            throws MessagingException {
+        if (subject == null) {
+            m_subject = null;
+            return;
+        }
+        try {
+            m_subject = new String(subject.getBytes(charset));
+        } catch (UnsupportedEncodingException e) {
+            throw new MessagingException("setting subject failed", e);
+        }
+    }
+
+    public Date getSentDate() throws MessagingException {
+        return m_sentDate;
+    }
+
+    public void setSentDate(Date date) throws MessagingException {
+        m_sentDate = date;
+    }
+
+    public Date getReceivedDate() throws MessagingException {
+        return null; // trivial implementation
+    }
+
+    public int getSize() throws MessagingException {
+        return m_size; // trivial implementation
+    }
+
+    public int getLineCount() throws MessagingException {
+        return -1; // trivial implementation
+    }
+
+    public String getContentType() throws MessagingException {
+        return getHeader("Content-Type", null);
+    }
+
+    public boolean isMimeType(String mimeType) throws MessagingException {
+        return mimeType.startsWith(getContentType());
+    }
+
+    public String getDisposition() throws MessagingException {
+        return getHeader("Content-Disposition", null);
+    }
+
+    public void setDisposition(String disposition) throws MessagingException {
+        setHeader("Content-Disposition", disposition);
+    }
+
+    public String getEncoding() throws MessagingException {
+        return getHeader("Content-Transfer-Encoding", null);
+    }
+
+    public String getContentID() throws MessagingException {
+        return getHeader("Content-ID", null);
+    }
+
+    public void setContentID(String contentID) throws MessagingException {
+        setHeader("Content-ID", contentID);
+    }
+
+    public String getContentMD5() throws MessagingException {
+        return getHeader("Content-MD5", null);
+    }
+
+    public void setContentMD5(String value) throws MessagingException {
+        setHeader("Content-MD5", value);
+    }
+
+    public String getDescription() throws MessagingException {
+        return getHeader("Content-Description", null);
+    }
+
+    public void setDescription(String description) throws MessagingException {
+        setHeader("Content-Description", description);
+    }
+
+    public void setDescription(String description, String charset)
+            throws MessagingException {
+        try {
+            setDescription(new String(description.getBytes(charset)));
+        } catch (UnsupportedEncodingException e) {
+            throw new MessagingException("setting description failed", e);
+        }
+    }
+
+    public String[] getContentLanguage() throws MessagingException {
+        return m_contentLanguage;
+    }
+
+    public void setContentLanguage(String[] contentLanguage)
+            throws MessagingException {
+        m_contentLanguage = contentLanguage;
+    }
+
+    public String getMessageID() throws MessagingException {
+        return "ID-" + m_iMessageNumber; // trivial implementation
+    }
+
+    public String getFileName() throws MessagingException {
+        return m_fileName;
+    }
+
+    public void setFileName(String fileName) throws MessagingException {
+        m_fileName = fileName;
+    }
+
+    public InputStream getInputStream() throws IOException, MessagingException {
+        return null; // trivial implementation
+    }
+
+    protected InputStream getContentStream() throws MessagingException {
+        return null; // trivial implementation
+    }
+
+    public InputStream getRawInputStream() throws MessagingException {
+        if (m_content instanceof String) {
+            return new ByteArrayInputStream(m_content.toString().getBytes());
+        }
+        throw new UnsupportedOperationException("Unimplementated method");
+    }
+
+    public synchronized DataHandler getDataHandler() throws MessagingException {
+        return m_dataHandler;
+    }
+
+    public synchronized void setDataHandler(DataHandler dataHandler)
+            throws MessagingException {
+        m_dataHandler = dataHandler;
+    }
+
+    public Object getContent() throws IOException, MessagingException {
+        return m_content;
+    }
+
+    public void setContent(Object object, String mimeType)
+            throws MessagingException {
+        m_content = object; // trivial implementation
+    }
+
+    public void setText(String string) throws MessagingException {
+        setContent(string, "text/plain");
+    }
+
+    public void setText(String string, String charset)
+            throws MessagingException {
+        try {
+            setContent(new String(string.getBytes(charset)), "text/plain");
+        } catch (UnsupportedEncodingException e) {
+            throw new MessagingException("setting text content failed", e);
+        }
+    }
+
+    public void setContent(Multipart multipart) throws MessagingException {
+        m_content = multipart;
+    }
+
+    public Message reply(boolean b) throws MessagingException {
+        return new MockMimeMessage(this); // trivial implementation
+    }
+
+    public void writeTo(OutputStream outputStream) throws IOException,
+            MessagingException {
+        ; // trivial implementation
+    }
+
+    public void writeTo(OutputStream outputStream, String[] strings)
+            throws IOException, MessagingException {
+        ; // trivial implementation
+    }
+
+    public String[] getHeader(String name) throws MessagingException {
+        Object value = m_contentHeaders.get(name);
+        if (value == null)
+            return null;
+        if (value instanceof String) {
+            String stringValue = (String) value;
+            return new String[] { stringValue };
+        } else {
+            Collection values = (Collection) value;
+            return (String[]) values.toArray(new String[values.size()]);
+        }
+    }
+
+    public String getHeader(String name, String delimiter)
+            throws MessagingException {
+        String[] header = getHeader(name);
+        if (header == null || header.length == 0)
+            return null;
+        return header[0];
+    }
+
+    public void setHeader(String name, String value) throws MessagingException {
+        addHeader(name, value);
+    }
+
+    public void addHeader(String name, String value) throws MessagingException {
+        Object newValue;
+        Object existingValue = m_contentHeaders.get(name);
+        if (existingValue == null) {
+            newValue = value;
+        } else if (existingValue instanceof String) {
+            List values = new ArrayList();
+            values.add(existingValue);
+            values.add(value);
+            newValue = values;
+        } else {
+            List values = (List) existingValue;
+            values.add(value);
+            newValue = values;
+        }
+        m_contentHeaders.put(name, newValue);
+    }
+
+    public void removeHeader(String name) throws MessagingException {
+        m_contentHeaders.remove(name);
+    }
+
+    public Enumeration getAllHeaders() throws MessagingException {
+        final Collection results = new ArrayList();
+        final Collection entries = m_contentHeaders.entrySet();
+        for (Iterator it = entries.iterator(); it.hasNext();) {
+            Map.Entry entry = (Map.Entry) it.next();
+            String name = entry.getKey().toString();
+            Object value = entry.getValue();
+            if (value == null) {
+                // ignore
+            } else if (value instanceof Collection) {
+                Collection values = (Collection) value;
+                for (Iterator iterValues = values.iterator(); iterValues
+                        .hasNext();) {
+                    String stringValue = (String) iterValues.next();
+                    results.add(new Header(name, stringValue));
+                }
+            } else {
+                results.add(new Header(name, value.toString()));
+            }
+        }
+        return Collections.enumeration(results);
+    }
+
+    public Enumeration getMatchingHeaders(String[] names)
+            throws MessagingException {
+        ArrayList matchingHeaders = new ArrayList();
+        for (int i = 0; i < names.length; i++) {
+            String name = names[i];
+            String value = getHeader(name, null);
+            if (value == null)
+                continue;
+            matchingHeaders.add(value);
+        }
+        return Collections.enumeration(matchingHeaders);
+    }
+
+    public Enumeration getNonMatchingHeaders(String[] names)
+            throws MessagingException {
+        List existingHeaders = Arrays.asList(names);
+
+        ArrayList nonMatchingHeaders = new ArrayList();
+
+        Iterator iterator = m_contentHeaders.keySet().iterator();
+        while (iterator.hasNext()) {
+            String name = (String) iterator.next();
+            if (existingHeaders.contains(name))
+                continue;
+            String value = getHeader(name, null);
+            if (value == null)
+                continue;
+            nonMatchingHeaders.add(value);
+        }
+        return Collections.enumeration(nonMatchingHeaders);
+    }
+
+    public void addHeaderLine(String headerLine) throws MessagingException {
+        int separatorIndex = headerLine.indexOf(":");
+        if (separatorIndex < 0)
+            throw new MessagingException(
+                    "header line does not conform to standard");
+
+        addHeader(headerLine.substring(0, separatorIndex), headerLine
+                .substring(separatorIndex, headerLine.length()));
+    }
+
+    public Enumeration getAllHeaderLines() throws MessagingException {
+        return Collections.enumeration(getHeadersAsStrings(m_contentHeaders));
+    }
+
+    private ArrayList getHeadersAsStrings(HashMap contentHeaders) {
+        ArrayList headerLines = new ArrayList();
+        Iterator iterator = contentHeaders.keySet().iterator();
+        while (iterator.hasNext()) {
+            String key = (String) iterator.next();
+            String value = (String) contentHeaders.get(key);
+            headerLines.add(key + ":" + value);
+        }
+        return headerLines;
+    }
+
+    public Enumeration getMatchingHeaderLines(String[] names)
+            throws MessagingException {
+        ArrayList matchingHeaders = new ArrayList();
+        for (int i = 0; i < names.length; i++) {
+            String name = names[i];
+            String value = getHeader(name, null);
+            if (value == null)
+                continue;
+            matchingHeaders.add(name + ":" + value);
+        }
+        return Collections.enumeration(matchingHeaders);
+    }
+
+    public Enumeration getNonMatchingHeaderLines(String[] names)
+            throws MessagingException {
+        List existingHeaders = names != null ? Arrays.asList(names) : null;
+
+        ArrayList nonMatchingHeaders = new ArrayList();
+
+        Iterator iterator = m_contentHeaders.keySet().iterator();
+        while (iterator.hasNext()) {
+            String name = (String) iterator.next();
+            if (existingHeaders != null && existingHeaders.contains(name))
+                continue;
+            String value = getHeader(name, null);
+            if (value == null)
+                continue;
+            nonMatchingHeaders.add(name + ":" + value);
+        }
+        return Collections.enumeration(nonMatchingHeaders);
+    }
+
+    public synchronized Flags getFlags() throws MessagingException {
+        return new Flags(m_setFlags);
+    }
+
+    public synchronized boolean isSet(Flags.Flag flag)
+            throws MessagingException {
+        return m_setFlags.contains(flag);
+    }
+
+    public synchronized void setFlags(Flags flags, boolean set)
+            throws MessagingException {
+        if (set)
+            m_setFlags.add(flags);
+        else
+            m_setFlags.remove(flags);
+    }
+
+    public void saveChanges() throws MessagingException {
+        ; // trivial implementation
+    }
+
+    protected void updateHeaders() throws MessagingException {
+        ; // trivial implementation
+    }
+
+    protected InternetHeaders createInternetHeaders(InputStream inputStream)
+            throws MessagingException {
+        return new InternetHeaders();
+    }
+
+    public void setRecipient(Message.RecipientType recipientType,
+            Address address) throws MessagingException {
+        setRecipients(recipientType, new Address[] { address });
+    }
+
+    public void addRecipient(Message.RecipientType recipientType,
+            Address address) throws MessagingException {
+        setRecipients(recipientType, new Address[] { address });
+    }
+
+    public void setFlag(Flags.Flag flag, boolean set) throws MessagingException {
+        if (set)
+            m_setFlags.add(flag);
+        else
+            m_setFlags.remove(flag);
+    }
+
+    public int getMessageNumber() {
+        return m_iMessageNumber;
+    }
+
+    protected void setMessageNumber(int i) {
+        m_iMessageNumber = i;
+    }
+
+    public Folder getFolder() {
+        return null;
+    }
+
+    public boolean isExpunged() {
+        return m_bIsExpunged;
+    }
+
+    protected void setExpunged(boolean b) {
+        m_bIsExpunged = b;
+    }
+
+    public void setShouldMatch(boolean doMatch) {
+        m_doMatch = doMatch;
+    }
+
+    public boolean match(SearchTerm searchTerm) throws MessagingException {
+        return m_doMatch;
+    }
+
+    public void setSize(int size) {
+        this.m_size = size;
+    }
+}
diff --git a/trunk/main/src/test/java/org/apache/jsieve/parser/SieveNodeCommentTest.java b/trunk/main/src/test/java/org/apache/jsieve/parser/SieveNodeCommentTest.java
new file mode 100644
index 0000000..10f6d73
--- /dev/null
+++ b/trunk/main/src/test/java/org/apache/jsieve/parser/SieveNodeCommentTest.java
@@ -0,0 +1,91 @@
+/****************************************************************
+ * 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.jsieve.parser;
+
+import java.util.List;
+
+import junit.framework.TestCase;
+
+import org.apache.jsieve.utils.JUnitUtils;
+
+public class SieveNodeCommentTest extends TestCase {
+
+    protected void setUp() throws Exception {
+        super.setUp();
+    }
+
+    protected void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    public void testGetNoCommentsBefore() throws Exception {
+        SieveNode node = (SieveNode) JUnitUtils.parse("if address :contains [\"To\", \"From\"] \"Fish!\"{ }");
+        List comments = node.getPrecedingComments();
+        assertNotNull(comments);
+        assertEquals(0, comments.size());
+    }
+    
+    public void testGetBracketCommentsBefore() throws Exception {
+        SieveNode node = (SieveNode) JUnitUtils.parse("/* A Comment *//* Another comment */if address :contains [\"To\", \"From\"] \"Fish!\"{ }");
+        List comments = node.getPrecedingComments();
+        assertNotNull(comments);
+        assertEquals(2, comments.size());
+        assertEquals(" A Comment ", comments.get(0));
+        assertEquals(" Another comment ", comments.get(1));
+    }
+    
+    public void testGetHashCommentsBefore() throws Exception {
+        SieveNode node = (SieveNode) JUnitUtils.parse("/* A Comment */#A Line Comment\nif address :contains [\"To\", \"From\"] \"Fish!\"{ }");
+        List comments = node.getPrecedingComments();
+        assertNotNull(comments);
+        assertEquals(2, comments.size());
+        assertEquals(" A Comment ", comments.get(0));
+        assertEquals("A Line Comment", comments.get(1));
+    }
+    
+    public void testGetHashCommentsBeforeCRLF() throws Exception {
+        SieveNode node = (SieveNode) JUnitUtils.parse("/* A Comment */#A Line Comment\r\nif address :contains [\"To\", \"From\"] \"Fish!\"{ }");
+        List comments = node.getPrecedingComments();
+        assertNotNull(comments);
+        assertEquals(2, comments.size());
+        assertEquals(" A Comment ", comments.get(0));
+        assertEquals("A Line Comment", comments.get(1));
+    }
+    
+
+    public void testGetLastCommentNoneBefore() throws Exception {
+        SieveNode node = (SieveNode) JUnitUtils.parse("if address :contains [\"To\", \"From\"] \"Fish!\"{ }");
+        assertNull(node.getLastComment());
+    }
+    
+    public void testGetBracketLastCommentBefore() throws Exception {
+        SieveNode node = (SieveNode) JUnitUtils.parse("/* A Comment *//* Another comment */if address :contains [\"To\", \"From\"] \"Fish!\"{ }");
+        assertEquals(" Another comment ", node.getLastComment());
+    }
+    
+    public void testGetHashLastCommentBefore() throws Exception {
+        SieveNode node = (SieveNode) JUnitUtils.parse("/* A Comment */#A Line Comment\nif address :contains [\"To\", \"From\"] \"Fish!\"{ }");
+        assertEquals("A Line Comment", node.getLastComment());
+    }
+    
+    public void testGetHashLastCommentBeforeCRLF() throws Exception {
+        SieveNode node = (SieveNode) JUnitUtils.parse("/* A Comment */#A Line Comment\r\nif address :contains [\"To\", \"From\"] \"Fish!\"{ }");;
+        assertEquals("A Line Comment", node.getLastComment());
+    }
+}
diff --git a/trunk/main/src/test/java/org/apache/jsieve/parser/address/SieveAddressBuilderTest.java b/trunk/main/src/test/java/org/apache/jsieve/parser/address/SieveAddressBuilderTest.java
new file mode 100644
index 0000000..1f699b5
--- /dev/null
+++ b/trunk/main/src/test/java/org/apache/jsieve/parser/address/SieveAddressBuilderTest.java
@@ -0,0 +1,106 @@
+/****************************************************************
+ * 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.jsieve.parser.address;
+
+import junit.framework.TestCase;
+
+import org.apache.jsieve.mail.MailAdapter.Address;
+import org.apache.jsieve.parser.generated.address.ParseException;
+
+public class SieveAddressBuilderTest extends TestCase {
+
+    public static final String DOMAIN = "example.org";
+
+    public static final String COYOTE = "coyote";
+
+    public static final String COYOTE_ADDRESS = COYOTE + "@" + DOMAIN;
+
+    public static final String ROADRUNNER = "roadrunner";
+
+    public static final String ROADRUNNER_ADDRESS = ROADRUNNER + "@" + DOMAIN;
+
+    public static final String BUGS = "bugs";
+
+    public static final String BUGS_ADDRESS = BUGS + "@" + DOMAIN;
+
+    public static final String HEROS = ROADRUNNER_ADDRESS + " , "
+            + BUGS_ADDRESS;
+
+    SieveAddressBuilder builder;
+
+    protected void setUp() throws Exception {
+        super.setUp();
+        builder = new SieveAddressBuilder();
+    }
+
+    public void testNotAddress() throws Exception {
+        try {
+            builder
+                    .addAddresses("What a load of rubbish - not an address in sight!");
+            fail("Parsing should fail when the input is not an address");
+        } catch (ParseException e) {
+            // expected
+        }
+    }
+
+    public void testAddAddresses() throws Exception {
+        assertNotNull(builder.getAddresses());
+        builder.addAddresses(COYOTE_ADDRESS);
+        Address[] addresses = builder.getAddresses();
+        assertNotNull(addresses);
+        assertEquals(1, addresses.length);
+        assertEquals(COYOTE, addresses[0].getLocalPart());
+        assertEquals(DOMAIN, addresses[0].getDomain());
+        builder.addAddresses(HEROS);
+        addresses = builder.getAddresses();
+        assertNotNull(addresses);
+        assertEquals(3, addresses.length);
+        assertEquals(COYOTE, addresses[0].getLocalPart());
+        assertEquals(DOMAIN, addresses[0].getDomain());
+        assertEquals(ROADRUNNER, addresses[1].getLocalPart());
+        assertEquals(DOMAIN, addresses[1].getDomain());
+        assertEquals(BUGS, addresses[2].getLocalPart());
+        assertEquals(DOMAIN, addresses[2].getDomain());
+    }
+
+    public void testReset() throws Exception {
+        assertNotNull(builder.getAddresses());
+        builder.addAddresses(COYOTE_ADDRESS);
+        Address[] addresses = builder.getAddresses();
+        assertNotNull(addresses);
+        assertEquals(1, addresses.length);
+        assertEquals(COYOTE, addresses[0].getLocalPart());
+        assertEquals(DOMAIN, addresses[0].getDomain());
+        addresses = builder.getAddresses();
+        assertNotNull(addresses);
+        assertEquals(1, addresses.length);
+        assertEquals(COYOTE, addresses[0].getLocalPart());
+        assertEquals(DOMAIN, addresses[0].getDomain());
+        builder.reset();
+        addresses = builder.getAddresses();
+        assertNotNull(addresses);
+        assertEquals(0, addresses.length);
+        builder.addAddresses(COYOTE_ADDRESS);
+        addresses = builder.getAddresses();
+        assertNotNull(addresses);
+        assertEquals(1, addresses.length);
+        assertEquals(COYOTE, addresses[0].getLocalPart());
+        assertEquals(DOMAIN, addresses[0].getDomain());
+    }
+}
diff --git a/trunk/main/src/test/java/org/apache/jsieve/util/check/MockAction.java b/trunk/main/src/test/java/org/apache/jsieve/util/check/MockAction.java
new file mode 100644
index 0000000..b1fc89b
--- /dev/null
+++ b/trunk/main/src/test/java/org/apache/jsieve/util/check/MockAction.java
@@ -0,0 +1,26 @@
+/****************************************************************
+ * 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.jsieve.util.check;
+
+import org.apache.jsieve.mail.Action;
+
+public class MockAction implements Action {
+
+}
diff --git a/trunk/main/src/test/java/org/apache/jsieve/util/check/ScriptCheckMailAdapter.java b/trunk/main/src/test/java/org/apache/jsieve/util/check/ScriptCheckMailAdapter.java
new file mode 100644
index 0000000..e50fd91
--- /dev/null
+++ b/trunk/main/src/test/java/org/apache/jsieve/util/check/ScriptCheckMailAdapter.java
@@ -0,0 +1,303 @@
+/****************************************************************
+ * 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.jsieve.util.check;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.List;
+
+import javax.mail.Header;
+import javax.mail.Message;
+import javax.mail.MessagingException;
+
+import org.apache.jsieve.SieveContext;
+import org.apache.jsieve.exception.SieveException;
+import org.apache.jsieve.mail.Action;
+import org.apache.jsieve.mail.MailAdapter;
+import org.apache.jsieve.mail.MailUtils;
+import org.apache.jsieve.mail.SieveMailException;
+import org.apache.jsieve.parser.address.SieveAddressBuilder;
+
+/**
+ * Checks script execution for an email. The wrapped email is set by called
+ * {@link #setMail}. Actions are recorded on {@link #executedActions} and can
+ * be retrieved by {@link #getExecutedActions()}.
+ */
+public class ScriptCheckMailAdapter implements MailAdapter {
+
+    private final List<Action> actions;
+
+    private final List<Action> executedActions;
+
+    private Message mail = null;
+
+    public ScriptCheckMailAdapter() {
+        actions = new ArrayList<Action>();
+        executedActions = new ArrayList<Action>();
+    }
+
+    /**
+     * Gets the wrapped email.
+     * 
+     * @return <code>Message</code>, possibly null
+     */
+    public Message getMail() {
+        return mail;
+    }
+
+    /**
+     * Sets the wrapped email and {@link #reset}s the adapter ready for another
+     * execution.
+     * 
+     * @param mail
+     *            <code>Message</code>, possibly null
+     */
+    public void setMail(Message mail) {
+        this.mail = mail;
+        reset();
+    }
+
+    /**
+     * Method addAction adds an Action to the List of Actions to be performed by
+     * the receiver.
+     * 
+     * @param action
+     */
+    public void addAction(final Action action) {
+        actions.add(action);
+    }
+
+    /**
+     * Method executeActions. Applies the Actions accumulated by the receiver.
+     */
+    public void executeActions() throws SieveException {
+        executedActions.clear();
+        executedActions.addAll(actions);
+    }
+
+    /**
+     * Gets the actions accumulated when {@link #executedActions} was last
+     * called.
+     * 
+     * @return <code>List</code> of {@link Action}s, not null. This list is a
+     *         modifiable copy
+     */
+    public List<Action> getExecutedActions() {
+        final ArrayList<Action> result = new ArrayList<Action>(executedActions);
+        return result;
+    }
+
+    /**
+     * Method getActions answers the List of Actions accumulated by the
+     * receiver. Implementations may elect to supply an unmodifiable collection.
+     * 
+     * @return <code>List</code> of {@link Action}'s, not null, possibly
+     *         unmodifiable
+     */
+    public List<Action> getActions() {
+        final List<Action> result = Collections.unmodifiableList(actions);
+        return result;
+    }
+
+    /**
+     * Resets executed and accumlated actions. An instance may be safely reused
+     * to check a script once this method has been called.
+     */
+    public void reset() {
+        executedActions.clear();
+        actions.clear();
+    }
+
+    /**
+     * Method getHeader answers a List of all of the headers in the receiver
+     * whose name is equal to the passed name. If no headers are found an empty
+     * List is returned.
+     * 
+     * @param name
+     * @return <code>List</code> not null, possibly empty
+     * @throws SieveMailException
+     */
+    @SuppressWarnings("unchecked")
+    public List<String> getHeader(String name) throws SieveMailException {
+        List<String> result = Collections.EMPTY_LIST;
+        if (mail != null) {
+            try {
+                String[] values = mail.getHeader(name);
+                if (values != null) {
+                    result = Arrays.asList(values);
+                }
+            } catch (MessagingException e) {
+                throw new SieveMailException(e);
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Method getHeaderNames answers a List of all of the headers in the
+     * receiver. No duplicates are allowed.
+     * 
+     * @return <code>List</code>, not null possible empty, possible
+     *         unmodifiable
+     * @throws SieveMailException
+     */
+    @SuppressWarnings("unchecked")
+    public List<String> getHeaderNames() throws SieveMailException {
+        List<String> results = Collections.EMPTY_LIST;
+        if (mail != null) {
+            try {
+                results = new ArrayList<String>();
+                for (final Enumeration en = mail.getAllHeaders(); en
+                        .hasMoreElements();) {
+                    final Header header = (Header) en.nextElement();
+                    final String name = header.getName();
+                    if (!results.contains(name)) {
+                        results.add(name);
+                    }
+                }
+            } catch (MessagingException e) {
+                throw new SieveMailException(e);
+            }
+        }
+        return results;
+    }
+
+    /**
+     * <p>
+     * Method getMatchingHeader answers a List of all of the headers in the
+     * receiver with the passed name. If no headers are found an empty List is
+     * returned.
+     * </p>
+     * 
+     * <p>
+     * This method differs from getHeader(String) in that it ignores case and
+     * the whitespace prefixes and suffixes of a header name when performing the
+     * match, as required by RFC 3028. Thus "From", "from ", " From" and " from "
+     * are considered equal.
+     * </p>
+     * 
+     * @param name
+     * @return <code>List</code>, not null possibly empty
+     * @throws SieveMailException
+     */
+    @SuppressWarnings("unchecked")
+    public List<String> getMatchingHeader(String name) throws SieveMailException {
+        List<String> result = Collections.EMPTY_LIST;
+        if (mail != null) {
+            result = MailUtils.getMatchingHeader(this, name);
+        }
+        return result;
+    }
+
+    /**
+     * Method getSize answers the receiver's message size in octets.
+     * 
+     * @return int
+     * @throws SieveMailException
+     */
+    public int getSize() throws SieveMailException {
+        int result = 0;
+        if (mail != null) {
+            try {
+                result = mail.getSize();
+            } catch (MessagingException e) {
+                throw new SieveMailException(e);
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Method getContentType returns string/mime representation of the message
+     * type.
+     * 
+     * @return String
+     * @throws SieveMailException
+     */
+    public String getContentType() throws SieveMailException {
+        String result = null;
+        if (mail != null) {
+            try {
+                result = mail.getContentType();
+            } catch (MessagingException e) {
+                throw new SieveMailException(e);
+            }
+        }
+        return result;
+    }
+
+    public boolean isInBodyText(String phraseCaseInsensitive) throws SieveMailException {
+        boolean result = false;
+        if (mail != null) {
+            try {
+                result = mail.getContent().toString().toLowerCase().contains(phraseCaseInsensitive);
+            } catch (MessagingException e) {
+                throw new SieveMailException(e);
+            } catch (IOException e) {
+                throw new SieveMailException(e);
+            }
+        }
+        return result;
+    }
+
+    public Address[] parseAddresses(String headerName)
+            throws SieveMailException {
+        return parseAddresses(headerName, mail);
+    }
+
+    /**
+     * Parses the value from the given message into addresses.
+     * 
+     * @param headerName
+     *            header name, to be matched case insensitively
+     * @param message
+     *            <code>Message</code>, not null
+     * @return <code>Address</code> array, not null possibly empty
+     * @throws SieveMailException
+     */
+    public Address[] parseAddresses(final String headerName,
+            final Message message) throws SieveMailException {
+        try {
+            final SieveAddressBuilder builder = new SieveAddressBuilder();
+
+            for (Enumeration en = message.getAllHeaders(); en.hasMoreElements();) {
+                final Header header = (Header) en.nextElement();
+                final String name = header.getName();
+                if (name.trim().equalsIgnoreCase(headerName)) {
+                    builder.addAddresses(header.getValue());
+                }
+            }
+
+            final Address[] results = builder.getAddresses();
+            return results;
+
+        } catch (MessagingException ex) {
+            throw new SieveMailException(ex);
+        } catch (org.apache.jsieve.parser.generated.address.ParseException ex) {
+            throw new SieveMailException(ex);
+        }
+    }
+
+    public void setContext(SieveContext context) {}
+
+}
diff --git a/trunk/main/src/test/java/org/apache/jsieve/util/check/ScriptCheckMailAdapterActionsTest.java b/trunk/main/src/test/java/org/apache/jsieve/util/check/ScriptCheckMailAdapterActionsTest.java
new file mode 100644
index 0000000..8576cd9
--- /dev/null
+++ b/trunk/main/src/test/java/org/apache/jsieve/util/check/ScriptCheckMailAdapterActionsTest.java
@@ -0,0 +1,141 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.jsieve.util.check;
+
+import java.util.ListIterator;
+
+import junit.framework.TestCase;
+
+import org.apache.jsieve.mail.Action;
+
+public class ScriptCheckMailAdapterActionsTest extends TestCase {
+
+    ScriptCheckMailAdapter adapter;
+
+    Action action;
+
+    Action anotherAction;
+
+    protected void setUp() throws Exception {
+        super.setUp();
+        adapter = new ScriptCheckMailAdapter();
+        action = new MockAction();
+        anotherAction = new MockAction();
+    }
+
+    public void testAddAction() {
+        adapter.addAction(action);
+        assertEquals("Running total updated", 1, adapter.getActions().size());
+        assertEquals("Running total updated", action, adapter.getActions().get(
+                0));
+        adapter.addAction(anotherAction);
+        assertEquals("Order preserved", 2, adapter.getActions().size());
+        assertEquals("Order preserved", anotherAction, adapter.getActions()
+                .get(1));
+    }
+
+    public void testExecuteActions() throws Exception {
+        assertNotNull(adapter.getExecutedActions());
+        assertEquals("No actions executed", 0, adapter.getExecutedActions()
+                .size());
+        adapter.addAction(action);
+        assertNotNull(adapter.getExecutedActions());
+        assertEquals("No actions executed", 0, adapter.getExecutedActions()
+                .size());
+        adapter.executeActions();
+        assertNotNull(adapter.getExecutedActions());
+        assertEquals("One action executed", 1, adapter.getExecutedActions()
+                .size());
+    }
+
+    public void testGetActions() {
+        assertNotNull(adapter.getActions());
+        try {
+            adapter.getActions().add(new Action() {});
+            fail("Should not be able to modify collection");
+        } catch (UnsupportedOperationException e) {
+            // expected
+        }
+        adapter.addAction(action);
+        assertNotNull(adapter.getActions());
+        assertEquals("Running total updated", 1, adapter.getActions().size());
+        assertEquals("Running total updated", action, adapter.getActions().get(
+                0));
+        adapter.addAction(anotherAction);
+        assertNotNull(adapter.getActions());
+        assertEquals("Order preserved", 2, adapter.getActions().size());
+        assertEquals("Order preserved", anotherAction, adapter.getActions()
+                .get(1));
+    }
+
+    public void testGetExecutedActions() throws Exception {
+        assertNotNull(adapter.getExecutedActions());
+        assertEquals("No actions executed", 0, adapter.getExecutedActions()
+                .size());
+        adapter.addAction(action);
+        assertNotNull(adapter.getExecutedActions());
+        assertEquals("No actions executed", 0, adapter.getExecutedActions()
+                .size());
+        adapter.executeActions();
+        assertEquals("One action executed", 1, adapter.getExecutedActions()
+                .size());
+        assertEquals("One action executed", action, adapter
+                .getExecutedActions().get(0));
+        adapter.addAction(anotherAction);
+        assertEquals("One action executed", 1, adapter.getExecutedActions()
+                .size());
+        assertEquals("One action executed", action, adapter
+                .getExecutedActions().get(0));
+        adapter.executeActions();
+        assertEquals("Two actions executed", 2, adapter.getExecutedActions()
+                .size());
+        assertEquals("Two actions executed", action, adapter
+                .getExecutedActions().get(0));
+        assertEquals("Two actions executed", anotherAction, adapter
+                .getExecutedActions().get(1));
+        adapter.getExecutedActions().add(new Action(){});
+        assertEquals("Two actions executed", 2, adapter.getExecutedActions()
+                .size());
+        assertEquals("Two actions executed", action, adapter
+                .getExecutedActions().get(0));
+        assertEquals("Two actions executed", anotherAction, adapter
+                .getExecutedActions().get(1));
+        adapter.executeActions();
+        assertEquals("Two actions executed", 2, adapter.getExecutedActions()
+                .size());
+        assertEquals("Two actions executed", action, adapter
+                .getExecutedActions().get(0));
+        assertEquals("Two actions executed", anotherAction, adapter
+                .getExecutedActions().get(1));
+    }
+
+    public void testReset() throws Exception {
+        adapter.addAction(action);
+        adapter.addAction(anotherAction);
+        adapter.executeActions();
+        assertEquals("Two actions executed", 2, adapter.getExecutedActions()
+                .size());
+        assertEquals("Two actions", 2, adapter.getActions().size());
+        adapter.reset();
+        assertEquals("Two actions executed", 0, adapter.getExecutedActions()
+                .size());
+        assertEquals("Two actions", 0, adapter.getActions().size());
+    }
+}
diff --git a/trunk/main/src/test/java/org/apache/jsieve/util/check/ScriptCheckMailAdapterHeadersTest.java b/trunk/main/src/test/java/org/apache/jsieve/util/check/ScriptCheckMailAdapterHeadersTest.java
new file mode 100644
index 0000000..44b478c
--- /dev/null
+++ b/trunk/main/src/test/java/org/apache/jsieve/util/check/ScriptCheckMailAdapterHeadersTest.java
@@ -0,0 +1,136 @@
+/****************************************************************
+ * 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.jsieve.util.check;
+
+import java.util.List;
+
+import junit.framework.TestCase;
+
+import org.apache.jsieve.javaxmail.MockMimeMessage;
+
+public class ScriptCheckMailAdapterHeadersTest extends TestCase {
+    private static final String BCC = "Bcc";
+
+    private static final String TO = "To";
+
+    private static final String FROM = "From";
+
+    private static final int MESSAGE_SIZE = 100;
+
+    private static final String BCC_ADDRESS_ONE = "bugs@toons.example.org";
+
+    private static final String BCC_ADDRESS_TWO = "daffy@toons.example.org";
+
+    private static final String TO_ADDRESS = "roadrunner@acme.example.com";
+
+    private static final String X_HEADER_NAME = "X-Toon";
+
+    private static final String X_HEADER_WITH_WS = "   "
+            + X_HEADER_NAME.toLowerCase();
+
+    private static final String X_HEADER_VALUE = "Road Runner";
+
+    private static final String X_HEADER_VALUE_ALT = "Wile E. Coyote And Road Runner";
+
+    private static final String FROM_ADDRESS = "coyote@desert.example.org";
+
+    ScriptCheckMailAdapter adapter;
+
+    MockMimeMessage message;
+
+    protected void setUp() throws Exception {
+        super.setUp();
+        adapter = new ScriptCheckMailAdapter();
+        message = new MockMimeMessage();
+        message.addHeader(FROM, FROM_ADDRESS);
+        message.addHeader(TO, TO_ADDRESS);
+        message.addHeader(BCC, BCC_ADDRESS_ONE);
+        message.addHeader(BCC, BCC_ADDRESS_TWO);
+        message.addHeader(X_HEADER_NAME, X_HEADER_VALUE);
+        message.addHeader(X_HEADER_NAME.toLowerCase(), X_HEADER_VALUE
+                .toLowerCase());
+        message.addHeader(X_HEADER_WITH_WS, X_HEADER_VALUE_ALT);
+        message.setSize(MESSAGE_SIZE);
+        adapter.setMail(message);
+    }
+
+    public void testGetHeader() throws Exception {
+        List<String> headers = adapter.getHeader(FROM);
+        assertNotNull(headers);
+        assertEquals("From header", 1, headers.size());
+        assertEquals("From header", FROM_ADDRESS, headers.get(0));
+        headers = adapter.getHeader(BCC);
+        assertEquals("Bcc headers", 2, headers.size());
+        assertTrue("Bcc headers", headers.contains(BCC_ADDRESS_ONE));
+        assertTrue("Bcc headers", headers.contains(BCC_ADDRESS_TWO));
+        headers = adapter.getHeader(X_HEADER_NAME);
+        assertEquals("Case and whitespace sensitive", 1, headers.size());
+        assertEquals("Case and whitespace sensitive", X_HEADER_VALUE, headers
+                .get(0));
+        headers = adapter.getHeader(X_HEADER_NAME.toLowerCase());
+        assertEquals("Case and whitespace sensitive", 1, headers.size());
+        assertEquals("Case and whitespace sensitive", X_HEADER_VALUE
+                .toLowerCase(), headers.get(0));
+        headers = adapter.getHeader(X_HEADER_WITH_WS);
+        assertEquals("Case and whitespace sensitive", 1, headers.size());
+        assertEquals("Case and whitespace sensitive", X_HEADER_VALUE_ALT,
+                headers.get(0));
+    }
+
+    public void testGetHeaderNames() throws Exception {
+        List headers = adapter.getHeaderNames();
+        assertNotNull(headers);
+        assertEquals("All headers set returned", 6, headers.size());
+        assertTrue("All headers set returned", headers.contains(BCC));
+        assertTrue("All headers set returned", headers.contains(TO));
+        assertTrue("All headers set returned", headers.contains(FROM));
+        assertTrue("All headers set returned", headers.contains(X_HEADER_NAME));
+        assertTrue("All headers set returned", headers.contains(X_HEADER_NAME
+                .toLowerCase()));
+        assertTrue("All headers set returned", headers
+                .contains(X_HEADER_WITH_WS));
+    }
+
+    public void testGetMatchingHeader() throws Exception {
+        List<String> headers = adapter.getMatchingHeader(FROM);
+        assertNotNull(headers);
+        assertEquals("From headers set returned", 1, headers.size());
+        assertTrue("From headers set returned", headers.contains(FROM_ADDRESS));
+        headers = adapter.getMatchingHeader(X_HEADER_NAME);
+        assertNotNull(headers);
+        assertEquals(
+                "Matches ignoring whitespace and capitalisation headers set returned",
+                3, headers.size());
+        assertTrue(
+                "Matches ignoring whitespace and capitalisation headers set returned",
+                headers.contains(X_HEADER_VALUE));
+        assertTrue(
+                "Matches ignoring whitespace and capitalisation headers set returned",
+                headers.contains(X_HEADER_VALUE_ALT));
+        assertTrue(
+                "Matches ignoring whitespace and capitalisation headers set returned",
+                headers.contains(X_HEADER_VALUE.toLowerCase()));
+    }
+
+    public void testGetSize() throws Exception {
+        int size = adapter.getSize();
+        assertEquals("Message size set", MESSAGE_SIZE, size);
+    }
+}
diff --git a/trunk/main/src/test/java/org/apache/jsieve/util/check/ScriptCheckMailAdapterMailTest.java b/trunk/main/src/test/java/org/apache/jsieve/util/check/ScriptCheckMailAdapterMailTest.java
new file mode 100644
index 0000000..521942a
--- /dev/null
+++ b/trunk/main/src/test/java/org/apache/jsieve/util/check/ScriptCheckMailAdapterMailTest.java
@@ -0,0 +1,53 @@
+/****************************************************************
+ * 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.jsieve.util.check;
+
+import junit.framework.TestCase;
+
+import org.apache.jsieve.mail.Action;
+
+public class ScriptCheckMailAdapterMailTest extends TestCase {
+
+    ScriptCheckMailAdapter adapter;
+
+    Action action;
+
+    Action anotherAction;
+
+    protected void setUp() throws Exception {
+        super.setUp();
+        adapter = new ScriptCheckMailAdapter();
+        action = new MockAction();
+        anotherAction = new MockAction();
+    }
+
+    public void testSetMail() throws Exception {
+        adapter.addAction(action);
+        adapter.addAction(anotherAction);
+        adapter.executeActions();
+        assertEquals("Two actions executed", 2, adapter.getExecutedActions()
+                .size());
+        assertEquals("Two actions", 2, adapter.getActions().size());
+        adapter.setMail(null);
+        assertEquals("Set mail resets", 0, adapter.getExecutedActions().size());
+        assertEquals("Set mail resets", 0, adapter.getActions().size());
+    }
+
+}
diff --git a/trunk/main/src/test/java/org/apache/jsieve/util/check/ScriptCheckMailAdapterNoMessageSetTest.java b/trunk/main/src/test/java/org/apache/jsieve/util/check/ScriptCheckMailAdapterNoMessageSetTest.java
new file mode 100644
index 0000000..688f6e8
--- /dev/null
+++ b/trunk/main/src/test/java/org/apache/jsieve/util/check/ScriptCheckMailAdapterNoMessageSetTest.java
@@ -0,0 +1,54 @@
+/****************************************************************
+ * 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.jsieve.util.check;
+
+import java.util.List;
+
+import junit.framework.TestCase;
+
+public class ScriptCheckMailAdapterNoMessageSetTest extends TestCase {
+
+    ScriptCheckMailAdapter adapter;
+
+    protected void setUp() throws Exception {
+        super.setUp();
+        adapter = new ScriptCheckMailAdapter();
+    }
+
+    public void testGetHeader() throws Exception {
+        List<String> headers = adapter.getHeader("From");
+        assertNotNull(headers);
+    }
+
+    public void testGetHeaderNames() throws Exception {
+        List headers = adapter.getHeaderNames();
+        assertNotNull(headers);
+    }
+
+    public void testGetMatchingHeader() throws Exception {
+        List headers = adapter.getMatchingHeader("From");
+        assertNotNull(headers);
+    }
+
+    public void tesGetSize() throws Exception {
+        int size = adapter.getSize();
+        assertEquals("When mail not set, size is zero", 0, size);
+    }
+}
diff --git a/trunk/main/src/test/java/org/apache/jsieve/util/check/ScriptChecker.java b/trunk/main/src/test/java/org/apache/jsieve/util/check/ScriptChecker.java
new file mode 100644
index 0000000..199d401
--- /dev/null
+++ b/trunk/main/src/test/java/org/apache/jsieve/util/check/ScriptChecker.java
@@ -0,0 +1,241 @@
+/****************************************************************
+ * 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.jsieve.util.check;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+
+import javax.mail.MessagingException;
+import javax.mail.internet.MimeMessage;
+
+import org.apache.jsieve.ConfigurationManager;
+import org.apache.jsieve.exception.SieveException;
+import org.apache.jsieve.mail.ActionFileInto;
+import org.apache.jsieve.mail.ActionKeep;
+import org.apache.jsieve.mail.ActionRedirect;
+import org.apache.jsieve.mail.ActionReject;
+import org.apache.jsieve.parser.generated.ParseException;
+
+/**
+ * Checks a <code>sieve</code> script by executing it against a given mail and
+ * reporting the results.
+ */
+public class ScriptChecker {
+
+    private final ScriptCheckMailAdapter adapter;
+
+    public ScriptChecker() {
+        adapter = new ScriptCheckMailAdapter();
+    }
+
+    /**
+     * Checks the <code>sieve</code> script contained in the given file by
+     * executing it against the given message.
+     * 
+     * @param message
+     *            <code>File</code> containing the mail message to be fed to
+     *            the script, not null
+     * @param script
+     *            <code>File</code> containing the script to be checked
+     * @return <code>Results</code> of that execution
+     * @throws IOException
+     * @throws MessageException
+     */
+    public Results check(final File message, final File script)
+            throws IOException, MessagingException {
+        final FileInputStream messageStream = new FileInputStream(message);
+        final FileInputStream scriptStream = new FileInputStream(script);
+        final Results results = check(messageStream, scriptStream);
+        return results;
+    }
+
+    /**
+     * Checks the <code>sieve</code> script contained in the given file by
+     * executing it against the given message.
+     * 
+     * @param script
+     *            <code>InputStream</code>, not null
+     * @return <code>Results</code> of the check, not null
+     * @throws IOException
+     * @throws MessagingException
+     */
+    public Results check(final InputStream message, final InputStream script)
+            throws IOException, MessagingException {
+        MimeMessage mimeMessage = new MimeMessage(null, message);
+        adapter.setMail(mimeMessage);
+        Results results;
+        try {
+            new ConfigurationManager().build().interpret(adapter, script);
+            final List executedActions = adapter.getExecutedActions();
+            results = new Results(executedActions);
+        } catch (ParseException e) {
+            e.printStackTrace();
+            results = new Results(e);
+        } catch (SieveException e) {
+            e.printStackTrace();
+            results = new Results(e);
+        }
+        return results;
+    }
+
+    /**
+     * Contains results of script execution.
+     */
+    public final static class Results {
+        private final boolean pass;
+
+        private final Exception exception;
+
+        private final List actionsExecuted;
+
+        public Results(final Exception ex) {
+            this.exception = ex;
+            pass = false;
+            actionsExecuted = null;
+        }
+
+        public Results(final List actions) {
+            this.pass = true;
+            exception = null;
+            this.actionsExecuted = actions;
+        }
+
+        /**
+         * Is this a pass?
+         * 
+         * @return true if the script executed without error, false if errors
+         *         were encountered
+         */
+        public boolean isPass() {
+            return pass;
+        }
+
+        /**
+         * Gets the exception which was thrown during execution.
+         * 
+         * @return <code>Exception</code> or null if no exception was thrown
+         */
+        public Exception getException() {
+            return exception;
+        }
+
+        /**
+         * Gets the actions executed by the script.
+         * 
+         * @return <code>List</code> of actions or null if the script failed
+         */
+        public List getActionsExecuted() {
+            return actionsExecuted;
+        }
+
+        /**
+         * Is the <code>n<code>'th action a <code>FileInto</code>
+         * with given destination?
+         * @param destination <code>String</code> destination for the file into
+         * @param n index to check
+         * @return  true if the <code>n<code>'th action is a <code>FileInto</code>
+         * with given destination
+         */
+        public boolean isActionFileInto(String destination, int n) {
+            boolean result = false;
+            Object action = actionsExecuted.get(n);
+            if (action != null && action instanceof ActionFileInto) {
+                ActionFileInto actionFileInto = (ActionFileInto) action;
+                result = destination.equals(actionFileInto.getDestination());
+            }
+            return result;
+        }
+
+        /**
+         * Is the <code>n<code>'th action a <code>Redirect</code>
+         * with given address?
+         * @param address <code>String</code> redirect address
+         * @param n index to check
+         * @return  true if the <code>n<code>'th action is a <code>Redirect</code>
+         * with given redirect address
+         */
+        public boolean isActionRedirect(String address, int n) {
+            boolean result = false;
+            Object action = actionsExecuted.get(n);
+            if (action != null && action instanceof ActionRedirect) {
+                ActionRedirect actionRedirect = (ActionRedirect) action;
+                result = address.equals(actionRedirect.getAddress());
+            }
+            return result;
+        }
+
+        /**
+         * Is the <code>n<code>'th action a <code>Reject</code>
+         * with given message?
+         * @param message <code>String</code> rejection message
+         * @param n index to check
+         * @return  true if the <code>n<code>'th action is a <code>Reject</code>
+         * with given reject message
+         */
+        public boolean isActionReject(String message, int n) {
+            boolean result = false;
+            Object action = actionsExecuted.get(n);
+            if (action != null && action instanceof ActionReject) {
+                ActionReject actionReject = (ActionReject) action;
+                result = message.equals(actionReject.getMessage());
+            }
+            return result;
+        }
+
+        /**
+         * Is the <code>n<code>'th action a <code>Keep</code>?
+         * @param n index to check
+         * @return  true if the <code>n<code>'th action is a <code>Keep</code>
+         */
+        public boolean isActionKeep(int n) {
+            boolean result = false;
+            Object action = actionsExecuted.get(n);
+            if (action != null && action instanceof ActionKeep) {
+                result = true;
+            }
+            return result;
+        }
+
+        /**
+         * Prints out details of results.
+         */
+        public String toString() {
+            StringBuffer buffer = new StringBuffer("Results: ");
+            if (pass) {
+                buffer.append("PASS");
+            } else {
+                buffer.append("FAIL: ");
+                if (exception != null) {
+                    if (exception instanceof ParseException) {
+                        buffer.append("Cannot parse script");
+                    } else {
+                        buffer.append("Cannot excute script");
+                    }
+                    buffer.append(exception.getMessage());
+                }
+            }
+            return buffer.toString();
+        }
+
+    }
+}
diff --git a/trunk/main/src/test/java/org/apache/jsieve/util/check/ScriptCheckerTestActionsTest.java b/trunk/main/src/test/java/org/apache/jsieve/util/check/ScriptCheckerTestActionsTest.java
new file mode 100644
index 0000000..2984e2a
--- /dev/null
+++ b/trunk/main/src/test/java/org/apache/jsieve/util/check/ScriptCheckerTestActionsTest.java
@@ -0,0 +1,162 @@
+/****************************************************************
+ * 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.jsieve.util.check;
+
+import java.util.Arrays;
+
+import junit.framework.TestCase;
+
+import org.apache.jsieve.mail.Action;
+import org.apache.jsieve.mail.ActionFileInto;
+import org.apache.jsieve.mail.ActionKeep;
+import org.apache.jsieve.mail.ActionRedirect;
+import org.apache.jsieve.mail.ActionReject;
+
+public class ScriptCheckerTestActionsTest extends TestCase {
+
+    private static final String REDIRECT_ADDRESS_TWO = "roadrunner@acme.example.org";
+
+    private static final String REDIRECT_ADDRESS_ONE = "coyote@desert.example.org";
+
+    private static final String REJECT_MESSAGE_TWO = "Oh well";
+
+    private static final String REJECT_MESSAGE_ONE = "Better luck next time";
+
+    private static final String DESTINATION_TWO = "org.apache.jakarta";
+
+    private static final String DESTINATION_ONE = "org.apache.james";
+
+    static final Action[] ACTIONS = { new ActionFileInto(DESTINATION_ONE),
+            new ActionKeep(), new ActionFileInto(DESTINATION_TWO),
+            new ActionReject(REJECT_MESSAGE_ONE),
+            new ActionRedirect(REDIRECT_ADDRESS_ONE),
+            new ActionRedirect(REDIRECT_ADDRESS_TWO),
+            new ActionReject(REJECT_MESSAGE_TWO), };
+
+    ScriptChecker.Results result;
+
+    protected void setUp() throws Exception {
+        super.setUp();
+        result = new ScriptChecker.Results(Arrays.asList(ACTIONS));
+    }
+
+    public void testFileInto() throws Exception {
+        assertTrue("Check for file into action with right destination", result
+                .isActionFileInto(DESTINATION_ONE, 0));
+        assertFalse("Check for file into action with right destination", result
+                .isActionFileInto(DESTINATION_ONE, 1));
+        assertFalse("Check for file into action with right destination", result
+                .isActionFileInto(DESTINATION_ONE, 2));
+        assertFalse("Check for file into action with right destination", result
+                .isActionFileInto(DESTINATION_ONE, 3));
+        assertFalse("Check for file into action with right destination", result
+                .isActionFileInto(DESTINATION_ONE, 4));
+        assertFalse("Check for file into action with right destination", result
+                .isActionFileInto(DESTINATION_ONE, 5));
+        assertFalse("Check for file into action with right destination", result
+                .isActionFileInto(DESTINATION_ONE, 6));
+        assertFalse("Check for file into action with right destination", result
+                .isActionFileInto(DESTINATION_TWO, 0));
+        assertFalse("Check for file into action with right destination", result
+                .isActionFileInto(DESTINATION_TWO, 1));
+        assertTrue("Check for file into action with right destination", result
+                .isActionFileInto(DESTINATION_TWO, 2));
+        assertFalse("Check for file into action with right destination", result
+                .isActionFileInto(DESTINATION_TWO, 3));
+        assertFalse("Check for file into action with right destination", result
+                .isActionFileInto(DESTINATION_TWO, 4));
+        assertFalse("Check for file into action with right destination", result
+                .isActionFileInto(DESTINATION_TWO, 5));
+        assertFalse("Check for file into action with right destination", result
+                .isActionFileInto(DESTINATION_TWO, 6));
+    }
+
+    public void testRedirect() throws Exception {
+        assertFalse("Check for redirect action with right message", result
+                .isActionRedirect(REDIRECT_ADDRESS_ONE, 0));
+        assertFalse("Check for redirect action with right message", result
+                .isActionRedirect(REDIRECT_ADDRESS_ONE, 1));
+        assertFalse("Check for redirect action with right message", result
+                .isActionRedirect(REDIRECT_ADDRESS_ONE, 2));
+        assertFalse("Check for redirect action with right message", result
+                .isActionRedirect(REDIRECT_ADDRESS_ONE, 3));
+        assertTrue("Check for redirect action with right message", result
+                .isActionRedirect(REDIRECT_ADDRESS_ONE, 4));
+        assertFalse("Check for redirect action with right message", result
+                .isActionRedirect(REDIRECT_ADDRESS_ONE, 5));
+        assertFalse("Check for redirect action with right message", result
+                .isActionRedirect(REDIRECT_ADDRESS_ONE, 6));
+        assertFalse("Check for redirect action with right message", result
+                .isActionRedirect(REDIRECT_ADDRESS_TWO, 0));
+        assertFalse("Check for redirect action with right message", result
+                .isActionRedirect(REDIRECT_ADDRESS_TWO, 1));
+        assertFalse("Check for redirect action with right message", result
+                .isActionRedirect(REDIRECT_ADDRESS_TWO, 2));
+        assertFalse("Check for redirect action with right message", result
+                .isActionRedirect(REDIRECT_ADDRESS_TWO, 3));
+        assertFalse("Check for redirect action with right message", result
+                .isActionRedirect(REDIRECT_ADDRESS_TWO, 4));
+        assertTrue("Check for redirect action with right message", result
+                .isActionRedirect(REDIRECT_ADDRESS_TWO, 5));
+        assertFalse("Check for redirect action with right message", result
+                .isActionRedirect(REDIRECT_ADDRESS_TWO, 6));
+    }
+
+    public void testReject() throws Exception {
+        assertFalse("Check for reject action with right message", result
+                .isActionReject(REJECT_MESSAGE_ONE, 0));
+        assertFalse("Check for reject action with right message", result
+                .isActionReject(REJECT_MESSAGE_ONE, 1));
+        assertFalse("Check for reject action with right message", result
+                .isActionReject(REJECT_MESSAGE_ONE, 2));
+        assertTrue("Check for reject action with right message", result
+                .isActionReject(REJECT_MESSAGE_ONE, 3));
+        assertFalse("Check for reject action with right message", result
+                .isActionReject(REJECT_MESSAGE_ONE, 4));
+        assertFalse("Check for reject action with right message", result
+                .isActionReject(REJECT_MESSAGE_ONE, 5));
+        assertFalse("Check for reject action with right message", result
+                .isActionReject(REJECT_MESSAGE_ONE, 6));
+        assertFalse("Check for reject action with right message", result
+                .isActionReject(REJECT_MESSAGE_TWO, 0));
+        assertFalse("Check for reject action with right message", result
+                .isActionReject(REJECT_MESSAGE_TWO, 1));
+        assertFalse("Check for reject action with right message", result
+                .isActionReject(REJECT_MESSAGE_TWO, 2));
+        assertFalse("Check for reject action with right message", result
+                .isActionReject(REJECT_MESSAGE_TWO, 3));
+        assertFalse("Check for reject action with right message", result
+                .isActionReject(REJECT_MESSAGE_TWO, 4));
+        assertFalse("Check for reject action with right message", result
+                .isActionReject(REJECT_MESSAGE_TWO, 5));
+        assertTrue("Check for reject action with right message", result
+                .isActionReject(REJECT_MESSAGE_TWO, 6));
+    }
+
+    public void testKeep() throws Exception {
+        assertFalse("Check for keep action ", result.isActionKeep(0));
+        assertTrue("Check for keep action ", result.isActionKeep(1));
+        assertFalse("Check for keep action ", result.isActionKeep(2));
+        assertFalse("Check for keep action ", result.isActionKeep(3));
+        assertFalse("Check for keep action ", result.isActionKeep(4));
+        assertFalse("Check for keep action ", result.isActionKeep(5));
+        assertFalse("Check for keep action ", result.isActionKeep(6));
+    }
+}
diff --git a/trunk/main/src/test/java/org/apache/jsieve/utils/JUnitUtils.java b/trunk/main/src/test/java/org/apache/jsieve/utils/JUnitUtils.java
new file mode 100644
index 0000000..a90f51d
--- /dev/null
+++ b/trunk/main/src/test/java/org/apache/jsieve/utils/JUnitUtils.java
@@ -0,0 +1,116 @@
+/****************************************************************
+ * 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.jsieve.utils;
+
+import java.io.ByteArrayInputStream;
+
+import javax.mail.MessagingException;
+import javax.mail.Session;
+import javax.mail.internet.MimeMessage;
+
+import org.apache.jsieve.ConfigurationManager;
+import org.apache.jsieve.exception.SieveException;
+import org.apache.jsieve.mail.MailAdapter;
+import org.apache.jsieve.parser.generated.Node;
+import org.apache.jsieve.parser.generated.ParseException;
+
+/**
+ * Class JUnitUtils implements utility methods used during unit testing.
+ */
+public class JUnitUtils {
+    /**
+     * Method interpret parses a script and evaluates it against a MailAdapter.
+     * 
+     * @param mail
+     * @param script
+     * @throws SieveException
+     * @throws ParseException
+     */
+    static public void interpret(MailAdapter mail, String script)
+            throws SieveException, ParseException {
+        new ConfigurationManager().build().interpret(mail,
+                new ByteArrayInputStream(script.getBytes()));
+    }
+
+    /**
+     * Method interpret parses a script and evaluates it against a MailAdapter.
+     * 
+     * @param mail
+     * @param script
+     * @throws SieveException
+     * @throws ParseException
+     */
+    static public Node parse(String script) throws SieveException,
+            ParseException {
+        return new ConfigurationManager().build().parse(
+                new ByteArrayInputStream(script.getBytes()));
+    }
+
+    /**
+     * Method createMimeMessage answers an empty MimeMessage.
+     * 
+     * @return MimeMessage
+     */
+    static public MimeMessage createMimeMessage() {
+        return new MimeMessage(Session.getDefaultInstance(System
+                .getProperties()));
+    }
+
+    /**
+     * Method createMail answers a SieveMailAdapter wrapping an empty
+     * MimeMessage.
+     * 
+     * @return SieveEnvelopeMailAdapter
+     */
+    static public MailAdapter createMail() {
+        return new SieveMailAdapter(createMimeMessage());
+    }
+
+    /**
+     * Method createEnvelopeMail answers a SieveEnvelopeMailAdapter wrapping an
+     * empty MimeMessage.
+     * 
+     * @return SieveEnvelopeMailAdapter
+     */
+    static public SieveEnvelopeMailAdapter createEnvelopeMail() {
+        return new SieveEnvelopeMailAdapter(createMimeMessage());
+    }
+
+    /**
+     * Method copyMail answers a copy of our mock MailAdapter.
+     * 
+     * @param mail
+     * @return MailAdapter
+     * @throws MessagingException
+     */
+    static public MailAdapter copyMail(SieveMailAdapter mail)
+            throws MessagingException {
+        MimeMessage message = new MimeMessage(mail.getMessage());
+        return new SieveMailAdapter(message);
+    }
+
+    /**
+     * Constructor for JUnitUtils.
+     */
+    private JUnitUtils() {
+        super();
+    }
+
+}
diff --git a/trunk/main/src/test/java/org/apache/jsieve/utils/SieveEnvelopeMailAdapter.java b/trunk/main/src/test/java/org/apache/jsieve/utils/SieveEnvelopeMailAdapter.java
new file mode 100644
index 0000000..da525de
--- /dev/null
+++ b/trunk/main/src/test/java/org/apache/jsieve/utils/SieveEnvelopeMailAdapter.java
@@ -0,0 +1,151 @@
+/****************************************************************
+ * 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.jsieve.utils;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import javax.mail.internet.MimeMessage;
+
+import org.apache.jsieve.mail.SieveMailException;
+import org.apache.jsieve.mail.optional.EnvelopeAccessors;
+
+/**
+ * <p>
+ * Class SieveEnvelopeMailAdapter extends class SieveMailAdapter, a mock
+ * implementation of a MailAdapter, to add support for EnvelopeAccessors.
+ * <p>
+ * 
+ * <p>
+ * As the Envelope Test is an optional Sieve test, MailAdapter support for the
+ * interface is optional too.
+ * </p>
+ */
+public class SieveEnvelopeMailAdapter extends SieveMailAdapter implements
+        EnvelopeAccessors {
+    /**
+     * The FROM address used in the SMTP MAIL command.
+     */
+    private String fieldEnvelopeFrom;
+
+    /**
+     * The TO address used in the SMTP RCPT command that resulted in this
+     * message getting delivered to this user.
+     */
+    private String fieldEnvelopeTo;
+
+    /**
+     * Constructor for SieveEnvelopeMailAdapter.
+     * 
+     * @param message
+     */
+    public SieveEnvelopeMailAdapter(MimeMessage message) {
+        super(message);
+    }
+
+    /**
+     * Method getEnvelopes.
+     * 
+     * @return Map
+     */
+    protected Map<String, String> getEnvelopes() {
+        Map<String, String> envelopes = new HashMap<String, String>(2);
+        if (null != getEnvelopeFrom())
+            envelopes.put("From", getEnvelopeFrom());
+        if (null != getEnvelopeTo())
+            envelopes.put("To", getEnvelopeTo());
+        return envelopes;
+    }
+
+    /**
+     * @see org.apache.jsieve.mail.optional.EnvelopeAccessors#getEnvelope(String)
+     */
+    public List<String> getEnvelope(String name) throws SieveMailException {
+        List<String> values = new ArrayList<String>(1);
+        String value = getEnvelopes().get(name);
+        if (null != value)
+            values.add(value);
+        return values;
+    }
+
+    /**
+     * @see org.apache.jsieve.mail.optional.EnvelopeAccessors#getEnvelopeNames()
+     */
+    public List<String> getEnvelopeNames() throws SieveMailException {
+        return new ArrayList<String>(getEnvelopes().keySet());
+    }
+
+    /**
+     * @see org.apache.jsieve.mail.optional.EnvelopeAccessors#getMatchingEnvelope(String)
+     */
+    public List<String> getMatchingEnvelope(String name) throws SieveMailException {
+        Iterator envelopeNamesIter = getEnvelopeNames().iterator();
+        List<String> matchedEnvelopeValues = new ArrayList<String>(32);
+        while (envelopeNamesIter.hasNext()) {
+            String envelopeName = (String) envelopeNamesIter.next();
+            if (envelopeName.trim().equalsIgnoreCase(name))
+                matchedEnvelopeValues.addAll(getEnvelope(envelopeName));
+        }
+
+        return matchedEnvelopeValues;
+    }
+
+    /**
+     * Returns the from.
+     * 
+     * @return String
+     */
+    public String getEnvelopeFrom() {
+        return fieldEnvelopeFrom;
+    }
+
+    /**
+     * Returns the recipient.
+     * 
+     * @return String
+     */
+    public String getEnvelopeTo() {
+        return fieldEnvelopeTo;
+    }
+
+    /**
+     * Sets the from.
+     * 
+     * @param from
+     *            The from to set
+     */
+    public void setEnvelopeFrom(String from) {
+        fieldEnvelopeFrom = from;
+    }
+
+    /**
+     * Sets the recipient.
+     * 
+     * @param recipient
+     *            The recipient to set
+     */
+    public void setEnvelopeTo(String recipient) {
+        fieldEnvelopeTo = recipient;
+    }
+
+}
diff --git a/trunk/main/src/test/java/org/apache/jsieve/utils/SieveMailAdapter.java b/trunk/main/src/test/java/org/apache/jsieve/utils/SieveMailAdapter.java
new file mode 100644
index 0000000..d86c8c5
--- /dev/null
+++ b/trunk/main/src/test/java/org/apache/jsieve/utils/SieveMailAdapter.java
@@ -0,0 +1,292 @@
+/****************************************************************
+ * 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.jsieve.utils;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.mail.Header;
+import javax.mail.Message;
+import javax.mail.MessagingException;
+import javax.mail.internet.MimeMessage;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.jsieve.SieveContext;
+import org.apache.jsieve.exception.SieveException;
+import org.apache.jsieve.mail.Action;
+import org.apache.jsieve.mail.MailAdapter;
+import org.apache.jsieve.mail.MailUtils;
+import org.apache.jsieve.mail.SieveMailException;
+import org.apache.jsieve.parser.address.SieveAddressBuilder;
+
+/**
+ * <p>
+ * Class SieveMailAdapter implements a mock MailAdapter for testing purposes.
+ * </p>
+ * 
+ * <p>
+ * Being a mock object, Actions are not performed against a mail server, but in
+ * most other respects it behaves as would expect a MailAdapter wrapping a
+ * JavaMail message should. To this extent, it is a useful demonstration of how
+ * to create an implementation of a MailAdapter.
+ */
+public class SieveMailAdapter implements MailAdapter {
+    private Log log = LogFactory.getLog(SieveMailAdapter.class);
+
+    /**
+     * The message being adapted.
+     */
+    private MimeMessage fieldMessage;
+
+    /**
+     * List of Actions to perform.
+     */
+    private List<Action> fieldActions;
+
+    private String contentAsLowerCaseString;
+
+    /**
+     * Constructor for SieveMailAdapter.
+     */
+    private SieveMailAdapter() {
+        super();
+    }
+
+    /**
+     * Constructor for SieveMailAdapter.
+     * 
+     * @param message
+     */
+    public SieveMailAdapter(MimeMessage message) {
+        this();
+        setMessage(message);
+    }
+
+    /**
+     * Returns the message.
+     * 
+     * @return MimeMessage
+     */
+    public MimeMessage getMessage() {
+        return fieldMessage;
+    }
+
+    /**
+     * Sets the message.
+     * 
+     * @param message
+     *            The message to set
+     */
+    protected void setMessage(MimeMessage message) {
+        fieldMessage = message;
+    }
+
+    /**
+     * Returns the List of actions.
+     * 
+     * @return List
+     */
+    public List<Action> getActions() {
+        List<Action> actions = null;
+        if (null == (actions = getActionsBasic())) {
+            updateActions();
+            return getActions();
+        }
+        return actions;
+    }
+
+    /**
+     * Returns a new List of actions.
+     * 
+     * @return List
+     */
+    protected List<Action> computeActions() {
+        return new ArrayList<Action>();
+    }
+
+    /**
+     * Returns the List of actions.
+     * 
+     * @return List
+     */
+    private List<Action> getActionsBasic() {
+        return fieldActions;
+    }
+
+    /**
+     * Adds an Action.
+     * 
+     * @param action
+     *            The action to set
+     */
+    public void addAction(Action action) {
+        getActions().add(action);
+    }
+
+    /**
+     * @see org.apache.jsieve.mail.MailAdapter#executeActions()
+     */
+    public void executeActions() throws SieveException {
+        boolean isDebugEnabled = log.isDebugEnabled();
+        final List<Action> actions = getActions();
+        for (final Action action:actions) {
+            if (isDebugEnabled)
+                log.debug("Executing " + action.toString());
+        }
+
+    }
+
+    /**
+     * Sets the actions.
+     * 
+     * @param actions
+     *            The actions to set
+     */
+    protected void setActions(List<Action> actions) {
+        fieldActions = actions;
+    }
+
+    /**
+     * Updates the actions.
+     */
+    protected void updateActions() {
+        setActions(computeActions());
+    }
+
+    /**
+     * @see org.apache.jsieve.mail.MailAdapter#getHeader(String)
+     */
+    public List<String> getHeader(String name) throws SieveMailException {
+        try {
+            String[] headers = getMessage().getHeader(name);
+            return (headers == null ? new ArrayList<String>(0) : Arrays.asList(headers));
+        } catch (MessagingException ex) {
+            throw new SieveMailException(ex);
+        }
+    }
+
+    /**
+     * @see org.apache.jsieve.mail.MailAdapter#getHeaderNames()
+     */
+    public List<String> getHeaderNames() throws SieveMailException {
+        Set<String> headerNames = new HashSet<String>();
+        try {
+            Enumeration allHeaders = getMessage().getAllHeaders();
+            while (allHeaders.hasMoreElements()) {
+                headerNames.add(((Header) allHeaders.nextElement()).getName());
+            }
+            return new ArrayList<String>(headerNames);
+        } catch (MessagingException ex) {
+            throw new SieveMailException(ex);
+        }
+    }
+
+    /**
+     * @see org.apache.jsieve.mail.MailAdapter#getMatchingHeader(String)
+     */
+    public List<String> getMatchingHeader(String name) throws SieveMailException {
+        return MailUtils.getMatchingHeader(this, name);
+    }
+
+    /**
+     * @see org.apache.jsieve.mail.MailAdapter#getSize()
+     */
+    public int getSize() throws SieveMailException {
+        try {
+            return getMessage().getSize();
+        } catch (MessagingException ex) {
+            throw new SieveMailException(ex);
+        }
+    }
+
+    /**
+     * @see org.apache.jsieve.mail.MailAdapter#getContentType()
+     */
+    public String getContentType() throws SieveMailException {
+        try {
+            return getMessage().getContentType();
+        } catch (MessagingException ex) {
+            throw new SieveMailException(ex);
+        }
+    }
+
+    public Address[] parseAddresses(final String headerName)
+            throws SieveMailException {
+        return parseAddresses(headerName, getMessage());
+    }
+
+    /**
+     * Parses the value from the given message into addresses.
+     * 
+     * @param headerName
+     *            header name, to be matched case insensitively
+     * @param message
+     *            <code>Message</code>, not null
+     * @return <code>Address</code> array, not null possibly empty
+     * @throws SieveMailException
+     */
+    public Address[] parseAddresses(final String headerName,
+            final Message message) throws SieveMailException {
+        try {
+            final SieveAddressBuilder builder = new SieveAddressBuilder();
+
+            for (Enumeration en = message.getAllHeaders(); en.hasMoreElements();) {
+                final Header header = (Header) en.nextElement();
+                final String name = header.getName();
+                if (name.trim().equalsIgnoreCase(headerName)) {
+                    builder.addAddresses(header.getValue());
+                }
+            }
+
+            final Address[] results = builder.getAddresses();
+            return results;
+
+        } catch (MessagingException ex) {
+            throw new SieveMailException(ex);
+        } catch (org.apache.jsieve.parser.generated.address.ParseException ex) {
+            throw new SieveMailException(ex);
+        }
+    }
+
+    public boolean isInBodyText(String phraseCaseInsensitive) throws SieveMailException {
+        try {
+            return contentAsText().indexOf(phraseCaseInsensitive.toLowerCase()) != -1;
+        } catch (MessagingException ex) {
+            throw new SieveMailException(ex);
+        } catch (IOException ex) {
+            throw new SieveMailException(ex);
+        }
+    }
+
+    private String contentAsText() throws IOException, MessagingException {
+        if (contentAsLowerCaseString == null) {
+            contentAsLowerCaseString = getMessage().getContent().toString().toLowerCase();
+        }
+        return contentAsLowerCaseString;
+    }
+
+    public void setContext(SieveContext context) {}
+}
diff --git a/trunk/main/src/test/resources/log4j.properties b/trunk/main/src/test/resources/log4j.properties
new file mode 100644
index 0000000..8124d05
--- /dev/null
+++ b/trunk/main/src/test/resources/log4j.properties
@@ -0,0 +1,27 @@
+#
+#  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.
+#   
+# Set root logger level to DEBUG and its only appender to A1.
+log4j.rootLogger=DEBUG, A1
+
+# A1 is set to be a ConsoleAppender.
+log4j.appender.A1=org.apache.log4j.ConsoleAppender
+
+# A1 uses PatternLayout.
+log4j.appender.A1.layout=org.apache.log4j.PatternLayout
+log4j.appender.A1.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n
diff --git a/trunk/main/src/test/resources/org/apache/jsieve/commandsmap.properties b/trunk/main/src/test/resources/org/apache/jsieve/commandsmap.properties
new file mode 100644
index 0000000..3dba4a1
--- /dev/null
+++ b/trunk/main/src/test/resources/org/apache/jsieve/commandsmap.properties
@@ -0,0 +1,35 @@
+################################################################
+# 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.                                           #
+################################################################
+
+if=org.apache.jsieve.commands.If
+else=org.apache.jsieve.commands.Else
+elsif=org.apache.jsieve.commands.Elsif
+require=org.apache.jsieve.commands.Require
+stop=org.apache.jsieve.commands.Stop
+# RFC3082 - Implementations MUST support these
+keep=org.apache.jsieve.commands.Keep
+discard=org.apache.jsieve.commands.Discard
+redirect=org.apache.jsieve.commands.Redirect
+# RFC3082 - Implementations SHOULD support these
+reject=org.apache.jsieve.commands.optional.Reject
+fileinto=org.apache.jsieve.commands.optional.FileInto
+# JUnit Commands for Testing
+throwtestexception=org.apache.jsieve.commands.ThrowTestException
+# Extension Commands
+log=org.apache.jsieve.commands.extensions.Log
diff --git a/trunk/pom.xml b/trunk/pom.xml
new file mode 100644
index 0000000..6285768
--- /dev/null
+++ b/trunk/pom.xml
@@ -0,0 +1,332 @@
+<?xml version="1.0" encoding="ISO-8859-15"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <!--
+    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.    
+  -->
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>org.apache.james</groupId>
+  <artifactId>jsieve-project</artifactId>
+  <name>Apache JAMES JSieve Project</name>
+  <version>0.4</version>
+  <packaging>pom</packaging>
+  <description>
+    Apache JAMES JSieve Project
+  </description>
+  <parent>
+    <groupId>org.apache.james</groupId>
+    <artifactId>james-project</artifactId>
+    <version>1.5</version>
+  </parent>
+  <url>http://james.apache.org/jsieve/</url>
+  <inceptionYear>2008</inceptionYear>
+  
+  <properties>
+    <!--
+    The website is committed to subversion. This property can be overriden
+    to upload the site to a local staging location.
+    For example, adding the following to ~/.m2/settings.xml will upload
+    to localhost:
+    
+      <profiles>
+        <profile>
+           <id>main</id>
+           <activation>
+              <activeByDefault>true</activeByDefault>
+           </activation>
+           <properties>
+              <james.www>scp://localhost/www</james.www>
+              <james.www.id>localhost</james.www.id>
+      ...
+    -->
+    <!-- General location for site stage -->
+    <james.www>scp://people.apache.org/www/james.apache.org/</james.www>
+    <!-- Project specific location, allowing specific override -->
+    <james.jsieve.www>${james.www}/jsieve/</james.jsieve.www>
+    <!-- Overridding this value allows single set of loopback settings to be maintained -->
+    <james.www.id>jsieve-website</james.www.id>
+  </properties>
+  
+  
+  <modules>
+    <module>main</module>
+    <module>mailet</module>
+    <module>util</module>
+    <module>assemble</module>
+  </modules>
+  
+  <build>
+    <pluginManagement>
+      <plugins>
+        <plugin>
+          <groupId>org.apache.maven.plugins</groupId>
+          <artifactId>maven-compiler-plugin</artifactId>
+          <version>2.0.2</version>
+          <configuration>
+            <source>1.5</source>
+            <target>1.5</target>
+            <encoding>iso8859-1</encoding>
+          </configuration>
+        </plugin> 
+        <plugin>
+          <groupId>org.apache.maven.plugins</groupId>
+          <artifactId>maven-jar-plugin</artifactId>
+          <version>2.2</version>
+          <configuration>
+            <archive>
+            <manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
+              <manifestEntries>
+                <Specification-Title>${pom.name}</Specification-Title>
+                <Specification-Version>${pom.version}</Specification-Version>
+                <Specification-Vendor>The Apache Software Foundation</Specification-Vendor>
+                <Implementation-Title>${pom.name}</Implementation-Title>
+                <Implementation-Version>${pom.version}</Implementation-Version>
+                <Implementation-Vendor>The Apache Software Foundation</Implementation-Vendor>
+                <Implementation-Vendor-Id>org.apache</Implementation-Vendor-Id>
+                <url>${pom.url}</url>
+              </manifestEntries>
+            </archive>
+          </configuration>
+        </plugin>
+        <plugin>
+          <groupId>org.codehaus.mojo</groupId>
+          <artifactId>rat-maven-plugin</artifactId>
+          <version>1.0-alpha-3</version>
+          <executions>
+            <execution>
+              <phase>verify</phase>
+              <goals>
+                <goal>check</goal>
+              </goals>
+            </execution>
+          </executions>
+        </plugin>
+        <plugin>
+          <groupId>org.apache.felix</groupId>
+          <artifactId>maven-bundle-plugin</artifactId>
+          <version>1.4.3</version>
+          <executions>
+            <execution>
+              <id>bundle-manifest</id>
+              <phase>process-classes</phase>
+              <goals>
+                <goal>manifest</goal>
+              </goals>
+            </execution>
+          </executions>
+          <extensions>true</extensions>
+          <configuration>
+            <instructions>
+              <Export-Package>org.apache.jsieve.*</Export-Package>
+              <Embed-Dependency>*;scope=runtime</Embed-Dependency>
+            </instructions>
+          </configuration>
+        </plugin>
+        <plugin>
+          <groupId>org.apache.maven.plugins</groupId>
+          <artifactId>maven-doap-plugin</artifactId>
+          <executions>
+            <execution>
+              <id>site</id>
+              <phase>site</phase>
+              <goals>
+                <goal>generate</goal>
+              </goals>
+            </execution>
+          </executions>
+          <configuration>
+           <doapOptions>
+             <programmingLanguage>java</programmingLanguage>
+             <category>mail</category>
+             <download-page>http://james.apache.org/download.cgi</download-page>
+           </doapOptions>
+  
+           <asfExtOptions>
+             <included>true</included>
+             <pmc>http://james.apache.org</pmc>
+             <name>Apache JAMES</name>
+             <standards>
+              <standard>
+                <id>RFC3028</id>
+                <title>Sieve: A Mail Filtering Language</title>
+                <body>IEFT</body>
+                <url>http://www.ietf.org/rfc/rfc3028.txt</url>
+              </standard>
+             </standards>
+           </asfExtOptions>
+  
+          </configuration>
+        </plugin>
+      </plugins>
+    </pluginManagement>
+  </build>
+
+  <distributionManagement>
+    <site>
+      <id>${james.www.id}</id>
+      <url>${james.jsieve.www}</url>
+    </site>
+  </distributionManagement>
+
+  <issueManagement>
+    <system>JIRA</system>
+    <url>https://issues.apache.org/jira/browse/JSIEVE</url>
+  </issueManagement>
+
+  <scm>
+    <connection>
+      scm:svn:http://svn.apache.org/repos/asf/james/jsieve/tags/jsieve-project-0.4
+    </connection>
+    <developerConnection>
+      scm:svn:https://svn.apache.org/repos/asf/james/jsieve/tags/jsieve-project-0.4
+    </developerConnection>
+    <url>
+      http://svn.apache.org/viewcvs.cgi/james/jsieve/tags/jsieve-project-0.4?root=Apache-SVN
+    </url>
+  </scm>
+
+  <mailingLists>
+    <mailingList>
+	    <name>Apache JAMES Server List</name>
+	    <subscribe>server-dev-subscribe@james.apache.org</subscribe>
+	    <unsubscribe>server-dev-unsubscribe@james.apache.org</unsubscribe>
+	    <post>server-dev@james.apache.org</post>
+	    <archive>http://mail-archives.apache.org/mod_mbox/james-server-dev/</archive>
+    </mailingList>
+  </mailingLists>
+  
+  <dependencyManagement>
+   <dependencies>
+    <dependency>
+      <groupId>org.apache.james</groupId>
+      <artifactId>apache-jsieve</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    
+    <dependency>
+      <groupId>org.apache.james</groupId>
+      <artifactId>apache-jsieve-mailet</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    
+    <dependency>
+      <groupId>org.apache.james</groupId>
+      <artifactId>apache-jsieve-util</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    
+    <dependency>
+      <groupId>org.apache.james</groupId>
+      <artifactId>apache-mailet</artifactId>
+      <version>2.4</version>
+    </dependency>
+    
+    <dependency>
+      <groupId>org.apache.james</groupId>
+      <artifactId>apache-mailet-base</artifactId>
+      <version>1.0</version>
+    </dependency>
+      
+    <dependency>
+      <groupId>org.apache.james</groupId>
+      <artifactId>apache-mime4j</artifactId>
+      <version>0.6</version>
+    </dependency>
+    
+    <dependency>
+      <groupId>commons-logging</groupId>
+      <artifactId>commons-logging</artifactId>
+      <version>1.1.1</version>
+      <exclusions>
+          <exclusion>
+            <groupId>avalon-framework</groupId>
+            <artifactId>avalon-framework</artifactId>
+          </exclusion>
+          <exclusion>
+            <groupId>logkit</groupId>
+            <artifactId>logkit</artifactId>
+          </exclusion>
+          <exclusion>
+            <groupId>log4j</groupId>
+            <artifactId>log4j</artifactId>
+          </exclusion>
+          <exclusion>
+            <groupId>javax.servlet</groupId>
+            <artifactId>servlet-api</artifactId>
+          </exclusion>
+       </exclusions>
+    </dependency>
+
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <version>3.8.1</version>
+      <scope>test</scope>
+    </dependency>
+    
+    <dependency>
+      <groupId>jmock</groupId>
+      <artifactId>jmock</artifactId>
+      <version>1.1.0</version>
+      <scope>test</scope>
+    </dependency>
+    
+    <dependency>
+      <groupId>log4j</groupId>
+      <artifactId>log4j</artifactId>
+      <version>1.2.14</version>
+      <scope>test</scope>
+    </dependency>
+
+    <dependency>
+      <groupId>javax.mail</groupId>
+      <artifactId>mail</artifactId>
+      <version>1.4</version>
+    </dependency>
+    
+    <dependency>
+      <groupId>javax.activation</groupId>
+      <artifactId>activation</artifactId>
+      <version>1.1</version>
+    </dependency>
+   </dependencies>
+  </dependencyManagement>
+
+  <reporting>
+    <plugins>
+      <plugin>
+        <artifactId>maven-site-plugin</artifactId>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-changes-plugin</artifactId>
+        <configuration>
+            <onlyCurrentVersion>true</onlyCurrentVersion>
+            <resolutionIds>Closed</resolutionIds>
+        </configuration>
+        <reportSets>
+            <reportSet>
+                <reports>
+                    <report>jira-report</report>
+                </reports>
+            </reportSet>
+        </reportSets>
+      </plugin>
+    </plugins>
+  </reporting>
+  
+</project>
diff --git a/trunk/src/site/apt/release-notes.apt b/trunk/src/site/apt/release-notes.apt
new file mode 100644
index 0000000..9b3cfb1
--- /dev/null
+++ b/trunk/src/site/apt/release-notes.apt
@@ -0,0 +1,107 @@
+ ------
+ Release Notes
+ ------
+ Apache JAMES Project Team
+ ------
+ 2009-04-09
+ ------
+  
+~~ 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.
+
+Release Notes
+
+Apache JSieve
+
+ jSieve is a Java implementation of the Sieve mail filtering language defined by 
+ RFC 3028. jSieve is implemented as a langauge processor that can be plugged into any
+ internet mail application to add Sieve support.
+
+*Comments, Questions and Issues
+
+ jSieve is a sub-project of Apache James. Please direct your comments and questions to 
+ the relevant James list.
+
+ To report issues, such as bugs, go to 
+ http://issues.apache.org/jira/browse/JSIEVE
+ As jSieve comes with a fairly extensive suite of jUnit Tests, it would be most 
+ helpful for bug reports to be accompanied by an illustrative jUnit test case.
+
+*Licensing and legal issues
+
+ For legal and licensing issues, please look in the legal section of
+ the documentation or read the LICENSE and NOTICE files.
+
+Version 0.3
+ 
+ This release is the first to include mailet and utility modules, in addition to the core parsing
+ library. The configuration API has changed significantly to replace magic singletons with POJOs
+ suitable for IoC. The default configuration mechanism has been retained so though calls have
+ changed, existing configurations should still be compatible.
+ 
+ Utilities include node serializers to xml and sieve script. The mailet module provides Sieve
+ in the James 3 codebase.
+ 
+*Changes   
+
+**Sub-tasks Completed
+
+ * [{{{https://issues.apache.org/jira/browse/JSIEVE-27}JSIEVE-27}}] -         Refactor ComparatorManager
+ 
+ * [{{{https://issues.apache.org/jira/browse/JSIEVE-29}JSIEVE-29}}] -         Refactor ConfigurationManager
+ 
+ * [{{{https://issues.apache.org/jira/browse/JSIEVE-32}JSIEVE-32}}] -         Refactor TestManager
+ 
+ * [{{{https://issues.apache.org/jira/browse/JSIEVE-37}JSIEVE-37}}] -         Push main source down a level
+ 
+ * [{{{https://issues.apache.org/jira/browse/JSIEVE-38}JSIEVE-38}}] -         Modular Ant Build
+ 
+ * [{{{https://issues.apache.org/jira/browse/JSIEVE-44}JSIEVE-44}}] -         Create Check Module
+ 
+ * [{{{https://issues.apache.org/jira/browse/JSIEVE-45}JSIEVE-45}}] -         Extract Reusable Ant Macros
+ 
+ * [{{{https://issues.apache.org/jira/browse/JSIEVE-46}JSIEVE-46}}] -         Unified Distribution
+ 
+ * [{{{https://issues.apache.org/jira/browse/JSIEVE-48}JSIEVE-48}}] -         Release Quality Maven Build
+    
+** Bugs Fixed
+
+ * [{{{href='https://issues.apache.org/jira/browse/JSIEVE-49}JSIEVE-49}}] -         CPU spins when :matches expression contains &quot;*************&quot;
+
+** Improvements Made
+
+ * [{{{https://issues.apache.org/jira/browse/JSIEVE-47}JSIEVE-47}}] -         Access to script comments
+ 
+ * [{{{https://issues.apache.org/jira/browse/JSIEVE-52}JSIEVE-52}}] -         Check Support For Numeric Quantifiers
+    
+** New Features Added
+
+ * [{{{https://issues.apache.org/jira/browse/JSIEVE-43}JSIEVE-43}}] -         Add API for generating a Sieve script from a parse tree 
+ 
+ * [{{{https://issues.apache.org/jira/browse/JSIEVE-50}JSIEVE-50}}] -         Sieve-In-XML (Experimental Preview)
+ 
+                
+** Tasks Completed
+
+ * [{{{https://issues.apache.org/jira/browse/JSIEVE-16}JSIEVE-16}}] -         Singletons -> IoC
+ 
+ * [{{{https://issues.apache.org/jira/browse/JSIEVE-24}JSIEVE-24}}] -         Include build time libraries (javacc, javamail, activation) in the source distribution
+ 
+ * [{{{https://issues.apache.org/jira/browse/JSIEVE-33}JSIEVE-33}}] -         Remove .junit. package name from tests.
+ 
+ * [{{{https://issues.apache.org/jira/browse/JSIEVE-34}JSIEVE-34}}] -         make jsieve a multimodule project
+
diff --git a/trunk/src/site/resources/images/asf-logo-reduced.gif b/trunk/src/site/resources/images/asf-logo-reduced.gif
new file mode 100644
index 0000000..93cc102
--- /dev/null
+++ b/trunk/src/site/resources/images/asf-logo-reduced.gif
Binary files differ
diff --git a/trunk/src/site/resources/images/james-jsieve-logo.gif b/trunk/src/site/resources/images/james-jsieve-logo.gif
new file mode 100644
index 0000000..9c7e34f
--- /dev/null
+++ b/trunk/src/site/resources/images/james-jsieve-logo.gif
Binary files differ
diff --git a/trunk/src/site/site.xml b/trunk/src/site/site.xml
new file mode 100644
index 0000000..786579d
--- /dev/null
+++ b/trunk/src/site/site.xml
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!--
+  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.    
+-->

+<project name="jSieve">
+  <bannerLeft>
+    <name>JAMES jSieve</name>
+    <src>images/james-jsieve-logo.gif</src>
+    <href>http://james.apache.org/</href>
+  </bannerLeft>
+
+  <bannerRight>

+    <name>The Apache Software Foundation</name>

+    <src>images/asf-logo-reduced.gif</src>

+    <href>http://www.apache.org/index.html</href>

+  </bannerRight>

+

+  <skin>
+    <groupId>org.apache.james</groupId>
+    <artifactId>maven-skin</artifactId>
+    <version>1.2</version>
+  </skin>
+
+  <body>
+    <menu name="jSieve">
+      <item name="Main" href="http://james.apache.org/jsieve/main/index.html" />

+      <item name="Mailet" href="http://james.apache.org/jsieve/mailet/index.html" />

+      <item name="Util" href="http://james.apache.org/jsieve/util/index.html" />
+      <item name="Changes" href="http://james.apache.org/jsieve/jira-report.html" />
+      <item name="Release Notes" href="http://james.apache.org/jsieve/release-notes.html" />
+      <item name="JavaDocs" href="http://james.apache.org/jsieve/apidocs/index.html" />
+    </menu>
+
+    <menu name='Specifications' inherit='bottom'>
+      <item name="RFC 2234 ABNF"
+        href="http://www.ietf.org/rfc/rfc2234.txt" />
+      <item name="RFC 2244 ACAP"
+        href="http://www.ietf.org/rfc/rfc2244.txt" />
+      <item name="RFC 2298 MDN"
+        href="http://www.ietf.org/rfc/rfc2298.txt" />
+      <item name="RFC 5228 Sieve"
+        href="http://tools.ietf.org/html/rfc5228" />
+      <item name='RFC 4790 IAPCR'
+        href='http://tools.ietf.org/html/rfc4790'/>
+      <item name='RFC 5173 Body Extension'
+        href='http://tools.ietf.org/html/rfc5173'/>
+    </menu>
+
+    <menu name='0.3 Release'>
+      <item name='Release Notes' href='0.3/release-notes.html'/>
+      <item name='Documentation' href='0.3/index.html'/>
+    </menu>
+  </body>
+</project>
diff --git a/trunk/src/site/xdoc/index.xml b/trunk/src/site/xdoc/index.xml
new file mode 100644
index 0000000..2aa246e
--- /dev/null
+++ b/trunk/src/site/xdoc/index.xml
@@ -0,0 +1,117 @@
+<?xml version="1.0"?>
+<!--
+  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.  
+-->
+<document>
+
+  <properties>
+    <title>Overview</title>
+    <author email="server-dev@james.apache.org">jSieve Project</author>
+  </properties>
+
+  <body>
+    <section name="What is jSieve?">
+      <p>
+        jSieve is a Java implementation of the Sieve mail filtering
+        language defined by
+        <a href='http://www.rfc-editor.org/rfc/rfc3028.txt'>RFC 3028</a>
+        . jSieve is implemented as a language processor that can be
+        plugged into any internet mail application to add Sieve support.
+      </p>
+      <p>
+        jSieve is a subproject of
+        <a href='http://james.apache.org'>Apache JAMES</a>
+        . All who are interested in developing jSieve and JAMES will be
+        warmly welcomed on the
+        <a href='mail-lists.html'>mailing lists</a>
+        .
+      </p>
+      <p>jSieve consists of three products:</p>
+      <ol>
+        <li>
+          <a href='main/index.html'>Apache JSieve</a>
+          is a Sieve library coded in Java.
+        </li>
+        <li>
+          <a href='mailet/index.html'>Apache JSieve Mailet</a>
+          is a
+          <a href='http://james.apache.org/mailet'>Mailet</a>
+          which builds on the JSieve library to create a comprehensive
+          server side Sieve filtering system.
+        </li>
+        <li>
+          <a href='util/index.html'>Apache JSieve Utilities</a>
+          contains utility classes helpful when using Sieve but not
+          considered sufficiently core to be included in the main
+          library.
+        </li>
+      </ol>
+      <subsection name='What is Sieve?'>
+        <p>
+          Sieve is an extensible mail filtering language. It's limited
+          expressiveness (no loops or variables, no tests with side
+          effects) allows user created scripts to be run safely on email
+          servers. Sieve is targeted at the final delivery phase (where
+          an incoming email is transferred to a user's mailbox).
+        </p>
+        <p>
+          Sieve scripts are composed of commands. Control commands
+          manage the execution of the script. Test commands define
+          side-effect free criteria. Action commands are mail operations
+          to be performed.
+        </p>
+      </subsection>
+    </section>
+    <section name='Extensions Supported'>
+      <subsection name='Body Extension'>
+        <abbr title='Sieve Email Filtering: Body Extension'><a href='http://tools.ietf.org/html/rfc5173'>RFC 5173</a></abbr> describes 
+        an extension command that tests the body of an email for one or more strings.
+      </subsection>
+      <subsection name='Sieve-In-Xml'>
+          <p>
+    <a href='http://tools.ietf.org/html/draft-freed-sieve-in-xml-04' rel='tag'>Sieve-in-xml</a> is an Internet Draft.
+    It describes a method for converting Sieve scripts to and from XML fragments.
+    <a href='util/index.html'>JSieve Utilities</a> contains an experimental preview of a subset of this draft
+    to allow early feedback. <a href='util/index.html#Sieve In Xml'>More...</a>
+        </p>
+      </subsection>
+    </section>
+    <section name="News">
+        <h4>Jun/2008 - jSieve-0.3 released</h4>
+        <p>
+          The Apache JAMES team is happy to announce the availability of
+          <a href='http://james.apache.org/download.cgi#Apache%20JSieve'>
+            Apache jSieve 0.3
+          </a>
+          . This is the first modular release including a filtering 
+          <a href='http://james.apache.org/mailet'>mailet</a> as well as many fixes 
+          and improvements. See the 
+          <a href='http://james.apache.org/jsieve/0.3/release-notes.html'>release notes</a> 
+          for more details.
+        </p>
+        <h4>Aug/2008 - jSieve-0.2 released</h4>
+        <p>
+          The Apache JAMES team is happy to announce the availability of
+          <a href='http://james.apache.org/download.cgi#jsieve'>
+            Apache jSieve 0.2
+          </a>
+          . This first public release is a major milestone for JSieve.
+        </p>
+    </section>
+  </body>
+</document>
diff --git a/trunk/util/BUILDING.txt b/trunk/util/BUILDING.txt
new file mode 100644
index 0000000..7c8f4ea
--- /dev/null
+++ b/trunk/util/BUILDING.txt
@@ -0,0 +1,54 @@
+#############################################################################
+# BUILDING JSIEVE WITH MAVEN
+#############################################################################
+
+1) Install maven 2.0.9
+
+2) Add maven to your path and make sure you also have a JAVA_HOME environment
+   variable to point a java 1.4+ virtual machine.
+
+3) Run "mvn package"
+
+
+#############################################################################
+# BUILDING JSIEVE WITH ANT
+#############################################################################
+
+The source trunk for JSIEVE no longer includes Ant, so in order to
+build JSIEVE, you will need to install Ant as well as acquire JSIEVE
+source from subversion or a source tarball.
+
+JSIEVE uses JavaCC during the build process to generate the script parser.
+Unfortunately, though the latest codebase is now available under the BSD
+license there is (at this time) no official open sourced release. So
+to build JSIEVE download the JavaCC 4.0 release from 
+https://javacc.dev.java.net/. Extract the javacc.jar and copy into the
+root of the JSIEVE directory. Rename this to javacc-4.0.jar.
+
+You also have to download activation-1.1.1.jar and mail-1.4.1.jar files to 
+the root folder in order to build/run unit tests.
+
+Steps:
+
+1) Install Ant (v1.6.5 as of the time of this writing)
+
+2) Add Ant to your path.  For me, I do the following:
+   $ tar zxvf apache-ant-1.6.5-bin.tar.gz
+   $ mv apache-ant-1.6.5 /usr/local
+   $ ln -sf /usr/local/apache-ant-1.6.5 /usr/local/ant
+   $ ln -sf /usr/local/ant/bin/ant /usr/local/bin/ant
+
+3) Change any JSIEVE-related build scripts that you might have to call
+    Ant directly, e.g.:  ./build.sh <target> ==> ant <target>
+
+That's it.  Please contact general@james.apache.org if you have any
+problems.
+
+##############################################################################
+# SET UP JSIEVE INSIDE ECLIPSE
+##############################################################################
+
+You can use maven facility to setup an eclipse project:
+mvn eclipse:eclipse
+
+You can also use q4e plugin from eclipse.
\ No newline at end of file
diff --git a/trunk/util/LICENSE.apache b/trunk/util/LICENSE.apache
new file mode 100644
index 0000000..94d8c58
--- /dev/null
+++ b/trunk/util/LICENSE.apache
@@ -0,0 +1,176 @@
+                                 Apache License

+                           Version 2.0, January 2004

+                        http://www.apache.org/licenses/

+

+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

+

+   1. Definitions.

+

+      "License" shall mean the terms and conditions for use, reproduction,

+      and distribution as defined by Sections 1 through 9 of this document.

+

+      "Licensor" shall mean the copyright owner or entity authorized by

+      the copyright owner that is granting the License.

+

+      "Legal Entity" shall mean the union of the acting entity and all

+      other entities that control, are controlled by, or are under common

+      control with that entity. For the purposes of this definition,

+      "control" means (i) the power, direct or indirect, to cause the

+      direction or management of such entity, whether by contract or

+      otherwise, or (ii) ownership of fifty percent (50%) or more of the

+      outstanding shares, or (iii) beneficial ownership of such entity.

+

+      "You" (or "Your") shall mean an individual or Legal Entity

+      exercising permissions granted by this License.

+

+      "Source" form shall mean the preferred form for making modifications,

+      including but not limited to software source code, documentation

+      source, and configuration files.

+

+      "Object" form shall mean any form resulting from mechanical

+      transformation or translation of a Source form, including but

+      not limited to compiled object code, generated documentation,

+      and conversions to other media types.

+

+      "Work" shall mean the work of authorship, whether in Source or

+      Object form, made available under the License, as indicated by a

+      copyright notice that is included in or attached to the work

+      (an example is provided in the Appendix below).

+

+      "Derivative Works" shall mean any work, whether in Source or Object

+      form, that is based on (or derived from) the Work and for which the

+      editorial revisions, annotations, elaborations, or other modifications

+      represent, as a whole, an original work of authorship. For the purposes

+      of this License, Derivative Works shall not include works that remain

+      separable from, or merely link (or bind by name) to the interfaces of,

+      the Work and Derivative Works thereof.

+

+      "Contribution" shall mean any work of authorship, including

+      the original version of the Work and any modifications or additions

+      to that Work or Derivative Works thereof, that is intentionally

+      submitted to Licensor for inclusion in the Work by the copyright owner

+      or by an individual or Legal Entity authorized to submit on behalf of

+      the copyright owner. For the purposes of this definition, "submitted"

+      means any form of electronic, verbal, or written communication sent

+      to the Licensor or its representatives, including but not limited to

+      communication on electronic mailing lists, source code control systems,

+      and issue tracking systems that are managed by, or on behalf of, the

+      Licensor for the purpose of discussing and improving the Work, but

+      excluding communication that is conspicuously marked or otherwise

+      designated in writing by the copyright owner as "Not a Contribution."

+

+      "Contributor" shall mean Licensor and any individual or Legal Entity

+      on behalf of whom a Contribution has been received by Licensor and

+      subsequently incorporated within the Work.

+

+   2. Grant of Copyright License. Subject to the terms and conditions of

+      this License, each Contributor hereby grants to You a perpetual,

+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable

+      copyright license to reproduce, prepare Derivative Works of,

+      publicly display, publicly perform, sublicense, and distribute the

+      Work and such Derivative Works in Source or Object form.

+

+   3. Grant of Patent License. Subject to the terms and conditions of

+      this License, each Contributor hereby grants to You a perpetual,

+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable

+      (except as stated in this section) patent license to make, have made,

+      use, offer to sell, sell, import, and otherwise transfer the Work,

+      where such license applies only to those patent claims licensable

+      by such Contributor that are necessarily infringed by their

+      Contribution(s) alone or by combination of their Contribution(s)

+      with the Work to which such Contribution(s) was submitted. If You

+      institute patent litigation against any entity (including a

+      cross-claim or counterclaim in a lawsuit) alleging that the Work

+      or a Contribution incorporated within the Work constitutes direct

+      or contributory patent infringement, then any patent licenses

+      granted to You under this License for that Work shall terminate

+      as of the date such litigation is filed.

+

+   4. Redistribution. You may reproduce and distribute copies of the

+      Work or Derivative Works thereof in any medium, with or without

+      modifications, and in Source or Object form, provided that You

+      meet the following conditions:

+

+      (a) You must give any other recipients of the Work or

+          Derivative Works a copy of this License; and

+

+      (b) You must cause any modified files to carry prominent notices

+          stating that You changed the files; and

+

+      (c) You must retain, in the Source form of any Derivative Works

+          that You distribute, all copyright, patent, trademark, and

+          attribution notices from the Source form of the Work,

+          excluding those notices that do not pertain to any part of

+          the Derivative Works; and

+

+      (d) If the Work includes a "NOTICE" text file as part of its

+          distribution, then any Derivative Works that You distribute must

+          include a readable copy of the attribution notices contained

+          within such NOTICE file, excluding those notices that do not

+          pertain to any part of the Derivative Works, in at least one

+          of the following places: within a NOTICE text file distributed

+          as part of the Derivative Works; within the Source form or

+          documentation, if provided along with the Derivative Works; or,

+          within a display generated by the Derivative Works, if and

+          wherever such third-party notices normally appear. The contents

+          of the NOTICE file are for informational purposes only and

+          do not modify the License. You may add Your own attribution

+          notices within Derivative Works that You distribute, alongside

+          or as an addendum to the NOTICE text from the Work, provided

+          that such additional attribution notices cannot be construed

+          as modifying the License.

+

+      You may add Your own copyright statement to Your modifications and

+      may provide additional or different license terms and conditions

+      for use, reproduction, or distribution of Your modifications, or

+      for any such Derivative Works as a whole, provided Your use,

+      reproduction, and distribution of the Work otherwise complies with

+      the conditions stated in this License.

+

+   5. Submission of Contributions. Unless You explicitly state otherwise,

+      any Contribution intentionally submitted for inclusion in the Work

+      by You to the Licensor shall be under the terms and conditions of

+      this License, without any additional terms or conditions.

+      Notwithstanding the above, nothing herein shall supersede or modify

+      the terms of any separate license agreement you may have executed

+      with Licensor regarding such Contributions.

+

+   6. Trademarks. This License does not grant permission to use the trade

+      names, trademarks, service marks, or product names of the Licensor,

+      except as required for reasonable and customary use in describing the

+      origin of the Work and reproducing the content of the NOTICE file.

+

+   7. Disclaimer of Warranty. Unless required by applicable law or

+      agreed to in writing, Licensor provides the Work (and each

+      Contributor provides its Contributions) on an "AS IS" BASIS,

+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or

+      implied, including, without limitation, any warranties or conditions

+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A

+      PARTICULAR PURPOSE. You are solely responsible for determining the

+      appropriateness of using or redistributing the Work and assume any

+      risks associated with Your exercise of permissions under this License.

+

+   8. Limitation of Liability. In no event and under no legal theory,

+      whether in tort (including negligence), contract, or otherwise,

+      unless required by applicable law (such as deliberate and grossly

+      negligent acts) or agreed to in writing, shall any Contributor be

+      liable to You for damages, including any direct, indirect, special,

+      incidental, or consequential damages of any character arising as a

+      result of this License or out of the use or inability to use the

+      Work (including but not limited to damages for loss of goodwill,

+      work stoppage, computer failure or malfunction, or any and all

+      other commercial damages or losses), even if such Contributor

+      has been advised of the possibility of such damages.

+

+   9. Accepting Warranty or Additional Liability. While redistributing

+      the Work or Derivative Works thereof, You may choose to offer,

+      and charge a fee for, acceptance of support, warranty, indemnity,

+      or other liability obligations and/or rights consistent with this

+      License. However, in accepting such obligations, You may act only

+      on Your own behalf and on Your sole responsibility, not on behalf

+      of any other Contributor, and only if You agree to indemnify,

+      defend, and hold each Contributor harmless for any liability

+      incurred by, or claims asserted against, such Contributor by reason

+      of your accepting any such warranty or additional liability.

+

+   END OF TERMS AND CONDITIONS	
\ No newline at end of file
diff --git a/trunk/util/NOTICE.base b/trunk/util/NOTICE.base
new file mode 100644
index 0000000..76a0011
--- /dev/null
+++ b/trunk/util/NOTICE.base
@@ -0,0 +1,10 @@
+   =========================================================================

+   ==      NOTICE file for use with the Apache License, Version 2.0,      ==

+   =========================================================================

+

+   Apache James JSieve Library

+   Copyright 2009 The Apache Software Foundation

+

+   This product includes software developed at

+   The Apache Software Foundation (http://www.apache.org/).

+
diff --git a/trunk/util/build.xml b/trunk/util/build.xml
new file mode 100644
index 0000000..5f96689
--- /dev/null
+++ b/trunk/util/build.xml
@@ -0,0 +1,91 @@
+<?xml version="1.0"?>
+<!-- 
+==========================================================================
+
+ jSieve build file 
+
+
+  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.                                           
+ 
+==============================================================================
+-->
+
+<project name='jsieve-util' default="main" basedir=".">
+    
+    <!--
+    Give user a chance to override without editing this file
+    (and without typing -D each time he compiles it)
+    -->
+    <property file=".ant.properties" />
+    <property file="${user.home}/.ant.properties" />
+    <property file="../include.properties" />
+    <property file="../default.properties" />
+
+    <path id="mail.class.path">
+        <pathelement location='${mail.jar}' />
+        <pathelement location='${activation.jar}' />
+    </path>
+
+    <path id="project.class.path">
+        <pathelement location="${commons-logging.jar}" />
+        <pathelement location="${log4j.jar}" />
+        <pathelement location='${lib.jsieve.dir}/${name}-${version}.jar' />
+        <pathelement location="${mailet.jar}" />
+        <path refid="mail.class.path" />
+        <pathelement path="${java.class.path}" />
+        <pathelement path="${build.classes}" />
+    </path>
+
+    <path id="project.test.class.path">
+        <path refid="project.class.path" />
+        <pathelement location="${junit.jar}" />
+        <pathelement location="${jmock.jar}" />
+    </path>
+    
+    <import file='../build.xml'/>
+    
+    <target name="main" depends="run-tests" description=" - main target"/>
+
+    <target name="prepare" description=" - paparations                   [internal]">
+        <CheckMailConditions/>
+    </target>
+
+    <target name="compile" depends="prepare" description=" - compiles test and main source">
+        <CompileMain>
+            <src path="${java.dir}" />
+        </CompileMain>
+        <CompileTests/>
+    </target>
+
+    <target name="jar" depends="compile" description=" - jars classes">
+        <Jar name='util'/>    
+    </target>
+
+    <target name="run-tests" depends="jar" description=" - runs all tests">
+        <RunTests/>
+    </target>
+
+    <target name="clean" description=" - cleans build files">
+        <Clean/>
+    </target>
+
+    <target name="usage" description=" - prints help">
+        <Usage/>
+    </target>
+
+</project>
diff --git a/trunk/util/pom.xml b/trunk/util/pom.xml
new file mode 100644
index 0000000..000e25f
--- /dev/null
+++ b/trunk/util/pom.xml
@@ -0,0 +1,226 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <!--
+    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.    
+  -->
+  <parent>
+    <artifactId>jsieve-project</artifactId>
+    <groupId>org.apache.james</groupId>
+    <version>0.4</version>
+  </parent>
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>org.apache.james</groupId>
+  <artifactId>apache-jsieve-util</artifactId>
+  <name>Apache JAMES jSieve Utilities</name>
+  <version>0.4</version>
+  <packaging>jar</packaging>
+  <description>
+Apache jSieve is a server side mail filtering system 
+implementing RFC3028. Apache jSieve is developed by the 
+JAMES project. This module contains additional utilities.
+  </description>
+  <url>http://james.apache.org/jsieve</url>
+  <inceptionYear>2004</inceptionYear>
+
+  <properties>
+    <!--
+    The website is committed to subversion. This property can be overriden
+    to upload the site to a local staging location.
+    For example, adding the following to ~/.m2/settings.xml will upload
+    to localhost:
+    
+      <profiles>
+        <profile>
+           <id>main</id>
+           <activation>
+              <activeByDefault>true</activeByDefault>
+           </activation>
+           <properties>
+              <james.www>scp://localhost/www</james.www>
+              <james.www.id>localhost</james.www.id>
+      ...
+    -->
+    <!-- General location for site stage -->
+    <james.www>scp://people.apache.org/www/james.apache.org/</james.www>
+    <!-- Project specific location, allowing specific override -->
+    <james.jsieve.www>${james.www}/jsieve/</james.jsieve.www>
+    <!-- Overridding this value allows single set of loopback settings to be maintained -->
+    <james.www.id>jsieve-website</james.www.id>
+  </properties>
+
+  <distributionManagement>
+    <site>
+      <id>${james.www.id}</id>
+      <url>${james.jsieve.www}/util</url>
+    </site>
+  </distributionManagement>
+
+  <issueManagement>
+    <system>JIRA</system>
+    <url>http://issues.apache.org/jira/browse/JSIEVE</url>
+  </issueManagement>
+
+  <scm>
+    <connection>scm:svn:http://svn.apache.org/repos/asf/james/jsieve/tags/jsieve-project-0.4</connection>
+    <developerConnection>scm:svn:https://svn.apache.org/repos/asf/james/jsieve/tags/jsieve-project-0.4</developerConnection>
+    <url>http://svn.apache.org/viewvc/james/jsieve/tags/jsieve-project-0.4</url>
+  </scm>
+  
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.james</groupId>
+      <artifactId>apache-jsieve</artifactId>
+    </dependency>
+    
+    <dependency>
+      <groupId>commons-logging</groupId>
+      <artifactId>commons-logging</artifactId>
+    </dependency>
+
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <scope>test</scope>
+    </dependency>
+    
+    <dependency>
+      <groupId>log4j</groupId>
+      <artifactId>log4j</artifactId>
+      <scope>test</scope>
+    </dependency>
+
+    <dependency>
+      <groupId>javax.mail</groupId>
+      <artifactId>mail</artifactId>
+    </dependency>
+    
+    <dependency>
+      <groupId>javax.activation</groupId>
+      <artifactId>activation</artifactId>
+    </dependency>
+
+    <dependency>
+      <groupId>jmock</groupId>
+      <artifactId>jmock</artifactId>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <resources>
+      <resource>
+        <directory>src/main/resources</directory>
+      </resource>
+    </resources>
+    
+    <plugins>  
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-jar-plugin</artifactId>
+      </plugin>
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>rat-maven-plugin</artifactId>
+      </plugin>
+      <plugin>
+      	<groupId>org.apache.felix</groupId>
+      	<artifactId>maven-bundle-plugin</artifactId>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-doap-plugin</artifactId>
+      </plugin>
+    </plugins>
+  </build>
+
+  <reporting>
+    <plugins>
+      <plugin>
+        <artifactId>maven-surefire-plugin</artifactId>
+        <version>2.4.3</version>
+      </plugin>
+      <plugin>
+        <artifactId>maven-surefire-report-plugin</artifactId>
+        <version>2.4.3</version>
+      </plugin> 
+      <plugin>
+        <artifactId>maven-javadoc-plugin</artifactId>
+        <version>2.4</version>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-jxr-plugin</artifactId>
+        <version>2.1</version>
+      </plugin>
+      <plugin>
+        <artifactId>maven-pmd-plugin</artifactId>
+        <version>2.4</version>
+        <configuration>
+          <targetJdk>1.5</targetJdk>
+          <rulesets>
+            <ruleset>/rulesets/basic.xml</ruleset>
+            <ruleset>/rulesets/controversial.xml</ruleset>
+          </rulesets>
+          <format>xml</format>
+          <linkXref>true</linkXref>
+          <sourceEncoding>utf-8</sourceEncoding>
+          <minimumTokens>100</minimumTokens>
+        </configuration>
+      </plugin>
+      <plugin>
+        <artifactId>maven-site-plugin</artifactId>
+        <version>2.0-beta-7</version>
+      </plugin>
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>rat-maven-plugin</artifactId>
+        <version>1.0-alpha-3</version>
+        <configuration>
+          <excludes>
+            <exclude>NOTICE.base</exclude>
+            <exclude>LICENSE.apache</exclude>
+            <exclude>release.properties</exclude>
+          </excludes>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>taglist-maven-plugin</artifactId>
+        <version>2.2</version>
+      </plugin>
+    </plugins>
+  </reporting>
+  
+  <mailingLists>
+    <mailingList>
+      <name>Apache James User</name>
+      <subscribe>server-user-subscribe@james.apache.org</subscribe>
+      <unsubscribe>server-user-unsubscribe@james.apache.org</unsubscribe>
+      <post>server-user@james.apache.org</post>
+      <archive>http://mail-archives.apache.org/mod_mbox/james-server-user/</archive>
+    </mailingList>
+    <mailingList>
+      <name>Apache James Developer</name>
+      <subscribe>server-dev-subscribe@james.apache.org</subscribe>
+      <unsubscribe>server-dev-unsubscribe@james.apache.org</unsubscribe>
+      <post>server-dev@james.apache.org</post>
+      <archive>http://mail-archives.apache.org/mod_mbox/james-server-dev/</archive>
+    </mailingList>
+  </mailingLists>
+
+</project>
diff --git a/trunk/util/src/main/appended-resources/supplemental-models.xml b/trunk/util/src/main/appended-resources/supplemental-models.xml
new file mode 100644
index 0000000..bdb831b
--- /dev/null
+++ b/trunk/util/src/main/appended-resources/supplemental-models.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<supplementalDataModels>
+  <!--
+  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.
+  -->
+  <!-- Also added manually in pom.xml appeneded text because test
+       dependencies are not automatically included by the NOTICE 
+       generator -->
+  <supplement>
+    <project>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <name>JUnit</name>
+      <url>http://www.junit.org/</url>
+      <organization>
+      	<name>Kent Beck, Erich Gamma, and David Saff</name>
+      </organization>
+      <licenses>
+        <license>
+          <name>Common Public License Version 1.0</name>
+          <url>http://www.opensource.org/licenses/cpl.php</url>
+        </license>
+      </licenses>
+    </project>
+  </supplement>
+</supplementalDataModels>
\ No newline at end of file
diff --git a/trunk/util/src/main/java/org/apache/jsieve/util/HaltTraversalException.java b/trunk/util/src/main/java/org/apache/jsieve/util/HaltTraversalException.java
new file mode 100644
index 0000000..b96318f
--- /dev/null
+++ b/trunk/util/src/main/java/org/apache/jsieve/util/HaltTraversalException.java
@@ -0,0 +1,44 @@
+/****************************************************************
+ * 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.jsieve.util;
+
+import org.apache.jsieve.exception.SieveException;
+
+/**
+ * Indicators that the node traversal should be abandoned.
+ */
+public class HaltTraversalException extends SieveException {
+
+    private static final long serialVersionUID = -1632011752987659040L;
+
+    public HaltTraversalException() {
+    }
+
+    public HaltTraversalException(String message) {
+        super(message);
+    }
+
+    public HaltTraversalException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public HaltTraversalException(Throwable cause) {
+        super(cause);
+    }
+}
diff --git a/trunk/util/src/main/java/org/apache/jsieve/util/InvalidXmlException.java b/trunk/util/src/main/java/org/apache/jsieve/util/InvalidXmlException.java
new file mode 100644
index 0000000..1607ae0
--- /dev/null
+++ b/trunk/util/src/main/java/org/apache/jsieve/util/InvalidXmlException.java
@@ -0,0 +1,34 @@
+/****************************************************************
+ * 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.jsieve.util;
+
+import java.io.IOException;
+
+public class InvalidXmlException extends IOException {
+
+    private static final long serialVersionUID = -7530730365576151197L;
+
+    public InvalidXmlException() {
+        super();
+    }
+
+    public InvalidXmlException(String s) {
+        super(s);
+    }
+}
diff --git a/trunk/util/src/main/java/org/apache/jsieve/util/NodeHandler.java b/trunk/util/src/main/java/org/apache/jsieve/util/NodeHandler.java
new file mode 100644
index 0000000..579922b
--- /dev/null
+++ b/trunk/util/src/main/java/org/apache/jsieve/util/NodeHandler.java
@@ -0,0 +1,209 @@
+/****************************************************************
+ * 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.jsieve.util;
+
+import org.apache.jsieve.parser.generated.ASTargument;
+import org.apache.jsieve.parser.generated.ASTarguments;
+import org.apache.jsieve.parser.generated.ASTblock;
+import org.apache.jsieve.parser.generated.ASTcommand;
+import org.apache.jsieve.parser.generated.ASTcommands;
+import org.apache.jsieve.parser.generated.ASTstart;
+import org.apache.jsieve.parser.generated.ASTstring;
+import org.apache.jsieve.parser.generated.ASTstring_list;
+import org.apache.jsieve.parser.generated.ASTtest;
+import org.apache.jsieve.parser.generated.ASTtest_list;
+import org.apache.jsieve.parser.generated.SimpleNode;
+
+/**
+ * Presents a low level reporting view of a Sieve node tree.
+ * Familiarity with the 
+ * <a href='http://james.apache.org/jsieve/'>JSieve</a> implementation is assumed.
+ * Anyone requiring a high level view should see {@link SieveHandler}.
+ * 
+ * @see NodeTraverser
+ * @see SieveHandler
+ */
+public interface NodeHandler {
+    
+    /**
+     * Starts a tree traversal.
+     * @throws HaltTraversalException
+     */
+    public void start() throws HaltTraversalException;
+    
+    /**
+     * Ends a tree traveral.
+     * @throws HaltTraversalException
+     */
+    public void end() throws HaltTraversalException;
+    
+    /**
+     * Starts traversal of given node.
+     * @param node not null
+     * @throws HaltTraversalException
+     */
+    public void start(SimpleNode node) throws HaltTraversalException;
+    
+    /**
+     * Ends traversal of given node.
+     * @param node not null
+     * @throws HaltTraversalException
+     */
+    public void end(SimpleNode node) throws HaltTraversalException;
+    
+    /**
+     * Starts traversal of given node.
+     * @param node not null
+     * @throws HaltTraversalException
+     */
+    public void start(ASTstart node) throws HaltTraversalException;
+    
+    /**
+     * Ends traversal of given node.
+     * @param node not null
+     * @throws HaltTraversalException
+     */
+    public void end(ASTstart node) throws HaltTraversalException;
+
+    /**
+     * Starts traversal of given node.
+     * @param node not null
+     * @throws HaltTraversalException
+     */
+    public void start(ASTcommands node) throws HaltTraversalException;
+    
+    /**
+     * Ends traversal of given node.
+     * @param node not null
+     * @throws HaltTraversalException
+     */
+    public void end(ASTcommands node) throws HaltTraversalException;
+
+    /**
+     * Starts traversal of given node.
+     * @param node not null
+     * @throws HaltTraversalException
+     */
+    public void start(ASTcommand node) throws HaltTraversalException;
+    
+    /**
+     * Ends traversal of given node.
+     * @param node not null
+     * @throws HaltTraversalException
+     */
+    public void end(ASTcommand node) throws HaltTraversalException;
+
+    /**
+     * Starts traversal of given node.
+     * @param node not null
+     * @throws HaltTraversalException
+     */
+    public void start(ASTblock node) throws HaltTraversalException;
+    
+    /**
+     * Ends traversal of given node.
+     * @param node not null
+     * @throws HaltTraversalException
+     */
+    public void end(ASTblock node) throws HaltTraversalException;
+
+    /**
+     * Starts traversal of given node.
+     * @param node not null
+     * @throws HaltTraversalException
+     */
+    public void start(ASTarguments node) throws HaltTraversalException;
+    
+    /**
+     * Ends traversal of given node.
+     * @param node not null
+     * @throws HaltTraversalException
+     */
+    public void end(ASTarguments node) throws HaltTraversalException;
+
+    /**
+     * Starts traversal of given node.
+     * @param node not null
+     * @throws HaltTraversalException
+     */
+    public void start(ASTargument node) throws HaltTraversalException;
+    
+    /**
+     * Ends traversal of given node.
+     * @param node not null
+     * @throws HaltTraversalException
+     */
+    public void end(ASTargument node) throws HaltTraversalException;
+
+    /**
+     * Starts traversal of given node.
+     * @param node not null
+     * @throws HaltTraversalException
+     */
+    public void start(ASTtest node) throws HaltTraversalException;
+    
+    /**
+     * Ends traversal of given node.
+     * @param node not null
+     * @throws HaltTraversalException
+     */
+    public void end(ASTtest node) throws HaltTraversalException;
+
+    /**
+     * Starts traversal of given node.
+     * @param node not null
+     * @throws HaltTraversalException
+     */
+    public void start(ASTtest_list node) throws HaltTraversalException;
+    
+    /**
+     * Ends traversal of given node.
+     * @param node not null
+     * @throws HaltTraversalException
+     */
+    public void end(ASTtest_list node) throws HaltTraversalException;
+
+    /**
+     * Starts traversal of given node.
+     * @param node not null
+     * @throws HaltTraversalException
+     */
+    public void start(ASTstring node) throws HaltTraversalException;
+    
+    /**
+     * Ends traversal of given node.
+     * @param node not null
+     * @throws HaltTraversalException
+     */
+    public void end(ASTstring node) throws HaltTraversalException;
+
+    /**
+     * Starts traversal of given node.
+     * @param node not null
+     * @throws HaltTraversalException
+     */
+    public void start(ASTstring_list node) throws HaltTraversalException;
+    
+    /**
+     * Ends traversal of given node.
+     * @param node not null
+     * @throws HaltTraversalException
+     */
+    public void end(ASTstring_list node) throws HaltTraversalException;
+}
diff --git a/trunk/util/src/main/java/org/apache/jsieve/util/NodeToSieveAdapter.java b/trunk/util/src/main/java/org/apache/jsieve/util/NodeToSieveAdapter.java
new file mode 100644
index 0000000..358540f
--- /dev/null
+++ b/trunk/util/src/main/java/org/apache/jsieve/util/NodeToSieveAdapter.java
@@ -0,0 +1,186 @@
+/****************************************************************
+ * 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.jsieve.util;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.jsieve.NumberArgument;
+import org.apache.jsieve.TagArgument;
+import org.apache.jsieve.parser.generated.ASTargument;
+import org.apache.jsieve.parser.generated.ASTarguments;
+import org.apache.jsieve.parser.generated.ASTblock;
+import org.apache.jsieve.parser.generated.ASTcommand;
+import org.apache.jsieve.parser.generated.ASTcommands;
+import org.apache.jsieve.parser.generated.ASTstart;
+import org.apache.jsieve.parser.generated.ASTstring;
+import org.apache.jsieve.parser.generated.ASTstring_list;
+import org.apache.jsieve.parser.generated.ASTtest;
+import org.apache.jsieve.parser.generated.ASTtest_list;
+import org.apache.jsieve.parser.generated.SimpleNode;
+
+/**
+ * Adapters low level {@link NodeHandler} output into a
+ * high level {@link SieveHandler}.
+ */
+public class NodeToSieveAdapter implements NodeHandler {
+
+    private static final Log LOG = LogFactory.getLog(NodeToSieveAdapter.class);
+    
+    private final SieveHandler handler;
+    
+    /**
+     * Constructs an adapter to the given {@link SieveHandler}.
+     * @param handler not null
+     * @throws NullPointerException when handler is null
+     */
+    public NodeToSieveAdapter(final SieveHandler handler) {
+        super();
+        // Hard to debug a null pointer during parsing
+        if (handler == null) {
+            throw new NullPointerException("Handler must not be null");
+        }
+        this.handler = handler;
+    }
+
+
+    public void start() throws HaltTraversalException {
+//      Ignore
+    }
+    
+    public void end() throws HaltTraversalException {
+//      Ignore
+    }
+
+    public void end(SimpleNode node) throws HaltTraversalException {
+//      Ignore
+    }
+
+    public void end(ASTstart node) throws HaltTraversalException {
+        handler.endScript();
+    }
+
+    public void end(ASTcommands node) throws HaltTraversalException {
+        handler.endCommands();
+    }
+
+    public void end(ASTcommand node) throws HaltTraversalException {
+        handler.endCommand(node.getName());
+    }
+
+    public void end(ASTblock node) throws HaltTraversalException {
+        handler.endBlock();
+    }
+
+    public void end(ASTarguments node) throws HaltTraversalException {
+        handler.endArguments();
+    }
+
+    public void end(ASTargument node) throws HaltTraversalException {
+        // Processed in start
+    }
+
+    public void end(ASTtest node) throws HaltTraversalException {
+        final String name = node.getName();
+        handler.endTest(name);
+    }
+
+    public void end(ASTtest_list node) throws HaltTraversalException {
+        handler.endTestList();
+    }
+
+    public void end(ASTstring node) throws HaltTraversalException {
+        // Process ASTstring on start
+    }
+
+    public void end(ASTstring_list node) throws HaltTraversalException {
+        handler.endStringListArgument();
+    }
+
+
+    public void start(SimpleNode node) throws HaltTraversalException {
+        // Ignore
+    }
+
+    public void start(ASTstart node) throws HaltTraversalException {
+        handler.startScript();
+    }
+
+    public void start(ASTcommands node) throws HaltTraversalException {
+        handler.startCommands();
+    }
+
+    public void start(ASTcommand node) throws HaltTraversalException {
+        handler.startCommand(node.getName());
+    }
+
+    public void start(ASTblock node) throws HaltTraversalException {
+        handler.startBlock();
+    }
+
+    public void start(ASTarguments node) throws HaltTraversalException {
+        handler.startArguments();
+    }
+
+    public void start(ASTargument node) throws HaltTraversalException {
+        final Object value = node.getValue();
+        if (value == null) {
+            LOG.debug("Ignoring null argument");
+        } else if (value instanceof NumberArgument) {
+            final NumberArgument numberArgument = (NumberArgument) value;
+            Integer integer = numberArgument.getInteger();
+            if (integer == null) {
+                LOG.debug("Ignoring null numeric argument");
+            } else {
+                final int number = integer.intValue();
+                handler.argument(number);
+            }
+        } else if (value instanceof TagArgument) {
+            final TagArgument tagArgument = (TagArgument) value;
+            final String tag = tagArgument.getTag();
+            // tag = ":" identifier
+            // handlers are only interesting in the identifier for the tag
+            final String identifier;
+            if (tag.charAt(0) == ':') {
+                identifier = tag.substring(1);
+            } else {
+                identifier = tag;
+            }
+            handler.argument(identifier);
+        }
+    }
+
+    public void start(ASTtest node) throws HaltTraversalException {
+        final String name = node.getName();
+        handler.startTest(name);
+    }
+
+    public void start(ASTtest_list node) throws HaltTraversalException {
+        handler.startTestList();
+    }
+
+    public void start(ASTstring node) throws HaltTraversalException {
+        final String string =(String) node.getValue();
+        handler.listMember(string);
+    }
+
+    public void start(ASTstring_list node) throws HaltTraversalException {
+        handler.startStringListArgument();
+    }
+
+}
diff --git a/trunk/util/src/main/java/org/apache/jsieve/util/NodeTraverser.java b/trunk/util/src/main/java/org/apache/jsieve/util/NodeTraverser.java
new file mode 100644
index 0000000..a352c57
--- /dev/null
+++ b/trunk/util/src/main/java/org/apache/jsieve/util/NodeTraverser.java
@@ -0,0 +1,169 @@
+/****************************************************************
+ * 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.jsieve.util;
+
+import org.apache.jsieve.exception.SieveException;
+import org.apache.jsieve.parser.generated.ASTargument;
+import org.apache.jsieve.parser.generated.ASTarguments;
+import org.apache.jsieve.parser.generated.ASTblock;
+import org.apache.jsieve.parser.generated.ASTcommand;
+import org.apache.jsieve.parser.generated.ASTcommands;
+import org.apache.jsieve.parser.generated.ASTstart;
+import org.apache.jsieve.parser.generated.ASTstring;
+import org.apache.jsieve.parser.generated.ASTstring_list;
+import org.apache.jsieve.parser.generated.ASTtest;
+import org.apache.jsieve.parser.generated.ASTtest_list;
+import org.apache.jsieve.parser.generated.Node;
+import org.apache.jsieve.parser.generated.SieveParserVisitor;
+import org.apache.jsieve.parser.generated.SimpleNode;
+
+/**
+ * Traverses nodes.
+ * Once instance can be safely shared between threads.
+ */
+public class NodeTraverser {
+    
+    /**
+     * Traverses the tree structure rooted at the given node.
+     * The nodes contained are reported to the handler.
+     * @param root not null
+     * @param handler not null
+     * @throws SieveException when traversal fails
+     * @throws HaltTraversalException when traversal is halted by handler
+     */
+    public void traverse(final NodeHandler handler, final Node root) throws SieveException {
+        final TraversalWorker worker = new TraversalWorker(handler);
+        handler.start();
+        root.jjtAccept(worker, null);
+        handler.end();
+    }
+    
+    /**
+     * Traverses the tree structure rooted at the given node.
+     * The nodes contained are reported to the handler.
+     * @param root not null
+     * @param handler not null
+     * @throws SieveException when traversal fails
+     * @throws HaltTraversalException when traversal is halted by handler
+     */
+    public void traverse(final SieveHandler handler, final Node root) throws SieveException {
+        traverse(new NodeToSieveAdapter(handler), root);
+    }
+  
+
+    /**
+     * <p>Traverses a nodal tree structure.
+     * An inner worker:
+     * </p>
+     * <ul>
+     * <li>Allows a more fluent public API</li>
+     * <li>Isolated the monotheaded code</li>
+     * </ul>
+     */
+    private static final class TraversalWorker implements SieveParserVisitor {
+        
+        private final NodeHandler handler;
+        
+        /**
+         * Constructs a traversal worker.
+         * @param handler not null
+         */
+        public TraversalWorker(final NodeHandler handler) {
+            super();
+            this.handler = handler;
+        }
+        
+        public Object visit(SimpleNode node, Object data) throws SieveException {
+            handler.start(node);
+            node.childrenAccept(this, null);
+            handler.end(node);
+            return null;
+        }
+    
+        public Object visit(ASTstart node, Object data) throws SieveException {
+            handler.start(node);
+            node.childrenAccept(this, null);
+            handler.end(node);
+            return null;
+        }
+    
+        public Object visit(ASTcommands node, Object data) throws SieveException {
+            handler.start(node);
+            node.childrenAccept(this, null);
+            handler.end(node);
+            return null;
+        }
+    
+        public Object visit(ASTcommand node, Object data) throws SieveException {
+            handler.start(node);
+            node.childrenAccept(this, null);
+            handler.end(node);
+            return null;
+        }
+    
+        public Object visit(ASTblock node, Object data) throws SieveException {
+            handler.start(node);
+            node.childrenAccept(this, null);
+            handler.end(node);
+            return null;
+        }
+    
+        public Object visit(ASTarguments node, Object data) throws SieveException {
+            handler.start(node);
+            node.childrenAccept(this, null);
+            handler.end(node);
+            return null;
+        }
+    
+        public Object visit(ASTargument node, Object data) throws SieveException {
+            handler.start(node);
+            node.childrenAccept(this, null);
+            handler.end(node);
+            return null;
+        }
+    
+        public Object visit(ASTtest node, Object data) throws SieveException {
+            handler.start(node);
+            node.childrenAccept(this, null);
+            handler.end(node);
+            return null;
+        }
+    
+        public Object visit(ASTtest_list node, Object data) throws SieveException {
+            handler.start(node);
+            node.childrenAccept(this, null);
+            handler.end(node);
+            return null;
+        }
+    
+        public Object visit(ASTstring node, Object data) throws SieveException {
+            handler.start(node);
+            node.childrenAccept(this, null);
+            handler.end(node);
+            return null;
+        }
+    
+        public Object visit(ASTstring_list node, Object data) throws SieveException {
+            handler.start(node);
+            node.childrenAccept(this, null);
+            handler.end(node);
+            return null;
+        }
+    }
+}
diff --git a/trunk/util/src/main/java/org/apache/jsieve/util/OperationNotAllowedException.java b/trunk/util/src/main/java/org/apache/jsieve/util/OperationNotAllowedException.java
new file mode 100644
index 0000000..76a9d26
--- /dev/null
+++ b/trunk/util/src/main/java/org/apache/jsieve/util/OperationNotAllowedException.java
@@ -0,0 +1,34 @@
+/****************************************************************
+ * 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.jsieve.util;
+
+import java.io.IOException;
+
+public class OperationNotAllowedException extends IOException {
+
+    private static final long serialVersionUID = -5447316582278562837L;
+
+    public OperationNotAllowedException() {
+        super();
+    }
+
+    public OperationNotAllowedException(String s) {
+        super(s);
+    }    
+}
diff --git a/trunk/util/src/main/java/org/apache/jsieve/util/OutputUtils.java b/trunk/util/src/main/java/org/apache/jsieve/util/OutputUtils.java
new file mode 100644
index 0000000..f534871
--- /dev/null
+++ b/trunk/util/src/main/java/org/apache/jsieve/util/OutputUtils.java
@@ -0,0 +1,90 @@
+/****************************************************************
+ * 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.jsieve.util;
+
+import java.io.IOException;
+import java.io.Writer;
+
+import org.apache.jsieve.exception.SieveException;
+import org.apache.jsieve.parser.generated.Node;
+
+/**
+ * Output utilities.
+ * These are mostly convenience methods.
+ * More power and flexibility is available when using the objects directly.
+ */
+public class OutputUtils {
+
+    /**
+     * <p>Writes the given node as xml.
+     * This convenience method first writes a prolog before calling {@link #toXml(Node, Writer)}.
+     * </p><p>
+     * The output format is a subset of the
+     * <a href='http://tools.ietf.org/html/draft-freed-sieve-in-xml-04'>sieve-in-xml</a>
+     * Internet Draft.
+     * </p>
+     * @param node not null
+     * @param writer not null
+     * @throws IOException when prolog cannot be written
+     * @throws SieveException when script cannot be converted to xml
+     * @see #toXml(Node, Writer)
+     */
+    public static void toXmlDocument(final Node node, final Writer writer) throws IOException, SieveException {
+        writer.append("<?xml version='1.0'?>");
+        toXml(node, writer);
+    }
+    
+    /**
+     * <p>Writes the given node as xml.
+     * Note that the xml will be written as a fragment.
+     * An appropriate prolog must be added to convert this fragment 
+     * to a document.
+     * </p><p>
+     * The output format is a subset of the
+     * <a href='http://tools.ietf.org/html/draft-freed-sieve-in-xml-04'>sieve-in-xml</a>
+     * Internet Draft. Note that this support is experimental.
+     * </p>
+     * @param node not null
+     * @param writer not null
+     * @throws SieveException when script cannot be converted to xml
+     * @see XmlOut
+     * @see SieveToXml
+     * @see SieveHandler
+     */
+    public static void toXml(final Node node, final Writer writer) throws SieveException {
+        final XmlOut out = new XmlOut(writer);
+        final SieveToXml sieveToXml = new SieveToXml();
+        final SieveHandler handler = sieveToXml.build(out);
+        final NodeTraverser traverser = new NodeTraverser();
+        traverser.traverse(handler, node);
+    }
+    
+    /**
+     * <p>Writes the tree rooted at the given node to a Sieve script.
+     * @param node not null
+     * @param writer not null
+     * @throws SieveException whenever the serialization fails
+     */
+    public static void toSieve(final Node node, final Writer writer) throws SieveException {
+        final ToSieveHandlerFactory factory = new ToSieveHandlerFactory();
+        final SieveHandler handler = factory.build(writer);
+        final NodeTraverser traverser = new NodeTraverser();
+        traverser.traverse(handler, node);
+    }
+}
diff --git a/trunk/util/src/main/java/org/apache/jsieve/util/SieveHandler.java b/trunk/util/src/main/java/org/apache/jsieve/util/SieveHandler.java
new file mode 100644
index 0000000..011fa08
--- /dev/null
+++ b/trunk/util/src/main/java/org/apache/jsieve/util/SieveHandler.java
@@ -0,0 +1,260 @@
+/****************************************************************
+ * 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.jsieve.util;
+
+/**
+ * Presents a high level reporting view of a Sieve node tree.
+ * Familiarity with 
+ * <a href='http://www.ietf.org/rfc/rfc3028.txt'>Sieve</a> is assumed 
+ * (but not of the internals of the 
+ * <a href='http://james.apache.org/jsieve/'>JSieve</a> implementation).
+ * Anyone who requires a low level, <code>JSieve</code> specific view
+ * should see {@link NodeHandler}
+ * 
+ * @see NodeTraverser
+ * @see NodeHandler
+ */
+public interface SieveHandler {
+    
+    /**
+     * Handles the start of a Sieve script.
+     * @throws HaltTraversalException
+     * @return this
+     */
+    public SieveHandler startScript() throws HaltTraversalException;
+    
+    /**
+     * Handles the end of a Sieve script.
+     * @throws HaltTraversalException
+     * @return this
+     */
+    public SieveHandler endScript() throws HaltTraversalException;
+    
+    /**
+     * Handles the start of a block.
+     * @throws HaltTraversalException
+     * @return this
+     */
+    public SieveHandler startBlock() throws HaltTraversalException;
+    
+    /**
+     * Handles the end of a block.
+     * @throws HaltTraversalException
+     * @return this
+     */
+    public SieveHandler endBlock() throws HaltTraversalException;
+    
+    /**
+     * Handles the start of a block of commands.
+     * @throws HaltTraversalException
+     * @return this
+     */
+    public SieveHandler startCommands() throws HaltTraversalException;
+    
+    /**
+     * Handles the end of a block of commands.
+     * @throws HaltTraversalException
+     * @return this
+     */
+    public SieveHandler endCommands() throws HaltTraversalException;
+
+    /**
+     * Handles the start of a command.
+     * @param commandName name identifying the command
+     * @throws HaltTraversalException
+     * @return this
+     */
+    public SieveHandler startCommand(String commandName) throws HaltTraversalException;
+    
+    /**
+     * Handles the end of a command.
+     * @param commandName name identifying the command
+     * @throws HaltTraversalException
+     * @return this
+     */
+    public SieveHandler endCommand(String commandName) throws HaltTraversalException;
+    
+    
+    /**
+     * Handles the start of a block of arguments.
+     * @throws HaltTraversalException
+     * @return this
+     */
+    public SieveHandler startArguments() throws HaltTraversalException;
+    
+    /**
+     * Handles the end of a block of arguments.
+     * @throws HaltTraversalException
+     * @return this
+     */
+    public SieveHandler endArguments() throws HaltTraversalException;
+    
+    /**
+     * Handles a tag argument.
+     * Note that this supplies the identifier for the tag
+     * (after the leading ':' has been stripped).
+     * @param identifier not null
+     * @throws HaltTraversalException
+     * @return this
+     */
+    public SieveHandler argument(String identifier) throws HaltTraversalException;
+    
+    /**
+     * Handler a numeric argument.
+     * @param number not null
+     * @throws HaltTraversalException
+     * @return this
+     */
+    public SieveHandler argument(int number) throws HaltTraversalException;
+    
+    /**
+     * Handles the start of an argument which is a list of strings.
+     * @throws HaltTraversalException
+     * @return this
+     */
+    public SieveHandler startStringListArgument() throws HaltTraversalException;
+    
+    /**
+     * Handles the end of an argument which is a list of strings.
+     * @throws HaltTraversalException
+     * @return this
+     */
+    public SieveHandler endStringListArgument() throws HaltTraversalException;
+    
+    /**
+     * One string from a list.
+     * @param string not null
+     * @throws HaltTraversalException
+     * @return this
+     */
+    public SieveHandler listMember(String string) throws HaltTraversalException;
+    
+    /**
+     * Handles the start of a list of tests.
+     * @throws HaltTraversalException
+     * @return this
+     */
+    public SieveHandler startTestList() throws HaltTraversalException;
+    
+    /**
+     * Handles the end of a list of tests.
+     * @throws HaltTraversalException
+     * @return this
+     */
+    public SieveHandler endTestList() throws HaltTraversalException;
+    
+    /**
+     * Handles the start of a test.
+     * @param testName name identifying the test
+     * @throws HaltTraversalException
+     * @return this
+     */
+    public SieveHandler startTest(String testName) throws HaltTraversalException;
+    
+    /**
+     * Handles the end of a test.
+     * @param testName name identifying the test
+     * @throws HaltTraversalException
+     * @return this
+     */
+    public SieveHandler endTest(String testName) throws HaltTraversalException;
+
+    /**
+     * Convenience basic implementation.
+     * All methods do nothing.
+     */
+    public abstract static class Base implements SieveHandler {
+
+        public SieveHandler argument(int number) throws HaltTraversalException {
+            return this;
+        }
+
+        public SieveHandler argument(String tag) throws HaltTraversalException {
+            return this;
+        }
+
+        public SieveHandler endArguments() throws HaltTraversalException {
+            return this;
+        }
+
+        public SieveHandler endBlock() throws HaltTraversalException {
+            return this;
+        }
+
+        public SieveHandler endCommand(String commandName) throws HaltTraversalException {
+            return this;
+        }
+
+        public SieveHandler endCommands() throws HaltTraversalException {
+            return this;
+        }
+
+        public SieveHandler endScript() throws HaltTraversalException {
+            return this;
+        }
+
+        public SieveHandler endStringListArgument() throws HaltTraversalException {
+            return this;
+        }
+
+        public SieveHandler endTest(String testName) throws HaltTraversalException {
+            return this;
+        }
+
+        public SieveHandler endTestList() throws HaltTraversalException {
+            return this;
+        }
+
+        public SieveHandler listMember(String string) throws HaltTraversalException {
+            return this;
+        }
+
+        public SieveHandler startArguments() throws HaltTraversalException {
+            return this;
+        }
+
+        public SieveHandler startBlock() throws HaltTraversalException {
+            return this;
+        }
+
+        public SieveHandler startCommand(String commandName) throws HaltTraversalException {
+            return this;
+        }
+
+        public SieveHandler startCommands() throws HaltTraversalException {
+            return this;
+        }
+
+        public SieveHandler startScript() throws HaltTraversalException {
+            return this;
+        }
+
+        public SieveHandler startStringListArgument() throws HaltTraversalException {
+            return this;
+        }
+
+        public SieveHandler startTest(String testName) throws HaltTraversalException {
+            return this;
+        }
+
+        public SieveHandler startTestList() throws HaltTraversalException {
+            return this;
+        }
+    }
+}
diff --git a/trunk/util/src/main/java/org/apache/jsieve/util/SieveToXml.java b/trunk/util/src/main/java/org/apache/jsieve/util/SieveToXml.java
new file mode 100644
index 0000000..49ce625
--- /dev/null
+++ b/trunk/util/src/main/java/org/apache/jsieve/util/SieveToXml.java
@@ -0,0 +1,471 @@
+/****************************************************************
+ * 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.jsieve.util;
+
+import java.io.IOException;
+
+/**
+ * <p>Converts Sieve nodes to xml.
+ * Settings default to <code>draft-freed-sieve-in-xml-01</code>
+ * <blockquote cite='http://tools.ietf.org/id/draft-freed-sieve-in-xml-01.txt'>
+ * Sieve Email Filtering: Sieves and display directives in XML</blockquote>. 
+ * </p>
+ * <h4>Known limitations</h4>
+ * <p>For simplicity, allow elements are out into a single namespace.</p>                   
+ */
+public class SieveToXml {
+    
+    public static final String DEFAULT_NAME_ATTRIBUTE = "name";
+
+    public static final String DEFAULT_NAME_ACTION_COMMAND = "action";
+
+    public static final String DEFAULT_NAME_CONTROL_COMMAND = "control";
+
+    public static final String DEFAULT_NAME_TEST = "test";
+
+    public static final String DEFAULT_NAME_LIST = "list";
+
+    public static final String DEFAULT_NAME_NUM = "num";
+
+    public static final String DEFAULT_NAME_TAG = "tag";
+
+    public static final String DEFAULT_NAME_STRING = "str";
+
+    public static final String DEFAULT_PREFIX = "sieve";
+    
+    public static final String DEFAULT_NAMESPACE = "urn:ietf:params:xml:ns:sieve";
+    
+    /** Control commands (as listed in RFC 3028) */
+    public static final String[] CONTROL_COMMANDS = {"If", "Require", "Stop"};
+    
+    /**
+     * <p>Simple infoset output.
+     * </p><p>
+     * Note that the approach taken to namespaces is slightly different from both 
+     * <a href='http://java.sun.com/j2ee/1.4/docs/api/javax/xml/namespace/QName.html'>QName</a>
+     * and <a href='http://www.saxproject.org'>SAX</a>.
+     * </p>
+     * <ul>
+     * <li>
+     * The <code>localPart</code> parameter gives the 
+     * <a href='http://www.w3.org/TR/REC-xml-names/#NT-LocalPart'  rel='tag'>LocalPart</a>
+     * and <code>MUST</code> be provided in all cases.
+     * When the name is an <a href='http://www.w3.org/TR/REC-xml-names/#NT-UnprefixedName'  rel='tag'>UnprefixedName</a>,
+     * this is the complete <a href='http://www.w3.org/TR/REC-xml-names/#dt-qualname'  rel='tag'>QName</a>.
+     * When the name is a <a href='http://www.w3.org/TR/REC-xml-names/#NT-PrefixedName'  rel='tag'>PrefixedName</a>,
+     * the output must combine this with the <a href='http://www.w3.org/TR/REC-xml-names/#NT-Prefix' rel='tag'>Prefix</a>
+     * </li>  
+     * <li>
+     * </li>
+     * </ul>
+     * </p>
+     */
+    public interface Out {
+        
+        /**
+         * Starts an XML element.
+         * @throws IOException when output fails
+         */
+        public void openElement(CharSequence localName, CharSequence uri, CharSequence prefix) throws IOException;
+        
+        /**
+         * Outputs a attribute.
+         * @param value unescaped XML attribute content, not null
+         * @throws IOException when output fails
+         */
+        public void attribute(CharSequence localName, CharSequence uri, CharSequence prefix, CharSequence value) throws IOException;
+        
+        /**
+         * Outputs body text.
+         * All attribute will be output before any body text
+         * so this call implicitly marks the end of any attributes
+         * for the element.
+         * @param text unescaped body text, not null
+         * @throws IOException when output fails
+         */
+        public void content(CharSequence text) throws IOException;
+        
+        /**
+         * Ends an XML Element.
+         * @throws IOException when output fails
+         */
+        public void closeElement() throws IOException;
+    }
+    
+    /**
+     * Maps node names to element names.
+     */
+    public interface NameMapper {
+        /**
+         * Converts the given node name to an
+         * element local name.
+         * @param name not null
+         * @return element local name to use for given name, not null
+         */
+        public String toElementName(String name);
+        
+    }
+    
+    /**
+     * Creates a mapper which will return the same
+     * name for any node.
+     * @param elementLocalName to be returned for all names, not null
+     * @return not null
+     */
+    public static final NameMapper uniformMapper(final String elementLocalName) {
+        return new NameMapper() {
+            public String toElementName(String name) {
+                return elementLocalName;
+            }
+        };
+    }
+    
+    /**
+     * Creates a mapper which returns values given in 
+     * <code>draft-freed-sieve-in-xml-01</code>
+     * <blockquote cite='http://tools.ietf.org/id/draft-freed-sieve-in-xml-01.txt'>
+     * Sieve Email Filtering: Sieves and display directives in XML</blockquote>.  
+     * @return not null
+     */
+    public static final NameMapper sieveInXmlMapper() {
+        return new NameMapper() {
+
+            public String toElementName(String name) {
+                boolean isControlCommand = false;
+                for (int i=0;i< CONTROL_COMMANDS.length;i++) {
+                    if (CONTROL_COMMANDS[i].equalsIgnoreCase(name)) {
+                        isControlCommand = true;
+                        break;
+                    }
+                }
+                final String result;
+                if (isControlCommand) {
+                    result = DEFAULT_NAME_CONTROL_COMMAND;
+                } else {
+                    result = DEFAULT_NAME_ACTION_COMMAND;
+                }
+                return result;
+            }
+        };
+    }
+   
+    private String namespaceUri = DEFAULT_NAMESPACE;
+    private String namespacePrefix = DEFAULT_PREFIX;
+    private String stringElementName = DEFAULT_NAME_STRING;
+    private String tagElementName = DEFAULT_NAME_TAG;
+    private String numberElementName = DEFAULT_NAME_NUM;
+    private String listElementName = DEFAULT_NAME_LIST;
+    
+    private String nameAttributeName = DEFAULT_NAME_ATTRIBUTE;
+    private NameMapper commandNameMapper = sieveInXmlMapper();
+    private NameMapper testNameMapper = uniformMapper(DEFAULT_NAME_TEST);
+    
+    /**
+     * Gets mapper for command names.
+     * @return not null
+     */
+    public NameMapper getCommandNameMapper() {
+        return commandNameMapper;
+    }
+
+    /**
+     * Sets mapper for command names.
+     * @param commandNameMapper
+     */
+    public void setCommandNameMapper(NameMapper commandNameMapper) {
+        this.commandNameMapper = commandNameMapper;
+    }
+
+    /**
+     * Gets the element name used for lists.
+     * @return element name used for lists, not null
+     */
+    public String getListElementName() {
+        return listElementName;
+    }
+
+    /**
+     * Sets the element name used for lists.
+     * @param listElementName not null
+     */
+    public void setListElementName(String listElementName) {
+        this.listElementName = listElementName;
+    }
+
+    /**
+     * Gets the name of the attribute to be used to name command and tests.
+     * @return name, or null when not attribute should be written
+     */
+    public String getNameAttributeName() {
+        return nameAttributeName;
+    }
+
+    /**
+     * Sets the name of the attribute to be used to indicate command and test names.
+     * @param nameAttributeName naming attribute, 
+     * or null when no attribute should be used
+     */
+    public void setNameAttributeName(String nameAttributeName) {
+        this.nameAttributeName = nameAttributeName;
+    }
+
+    /**
+     * Gets the namespace prefix to be used for all elements and attributes.
+     * @return namespace prefix, or null when no namespace should be used 
+     */
+    public String getNamespacePrefix() {
+        return namespacePrefix;
+    }
+
+    /**
+     * Sets the namespace prefix to be used for all elements and attributes.
+     * @param namespacePrefix namespace, or null when no namespace should be used
+     */
+    public void setNamespacePrefix(String namespacePrefix) {
+        this.namespacePrefix = namespacePrefix;
+    }
+
+    /**
+     * Gets the namespace URI to be used for all elements and attributes.
+     * @return namespace URI, or null when no namespace should be used
+     */
+    public String getNamespaceUri() {
+        return namespaceUri;
+    }
+
+    /**
+     * Sets the namespace uri to be used for all elements and attributes.
+     * @param namespaceUri namespace URI, or null when no namespace should be used
+     */
+    public void setNamespaceUri(String namespaceUri) {
+        this.namespaceUri = namespaceUri;
+    }
+
+    /**
+     * Gets the name of the element that wraps a numeric argument.
+     * @return not null
+     */
+    public String getNumberElementName() {
+        return numberElementName;
+    }
+
+    /**
+     * Sets the name of the element that wraps a numeric argument.
+     * @param numberElementName not null
+     */
+    public void setNumberElementName(String numberElementName) {
+        this.numberElementName = numberElementName;
+    }
+
+    /**
+     * Gets the name of the element that wraps a string element.
+     * @return not null
+     */
+    public String getStringElementName() {
+        return stringElementName;
+    }
+
+    /**
+     * Sets the name of the element that wraps a string element.
+     * @param stringElementName not null
+     */
+    public void setStringElementName(String stringElementName) {
+        this.stringElementName = stringElementName;
+    }
+
+    /**
+     * Gets the name of the element that wraps a tag element.
+     * @return not null
+     */
+    public String getTagElementName() {
+        return tagElementName;
+    }
+
+    /**
+     * Sets the name of the element that wraps a tag element
+     * @param tagElementName not null
+     */
+    public void setTagElementName(String tagElementName) {
+        this.tagElementName = tagElementName;
+    }
+
+    /**
+     * Gets the mapper for names of test nodes.
+     * @return not null
+     */
+    public NameMapper getTestNameMapper() {
+        return testNameMapper;
+    }
+
+    /**
+     * Sets the mapper for names of test nodes.
+     * @param testNameMapper not null
+     */
+    public void setTestNameMapper(NameMapper testNameMapper) {
+        this.testNameMapper = testNameMapper;
+    }
+
+    /**
+     * Builds a handler to writes to the given output.
+     * @param out output, not null
+     * @return hanlder, not null
+     */
+    public SieveHandler build(final Out out) {
+        final Worker worker = new Worker(nameAttributeName, namespaceUri, namespacePrefix, stringElementName, 
+                tagElementName, numberElementName, listElementName, commandNameMapper, testNameMapper, out);
+        return worker;
+    }
+    
+    /**
+     * Worker performs actual conversion allowing enclosing to be shared safely
+     * between threads.
+     */
+    private static final class Worker extends SieveHandler.Base {
+        
+        
+        private final String nameAttributeName;
+        
+        private final String namespaceUri;
+        private final String namespacePrefix;
+        private final String stringElementName;
+        private final String tagElementName;
+        private final String numberElementName;
+        private final String listElementName;
+        
+        private final NameMapper commandNameMapper;
+        private final NameMapper testNameMapper;
+        
+        private final Out out;
+        
+        public Worker(final String nameAttributeName, final String namespaceUri, final String namespacePrefix, 
+                final String stringElementName, final String tagElementName, final String numberElementName, 
+                final String listElementName, final NameMapper commandNameMapper, final NameMapper testNameMapper, 
+                final Out out) {
+            super();
+            this.nameAttributeName = nameAttributeName;
+            this.namespaceUri = namespaceUri;
+            this.namespacePrefix = namespacePrefix;
+            this.stringElementName = stringElementName;
+            this.tagElementName = tagElementName;
+            this.numberElementName = numberElementName;
+            this.listElementName = listElementName;
+            this.commandNameMapper = commandNameMapper;
+            this.testNameMapper = testNameMapper;
+            this.out = out;
+        }
+
+        @Override
+        public SieveHandler endCommand(String commandName) throws HaltTraversalException {
+            return closeElement();
+        }
+
+        private SieveHandler closeElement() throws HaltTraversalException {
+            try {
+                out.closeElement();
+                return this;
+            } catch (IOException e) {
+                throw new HaltTraversalException(e);
+            }
+        }
+
+        @Override
+        public SieveHandler startCommand(String commandName) throws HaltTraversalException {
+            try {
+                out.openElement(commandNameMapper.toElementName(commandName), namespaceUri, namespacePrefix);
+                nameAttribute(commandName);
+                return this;
+            } catch (IOException e) {
+                throw new HaltTraversalException(e);
+            }
+        }
+
+        @Override
+        public SieveHandler listMember(String string) throws HaltTraversalException {
+            try {
+                out.openElement(stringElementName, namespaceUri, namespacePrefix);
+                out.content(string);
+                out.closeElement();
+                return this;
+            } catch (IOException e) {
+                throw new HaltTraversalException(e);
+            }
+        }
+
+        @Override
+        public SieveHandler argument(int number) throws HaltTraversalException {
+            try {
+                out.openElement(numberElementName, namespaceUri, namespacePrefix);
+                out.content(Integer.toString(number));
+                out.closeElement();
+                return this;
+            } catch (IOException e) {
+                throw new HaltTraversalException(e);
+            }
+        }
+
+        @Override
+        public SieveHandler argument(String tag) throws HaltTraversalException {
+            try {
+                out.openElement(tagElementName, namespaceUri, namespacePrefix);
+                out.content(tag);
+                out.closeElement();
+                return this;
+            } catch (IOException e) {
+                throw new HaltTraversalException(e);
+            }
+        }
+
+        @Override
+        public SieveHandler endTest(String testName) throws HaltTraversalException {
+            return closeElement();
+        }
+
+        @Override
+        public SieveHandler startTest(String testName) throws HaltTraversalException {
+            try {
+                out.openElement(testNameMapper.toElementName(testName), namespaceUri, namespacePrefix);
+                nameAttribute(testName);
+                return this;
+            } catch (IOException e) {
+                throw new HaltTraversalException(e);
+            }
+        }
+
+        @Override
+        public SieveHandler endTestList() throws HaltTraversalException {
+            return closeElement();
+        }
+
+        @Override
+        public SieveHandler startTestList() throws HaltTraversalException {
+            try {
+                out.openElement(listElementName, namespaceUri, namespacePrefix);
+                return this;
+            } catch (IOException e) {
+                throw new HaltTraversalException(e);
+            }
+        }
+
+        private void nameAttribute(String name) throws IOException {
+            if (nameAttributeName != null) {
+                out.attribute(nameAttributeName, namespaceUri, namespacePrefix, name);
+            }
+        }
+    }
+}
diff --git a/trunk/util/src/main/java/org/apache/jsieve/util/ToSieveHandlerFactory.java b/trunk/util/src/main/java/org/apache/jsieve/util/ToSieveHandlerFactory.java
new file mode 100644
index 0000000..d4f7d22
--- /dev/null
+++ b/trunk/util/src/main/java/org/apache/jsieve/util/ToSieveHandlerFactory.java
@@ -0,0 +1,216 @@
+/****************************************************************
+ * 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.jsieve.util;
+
+import java.io.IOException;
+import java.io.Writer;
+
+/**
+ * <p>Builds sieve handlers that convert nodes to a Sieve script.</p>
+ * <h4>Usage</h4>
+ * <ul>
+ * <li>Create an instance</li>
+ * <li>Configure using setters</li>
+ * <li>{@link #build(Writer)} configured {@link SieveHandler}</li>
+ * <li>Supply handler to {@link NodeTraverser}</li>
+ * </ul>
+ * <p>
+ * Handler instances are fully configured when built. 
+ * Changes to the factory configuration will not effect 
+ * them.
+ * </p>
+ */
+public class ToSieveHandlerFactory {
+
+    /**
+     * Builds a configured handler.
+     * @return not null
+     * @see NodeToSieveAdapter
+     * @see NodeTraverser
+     */
+    public SieveHandler build(final Writer writer) {
+        return new ToSieveHandler(writer);
+    }
+    
+    /**
+     * Thread safe worker.
+     */
+    private static final class ToSieveHandler extends SieveHandler.Base {
+        private final Writer writer;
+        
+        private boolean commaRequiredBeforeNextTest;
+        private boolean firstTestInList;
+        private boolean commandUsedBlock;
+        private boolean commandAfterEndCommand;
+     
+        public ToSieveHandler(final Writer writer) {
+            this.writer = writer;
+            commaRequiredBeforeNextTest = false;
+            firstTestInList = false;
+            commandUsedBlock = false;
+            commandAfterEndCommand = false;
+        }
+        
+        /** @see SieveHandler#endBlock() */
+        @Override
+        public SieveHandler endBlock() throws HaltTraversalException {
+            commandUsedBlock = true;
+            return append('}');
+        }
+
+        /** @see SieveHandler#startBlock() */
+        @Override
+        public SieveHandler startBlock() throws HaltTraversalException {
+            space();
+            return append('{');
+        }
+        
+        /** @see SieveHandler#startCommand() */
+        @Override
+        public SieveHandler startCommand(String commandName) throws HaltTraversalException {
+            commaRequiredBeforeNextTest = false;
+            if (commandAfterEndCommand) {
+                space();
+                commandAfterEndCommand = false;
+            }
+            return append(commandName);
+        }
+        
+        /** @see SieveHandler#endCommand() */
+        @Override
+        public SieveHandler endCommand(String commandName) throws HaltTraversalException {
+            if (!commandUsedBlock) {
+                append(';');
+            }
+            commandAfterEndCommand = true;
+            commandUsedBlock = false;
+            return this;
+        }
+        
+        /** @see SieveHandler#argument(String) */
+        @Override
+        public SieveHandler argument(String tag) throws HaltTraversalException {
+            space();
+            append(':');
+            return append(tag);
+        }
+
+        /**
+         * Appends white space.
+         * @throws HaltTraversalException
+         */
+        private void space() throws HaltTraversalException {
+            append(' ');
+        }
+
+        /** @see SieveHandler#argument(int) */
+        @Override
+        public SieveHandler argument(int number) throws HaltTraversalException {
+            space();
+            return append(Integer.toString(number));
+        }
+        
+        /** @see SieveHandler#startStringListArgument() */
+        @Override
+        public SieveHandler startStringListArgument() throws HaltTraversalException {
+            space();
+            return append('[');
+        }
+        
+        /** @see SieveHandler#endStringListArgument() */
+        @Override
+        public SieveHandler endStringListArgument() throws HaltTraversalException {
+            return append(']');
+        }
+        
+        /** @see SieveHandler#listMember(String) */
+        @Override
+        public SieveHandler listMember(String string) throws HaltTraversalException {
+            append('"');
+            for (int i=0;i<string.length();i++) {
+                char next = string.charAt(i);
+                if (next == '"' || next == '\\' || next =='\r' || next =='\f') {
+                    append('\\');
+                } 
+                append(next);
+            }
+            return append('"');
+        }
+        
+        
+        /** @see SieveHandler#startTestList() */
+        @Override
+        public SieveHandler startTestList() throws HaltTraversalException {
+            firstTestInList = true;
+            commaRequiredBeforeNextTest = false;
+            return append('(');
+        }
+        
+        /** @see SieveHandler#endTestList() */
+        @Override
+        public SieveHandler endTestList() throws HaltTraversalException {
+            commaRequiredBeforeNextTest = false;
+            return append(')');
+        }
+
+        /** @see SieveHandler#startTest(String) */
+        @Override
+        public SieveHandler startTest(String testName) throws HaltTraversalException {
+            if (commaRequiredBeforeNextTest) {
+                append(",");
+            }
+            if (!firstTestInList) {
+                space();
+            }
+            commaRequiredBeforeNextTest = true;
+            firstTestInList = false;
+            return append(testName);
+        }
+
+        /**
+         * Appends the given sequence.
+         * @param character to be appended
+         * @return this
+         * @throws HaltTraversalException when character cannot be written
+         */
+        private SieveHandler append(CharSequence characterSequence) throws HaltTraversalException {
+            try {
+                writer.append(characterSequence);
+                return this;
+            } catch (IOException e) {
+                throw new HaltTraversalException(e);
+            }
+        }
+        
+        /**
+         * Appends the given character.
+         * @param character to be appended
+         * @return this
+         * @throws HaltTraversalException when character cannot be written
+         */
+        private SieveHandler append(char character) throws HaltTraversalException {
+            try {
+                writer.append(character);
+                return this;
+            } catch (IOException e) {
+                throw new HaltTraversalException(e);
+            }
+        }
+    }
+}
diff --git a/trunk/util/src/main/java/org/apache/jsieve/util/XmlOut.java b/trunk/util/src/main/java/org/apache/jsieve/util/XmlOut.java
new file mode 100644
index 0000000..157347a
--- /dev/null
+++ b/trunk/util/src/main/java/org/apache/jsieve/util/XmlOut.java
@@ -0,0 +1,672 @@
+/*
+ * 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.jsieve.util;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.Stack;
+
+import org.apache.jsieve.util.SieveToXml.Out;
+
+/**
+ * <p>Lightweight {@link Out} implementation.</p>
+ * <p>
+ * Requires a wrapper to be used safely in a multithreaded
+ * environment.</p>
+ * <p>
+ * Not intended to be subclassed. Please copy and hack!</p>
+ */
+public final class XmlOut implements SieveToXml.Out {
+
+    private static final byte NAME_START_MASK = 1 << 1;
+    private static final byte NAME_MASK = 1 << 2;
+    private static final byte NAME_BODY_CHAR = NAME_MASK;
+    private static final byte NAME_START_OR_BODY_CHAR = NAME_MASK | NAME_START_MASK;
+    
+    private final static boolean[] ALLOWED_CHARACTERS = new boolean[1 << 16]; 
+    
+    static {
+        Arrays.fill(ALLOWED_CHARACTERS, false);
+        ALLOWED_CHARACTERS[0x9] = true;
+        ALLOWED_CHARACTERS[0xA] = true;
+        ALLOWED_CHARACTERS[0xD] = true;
+        Arrays.fill(ALLOWED_CHARACTERS, 0x20, 0xD7FF, true);
+        Arrays.fill(ALLOWED_CHARACTERS, 0xE000, 0xFFFD, true);
+    }
+    
+    private final static byte[] CHARACTER_CODES = new byte[1 << 16]; 
+                              
+    static {
+        // Name ::= (Letter | '_' | ':') (NameChar)*
+        CHARACTER_CODES['_'] = NAME_START_OR_BODY_CHAR;
+        CHARACTER_CODES[':'] = NAME_START_OR_BODY_CHAR;
+        // Letter ::= BaseChar | Ideographic
+        // BaseChar
+        Arrays.fill(CHARACTER_CODES, 0x0041, 0x005A, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0061, 0x007A, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x00C0, 0x00D6, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x00D8, 0x00F6, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x00F8, 0x00FF, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0100, 0x0131, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0134, 0x013E, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0141, 0x0148, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x014A, 0x017E, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0180, 0x01C3, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x01CD, 0x01F0, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x01F4, 0x01F5, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x01FA, 0x0217, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0250, 0x02A8, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x02BB, 0x02C1, NAME_START_OR_BODY_CHAR);
+        CHARACTER_CODES[0x0386] = NAME_START_OR_BODY_CHAR;
+        Arrays.fill(CHARACTER_CODES, 0x0388, 0x038A, NAME_START_OR_BODY_CHAR);
+        CHARACTER_CODES[0x038C] = NAME_START_OR_BODY_CHAR;
+        Arrays.fill(CHARACTER_CODES, 0x038E, 0x03A1, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x03A3, 0x03CE, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x03D0, 0x03D6, NAME_START_OR_BODY_CHAR);
+        CHARACTER_CODES[0x03DA] = NAME_START_OR_BODY_CHAR;
+        CHARACTER_CODES[0x03DC] = NAME_START_OR_BODY_CHAR;
+        CHARACTER_CODES[0x03DE] = NAME_START_OR_BODY_CHAR;
+        CHARACTER_CODES[0x03E0] = NAME_START_OR_BODY_CHAR;
+        Arrays.fill(CHARACTER_CODES, 0x03E2, 0x03F3, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0401, 0x040C, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x040E, 0x044F, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0451, 0x045C, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x045E, 0x0481, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0490, 0x04C4, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x04C7, 0x04C8, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x04CB, 0x04CC, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x04D0, 0x04EB, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x04EE, 0x04F5, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x04F8, 0x04F9, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0531, 0x0556, NAME_START_OR_BODY_CHAR);
+        CHARACTER_CODES[0x0559] = NAME_START_OR_BODY_CHAR;
+        Arrays.fill(CHARACTER_CODES, 0x0561, 0x0586, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x05D0, 0x05EA, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x05F0, 0x05F2, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0621, 0x063A, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0641, 0x064A, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0671, 0x06B7, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x06BA, 0x06BE, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x06C0, 0x06CE, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x06D0, 0x06D3, NAME_START_OR_BODY_CHAR);
+        CHARACTER_CODES[0x06D5] = NAME_START_OR_BODY_CHAR;
+        Arrays.fill(CHARACTER_CODES, 0x06E5, 0x06E6, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0905, 0x0939, NAME_START_OR_BODY_CHAR);
+        CHARACTER_CODES[0x093D] = NAME_START_OR_BODY_CHAR;
+        Arrays.fill(CHARACTER_CODES, 0x0958, 0x0961, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0985, 0x098C, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x098F, 0x0990, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0993, 0x09A8, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x09AA, 0x09B0, NAME_START_OR_BODY_CHAR);
+        CHARACTER_CODES[0x09B2] = NAME_START_OR_BODY_CHAR;
+        Arrays.fill(CHARACTER_CODES, 0x09B6, 0x09B9, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x09DC, 0x09DD, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x09DF, 0x09E1, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x09F0, 0x09F1, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0A05, 0x0A0A, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0A0F, 0x0A10, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0A13, 0x0A28, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0A2A, 0x0A30, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0A32, 0x0A33, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0A35, 0x0A36, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0A38, 0x0A39, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0A59, 0x0A5C, NAME_START_OR_BODY_CHAR);
+        CHARACTER_CODES[0x0A5E] = NAME_START_OR_BODY_CHAR;
+        Arrays.fill(CHARACTER_CODES, 0x0A72, 0x0A74, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0A85, 0x0A8B, NAME_START_OR_BODY_CHAR);
+        CHARACTER_CODES[0x0A8D] = NAME_START_OR_BODY_CHAR;
+        Arrays.fill(CHARACTER_CODES, 0x0A8F, 0x0A91, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0A93, 0x0AA8, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0AAA, 0x0AB0, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0AB2, 0x0AB3, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0AB5, 0x0AB9, NAME_START_OR_BODY_CHAR);
+        CHARACTER_CODES[0x0ABD] = NAME_START_OR_BODY_CHAR;
+        CHARACTER_CODES[0x0AE0] = NAME_START_OR_BODY_CHAR;
+        Arrays.fill(CHARACTER_CODES, 0x0B05, 0x0B0C, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0B0F, 0x0B10, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0B13, 0x0B28, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0B2A, 0x0B30, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0B32, 0x0B33, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0B36, 0x0B39, NAME_START_OR_BODY_CHAR);
+        CHARACTER_CODES[0x0B3D] = NAME_START_OR_BODY_CHAR;
+        Arrays.fill(CHARACTER_CODES, 0x0B5C, 0x0B5D, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0B5F, 0x0B61, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0B85, 0x0B8A, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0B8E, 0x0B90, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0B92, 0x0B95, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0B99, 0x0B9A, NAME_START_OR_BODY_CHAR);
+        CHARACTER_CODES[0x0B9C] = NAME_START_OR_BODY_CHAR;
+        Arrays.fill(CHARACTER_CODES, 0x0B9E, 0x0B9F, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0BA3, 0x0BA4, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0BA8, 0x0BAA, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0BAE, 0x0BB5, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0BB7, 0x0BB9, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0C05, 0x0C0C, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0C0E, 0x0C10, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0C12, 0x0C28, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0C2A, 0x0C33, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0C35, 0x0C39, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0C60, 0x0C61, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0C85, 0x0C8C, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0C8E, 0x0C90, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0C92, 0x0CA8, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0CAA, 0x0CB3, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0CB5, 0x0CB9, NAME_START_OR_BODY_CHAR);
+        CHARACTER_CODES[0x0CDE] = NAME_START_OR_BODY_CHAR;
+        Arrays.fill(CHARACTER_CODES, 0x0CE0, 0x0CE1, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0D05, 0x0D0C, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0D0E, 0x0D10, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0D12, 0x0D28, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0D2A, 0x0D39, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0D60, 0x0D61, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0E01, 0x0E2E, NAME_START_OR_BODY_CHAR);
+        CHARACTER_CODES[0x0E30] = NAME_START_OR_BODY_CHAR;
+        Arrays.fill(CHARACTER_CODES, 0x0E32, 0x0E33, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0E40, 0x0E45, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0E81, 0x0E82, NAME_START_OR_BODY_CHAR);
+        CHARACTER_CODES[0x0E84] = NAME_START_OR_BODY_CHAR;
+        Arrays.fill(CHARACTER_CODES, 0x0E87, 0x0E88, NAME_START_OR_BODY_CHAR);
+        CHARACTER_CODES[0x0E8A] = NAME_START_OR_BODY_CHAR;
+        CHARACTER_CODES[0x0E8D] = NAME_START_OR_BODY_CHAR;
+        Arrays.fill(CHARACTER_CODES, 0x0E94, 0x0E97, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0E99, 0x0E9F, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0EA1, 0x0EA3, NAME_START_OR_BODY_CHAR);
+        CHARACTER_CODES[0x0EA5] = NAME_START_OR_BODY_CHAR;
+        CHARACTER_CODES[0x0EA7] = NAME_START_OR_BODY_CHAR;
+        Arrays.fill(CHARACTER_CODES, 0x0EAA, 0x0EAB, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0EAD, 0x0EAE, NAME_START_OR_BODY_CHAR);
+        CHARACTER_CODES[0x0EB0] = NAME_START_OR_BODY_CHAR;
+        Arrays.fill(CHARACTER_CODES, 0x0EB2, 0x0EB3, NAME_START_OR_BODY_CHAR);
+        CHARACTER_CODES[0x0EBD] = NAME_START_OR_BODY_CHAR;
+        Arrays.fill(CHARACTER_CODES, 0x0EC0, 0x0EC4, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0F40, 0x0F47, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0F49, 0x0F69, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x10A0, 0x10C5, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x10D0, 0x10F6, NAME_START_OR_BODY_CHAR);
+        CHARACTER_CODES[0x1100] = NAME_START_OR_BODY_CHAR;
+        Arrays.fill(CHARACTER_CODES, 0x1102, 0x1103, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x1105, 0x1107, NAME_START_OR_BODY_CHAR);
+        CHARACTER_CODES[0x1109] = NAME_START_OR_BODY_CHAR;
+        Arrays.fill(CHARACTER_CODES, 0x110B, 0x110C, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x110E, 0x1112, NAME_START_OR_BODY_CHAR);
+        CHARACTER_CODES[0x113C] = NAME_START_OR_BODY_CHAR;
+        CHARACTER_CODES[0x113E] = NAME_START_OR_BODY_CHAR;
+        CHARACTER_CODES[0x1140] = NAME_START_OR_BODY_CHAR;
+        CHARACTER_CODES[0x114C] = NAME_START_OR_BODY_CHAR;
+        CHARACTER_CODES[0x114E] = NAME_START_OR_BODY_CHAR;
+        CHARACTER_CODES[0x1150] = NAME_START_OR_BODY_CHAR;
+        Arrays.fill(CHARACTER_CODES, 0x1154, 0x1155, NAME_START_OR_BODY_CHAR);
+        CHARACTER_CODES[0x1159] = NAME_START_OR_BODY_CHAR;
+        Arrays.fill(CHARACTER_CODES, 0x115F, 0x1161, NAME_START_OR_BODY_CHAR);
+        CHARACTER_CODES[0x1163] = NAME_START_OR_BODY_CHAR;
+        CHARACTER_CODES[0x1165] = NAME_START_OR_BODY_CHAR;
+        CHARACTER_CODES[0x1167] = NAME_START_OR_BODY_CHAR;
+        CHARACTER_CODES[0x1169] = NAME_START_OR_BODY_CHAR;
+        Arrays.fill(CHARACTER_CODES, 0x116D, 0x116E, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x1172, 0x1173, NAME_START_OR_BODY_CHAR);
+        CHARACTER_CODES[0x1175] = NAME_START_OR_BODY_CHAR;
+        CHARACTER_CODES[0x119E] = NAME_START_OR_BODY_CHAR;
+        CHARACTER_CODES[0x11A8] = NAME_START_OR_BODY_CHAR;
+        CHARACTER_CODES[0x11AB] = NAME_START_OR_BODY_CHAR;
+        Arrays.fill(CHARACTER_CODES, 0x11AE, 0x11AF, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x11B7, 0x11B8, NAME_START_OR_BODY_CHAR);
+        CHARACTER_CODES[0x11BA] = NAME_START_OR_BODY_CHAR;
+        Arrays.fill(CHARACTER_CODES, 0x11BC, 0x11C2, NAME_START_OR_BODY_CHAR);
+        CHARACTER_CODES[0x11EB] = NAME_START_OR_BODY_CHAR;
+        CHARACTER_CODES[0x11F0] = NAME_START_OR_BODY_CHAR;
+        CHARACTER_CODES[0x11F9] = NAME_START_OR_BODY_CHAR;
+        Arrays.fill(CHARACTER_CODES, 0x1E00, 0x1E9B, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x1EA0, 0x1EF9, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x1F00, 0x1F15, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x1F18, 0x1F1D, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x1F20, 0x1F45, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x1F48, 0x1F4D, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x1F50, 0x1F57, NAME_START_OR_BODY_CHAR);
+        CHARACTER_CODES[0x1F59] = NAME_START_OR_BODY_CHAR;
+        CHARACTER_CODES[0x1F5B] = NAME_START_OR_BODY_CHAR;
+        CHARACTER_CODES[0x1F5D] = NAME_START_OR_BODY_CHAR;
+        Arrays.fill(CHARACTER_CODES, 0x1F5F, 0x1F7D, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x1F80, 0x1FB4, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x1FB6, 0x1FBC, NAME_START_OR_BODY_CHAR);
+        CHARACTER_CODES[0x1FBE] = NAME_START_OR_BODY_CHAR;
+        Arrays.fill(CHARACTER_CODES, 0x1FC2, 0x1FC4, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x1FC6, 0x1FCC, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x1FD0, 0x1FD3, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x1FD6, 0x1FDB, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x1FE0, 0x1FEC, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x1FF2, 0x1FF4, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x1FF6, 0x1FFC, NAME_START_OR_BODY_CHAR);
+        CHARACTER_CODES[0x2126] = NAME_START_OR_BODY_CHAR;
+        Arrays.fill(CHARACTER_CODES, 0x212A, 0x212B, NAME_START_OR_BODY_CHAR);
+        CHARACTER_CODES[0x212E] = NAME_START_OR_BODY_CHAR;
+        Arrays.fill(CHARACTER_CODES, 0x2180, 0x2182, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x3041, 0x3094, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x30A1, 0x30FA, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x3105, 0x312C, NAME_START_OR_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0xAC00, 0xD7A3, NAME_START_OR_BODY_CHAR);
+        // Ideographic
+        Arrays.fill(CHARACTER_CODES, 0x4E00, 0x9FA5, NAME_START_OR_BODY_CHAR);
+        CHARACTER_CODES[0x3007] = NAME_START_OR_BODY_CHAR;
+        Arrays.fill(CHARACTER_CODES, 0x3021, 0x3029, NAME_START_OR_BODY_CHAR);
+        // NameChar ::= Letter | Digit | '.' | '-' | '_' | ':' | CombiningChar | Extender
+        CHARACTER_CODES['.'] = NAME_BODY_CHAR;
+        CHARACTER_CODES['-'] = NAME_BODY_CHAR;
+        // CombiningChar 
+        Arrays.fill(CHARACTER_CODES, 0x0300, 0x0345, NAME_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0360, 0x0361, NAME_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0483, 0x0486, NAME_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0591, 0x05A1, NAME_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x05A3, 0x05B9, NAME_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x05BB, 0x05BD, NAME_BODY_CHAR);
+        CHARACTER_CODES[0x05BF] = NAME_BODY_CHAR;
+        Arrays.fill(CHARACTER_CODES, 0x05C1, 0x05C2, NAME_BODY_CHAR);
+        CHARACTER_CODES[0x05C4] = NAME_BODY_CHAR;
+        Arrays.fill(CHARACTER_CODES, 0x064B, 0x0652, NAME_BODY_CHAR);
+        CHARACTER_CODES[0x0670] = NAME_BODY_CHAR;
+        Arrays.fill(CHARACTER_CODES, 0x06D6, 0x06DC, NAME_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x06DD, 0x06DF, NAME_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x06E0, 0x06E4, NAME_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x06E7, 0x06E8, NAME_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x06EA, 0x06ED, NAME_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0901, 0x0903, NAME_BODY_CHAR);
+        CHARACTER_CODES[0x093C] = NAME_BODY_CHAR;
+        Arrays.fill(CHARACTER_CODES, 0x093E, 0x094C, NAME_BODY_CHAR);
+        CHARACTER_CODES[0x094D] = NAME_BODY_CHAR;
+        Arrays.fill(CHARACTER_CODES, 0x0951, 0x0954, NAME_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0962, 0x0963, NAME_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0981, 0x0983, NAME_BODY_CHAR);
+        CHARACTER_CODES[0x09BC] = NAME_BODY_CHAR;
+        CHARACTER_CODES[0x09BE] = NAME_BODY_CHAR;
+        CHARACTER_CODES[0x09BF] = NAME_BODY_CHAR;
+        Arrays.fill(CHARACTER_CODES, 0x09C0, 0x09C4, NAME_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x09C7, 0x09C8, NAME_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x09CB, 0x09CD, NAME_BODY_CHAR);
+        CHARACTER_CODES[0x09D7] = NAME_BODY_CHAR;
+        Arrays.fill(CHARACTER_CODES, 0x09E2, 0x09E3, NAME_BODY_CHAR);
+        CHARACTER_CODES[0x0A02] = NAME_BODY_CHAR;
+        CHARACTER_CODES[0x0A3C] = NAME_BODY_CHAR;
+        CHARACTER_CODES[0x0A3E] = NAME_BODY_CHAR;
+        CHARACTER_CODES[0x0A3F] = NAME_BODY_CHAR;
+        Arrays.fill(CHARACTER_CODES, 0x0A40, 0x0A42, NAME_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0A47, 0x0A48, NAME_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0A4B, 0x0A4D, NAME_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0A70, 0x0A71, NAME_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0A81, 0x0A83, NAME_BODY_CHAR);
+        CHARACTER_CODES[0x0ABC] = NAME_BODY_CHAR;
+        Arrays.fill(CHARACTER_CODES, 0x0ABE, 0x0AC5, NAME_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0AC7, 0x0AC9, NAME_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0ACB, 0x0ACD, NAME_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0B01, 0x0B03, NAME_BODY_CHAR);
+        CHARACTER_CODES[0x0B3C] = NAME_BODY_CHAR;
+        Arrays.fill(CHARACTER_CODES, 0x0B3E, 0x0B43, NAME_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0B47, 0x0B48, NAME_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0B4B, 0x0B4D, NAME_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0B56, 0x0B57, NAME_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0B82, 0x0B83, NAME_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0BBE, 0x0BC2, NAME_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0BC6, 0x0BC8, NAME_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0BCA, 0x0BCD, NAME_BODY_CHAR);
+        CHARACTER_CODES[0x0BD7] = NAME_BODY_CHAR;
+        Arrays.fill(CHARACTER_CODES, 0x0C01, 0x0C03, NAME_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0C3E, 0x0C44, NAME_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0C46, 0x0C48, NAME_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0C4A, 0x0C4D, NAME_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0C55, 0x0C56, NAME_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0C82, 0x0C83, NAME_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0CBE, 0x0CC4, NAME_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0CC6, 0x0CC8, NAME_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0CCA, 0x0CCD, NAME_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0CD5, 0x0CD6, NAME_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0D02, 0x0D03, NAME_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0D3E, 0x0D43, NAME_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0D46, 0x0D48, NAME_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0D4A, 0x0D4D, NAME_BODY_CHAR);
+        CHARACTER_CODES[0x0D57] = NAME_BODY_CHAR;
+        CHARACTER_CODES[0x0E31] = NAME_BODY_CHAR;
+        Arrays.fill(CHARACTER_CODES, 0x0E34, 0x0E3A, NAME_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0E47, 0x0E4E, NAME_BODY_CHAR);
+        CHARACTER_CODES[0x0EB1] = NAME_BODY_CHAR;
+        Arrays.fill(CHARACTER_CODES, 0x0EB4, 0x0EB9, NAME_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0EBB, 0x0EBC, NAME_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0EC8, 0x0ECD, NAME_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0F18, 0x0F19, NAME_BODY_CHAR);
+        CHARACTER_CODES[0x0F35] = NAME_BODY_CHAR;
+        CHARACTER_CODES[0x0F37] = NAME_BODY_CHAR;
+        CHARACTER_CODES[0x0F39] = NAME_BODY_CHAR;
+        CHARACTER_CODES[0x0F3E] = NAME_BODY_CHAR;
+        CHARACTER_CODES[0x0F3F] = NAME_BODY_CHAR;
+        Arrays.fill(CHARACTER_CODES, 0x0F71, 0x0F84, NAME_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0F86, 0x0F8B, NAME_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0F90, 0x0F95, NAME_BODY_CHAR);
+        CHARACTER_CODES[0x0F97] = NAME_BODY_CHAR;
+        Arrays.fill(CHARACTER_CODES, 0x0F99, 0x0FAD, NAME_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0FB1, 0x0FB7, NAME_BODY_CHAR);
+        CHARACTER_CODES[0x0FB9] = NAME_BODY_CHAR;
+        Arrays.fill(CHARACTER_CODES, 0x20D0, 0x20DC, NAME_BODY_CHAR);
+        CHARACTER_CODES[0x20E1] = NAME_BODY_CHAR;
+        Arrays.fill(CHARACTER_CODES, 0x302A, 0x302F, NAME_BODY_CHAR);
+        CHARACTER_CODES[0x3099] = NAME_BODY_CHAR;
+        CHARACTER_CODES[0x309A] = NAME_BODY_CHAR;
+        // Digit 
+        Arrays.fill(CHARACTER_CODES, 0x0030, 0x0039, NAME_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0660, 0x0669, NAME_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x06F0, 0x06F9, NAME_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0966, 0x096F, NAME_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x09E6, 0x09EF, NAME_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0A66, 0x0A6F, NAME_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0AE6, 0x0AEF, NAME_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0B66, 0x0B6F, NAME_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0BE7, 0x0BEF, NAME_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0C66, 0x0C6F, NAME_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0CE6, 0x0CEF, NAME_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0D66, 0x0D6F, NAME_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0E50, 0x0E59, NAME_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0ED0, 0x0ED9, NAME_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x0F20, 0x0F29, NAME_BODY_CHAR);
+        // Extender 
+        CHARACTER_CODES[0x00B7] = NAME_BODY_CHAR;
+        CHARACTER_CODES[0x02D0] = NAME_BODY_CHAR;
+        CHARACTER_CODES[0x02D1] = NAME_BODY_CHAR;
+        CHARACTER_CODES[0x0387] = NAME_BODY_CHAR;
+        CHARACTER_CODES[0x0640] = NAME_BODY_CHAR;
+        CHARACTER_CODES[0x0E46] = NAME_BODY_CHAR;
+        CHARACTER_CODES[0x0EC6] = NAME_BODY_CHAR;
+        CHARACTER_CODES[0x3005] = NAME_BODY_CHAR;
+        Arrays.fill(CHARACTER_CODES, 0x3031, 0x3035, NAME_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x309D, 0x309E, NAME_BODY_CHAR);
+        Arrays.fill(CHARACTER_CODES, 0x30FC, 0x30FE, NAME_BODY_CHAR);
+
+    }
+    
+    private final List<CharSequence> prefixesDefined;
+    private final Writer writer;
+    private final Stack<CharSequence> elementNames;
+    private final Set<CharSequence> currentAttributes = new HashSet<CharSequence>();
+    
+    boolean elementsWritten = false;
+    boolean inElement = false;
+    boolean prologWritten = false;
+    
+    public XmlOut(final Writer writer) {
+        this.writer = writer;
+        this.elementNames = new Stack<CharSequence>();
+        prefixesDefined = new ArrayList<CharSequence>();
+    }
+    
+    /**
+     * Starts a document by writing a prolog.
+     * Calling this method is optional.
+     * When writing a document fragment, it should <em>not</em> be called.
+     * @throws OperationNotAllowedException 
+     * if called after the first element has been written
+     * or once a prolog has already been written
+     */
+    public void startDocument() throws IOException {
+        if (elementsWritten) {
+            throw new OperationNotAllowedException("Document already started");
+        }
+        if (prologWritten) {
+            throw new OperationNotAllowedException("Only one prolog allowed");
+        }
+        writer.write("<?xml version='1.0'?>");
+        prologWritten = true;
+    }
+    
+    /**
+     * Writes the start of an element.
+     * 
+     * @param elementName the name of the element, not null
+     * @throws InvalidXmlException if the name is not valid for an xml element
+     * @throws OperationNotAllowedException 
+     * if called after the first element has been closed
+     */
+    public void openElement(final CharSequence elementName) throws IOException {
+        if (elementsWritten && elementNames.isEmpty()) {
+            throw new OperationNotAllowedException("Root element already closed. Cannot open new element.");
+        }
+        if (!isValidName(elementName)) {
+            throw new InvalidXmlException("'" + elementName + "' is not a valid element name");
+        }
+        elementsWritten = true;
+        if (inElement) {
+            writer.write('>');
+        }
+        writer.write('<');
+        rawWrite(elementName);
+        inElement = true;
+        elementNames.push(elementName);
+        currentAttributes.clear();
+    }
+    
+    /**
+     * Writes an attribute of an element.
+     * Note that this is only allowed directly after {@link #openElement(CharSequence)}
+     * or {@link #attribute}.
+     * 
+     * @param name the attribute name, not null
+     * @param value the attribute value, not null
+     * @throws InvalidXmlException if the name is not valid for an xml attribute 
+     * or if a value for the attribute has already been written
+     * @throws OperationNotAllowedException if called after {@link #content} 
+     * or {@link #closeElement()} or before any call to {@link #openElement}
+     */
+    public void attribute(CharSequence name, CharSequence value) throws IOException {
+        if (elementNames.isEmpty()) {
+            if (elementsWritten) {
+                throw new OperationNotAllowedException("Root element has already been closed.");
+            } else {
+                throw new OperationNotAllowedException("Close called before an element has been opened.");            
+            }
+        }
+        if (!isValidName(name)) {
+            throw new InvalidXmlException("'" + name + "' is not a valid attribute name.");
+        }
+        if (!inElement) {
+            throw new InvalidXmlException("Attributes can only be written in elements");
+        }
+        if (currentAttributes.contains(name)) {
+            throw new InvalidXmlException("Each attribute can only be written once");
+        }
+        writer.write(' ');
+        rawWrite(name);
+        writer.write('=');
+        writer.write('\'');
+        writeAttributeContent(value);
+        writer.write('\'');
+        currentAttributes.add(name);
+    }
+    
+    private void writeAttributeContent(CharSequence content) throws IOException {
+        writeEscaped(content, true);
+    }
+
+    /**
+     * Writes content.
+     * Calling this method will automatically 
+     * Note that this method does not use CDATA.
+     * 
+     * @param content the content to write
+     * @throws OperationNotAllowedException 
+     * if called before any call to {@link #openElement} 
+     * or after the first element has been closed
+     */
+    public void content(CharSequence content) throws IOException {
+        if (elementNames.isEmpty()) {
+            if (elementsWritten) {
+                throw new OperationNotAllowedException("Root element has already been closed.");
+            } else {
+                throw new OperationNotAllowedException("An element must be opened before content can be written.");            
+            }
+        }
+        if (inElement) {
+            writer.write('>');
+        }
+        writeBodyContent(content);
+        inElement = false;
+    }
+    
+    private void writeBodyContent(final CharSequence content) throws IOException {
+        writeEscaped(content, false);
+    }
+
+    private void writeEscaped(final CharSequence content, boolean isAttributeContent) throws IOException {
+        final int length = content.length();
+        for (int i=0;i<length;i++) {
+            char character = content.charAt(i);
+            if (character == '&') {
+                writer.write("&amp;");
+            } else if (character == '<') {
+                writer.write("&lt;");
+            } else if (character == '>') {
+                writer.write("&gt;");
+            } else if (isAttributeContent && character == '\'') {
+                writer.write("&apos;");
+            } else if (isAttributeContent && character == '\"') {
+                writer.write("&quot;");
+            } else if (isOutOfRange(character)) {
+                writer.write('?');
+            } else {
+                writer.write(character);
+            }
+        }
+    }
+    
+    private boolean isOutOfRange(final char character) {
+        final boolean result = !ALLOWED_CHARACTERS[character];
+        return result;
+    }
+
+    /**
+     * Closes the last element written.
+     * 
+     * @throws OperationNotAllowedException 
+     * if called before any call to {@link #openElement} 
+     * or after the first element has been closed
+     */
+    public void closeElement() throws IOException {
+        if (elementNames.isEmpty()) {
+            if (elementsWritten) {
+                throw new OperationNotAllowedException("Root element has already been closed.");
+            } else {
+                throw new OperationNotAllowedException("Close called before an element has been opened.");            
+            }
+        }
+        final CharSequence elementName = (CharSequence) elementNames.pop();
+        if (inElement) {
+            writer.write('/');
+            writer.write('>');
+        } else {
+            writer.write('<');
+            writer.write('/');
+            rawWrite(elementName);
+            writer.write('>');
+        }
+        writer.flush();
+        inElement = false;
+    }
+                                                         
+    
+    /**
+     * Closes all pending elements.
+     * When appropriate, resources are also flushed and closed.
+     * No exception is raised when called upon a document whose
+     * root element has already been closed.
+     * @throws OperationNotAllowedException 
+     * if called before any call to {@link #openElement} 
+     */
+    public void closeDocument() throws IOException {
+        if (elementNames.isEmpty()) {
+            if (!elementsWritten) {
+                throw new OperationNotAllowedException("Close called before an element has been opened.");            
+            }
+        }
+        while(!elementNames.isEmpty()) {
+            closeElement();
+        }
+        writer.flush();
+    }
+    
+    private void rawWrite(final CharSequence sequence) throws IOException {
+        for (int i=0;i<sequence.length();i++) {
+            final char charAt = sequence.charAt(i);
+            writer.write(charAt);
+        }
+    }
+    
+    private boolean isValidName(final CharSequence sequence) {
+        boolean result = true;
+        final int length = sequence.length();
+        for (int i=0;i<length;i++) {
+            char character = sequence.charAt(i);
+            if (i==0) {
+                if (!isValidNameStart(character)) {
+                    result = false;
+                    break;
+                }
+            } else {
+                if (!isValidNameBody(character)) {
+                    result = false;
+                    break;
+                }
+            }
+        }
+        return result;
+    }
+    
+    private boolean isValidNameStart(final char character) {
+        final byte code = CHARACTER_CODES[character];
+        final boolean result = (code & NAME_START_MASK) > 0;
+        return result;
+    }
+    
+    private boolean isValidNameBody(final char character) {
+        final byte code = CHARACTER_CODES[character];
+        final boolean result = (code & NAME_MASK) > 0;
+        return result;
+    }
+
+    public void attribute(CharSequence localName, CharSequence uri, CharSequence prefix, CharSequence value) throws IOException {
+        final CharSequence name = toName(localName, uri, prefix);
+        attribute(name, value);
+    }
+    
+    private CharSequence toName(CharSequence localName, CharSequence uri, CharSequence prefix) {
+        final CharSequence name;
+        if (prefix == null || "".equals(prefix) || uri == null || "".equals(uri)) {
+            name = localName;
+        } else {
+            name = prefix + ":" + localName;
+        }
+        return name;
+    }
+
+    public void openElement(CharSequence localName, CharSequence uri, CharSequence prefix) throws IOException {
+        final CharSequence name = toName(localName, uri, prefix);
+        openElement(name);
+        if (!prefixesDefined.contains(prefix)) {
+            prefixesDefined.add(prefix);
+            attribute("xmlns:" + prefix, uri);
+        }
+    }
+}
diff --git a/trunk/util/src/main/java/org/apache/jsieve/util/check/ScriptCheckMailAdapter.java b/trunk/util/src/main/java/org/apache/jsieve/util/check/ScriptCheckMailAdapter.java
new file mode 100644
index 0000000..02bf4ea
--- /dev/null
+++ b/trunk/util/src/main/java/org/apache/jsieve/util/check/ScriptCheckMailAdapter.java
@@ -0,0 +1,304 @@
+/****************************************************************
+ * 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.jsieve.util.check;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.List;
+
+import javax.mail.Header;
+import javax.mail.Message;
+import javax.mail.MessagingException;
+
+import org.apache.jsieve.SieveContext;
+import org.apache.jsieve.exception.SieveException;
+import org.apache.jsieve.mail.Action;
+import org.apache.jsieve.mail.MailAdapter;
+import org.apache.jsieve.mail.MailUtils;
+import org.apache.jsieve.mail.SieveMailException;
+import org.apache.jsieve.parser.address.SieveAddressBuilder;
+import org.apache.jsieve.parser.generated.address.ParseException;
+
+/**
+ * Checks script execution for an email. The wrapped email is set by called
+ * {@link #setMail}. Actions are recorded on {@link #executedActions} and can
+ * be retrieved by {@link #getExecutedActions()}.
+ */
+public class ScriptCheckMailAdapter implements MailAdapter {
+
+    private final List<Action> actions;
+
+    private final List<Action> executedActions;
+
+    private Message mail = null;
+
+    public ScriptCheckMailAdapter() {
+        actions = new ArrayList<Action>();
+        executedActions = new ArrayList<Action>();
+    }
+
+    /**
+     * Gets the wrapped email.
+     * 
+     * @return <code>Message</code>, possibly null
+     */
+    public Message getMail() {
+        return mail;
+    }
+
+    /**
+     * Sets the wrapped email and {@link #reset}s the adapter ready for another
+     * execution.
+     * 
+     * @param mail
+     *                <code>Message</code>, possibly null
+     */
+    public void setMail(Message mail) {
+        this.mail = mail;
+        reset();
+    }
+
+    /**
+     * Method addAction adds an Action to the List of Actions to be performed by
+     * the receiver.
+     * 
+     * @param action
+     */
+    public void addAction(final Action action) {
+        actions.add(action);
+    }
+
+    /**
+     * Method executeActions. Applies the Actions accumulated by the receiver.
+     */
+    public void executeActions() throws SieveException {
+        executedActions.clear();
+        executedActions.addAll(actions);
+    }
+
+    /**
+     * Gets the actions accumulated when {@link #executedActions} was last
+     * called.
+     * 
+     * @return <code>List</code> of {@link Action}s, not null. This list is a
+     *         modifiable copy
+     */
+    public List<Action> getExecutedActions() {
+        final ArrayList<Action> result = new ArrayList<Action>(executedActions);
+        return result;
+    }
+
+    /**
+     * Method getActions answers the List of Actions accumulated by the
+     * receiver. Implementations may elect to supply an unmodifiable collection.
+     * 
+     * @return <code>List</code> of {@link Action}'s, not null, possibly
+     *         unmodifiable
+     */
+    public List<Action> getActions() {
+        final List<Action> result = Collections.unmodifiableList(actions);
+        return result;
+    }
+
+    /**
+     * Resets executed and accumlated actions. An instance may be safely reused
+     * to check a script once this method has been called.
+     */
+    public void reset() {
+        executedActions.clear();
+        actions.clear();
+    }
+
+    /**
+     * Method getHeader answers a List of all of the headers in the receiver
+     * whose name is equal to the passed name. If no headers are found an empty
+     * List is returned.
+     * 
+     * @param name
+     * @return <code>List</code> not null, possibly empty
+     * @throws SieveMailException
+     */
+    @SuppressWarnings("unchecked")
+    public List<String> getHeader(String name) throws SieveMailException {
+        List<String> result = Collections.EMPTY_LIST;
+        if (mail != null) {
+            try {
+                String[] values = mail.getHeader(name);
+                if (values != null) {
+                    result = Arrays.asList(values);
+                }
+            } catch (MessagingException e) {
+                throw new SieveMailException(e);
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Method getHeaderNames answers a List of all of the headers in the
+     * receiver. No duplicates are allowed.
+     * 
+     * @return <code>List</code>, not null possible empty, possible
+     *         unmodifiable
+     * @throws SieveMailException
+     */
+    @SuppressWarnings("unchecked")
+    public List<String> getHeaderNames() throws SieveMailException {
+        List<String> results = Collections.EMPTY_LIST;
+        if (mail != null) {
+            try {
+                results = new ArrayList<String>();
+                for (final Enumeration en = mail.getAllHeaders(); en
+                        .hasMoreElements();) {
+                    final Header header = (Header) en.nextElement();
+                    final String name = header.getName();
+                    if (!results.contains(name)) {
+                        results.add(name);
+                    }
+                }
+            } catch (MessagingException e) {
+                throw new SieveMailException(e);
+            }
+        }
+        return results;
+    }
+
+    /**
+     * <p>
+     * Method getMatchingHeader answers a List of all of the headers in the
+     * receiver with the passed name. If no headers are found an empty List is
+     * returned.
+     * </p>
+     * 
+     * <p>
+     * This method differs from getHeader(String) in that it ignores case and
+     * the whitespace prefixes and suffixes of a header name when performing the
+     * match, as required by RFC 3028. Thus "From", "from ", " From" and " from "
+     * are considered equal.
+     * </p>
+     * 
+     * @param name
+     * @return <code>List</code>, not null possibly empty
+     * @throws SieveMailException
+     */
+    @SuppressWarnings("unchecked")
+    public List<String> getMatchingHeader(String name) throws SieveMailException {
+        List<String> result = Collections.EMPTY_LIST;
+        if (mail != null) {
+            result = MailUtils.getMatchingHeader(this, name);
+        }
+        return result;
+    }
+
+    /**
+     * Method getSize answers the receiver's message size in octets.
+     * 
+     * @return int
+     * @throws SieveMailException
+     */
+    public int getSize() throws SieveMailException {
+        int result = 0;
+        if (mail != null) {
+            try {
+                result = mail.getSize();
+            } catch (MessagingException e) {
+                throw new SieveMailException(e);
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Method getContentType returns string/mime representation of the message
+     * type.
+     * 
+     * @return String
+     * @throws SieveMailException
+     */
+    public String getContentType() throws SieveMailException {
+        String result = null;
+        if (mail != null) {
+            try {
+                result = mail.getContentType();
+            } catch (MessagingException e) {
+                throw new SieveMailException(e);
+            }
+        }
+        return result;
+    }
+
+    public Address[] parseAddresses(String headerName)
+            throws SieveMailException {
+        return parseAddresses(headerName, mail);
+    }
+
+    /**
+     * Parses the value from the given message into addresses.
+     * 
+     * @param headerName
+     *                header name, to be matched case insensitively
+     * @param message
+     *                <code>Message</code>, not null
+     * @return <code>Address</code> array, not null possibly empty
+     * @throws SieveMailException
+     */
+    public Address[] parseAddresses(final String headerName,
+            final Message message) throws SieveMailException {
+        try {
+           final SieveAddressBuilder builder = new SieveAddressBuilder();
+
+            for (Enumeration en = message.getAllHeaders(); en.hasMoreElements();) {
+                final Header header = (Header) en.nextElement();
+                final String name = header.getName();
+                if (name.trim().equalsIgnoreCase(headerName)) {
+                    builder.addAddresses(header.getValue());
+                }
+            }
+
+            final Address[] results = builder.getAddresses();
+            return results;
+
+        } catch (MessagingException ex) {
+           throw new SieveMailException(ex);
+        } catch (ParseException ex) {
+            throw new SieveMailException(ex);
+        }
+    }
+
+    public boolean isInBodyText(String phraseCaseInsensitive) throws SieveMailException {
+        boolean result = false;
+        if (mail != null) {
+            try {
+                result = mail.getContent().toString().toLowerCase().contains(phraseCaseInsensitive);
+            } catch (MessagingException e) {
+                throw new SieveMailException(e);
+            } catch (IOException e) {
+                throw new SieveMailException(e);
+            }
+        }
+        return result;
+    }
+
+    public void setContext(SieveContext context) {}
+
+}
diff --git a/trunk/util/src/main/java/org/apache/jsieve/util/check/ScriptChecker.java b/trunk/util/src/main/java/org/apache/jsieve/util/check/ScriptChecker.java
new file mode 100644
index 0000000..c26ec74
--- /dev/null
+++ b/trunk/util/src/main/java/org/apache/jsieve/util/check/ScriptChecker.java
@@ -0,0 +1,241 @@
+/****************************************************************
+ * 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.jsieve.util.check;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+
+import javax.mail.MessagingException;
+import javax.mail.internet.MimeMessage;
+
+import org.apache.jsieve.ConfigurationManager;
+import org.apache.jsieve.exception.SieveException;
+import org.apache.jsieve.mail.ActionFileInto;
+import org.apache.jsieve.mail.ActionKeep;
+import org.apache.jsieve.mail.ActionRedirect;
+import org.apache.jsieve.mail.ActionReject;
+import org.apache.jsieve.parser.generated.ParseException;
+
+/**
+ * Checks a <code>sieve</code> script by executing it against a given mail and
+ * reporting the results.
+ */
+public class ScriptChecker {
+
+    private final ScriptCheckMailAdapter adapter;
+
+    public ScriptChecker() {
+        adapter = new ScriptCheckMailAdapter();
+    }
+
+    /**
+     * Checks the <code>sieve</code> script contained in the given file by
+     * executing it against the given message.
+     * 
+     * @param message
+     *                <code>File</code> containing the mail message to be fed
+     *                to the script, not null
+     * @param script
+     *                <code>File</code> containing the script to be checked
+     * @return <code>Results</code> of that execution
+     * @throws IOException
+     * @throws MessageException
+     */
+    public Results check(final File message, final File script)
+            throws IOException, MessagingException {
+        final FileInputStream messageStream = new FileInputStream(message);
+        final FileInputStream scriptStream = new FileInputStream(script);
+        final Results results = check(messageStream, scriptStream);
+        return results;
+    }
+
+    /**
+     * Checks the <code>sieve</code> script contained in the given file by
+     * executing it against the given message.
+     * 
+     * @param script
+     *                <code>InputStream</code>, not null
+     * @return <code>Results</code> of the check, not null
+     * @throws IOException
+     * @throws MessagingException
+     */
+    public Results check(final InputStream message, final InputStream script)
+            throws IOException, MessagingException {
+        MimeMessage mimeMessage = new MimeMessage(null, message);
+        adapter.setMail(mimeMessage);
+        Results results;
+        try {
+            new ConfigurationManager().build().interpret(adapter, script);
+            final List executedActions = adapter.getExecutedActions();
+            results = new Results(executedActions);
+        } catch (ParseException e) {
+            e.printStackTrace();
+            results = new Results(e);
+        } catch (SieveException e) {
+            e.printStackTrace();
+            results = new Results(e);
+        }
+        return results;
+    }
+
+    /**
+     * Contains results of script execution.
+     */
+    public final static class Results {
+        private final boolean pass;
+
+        private final Exception exception;
+
+        private final List actionsExecuted;
+
+        public Results(final Exception ex) {
+            this.exception = ex;
+            pass = false;
+            actionsExecuted = null;
+        }
+
+        public Results(final List actions) {
+            this.pass = true;
+            exception = null;
+            this.actionsExecuted = actions;
+        }
+
+        /**
+         * Is this a pass?
+         * 
+         * @return true if the script executed without error, false if errors
+         *         were encountered
+         */
+        public boolean isPass() {
+            return pass;
+        }
+
+        /**
+         * Gets the exception which was thrown during execution.
+         * 
+         * @return <code>Exception</code> or null if no exception was thrown
+         */
+        public Exception getException() {
+            return exception;
+        }
+
+        /**
+         * Gets the actions executed by the script.
+         * 
+         * @return <code>List</code> of actions or null if the script failed
+         */
+        public List getActionsExecuted() {
+            return actionsExecuted;
+        }
+
+        /**
+         * Is the <code>n<code>'th action a <code>FileInto</code>
+         * with given destination?
+         * @param destination <code>String</code> destination for the file into
+         * @param n index to check
+         * @return  true if the <code>n<code>'th action is a <code>FileInto</code>
+         * with given destination
+         */
+        public boolean isActionFileInto(String destination, int n) {
+            boolean result = false;
+            Object action = actionsExecuted.get(n);
+            if (action != null && action instanceof ActionFileInto) {
+                ActionFileInto actionFileInto = (ActionFileInto) action;
+                result = destination.equals(actionFileInto.getDestination());
+            }
+            return result;
+        }
+
+        /**
+         * Is the <code>n<code>'th action a <code>Redirect</code>
+         * with given address?
+         * @param address <code>String</code> redirect address
+         * @param n index to check
+         * @return  true if the <code>n<code>'th action is a <code>Redirect</code>
+         * with given redirect address
+         */
+        public boolean isActionRedirect(String address, int n) {
+            boolean result = false;
+            Object action = actionsExecuted.get(n);
+            if (action != null && action instanceof ActionRedirect) {
+                ActionRedirect actionRedirect = (ActionRedirect) action;
+                result = address.equals(actionRedirect.getAddress());
+            }
+            return result;
+        }
+
+        /**
+         * Is the <code>n<code>'th action a <code>Reject</code>
+         * with given message?
+         * @param message <code>String</code> rejection message
+         * @param n index to check
+         * @return  true if the <code>n<code>'th action is a <code>Reject</code>
+         * with given reject message
+         */
+        public boolean isActionReject(String message, int n) {
+            boolean result = false;
+            Object action = actionsExecuted.get(n);
+            if (action != null && action instanceof ActionReject) {
+                ActionReject actionReject = (ActionReject) action;
+                result = message.equals(actionReject.getMessage());
+            }
+            return result;
+        }
+
+        /**
+         * Is the <code>n<code>'th action a <code>Keep</code>?
+         * @param n index to check
+         * @return  true if the <code>n<code>'th action is a <code>Keep</code>
+         */
+        public boolean isActionKeep(int n) {
+            boolean result = false;
+            Object action = actionsExecuted.get(n);
+            if (action != null && action instanceof ActionKeep) {
+                result = true;
+            }
+            return result;
+        }
+
+        /**
+         * Prints out details of results.
+         */
+        public String toString() {
+            StringBuffer buffer = new StringBuffer("Results: ");
+            if (pass) {
+                buffer.append("PASS");
+            } else {
+                buffer.append("FAIL: ");
+                if (exception != null) {
+                    if (exception instanceof ParseException) {
+                        buffer.append("Cannot parse script");
+                    } else {
+                        buffer.append("Cannot excute script");
+                    }
+                    buffer.append(exception.getMessage());
+                }
+            }
+            return buffer.toString();
+        }
+
+    }
+}
diff --git a/trunk/util/src/main/java/org/apache/jsieve/util/check/package.html b/trunk/util/src/main/java/org/apache/jsieve/util/check/package.html
new file mode 100644
index 0000000..37dd94b
--- /dev/null
+++ b/trunk/util/src/main/java/org/apache/jsieve/util/check/package.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<HTML>
+<HEAD>
+<!--
+
+  @(#)package.html
+
+  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.       
+-->
+
+</HEAD>
+<BODY>
+
+<p>This package contains the <code>ScriptCheckMailAdapter</code> 
+MailAdapter implementation. This adapter wraps a standard JavaMail
+message and records the actions to be executed.
+</p>
+</BODY>
+</HTML>
diff --git a/trunk/util/src/main/java/org/apache/jsieve/util/package.html b/trunk/util/src/main/java/org/apache/jsieve/util/package.html
new file mode 100644
index 0000000..117ef8b
--- /dev/null
+++ b/trunk/util/src/main/java/org/apache/jsieve/util/package.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<HTML>
+<HEAD>
+<!--
+
+  @(#)package.html
+
+  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.       
+-->
+
+</HEAD>
+<BODY>
+
+<p>Useful utilities for Sieve.</p>
+<p>The general design approach has been to create finely grained flexible POJOs,
+and then use coursely grained static utilities (<code>XYZUtils</code>) to offer 
+an easy interface to recommended defaults.</p>
+</BODY>
+</HTML>
\ No newline at end of file
diff --git a/trunk/util/src/site/resources/images/asf-logo-reduced.gif b/trunk/util/src/site/resources/images/asf-logo-reduced.gif
new file mode 100644
index 0000000..93cc102
--- /dev/null
+++ b/trunk/util/src/site/resources/images/asf-logo-reduced.gif
Binary files differ
diff --git a/trunk/util/src/site/resources/images/james-jsieve-logo.gif b/trunk/util/src/site/resources/images/james-jsieve-logo.gif
new file mode 100644
index 0000000..9c7e34f
--- /dev/null
+++ b/trunk/util/src/site/resources/images/james-jsieve-logo.gif
Binary files differ
diff --git a/trunk/util/src/site/site.xml b/trunk/util/src/site/site.xml
new file mode 100644
index 0000000..737436e
--- /dev/null
+++ b/trunk/util/src/site/site.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!--

+  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.    

+-->

+<project name="jSieve">
+  <bannerLeft>
+    <name>JAMES jSieve</name>
+    <src>images/james-jsieve-logo.gif</src>
+    <href>http://james.apache.org/</href>
+  </bannerLeft>
+
+  <bannerRight>

+    <name>The Apache Software Foundation</name>

+    <src>images/asf-logo-reduced.gif</src>

+    <href>http://www.apache.org/index.html</href>

+  </bannerRight> 

+
+  <body>
+
+    <menu name="Sieve Utilities">
+      <item name="Overview" href="index.html"/>
+      <item 
+        name="DOAP" 
+        href="doap_apache-jsieve.rdf" 
+        img='http://www.w3.org/RDF/icons/rdf_metadata_button.32'/>
+    </menu>
+    
+    <menu ref="reports" />
+  </body>
+</project>
diff --git a/trunk/util/src/site/xdoc/index.xml b/trunk/util/src/site/xdoc/index.xml
new file mode 100644
index 0000000..7d89e31
--- /dev/null
+++ b/trunk/util/src/site/xdoc/index.xml
@@ -0,0 +1,75 @@
+<?xml version="1.0"?>
+<!--
+  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.  
+-->
+<document>
+
+ <properties>
+  <title>Overview</title>
+  <author email="jsieve-dev@jakarta.apache.org">jSieve Project</author>
+ </properties>
+
+<body>
+<section name="Introduction">
+<p>
+jSieve is a Java implementation of the Sieve mail filtering language defined by 
+<a href='http://www.rfc-editor.org/rfc/rfc3028.txt'>RFC 3028</a>. jSieve is implemented 
+as a language processor that can be plugged into any internet mail application to add 
+Sieve support.
+</p>
+<p>
+JSieve Utilities collects lightweight frameworks and utilities useful when
+working with Sieve (in general) and 
+<a href='http://james.apache.org/jsieve'>JSieve</a> (in particular).
+</p>
+<subsection name='What is Sieve?'>
+<p>
+Sieve is an extensible mail filtering language. It's limited expressiveness (no loops
+or variables, no tests with side effects) allows user created scripts to be run 
+safely on email servers. Sieve is targeted at the final delivery phase 
+(where an incoming email is transferred to a user's mailbox).
+</p>
+<p>
+Sieve scripts are composed of commands. Control commands manage the execution of the script.
+Test commands define side-effect free criteria. 
+Action commands are mail operations to be performed.
+</p>
+</subsection>
+</section>
+<section name='Highlights'>
+  <subsection name='Script Checker'>
+    <p>
+      Checks Sieve scripts. Includes monitoring controlled execution of a script against a message.
+    </p>
+  </subsection>
+  <subsection name='Sieve In Xml'>
+    <p>
+    <a href='http://tools.ietf.org/html/draft-freed-sieve-in-xml-04' rel='tag'>Sieve-in-xml</a> is an 
+    <a href='http://dbpedia.org/resource/Internet_Draft' rel='tag'>Internet Draft</a>.
+    It describes a method to convert Sieve scripts to and from an XML format suitable
+    for editing by a UI. A typical use case is as the payload for a web based editor backed by web services.
+    </p><p>
+    <code>OutputUtils</code> contains an experimental preview of a subset of this draft. It allows a 
+    JSieve node tree to be output as sieve-in-xml. Full support is planned but feedback is 
+    encouraged, through the <a href='http://james.apache.org/mail.html'>mailing lists</a> 
+    or the <a href='http://issues.apache.org/jira/browse/JSIEVE'>issue tracker</a>.
+    </p>
+  </subsection>
+</section>
+</body>
+</document>
diff --git a/trunk/util/src/test/java/org/apache/jsieve/util/NodeToSieveAdapterTest.java b/trunk/util/src/test/java/org/apache/jsieve/util/NodeToSieveAdapterTest.java
new file mode 100644
index 0000000..9d9aebb
--- /dev/null
+++ b/trunk/util/src/test/java/org/apache/jsieve/util/NodeToSieveAdapterTest.java
@@ -0,0 +1,193 @@
+/****************************************************************
+ * 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.jsieve.util;
+
+import org.apache.jsieve.NumberArgument;
+import org.apache.jsieve.TagArgument;
+import org.apache.jsieve.parser.generated.ASTargument;
+import org.apache.jsieve.parser.generated.ASTarguments;
+import org.apache.jsieve.parser.generated.ASTblock;
+import org.apache.jsieve.parser.generated.ASTcommand;
+import org.apache.jsieve.parser.generated.ASTcommands;
+import org.apache.jsieve.parser.generated.ASTstart;
+import org.apache.jsieve.parser.generated.ASTstring;
+import org.apache.jsieve.parser.generated.ASTstring_list;
+import org.apache.jsieve.parser.generated.ASTtest;
+import org.apache.jsieve.parser.generated.ASTtest_list;
+import org.apache.jsieve.parser.generated.SieveParserTreeConstants;
+import org.apache.jsieve.parser.generated.SimpleNode;
+import org.apache.jsieve.parser.generated.Token;
+import org.jmock.Mock;
+import org.jmock.MockObjectTestCase;
+
+public class NodeToSieveAdapterTest extends MockObjectTestCase {
+
+    Mock mock;
+    NodeToSieveAdapter subject;
+    
+    protected void setUp() throws Exception {
+        super.setUp();
+        mock = mock(SieveHandler.class);
+        subject = new NodeToSieveAdapter((SieveHandler)mock.proxy());
+    }
+
+    protected void tearDown() throws Exception {
+        super.tearDown();
+    }
+    
+    public void testStart() throws Exception {
+        // Not propergated
+        subject.start();
+    }
+    
+    public void testEnd() throws Exception {
+        // Not propergated
+        subject.end();
+    }
+
+    public void testEndSimpleNode() throws Exception {
+        // Not propergated
+        subject.end(new SimpleNode(SieveParserTreeConstants.JJTBLOCK));
+    }
+
+    public void testEndASTstart() throws Exception {
+        mock.expects(once()).method("endScript");
+        subject.end(new ASTstart(SieveParserTreeConstants.JJTSTART));
+    }
+
+    public void testEndASTcommands() throws Exception {
+        mock.expects(once()).method("endCommands");
+        subject.end(new ASTcommands(SieveParserTreeConstants.JJTCOMMANDS));
+    }
+
+    public void testEndASTcommand() throws Exception {
+        String name = "CommandName";
+        mock.expects(once()).method("endCommand").with(eq(name));
+        ASTcommand node = new ASTcommand(SieveParserTreeConstants.JJTCOMMAND);
+        node.setName(name);
+        subject.end(node);
+    }
+
+    public void testEndASTblock() throws Exception {
+        mock.expects(once()).method("endBlock");
+        subject.end(new ASTblock(SieveParserTreeConstants.JJTBLOCK)); 
+    }
+
+    public void testEndASTarguments() throws Exception {
+        mock.expects(once()).method("endArguments");
+        subject.end(new ASTarguments(SieveParserTreeConstants.JJTARGUMENTS)); 
+    }
+
+    public void testASTargumentTag() throws Exception {
+        String tag = "Hugo";
+        mock.expects(once()).method("argument").with(eq(tag));
+        ASTargument argument = new ASTargument(SieveParserTreeConstants.JJTARGUMENTS);
+        argument.setValue(new TagArgument(new Token(0, tag)));
+        subject.start(argument); 
+        subject.end(argument); 
+    }
+    
+    public void testASTargumentNumber() throws Exception {
+        int number = 17;
+        mock.expects(once()).method("argument").with(eq(number));
+        ASTargument argument = new ASTargument(SieveParserTreeConstants.JJTARGUMENTS);
+        argument.setValue(new NumberArgument(new Token(0, "17")));
+        subject.start(argument); 
+        subject.end(argument); 
+    }
+
+    public void testEndASTtest() throws Exception {
+        String name = "ATestName";
+        mock.expects(once()).method("endTest").with(eq(name));
+        ASTtest node = new ASTtest(SieveParserTreeConstants.JJTTEST);
+        node.setName(name);
+        subject.end(node);
+    }
+
+    public void testEndASTtest_list() throws Exception {
+        mock.expects(once()).method("endTestList");
+        subject.end(new ASTtest_list(SieveParserTreeConstants.JJTTEST_LIST)); 
+    }
+
+    public void testEndASTstring_list() throws Exception {
+        mock.expects(once()).method("endStringListArgument");
+        subject.end(new ASTstring_list(SieveParserTreeConstants.JJTSTRING_LIST)); 
+    }
+
+    public void testStartSimpleNode() throws Exception {
+        // Not propergated
+        subject.end(new SimpleNode(SieveParserTreeConstants.JJTBLOCK));
+    }
+
+    public void testStartASTstart() throws Exception {
+        mock.expects(once()).method("startScript");
+        subject.start(new ASTstart(SieveParserTreeConstants.JJTSTART));
+    }
+
+    public void testStartASTcommands() throws Exception {
+        mock.expects(once()).method("startCommands");
+        subject.start(new ASTcommands(SieveParserTreeConstants.JJTCOMMANDS));
+    }
+
+    public void testStartASTcommand() throws Exception {
+        String name = "CommandName";
+        mock.expects(once()).method("startCommand").with(eq(name));
+        ASTcommand node = new ASTcommand(SieveParserTreeConstants.JJTCOMMAND);
+        node.setName(name);
+        subject.start(node);
+    }
+
+    public void testStartASTblock() throws Exception {
+        mock.expects(once()).method("startBlock");
+        subject.start(new ASTblock(SieveParserTreeConstants.JJTBLOCK)); 
+    }
+
+    public void testStartASTarguments() throws Exception {
+        mock.expects(once()).method("startArguments");
+        subject.start(new ASTarguments(SieveParserTreeConstants.JJTARGUMENTS)); 
+    }
+
+    public void testStartASTtest() throws Exception {
+        String name = "ATestName";
+        mock.expects(once()).method("startTest").with(eq(name));
+        ASTtest node = new ASTtest(SieveParserTreeConstants.JJTTEST);
+        node.setName(name);
+        subject.start(node);
+    }
+
+    public void testStartASTtest_list() throws Exception {
+        mock.expects(once()).method("startTestList");
+        subject.start(new ASTtest_list(SieveParserTreeConstants.JJTTEST_LIST));
+    }
+
+    public void testASTstring() throws Exception {
+        String string = "A Value";
+        mock.expects(once()).method("listMember").with(eq(string));
+        ASTstring node = new ASTstring(SieveParserTreeConstants.JJTSTRING);
+        node.setValue(string);
+        subject.start(node); 
+        subject.end(node); 
+    }
+
+    public void testStartASTstring_list() throws Exception {
+        mock.expects(once()).method("startStringListArgument");
+        subject.start(new ASTstring_list(SieveParserTreeConstants.JJTSTRING_LIST)); 
+    }
+
+}
diff --git a/trunk/util/src/test/java/org/apache/jsieve/util/SieveGenerationTest.java b/trunk/util/src/test/java/org/apache/jsieve/util/SieveGenerationTest.java
new file mode 100644
index 0000000..5f70d6d
--- /dev/null
+++ b/trunk/util/src/test/java/org/apache/jsieve/util/SieveGenerationTest.java
@@ -0,0 +1,71 @@
+/****************************************************************
+ * 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.jsieve.util;
+
+import java.io.ByteArrayInputStream;
+import java.io.StringWriter;
+
+import org.apache.jsieve.ConfigurationManager;
+import org.apache.jsieve.parser.generated.Node;
+
+import junit.framework.TestCase;
+
+public class SieveGenerationTest extends TestCase {
+    
+    protected void setUp() throws Exception {
+        super.setUp();
+    }
+
+    protected void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    public void testShouldGenerateSoloToEmailScriptFromNode() throws Exception {
+        assertShouldRoundtripScript(
+                "if address :is :all [\"to\"] [\"coyote@desert.example.org\"] {"
+                + "fileinto [\"coyote\"];} "
+                + "if address :is :all [\"to\"] [\"bugs@example.org\"] {"
+                + "fileinto [\"bugs\"];} "
+                + "if address :is :all [\"to\"] [\"roadrunneracme.@example.org\"] {"
+                + "fileinto [\"rr\"];} "
+                + "if address :is :all [\"to\"] [\"elmer@hunters.example.org\"] {"
+                + "fileinto [\"elmer\"];}");
+    }
+    
+    public void testShouldGenerateSimpleScriptFromNode() throws Exception {
+        assertShouldRoundtripScript("if address :all :is [\"from\"] [\"user@domain\"] {stop;}");
+    }
+    
+    public void testShouldGenerateFileintoFromNode() throws Exception {
+        assertShouldRoundtripScript("fileinto [\"INBOX.test1\"]; fileinto [\"INBOX.test1\"];");
+    }
+    
+    private void assertShouldRoundtripScript(final String script) throws Exception {
+        // Set up
+        final Node node = new ConfigurationManager().build().parse(new ByteArrayInputStream(script.getBytes()));
+        final StringWriter monitor = new StringWriter();
+        
+        // Exercise
+        OutputUtils.toSieve(node, monitor);
+        
+        // Verify
+        assertEquals(script, monitor.toString());
+    }
+
+}
diff --git a/trunk/util/src/test/java/org/apache/jsieve/util/SieveToXmlNullNameTest.java b/trunk/util/src/test/java/org/apache/jsieve/util/SieveToXmlNullNameTest.java
new file mode 100644
index 0000000..a583006
--- /dev/null
+++ b/trunk/util/src/test/java/org/apache/jsieve/util/SieveToXmlNullNameTest.java
@@ -0,0 +1,75 @@
+ /****************************************************************
+ * 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.jsieve.util;
+
+import org.jmock.Mock;
+import org.jmock.MockObjectTestCase;
+
+public class SieveToXmlNullNameTest extends MockObjectTestCase {
+
+    SieveToXml subject;
+    Mock mockOut;
+    SieveHandler instance;
+    
+    protected void setUp() throws Exception {
+        super.setUp();
+        subject = new SieveToXml();
+        subject.setNameAttributeName(null);
+        mockOut = mock(SieveToXml.Out.class);
+        instance = subject.build((SieveToXml.Out) mockOut.proxy());
+    }
+
+    protected void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    private void assertElementIsOutput(final String commandName, final String elementName) throws HaltTraversalException {
+        mockOut.expects(once()).method("openElement").with(eq(elementName), 
+                eq(SieveToXml.DEFAULT_NAMESPACE), eq(SieveToXml.DEFAULT_PREFIX)).id("1");
+        mockOut.expects(once()).method("closeElement").after("1");
+        assertBuilderIsReturned(instance.startCommand(commandName));
+        assertBuilderIsReturned(instance.endCommand(commandName));
+    }
+
+    private void assertBuilderIsReturned(SieveHandler handler) {
+        assertEquals("Builder pattern so instance should be returned", instance, handler);
+    }
+    
+    public void testBuildIsNotNull() {
+        assertNotNull(instance);
+    }
+        
+    public void testControlCommandShouldOutputElement() throws Exception {
+        assertElementIsOutput("if", SieveToXml.DEFAULT_NAME_CONTROL_COMMAND);
+    }
+
+    public void testActionCommandShouldOutputElement() throws Exception {
+        assertElementIsOutput("other", SieveToXml.DEFAULT_NAME_ACTION_COMMAND);
+    }
+    
+    public void testTestShouldOutputElement() throws Exception {
+        final String testName = "is";
+        mockOut.expects(once()).method("openElement").with(eq(SieveToXml.DEFAULT_NAME_TEST), 
+                eq(SieveToXml.DEFAULT_NAMESPACE), eq(SieveToXml.DEFAULT_PREFIX)).id("1");
+        mockOut.expects(once()).method("closeElement").after("1");
+        assertBuilderIsReturned(instance.startTest(testName));
+        assertBuilderIsReturned(instance.endTest(testName));
+    }
+   
+}
diff --git a/trunk/util/src/test/java/org/apache/jsieve/util/SieveToXmlTest.java b/trunk/util/src/test/java/org/apache/jsieve/util/SieveToXmlTest.java
new file mode 100644
index 0000000..5e4aef0
--- /dev/null
+++ b/trunk/util/src/test/java/org/apache/jsieve/util/SieveToXmlTest.java
@@ -0,0 +1,140 @@
+ /****************************************************************
+ * 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.jsieve.util;
+
+import org.jmock.Mock;
+import org.jmock.MockObjectTestCase;
+
+public class SieveToXmlTest extends MockObjectTestCase {
+
+    SieveToXml subject;
+    Mock mockOut;
+    SieveHandler instance;
+    
+    protected void setUp() throws Exception {
+        super.setUp();
+        subject = new SieveToXml();
+        mockOut = mock(SieveToXml.Out.class);
+        instance = subject.build((SieveToXml.Out) mockOut.proxy());
+    }
+
+    protected void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    private void assertElementIsOutput(final String commandName, final String elementName) throws HaltTraversalException {
+        mockOut.expects(once()).method("openElement").with(eq(elementName), 
+                eq(SieveToXml.DEFAULT_NAMESPACE), eq(SieveToXml.DEFAULT_PREFIX)).id("1");
+        mockOut.expects(once()).method("attribute").with(eq(SieveToXml.DEFAULT_NAME_ATTRIBUTE), eq(SieveToXml.DEFAULT_NAMESPACE), 
+                eq(SieveToXml.DEFAULT_PREFIX), eq(commandName)).id("2");
+        mockOut.expects(once()).method("closeElement").after("2");
+        assertBuilderIsReturned(instance.startCommand(commandName));
+        assertBuilderIsReturned(instance.endCommand(commandName));
+    }
+
+    private void assertBuilderIsReturned(SieveHandler handler) {
+        assertEquals("Builder pattern so instance should be returned", instance, handler);
+    }
+    
+    public void testBuildIsNotNull() {
+        assertNotNull(instance);
+    }
+    
+    public void testStartScriptShouldBeIgnored() throws Exception {
+        assertBuilderIsReturned(instance.startScript());
+    }
+    
+    public void testEndScriptShouldBeIgnored() throws Exception {
+        assertBuilderIsReturned(instance.endScript());
+    }
+    
+    public void testStartBlockShouldBeIgnored() throws Exception {
+        assertBuilderIsReturned(instance.startBlock());
+    }
+    
+    public void testEndBlockShouldBeIgnored() throws Exception {
+        assertBuilderIsReturned(instance.endBlock());
+    }
+    
+    public void testStartCommandsShouldBeIgnored() throws Exception {
+        assertBuilderIsReturned(instance.startCommands());
+    }
+    
+    public void testEndCommandsShouldBeIgnored() throws Exception {
+        assertBuilderIsReturned(instance.endCommands());
+    }
+    
+    public void testControlCommandShouldOutputElement() throws Exception {
+        assertElementIsOutput("if", SieveToXml.DEFAULT_NAME_CONTROL_COMMAND);
+    }
+
+    public void testActionCommand() throws Exception {
+        assertElementIsOutput("other", SieveToXml.DEFAULT_NAME_ACTION_COMMAND);
+    }
+    
+    public void testStringArgument() throws Exception {
+        final String stringArgument = "A String Tag";
+        mockOut.expects(once()).method("openElement").with(eq(SieveToXml.DEFAULT_NAME_STRING), 
+                eq(SieveToXml.DEFAULT_NAMESPACE), eq(SieveToXml.DEFAULT_PREFIX)).id("1");
+        mockOut.expects(once()).method("content").with(eq(stringArgument)).after("1").id("2");
+        mockOut.expects(once()).method("closeElement").after("2");
+        assertBuilderIsReturned(instance.listMember(stringArgument));
+    }
+    
+    public void testNumericArgument()throws Exception {
+        int number = 42;
+        mockOut.expects(once()).method("openElement").with(eq(SieveToXml.DEFAULT_NAME_NUM), 
+                eq(SieveToXml.DEFAULT_NAMESPACE), eq(SieveToXml.DEFAULT_PREFIX)).id("1");
+        mockOut.expects(once()).method("content").with(eq(Integer.toString(number))).after("1").id("2");
+        mockOut.expects(once()).method("closeElement").after("2");
+        assertBuilderIsReturned(instance.argument(number));
+    }
+    
+    public void testTagArgument() throws Exception {
+        String tag = "A Tag";
+        mockOut.expects(once()).method("openElement").with(eq(SieveToXml.DEFAULT_NAME_TAG), 
+                eq(SieveToXml.DEFAULT_NAMESPACE), eq(SieveToXml.DEFAULT_PREFIX)).id("1");
+        mockOut.expects(once()).method("content").with(eq(tag)).after("1").id("2");
+        mockOut.expects(once()).method("closeElement").after("2");
+        assertBuilderIsReturned(instance.argument(tag));
+    }
+    
+    public void testStartTestList() throws Exception {
+        mockOut.expects(once()).method("openElement").with(eq(SieveToXml.DEFAULT_NAME_LIST), 
+                eq(SieveToXml.DEFAULT_NAMESPACE), eq(SieveToXml.DEFAULT_PREFIX));
+        assertBuilderIsReturned(instance.startTestList());
+    }
+    
+    public void testEndTestList() throws Exception {
+        mockOut.expects(once()).method("closeElement");
+        assertBuilderIsReturned(instance.endTestList());
+    }
+    
+    public void testTest() throws Exception {
+        final String testName = "is";
+        mockOut.expects(once()).method("openElement").with(eq(SieveToXml.DEFAULT_NAME_TEST), 
+                eq(SieveToXml.DEFAULT_NAMESPACE), eq(SieveToXml.DEFAULT_PREFIX)).id("1");
+        mockOut.expects(once()).method("attribute").with(eq(SieveToXml.DEFAULT_NAME_ATTRIBUTE), eq(SieveToXml.DEFAULT_NAMESPACE), 
+                eq(SieveToXml.DEFAULT_PREFIX), eq(testName)).id("2");
+        mockOut.expects(once()).method("closeElement").after("2");
+        assertBuilderIsReturned(instance.startTest(testName));
+        assertBuilderIsReturned(instance.endTest(testName));
+    }
+   
+}
diff --git a/trunk/util/src/test/java/org/apache/jsieve/util/SimpleNodeTraverserTest.java b/trunk/util/src/test/java/org/apache/jsieve/util/SimpleNodeTraverserTest.java
new file mode 100644
index 0000000..c00e5c4
--- /dev/null
+++ b/trunk/util/src/test/java/org/apache/jsieve/util/SimpleNodeTraverserTest.java
@@ -0,0 +1,73 @@
+/****************************************************************
+ * 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.jsieve.util;
+
+import java.io.ByteArrayInputStream;
+
+import org.apache.jsieve.ConfigurationManager;
+import org.apache.jsieve.parser.generated.ASTarguments;
+import org.apache.jsieve.parser.generated.ASTcommand;
+import org.apache.jsieve.parser.generated.ASTcommands;
+import org.apache.jsieve.parser.generated.ASTstart;
+import org.apache.jsieve.parser.generated.Node;
+import org.jmock.Mock;
+import org.jmock.MockObjectTestCase;
+
+public class SimpleNodeTraverserTest extends MockObjectTestCase {
+
+    Mock mock;
+    NodeHandler handler;
+    
+    NodeTraverser subject;
+    
+    protected void setUp() throws Exception {
+        super.setUp();
+        mock = mock(NodeHandler.class);
+        handler = (NodeHandler) mock.proxy();
+        
+        subject = new NodeTraverser();
+    }
+
+    protected void tearDown() throws Exception {
+        super.tearDown();
+    }
+    
+    public void testTraverseSimpleScript() throws Exception {
+        mock.expects(once()).method("start").id("1");
+        mock.expects(once()).method("start").with(isA(ASTstart.class)).after("1").id("2");
+        mock.expects(once()).method("start").with(isA(ASTcommands.class)).after("2").id("3");
+        mock.expects(once()).method("start").with(isA(ASTcommand.class)).after("3").id("4");
+        mock.expects(once()).method("start").with(isA(ASTarguments.class)).after("4").id("5");
+        mock.expects(once()).method("end").with(isA(ASTarguments.class)).after("5").id("6");
+        mock.expects(once()).method("end").with(isA(ASTcommand.class)).after("6").id("7");
+        mock.expects(once()).method("end").with(isA(ASTcommands.class)).after("7").id("8");
+        mock.expects(once()).method("end").with(isA(ASTstart.class)).after("8").id("9");
+        mock.expects(once()).method("end").after("9");
+        traverse("Keep;");
+    }
+    
+    private void traverse(String script) throws Exception {
+        subject.traverse(handler, parse(script));
+    }
+
+    private Node parse(String script) throws Exception {
+        return new ConfigurationManager().build().parse(
+                new ByteArrayInputStream(script.getBytes()));
+    }
+}
diff --git a/trunk/util/src/test/java/org/apache/jsieve/util/ToSieveHandlerFactoryTest.java b/trunk/util/src/test/java/org/apache/jsieve/util/ToSieveHandlerFactoryTest.java
new file mode 100644
index 0000000..96d5bd3
--- /dev/null
+++ b/trunk/util/src/test/java/org/apache/jsieve/util/ToSieveHandlerFactoryTest.java
@@ -0,0 +1,370 @@
+/****************************************************************
+ * 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.jsieve.util;
+
+import java.io.StringWriter;
+
+import junit.framework.TestCase;
+
+public class ToSieveHandlerFactoryTest extends TestCase {
+
+    ToSieveHandlerFactory factory;
+    StringWriter monitor;
+    
+    protected void setUp() throws Exception {
+        super.setUp();
+        factory = new ToSieveHandlerFactory();
+        monitor = new StringWriter();
+    }
+
+    protected void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    public void testDefaultConfigurationShouldBuildNotNullHandler() throws Exception {
+        assertNotNull(factory.build(monitor));
+    }
+    
+    public void testStartScriptShouldBeIgnored() throws Exception {
+        // Setup
+        SieveHandler handler = factory.build(monitor);
+        
+        // Exercise
+        handler.startScript();
+        
+        // Verify
+        assertEquals("", monitor.toString());
+    }
+    
+    public void testEndScriptShouldBeIgnored() throws Exception {
+        // Setup
+        SieveHandler handler = factory.build(monitor);
+        
+        // Exercise
+        handler.endScript();
+        
+        // Verify
+        assertEquals("", monitor.toString());
+    }
+    
+    public void testStartBlockShouldOpenBracket() throws Exception {
+        // Setup
+        SieveHandler handler = factory.build(monitor);
+        
+        // Exercise
+        handler.startBlock();
+        
+        // Verify
+        assertEquals(" {", monitor.toString());
+    }
+    
+    public void testEndBlockShouldCloseBracket() throws Exception {
+        // Setup
+        SieveHandler handler = factory.build(monitor);
+        
+        // Exercise
+        handler.endBlock();
+        
+        // Verify
+        assertEquals("}", monitor.toString());
+    }
+    
+    public void testStartCommandsShouldBeIgnored() throws Exception {
+        // Setup
+        SieveHandler handler = factory.build(monitor);
+        
+        // Exercise
+        handler.startCommands();
+        
+        // Verify
+        assertEquals("", monitor.toString());
+    }
+    
+    public void testEndCommandsShouldBeIgnored() throws Exception {
+        // Setup
+        SieveHandler handler = factory.build(monitor);
+        
+        // Exercise
+        handler.endCommands();
+        
+        // Verify
+        assertEquals("", monitor.toString());
+    }
+    
+    public void testStartCommandShouldPrintIdentifier() throws Exception {
+        // Setup
+        SieveHandler handler = factory.build(monitor);
+        String commandName = "SomeCommand";
+        
+        // Exercise
+        handler.startCommand(commandName);
+        
+        // Verify
+        assertEquals("SomeCommand", monitor.toString());
+    }
+    
+    public void testEndCommandShouldPrintColon() throws Exception {
+        // Setup
+        SieveHandler handler = factory.build(monitor);
+        String commandName = "SomeCommand";
+        
+        // Exercise
+        handler.endCommand(commandName);
+        
+        // Verify
+        assertEquals(";", monitor.toString());
+    }
+    
+    public void testStartArgumentsShouldBeIgnored() throws Exception {
+        // Setup
+        SieveHandler handler = factory.build(monitor);
+        
+        // Exercise
+        handler.startArguments();
+        
+        // Verify
+        assertEquals("", monitor.toString());
+    }
+    
+    public void testEndArgumentsShouldBeIgnored() throws Exception {
+        // Setup
+        SieveHandler handler = factory.build(monitor);
+        
+        // Exercise
+        handler.endArguments();
+        
+        // Verify
+        assertEquals("", monitor.toString());
+    }
+    
+    public void testArgumentShouldPrintTag() throws Exception {
+        // Setup
+        SieveHandler handler = factory.build(monitor);
+        String identifier = "AnIdentifier";
+        
+        // Exercise
+        handler.argument(identifier);
+        
+        // Verify
+        assertEquals(" :" + identifier, monitor.toString());
+    }
+    
+    public void testArgumentShouldPrintNumber() throws Exception {
+        // Setup
+        SieveHandler handler = factory.build(monitor);
+        int number = 99;
+        
+        // Exercise
+        handler.argument(number);
+        
+        // Verify
+        assertEquals(" " + Integer.toString(number), monitor.toString());
+    }
+    
+    public void testStartStringListShouldOpenBracket() throws Exception {
+        // Setup
+        SieveHandler handler = factory.build(monitor);
+        
+        // Exercise
+        handler.startStringListArgument();
+        
+        // Verify
+        assertEquals(" [", monitor.toString());
+    }
+    
+    public void testEndStringListShouldCloseBracket() throws Exception {
+        // Setup
+        SieveHandler handler = factory.build(monitor);
+        
+        // Exercise
+        handler.endStringListArgument();
+        
+        // Verify
+        assertEquals("]", monitor.toString());
+    }
+    
+    public void testListMemberShouldQuoteString() throws Exception {
+        // Setup
+        SieveHandler handler = factory.build(monitor);
+        String member = "A List Member";
+        
+        // Exercise
+        handler.listMember(member);
+        
+        // Verify
+        assertEquals('"' + member + '"', monitor.toString());
+    }
+    
+    public void testListMemberShouldEscapeDoubleQuote() throws Exception {
+        // Setup
+        SieveHandler handler = factory.build(monitor);
+        String prefix = "A Prefix";
+        String suffix = "A Suffix";
+        
+        // Exercise
+        handler.listMember(prefix + '"' + suffix);
+        
+        // Verify
+        assertEquals('"' + prefix + "\\\"" + suffix + '"', monitor.toString());
+    }
+    
+    public void testListMemberShouldEscapeBackSlash() throws Exception {
+        // Setup
+        SieveHandler handler = factory.build(monitor);
+        String prefix = "A Prefix";
+        String suffix = "A Suffix";
+        
+        // Exercise
+        handler.listMember(prefix + '\\' + suffix);
+        
+        // Verify
+        assertEquals('"' + prefix + "\\\\" + suffix + '"', monitor.toString());
+    }
+    
+    public void testListMemberShouldEscapeCR() throws Exception {
+        // Setup
+        SieveHandler handler = factory.build(monitor);
+        String prefix = "A Prefix";
+        String suffix = "A Suffix";
+        
+        // Exercise
+        handler.listMember(prefix + '\r' + suffix);
+        
+        // Verify
+        assertEquals('"' + prefix + "\\\r" + suffix + '"', monitor.toString());
+    }
+    
+    public void testListMemberShouldEscapeLF() throws Exception {
+        // Setup
+        SieveHandler handler = factory.build(monitor);
+        String prefix = "A Prefix";
+        String suffix = "A Suffix";
+        
+        // Exercise
+        handler.listMember(prefix + '\f' + suffix);
+        
+        // Verify
+        assertEquals('"' + prefix + "\\\f" + suffix + '"', monitor.toString());
+    }
+    
+    public void testStartTestListShouldOpenBracket() throws Exception {
+        // Setup
+        SieveHandler handler = factory.build(monitor);
+        
+        // Exercise
+        handler.startTestList();
+        
+        // Verify
+        assertEquals("(", monitor.toString());
+    }
+    
+    public void testEndTestListShouldCloseBracket() throws Exception {
+        // Setup
+        SieveHandler handler = factory.build(monitor);
+        
+        // Exercise
+        handler.endTestList();
+        
+        // Verify
+        assertEquals(")", monitor.toString());
+    }
+    
+    public void testStartTestShouldPrintIdentifier() throws Exception {
+        // Setup
+        SieveHandler handler = factory.build(monitor);
+        String identifier = "AnIdentifier";
+        
+        // Exercise
+        handler.startTest(identifier);
+        
+        // Verify
+        assertEquals(" " + identifier, monitor.toString());
+    }
+    
+    public void testStartSecondTestShouldPrefixComma() throws Exception {
+        // Setup
+        SieveHandler handler = factory.build(monitor);
+        String firstIdentifier = "FirstIdentifier";
+        String secondIdentifier = "SecondIdentifier";
+        
+        // Exercise
+        handler.startTest(firstIdentifier);
+        handler.endTest(firstIdentifier);
+        handler.startTest(secondIdentifier);
+        
+        // Verify
+        assertEquals(" " +firstIdentifier + ", " + secondIdentifier, monitor.toString());
+    }
+    
+    public void testAfterEndTestListShouldNotNextPrefixTestWithComma() throws Exception {
+        // Setup
+        SieveHandler handler = factory.build(monitor);
+        String firstIdentifier = "FirstIdentifier";
+        String secondIdentifier = "SecondIdentifier";
+        
+        // Exercise
+        handler.startTest(firstIdentifier);
+        handler.endTest(firstIdentifier);
+        handler.endTestList();
+        handler.startTestList();
+        handler.startTest(secondIdentifier);
+        
+        // Verify
+        assertEquals(" " + firstIdentifier + ")(" + secondIdentifier, monitor.toString());
+    }
+    
+    public void testEndTestShouldBeIgnored() throws Exception {
+        // Setup
+        SieveHandler handler = factory.build(monitor);
+        String identifier = "AnIdentifier";
+        
+        // Exercise
+        handler.endTest(identifier);
+        
+        // Verify
+        assertEquals("", monitor.toString());
+    }
+    
+    public void testEndCommandShouldNotPrintSemiColonAfterBlock() throws Exception {
+        // Setup
+        SieveHandler handler = factory.build(monitor);
+        String commandName = "SomeCommand";
+        
+        // Exercise
+        handler.endBlock();
+        handler.endCommand(commandName);
+        
+        // Verify
+        assertEquals("}", monitor.toString());
+    }
+    
+    public void testAfterEndCommandNextShouldPrintSpace() throws Exception {
+        // Setup
+        SieveHandler handler = factory.build(monitor);
+        String firstCommandName = "FirstCommand";
+        String secondCommandName = "SecondCommand";
+        
+        // Exercise
+        handler.endCommand(firstCommandName);
+        handler.startCommand(secondCommandName);
+        
+        // Verify
+        assertEquals("; " + secondCommandName, monitor.toString());
+    }
+}
diff --git a/trunk/util/src/test/java/org/apache/jsieve/util/XmlGenerationTest.java b/trunk/util/src/test/java/org/apache/jsieve/util/XmlGenerationTest.java
new file mode 100644
index 0000000..3030ad6
--- /dev/null
+++ b/trunk/util/src/test/java/org/apache/jsieve/util/XmlGenerationTest.java
@@ -0,0 +1,61 @@
+/****************************************************************
+ * 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.jsieve.util;
+
+import java.io.ByteArrayInputStream;
+import java.io.StringWriter;
+
+import org.apache.jsieve.ConfigurationManager;
+import org.apache.jsieve.parser.generated.Node;
+
+import junit.framework.TestCase;
+
+public class XmlGenerationTest extends TestCase {
+
+    //@Override
+    protected void setUp() throws Exception {
+        super.setUp();
+    }
+
+    //@Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    public void testShouldGenerateXmlFromSimpleScript() throws Exception {
+        // Set up
+        final String script = "if address :all :is \"from\" \"user@domain\" {stop;}";
+        final Node node = new ConfigurationManager().build().parse(new ByteArrayInputStream(script.getBytes()));
+        final StringWriter monitor = new StringWriter();
+        
+        // Exercise
+        OutputUtils.toXml(node, monitor);
+        
+        // Verify
+        assertEquals("<sieve:control xmlns:sieve='urn:ietf:params:xml:ns:sieve' sieve:name='if'>" +
+                "<sieve:test sieve:name='address'>" +
+                "<sieve:tag>all</sieve:tag>" +
+                "<sieve:tag>is</sieve:tag>" +
+                "<sieve:str>from</sieve:str>" +
+                "<sieve:str>user@domain</sieve:str>" +
+                "</sieve:test>" +
+                "<sieve:control sieve:name='stop'/>" +
+                "</sieve:control>", monitor.toString());
+    }
+}
diff --git a/trunk/util/src/test/resources/log4j.properties b/trunk/util/src/test/resources/log4j.properties
new file mode 100644
index 0000000..8124d05
--- /dev/null
+++ b/trunk/util/src/test/resources/log4j.properties
@@ -0,0 +1,27 @@
+#
+#  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.
+#   
+# Set root logger level to DEBUG and its only appender to A1.
+log4j.rootLogger=DEBUG, A1
+
+# A1 is set to be a ConsoleAppender.
+log4j.appender.A1=org.apache.log4j.ConsoleAppender
+
+# A1 uses PatternLayout.
+log4j.appender.A1.layout=org.apache.log4j.PatternLayout
+log4j.appender.A1.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n
diff --git a/trunk/util/src/test/resources/org/apache/jsieve/commandsmap.properties b/trunk/util/src/test/resources/org/apache/jsieve/commandsmap.properties
new file mode 100644
index 0000000..3dba4a1
--- /dev/null
+++ b/trunk/util/src/test/resources/org/apache/jsieve/commandsmap.properties
@@ -0,0 +1,35 @@
+################################################################
+# 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.                                           #
+################################################################
+
+if=org.apache.jsieve.commands.If
+else=org.apache.jsieve.commands.Else
+elsif=org.apache.jsieve.commands.Elsif
+require=org.apache.jsieve.commands.Require
+stop=org.apache.jsieve.commands.Stop
+# RFC3082 - Implementations MUST support these
+keep=org.apache.jsieve.commands.Keep
+discard=org.apache.jsieve.commands.Discard
+redirect=org.apache.jsieve.commands.Redirect
+# RFC3082 - Implementations SHOULD support these
+reject=org.apache.jsieve.commands.optional.Reject
+fileinto=org.apache.jsieve.commands.optional.FileInto
+# JUnit Commands for Testing
+throwtestexception=org.apache.jsieve.commands.ThrowTestException
+# Extension Commands
+log=org.apache.jsieve.commands.extensions.Log