[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<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]
+ * <header-list: string-list> <key-list: string-list>
+ * </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]
+ * <header-names: string-list> <key-list: string-list>
+ * </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 <"e;:over""e; / "e;:under"e;> <limit: number>
+ * </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 "*************"
+
+** 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("&");
+ } else if (character == '<') {
+ writer.write("<");
+ } else if (character == '>') {
+ writer.write(">");
+ } else if (isAttributeContent && character == '\'') {
+ writer.write("'");
+ } else if (isAttributeContent && character == '\"') {
+ writer.write(""");
+ } 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