JSIEVE-103 introduce ArgumentParser utility
diff --git a/core/pom.xml b/core/pom.xml
index d5c5329..b2114a8 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -80,6 +80,17 @@
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.assertj</groupId>
+ <artifactId>assertj-core</artifactId>
+ <version>${assertj.version}</version>
+ <scope>test</scope>
+ </dependency>
</dependencies>
<build>
diff --git a/core/src/main/java/org/apache/jsieve/utils/ArgumentParser.java b/core/src/main/java/org/apache/jsieve/utils/ArgumentParser.java
new file mode 100644
index 0000000..5d47369
--- /dev/null
+++ b/core/src/main/java/org/apache/jsieve/utils/ArgumentParser.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.utils;
+
+import org.apache.jsieve.Argument;
+import org.apache.jsieve.NumberArgument;
+import org.apache.jsieve.StringListArgument;
+import org.apache.jsieve.TagArgument;
+import org.apache.jsieve.exception.SyntaxException;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public class ArgumentParser {
+
+ private List<Argument> remainingArguments;
+ private Set<String> singleTags;
+ private Map<String, Argument> tagsWithValues;
+
+ public ArgumentParser(List<Argument> arguments) {
+ this.remainingArguments = new ArrayList<Argument>();
+ this.singleTags = new HashSet<String>();
+ this.tagsWithValues = new HashMap<String, Argument>();
+ initialize(arguments);
+ }
+
+ public void initialize(List<Argument> arguments) {
+ TagArgument previousSeenTagArgument = null;
+ for (Argument argument : arguments) {
+ if (argument instanceof TagArgument) {
+ TagArgument tagArgument = (TagArgument) argument;
+ handlePreviousTagArgument(previousSeenTagArgument);
+ previousSeenTagArgument = tagArgument;
+ } else {
+ handleOtherArguments(previousSeenTagArgument, argument);
+ previousSeenTagArgument = null;
+ }
+ }
+ if (previousSeenTagArgument != null) {
+ singleTags.add(previousSeenTagArgument.getTag());
+ }
+ }
+
+ private void handlePreviousTagArgument(TagArgument previousSeenTagArgument) {
+ if (previousSeenTagArgument != null) {
+ singleTags.add(previousSeenTagArgument.getTag());
+ }
+ }
+
+ private void handleOtherArguments(TagArgument lastSeenTagArgument, Argument argument) {
+ if (lastSeenTagArgument == null) {
+ remainingArguments.add(argument);
+ } else {
+ tagsWithValues.put(lastSeenTagArgument.getTag(), argument);
+ }
+ }
+
+ public String getStringValueForTag(String tag, String exceptionMessage) throws SyntaxException {
+ Argument argument = retrieveArgumentIfExists(tag, exceptionMessage);
+ if (argument == null) {
+ return null;
+ }
+ return retrieveSingleStringValue(argument, exceptionMessage);
+ }
+
+ public Integer getNumericValueForTag(String tag, String exceptionMessage) throws SyntaxException {
+ Argument argument = retrieveArgumentIfExists(tag, exceptionMessage);
+ if (argument == null) {
+ return null;
+ }
+ return retrieveNumericValue(argument, exceptionMessage);
+ }
+
+ public List<String> getStringListForTag(String tag, String exceptionMessage) throws SyntaxException {
+ Argument argument = retrieveArgumentIfExists(tag, exceptionMessage);
+ if (argument == null) {
+ return new ArrayList<String>();
+ }
+ return retrieveStringValues(argument, exceptionMessage);
+ }
+
+ public Set<String> getSingleTags() {
+ return new HashSet<String>(singleTags);
+ }
+
+ public String getRemainingStringValue(String exceptionMessage) throws SyntaxException {
+ if (remainingArguments.size() > 1) {
+ throw new SyntaxException(exceptionMessage);
+ }
+ if (remainingArguments.size() == 0) {
+ return null;
+ }
+ return retrieveSingleStringValue(remainingArguments.get(0), exceptionMessage);
+ }
+
+ public void throwOnUnvalidSeenSingleTag(String... validTags) throws SyntaxException {
+ validateTagCollectionWithExpectations(singleTags, validTags);
+ }
+
+ public void throwOnUnvalidSeenTagWithValue(String... validTags) throws SyntaxException {
+ validateTagCollectionWithExpectations(tagsWithValues.keySet(), validTags);
+ }
+
+ private void validateTagCollectionWithExpectations(Set<String> seenTags, String[] expectations) throws SyntaxException {
+ Set<String> validTagList = getSetFromStringArray(expectations);
+ if (!validTagList.containsAll(seenTags)) {
+ throw new SyntaxException(buildUnwantedTagsErrorMessage(retrieveUnwantedTags(seenTags, validTagList)));
+ }
+ }
+
+ private Set<String> getSetFromStringArray(String[] validTags) {
+ Set<String> validTagList = new HashSet<String>();
+ for(String validTag : validTags) {
+ validTagList.add(validTag);
+ }
+ return validTagList;
+ }
+
+ private Set<String> retrieveUnwantedTags(Set<String> seenTags, Set<String> validTagList) {
+ Set<String> unwantedTags = new HashSet<String>(seenTags);
+ unwantedTags.removeAll(validTagList);
+ return unwantedTags;
+ }
+
+ private String buildUnwantedTagsErrorMessage(Set<String> unwantedTags) {
+ String errorMessage;
+ StringBuilder errorMessageBuilder = new StringBuilder().append("Unexpected tags : [");
+ for (String unwantedTag : unwantedTags) {
+ errorMessageBuilder.append("\"").append(unwantedTag).append("\"");
+ }
+ errorMessageBuilder.append("]");
+ errorMessage = errorMessageBuilder.toString();
+ return errorMessage;
+ }
+
+ private Argument retrieveArgumentIfExists(String tag, String exceptionMessage) throws SyntaxException {
+ Argument argument = tagsWithValues.get(tag);
+ if (argument == null) {
+ if (singleTags.contains(tag)) {
+ throw new SyntaxException(exceptionMessage);
+ } else {
+ return null;
+ }
+ }
+ return argument;
+ }
+
+ private List<String> retrieveStringValues(Argument argument, String exceptionMessage) throws SyntaxException {
+ if (! (argument instanceof StringListArgument)) {
+ throw new SyntaxException(exceptionMessage);
+ }
+ StringListArgument stringListArgument = (StringListArgument) argument;
+ return stringListArgument.getList();
+ }
+
+ private String retrieveSingleStringValue(Argument argument, String exceptionMessage) throws SyntaxException {
+ List<String> stringsValue = retrieveStringValues(argument, exceptionMessage);
+ if (stringsValue.size() != 1) {
+ throw new SyntaxException(exceptionMessage);
+ }
+ return stringsValue.get(0);
+ }
+
+ private Integer retrieveNumericValue(Argument argument, String exceptionMessage) throws SyntaxException {
+ if (! (argument instanceof NumberArgument)) {
+ throw new SyntaxException(exceptionMessage);
+ }
+ NumberArgument numberArgument = (NumberArgument) argument;
+ return numberArgument.getInteger();
+ }
+
+}
diff --git a/core/src/test/java/org/apache/jsieve/utils/ArgumentParserTest.java b/core/src/test/java/org/apache/jsieve/utils/ArgumentParserTest.java
new file mode 100644
index 0000000..59fc69d
--- /dev/null
+++ b/core/src/test/java/org/apache/jsieve/utils/ArgumentParserTest.java
@@ -0,0 +1,248 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one *
+ * or more contributor license agreements. See the NOTICE file *
+ * distributed with this work for additional information *
+ * regarding copyright ownership. The ASF licenses this file *
+ * to you under the Apache License, Version 2.0 (the *
+ * "License"); you may not use this file except in compliance *
+ * with the License. You may obtain a copy of the License at *
+ * *
+ * http://www.apache.org/licenses/LICENSE-2.0 *
+ * *
+ * Unless required by applicable law or agreed to in writing, *
+ * software distributed under the License is distributed on an *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 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 static org.assertj.core.api.Assertions.assertThat;
+
+import com.google.common.collect.Lists;
+import org.apache.jsieve.Argument;
+import org.apache.jsieve.NumberArgument;
+import org.apache.jsieve.StringListArgument;
+import org.apache.jsieve.TagArgument;
+import org.apache.jsieve.exception.SyntaxException;
+import org.apache.jsieve.parser.generated.Token;
+import org.junit.Test;
+
+public class ArgumentParserTest {
+ public static final int KIND = 18;
+ public static final int NUMERIC_VALUE = 42;
+ public static final String ANY_TAG = ":any";
+ public static final String OTHER_TAG = ":other";
+ public static final String EXCEPTION_MESSAGE = "ExceptionMessage";
+ public static final String STRING_VALUE = "string";
+ public static final String STRING_VALUE_2 = "string2";
+ public static final TagArgument ANY_TAG_ARGUMENT = new TagArgument(new Token(KIND, ANY_TAG));
+ public static final TagArgument OTHER_TAG_ARGUMENT = new TagArgument(new Token(KIND, OTHER_TAG));
+ public static final NumberArgument NUMBER_ARGUMENT = new NumberArgument(new Token(KIND, Integer.toString(NUMERIC_VALUE)));
+ public static final StringListArgument SINGLE_STRING_ARGUMENT = new StringListArgument(Lists.newArrayList(STRING_VALUE));
+ public static final StringListArgument STRING_LIST_ARGUMENT = new StringListArgument(Lists.newArrayList(STRING_VALUE, STRING_VALUE_2));
+
+ @Test
+ public void getRemainingStringValueShouldReturnNullByDefault() throws Exception {
+ ArgumentParser argumentParser = new ArgumentParser(Lists.<Argument>newArrayList());
+ assertThat(argumentParser.getRemainingStringValue(EXCEPTION_MESSAGE)).isNull();
+ }
+
+ @Test
+ public void getNumericValueForTagShouldReturnNullByDefault() throws Exception {
+ ArgumentParser argumentParser = new ArgumentParser(Lists.<Argument>newArrayList());
+ assertThat(argumentParser.getNumericValueForTag(ANY_TAG, EXCEPTION_MESSAGE)).isNull();
+ }
+
+ @Test
+ public void getStringListForTagShouldReturnEmptyByDefault() throws Exception {
+ ArgumentParser argumentParser = new ArgumentParser(Lists.<Argument>newArrayList());
+ assertThat(argumentParser.getStringListForTag(ANY_TAG, EXCEPTION_MESSAGE)).isEmpty();
+ }
+
+
+ @Test
+ public void getStringValueForTagShouldReturnNullByDefault() throws Exception {
+ ArgumentParser argumentParser = new ArgumentParser(Lists.<Argument>newArrayList());
+ assertThat(argumentParser.getStringValueForTag(ANY_TAG, EXCEPTION_MESSAGE)).isNull();
+ }
+
+ @Test
+ public void validateSingleTagsShouldNotThrowByDefault() throws Exception {
+ ArgumentParser argumentParser = new ArgumentParser(Lists.<Argument>newArrayList());
+ argumentParser.throwOnUnvalidSeenSingleTag();
+ }
+
+ @Test
+ public void validateTagsWithValueShouldNotThrowByDefault() throws Exception {
+ ArgumentParser argumentParser = new ArgumentParser(Lists.<Argument>newArrayList());
+ argumentParser.throwOnUnvalidSeenTagWithValue();
+ }
+
+ @Test
+ public void validateSingleTagsShouldNotThrowWhenAllowedValuesAreNotPresent() throws Exception {
+ ArgumentParser argumentParser = new ArgumentParser(Lists.<Argument>newArrayList());
+ argumentParser.throwOnUnvalidSeenSingleTag(ANY_TAG);
+ }
+
+ @Test
+ public void validateTagsWithValueShouldNotThrowWhenAllowedValuesAreNotPresent() throws Exception {
+ ArgumentParser argumentParser = new ArgumentParser(Lists.<Argument>newArrayList());
+ argumentParser.throwOnUnvalidSeenTagWithValue(ANY_TAG);
+ }
+
+ @Test(expected = SyntaxException.class)
+ public void validateSingleTagsShouldThrowOnUnexpectedSingleTag() throws Exception {
+ ArgumentParser argumentParser = new ArgumentParser(Lists.<Argument>newArrayList(ANY_TAG_ARGUMENT));
+ argumentParser.throwOnUnvalidSeenSingleTag();
+ }
+
+ @Test
+ public void validateTagsWithValueShouldNotThrowOnSingleTag() throws Exception {
+ ArgumentParser argumentParser = new ArgumentParser(Lists.<Argument>newArrayList(ANY_TAG_ARGUMENT));
+ argumentParser.throwOnUnvalidSeenTagWithValue();
+ }
+
+ @Test
+ public void validateSingleTagsShouldNotThrowOnExpectedSingleTag() throws Exception {
+ ArgumentParser argumentParser = new ArgumentParser(Lists.<Argument>newArrayList(ANY_TAG_ARGUMENT));
+ argumentParser.throwOnUnvalidSeenSingleTag(ANY_TAG);
+ }
+
+ @Test
+ public void validateSingleTagsShouldNotThrowOnTagWithValue() throws Exception {
+ ArgumentParser argumentParser = new ArgumentParser(Lists.newArrayList(OTHER_TAG_ARGUMENT, NUMBER_ARGUMENT));
+ argumentParser.throwOnUnvalidSeenSingleTag(ANY_TAG);
+ }
+
+ @Test(expected = SyntaxException.class)
+ public void validateTagsWithValueShouldThrowOnUnexpectedTagWithValue() throws Exception {
+ ArgumentParser argumentParser = new ArgumentParser(Lists.newArrayList(OTHER_TAG_ARGUMENT, NUMBER_ARGUMENT));
+ argumentParser.throwOnUnvalidSeenTagWithValue(ANY_TAG);
+ }
+
+ @Test(expected = SyntaxException.class)
+ public void validateSingleTagsShouldThrowOnUnexpectedValue() throws Exception {
+ ArgumentParser argumentParser = new ArgumentParser(Lists.<Argument>newArrayList(OTHER_TAG_ARGUMENT));
+ argumentParser.throwOnUnvalidSeenSingleTag(ANY_TAG);
+ }
+
+ @Test(expected = SyntaxException.class)
+ public void getRemainingStringValueShouldThrowOnWrongArgumentType() throws Exception {
+ ArgumentParser argumentParser = new ArgumentParser(Lists.<Argument>newArrayList(NUMBER_ARGUMENT));
+ argumentParser.getRemainingStringValue(EXCEPTION_MESSAGE);
+ }
+
+ @Test(expected = SyntaxException.class)
+ public void getRemainingStringValueShouldThrowOnMultipleElementsInStringList() throws Exception {
+ ArgumentParser argumentParser = new ArgumentParser(Lists.<Argument>newArrayList(STRING_LIST_ARGUMENT));
+ argumentParser.getRemainingStringValue(EXCEPTION_MESSAGE);
+ }
+
+ @Test
+ public void getRemainingStringValueShouldWork() throws Exception {
+ ArgumentParser argumentParser = new ArgumentParser(Lists.<Argument>newArrayList(SINGLE_STRING_ARGUMENT));
+ assertThat(argumentParser.getRemainingStringValue(EXCEPTION_MESSAGE)).isEqualTo(STRING_VALUE);
+ }
+
+ @Test
+ public void getRemainingStringValueShouldReturnNullWhenCalledWithOnlyTagWithValue() throws Exception {
+ ArgumentParser argumentParser = new ArgumentParser(Lists.newArrayList(ANY_TAG_ARGUMENT, STRING_LIST_ARGUMENT));
+ assertThat(argumentParser.getRemainingStringValue(EXCEPTION_MESSAGE)).isNull();
+ }
+
+ @Test(expected = SyntaxException.class)
+ public void getNumericValueForTagShouldThrowOnSingleStringArgument() throws Exception {
+ ArgumentParser argumentParser = new ArgumentParser(Lists.newArrayList(ANY_TAG_ARGUMENT, SINGLE_STRING_ARGUMENT));
+ argumentParser.getNumericValueForTag(ANY_TAG, EXCEPTION_MESSAGE);
+ }
+
+ @Test
+ public void getStringListForTagShouldWorkWithSingleValue() throws Exception {
+ ArgumentParser argumentParser = new ArgumentParser(Lists.newArrayList(ANY_TAG_ARGUMENT, SINGLE_STRING_ARGUMENT));
+ assertThat(argumentParser.getStringListForTag(ANY_TAG, EXCEPTION_MESSAGE)).containsExactly(STRING_VALUE);
+ }
+
+ @Test
+ public void getStringValueForTagShouldWork() throws Exception {
+ ArgumentParser argumentParser = new ArgumentParser(Lists.newArrayList(ANY_TAG_ARGUMENT, SINGLE_STRING_ARGUMENT));
+ assertThat(argumentParser.getStringValueForTag(ANY_TAG, EXCEPTION_MESSAGE)).isEqualTo(STRING_VALUE);
+ }
+
+ @Test
+ public void getNumericValueForTagShouldWork() throws Exception {
+ ArgumentParser argumentParser = new ArgumentParser(Lists.newArrayList(ANY_TAG_ARGUMENT, NUMBER_ARGUMENT));
+ assertThat(argumentParser.getNumericValueForTag(ANY_TAG, EXCEPTION_MESSAGE)).isEqualTo(NUMERIC_VALUE);
+ }
+
+ @Test(expected = SyntaxException.class)
+ public void getStringListForTagShouldThrowWhenUsedWithNumberArgument() throws Exception {
+ ArgumentParser argumentParser = new ArgumentParser(Lists.newArrayList(ANY_TAG_ARGUMENT, NUMBER_ARGUMENT));
+ argumentParser.getStringListForTag(ANY_TAG, EXCEPTION_MESSAGE);
+ }
+
+ @Test(expected = SyntaxException.class)
+ public void getStringValueForTagShouldThrowWhenUsedWithNumberArgument() throws Exception {
+ ArgumentParser argumentParser = new ArgumentParser(Lists.newArrayList(ANY_TAG_ARGUMENT, NUMBER_ARGUMENT));
+ argumentParser.getStringValueForTag(ANY_TAG, EXCEPTION_MESSAGE);
+ }
+
+ @Test(expected = SyntaxException.class)
+ public void getNumericValueForTagShouldThrowWhenUsedWithStringListArgument() throws Exception {
+ ArgumentParser argumentParser = new ArgumentParser(Lists.newArrayList(ANY_TAG_ARGUMENT, STRING_LIST_ARGUMENT));
+ argumentParser.getNumericValueForTag(ANY_TAG, EXCEPTION_MESSAGE);
+ }
+
+ @Test
+ public void getStringListForTagShouldWork() throws Exception {
+ ArgumentParser argumentParser = new ArgumentParser(Lists.newArrayList(ANY_TAG_ARGUMENT, STRING_LIST_ARGUMENT));
+ assertThat(argumentParser.getStringListForTag(ANY_TAG, EXCEPTION_MESSAGE)).containsOnly(STRING_VALUE, STRING_VALUE_2);
+ }
+
+ @Test(expected = SyntaxException.class)
+ public void getStringValueForTagShouldThrowWhenUsedWithMultipleValues() throws Exception {
+ ArgumentParser argumentParser = new ArgumentParser(Lists.newArrayList(ANY_TAG_ARGUMENT, STRING_LIST_ARGUMENT));
+ argumentParser.getStringValueForTag(ANY_TAG, EXCEPTION_MESSAGE);
+ }
+
+ @Test
+ public void getNumericValueForTagShouldReturnNullWhenUsedOnAnOtherTag() throws Exception {
+ ArgumentParser argumentParser = new ArgumentParser(Lists.newArrayList(OTHER_TAG_ARGUMENT, NUMBER_ARGUMENT));
+ assertThat(argumentParser.getNumericValueForTag(ANY_TAG, EXCEPTION_MESSAGE)).isNull();
+ }
+
+ @Test
+ public void getStringListForTagShouldReturnNullWhenUsedOnAnOtherTag() throws Exception {
+ ArgumentParser argumentParser = new ArgumentParser(Lists.newArrayList(OTHER_TAG_ARGUMENT, STRING_LIST_ARGUMENT));
+ assertThat(argumentParser.getStringListForTag(ANY_TAG, EXCEPTION_MESSAGE)).isEmpty();
+ }
+
+ @Test
+ public void getStringValueForTagShouldReturnNullWhenUsedOnAnOtherTag() throws Exception {
+ ArgumentParser argumentParser = new ArgumentParser(Lists.newArrayList(OTHER_TAG_ARGUMENT, SINGLE_STRING_ARGUMENT));
+ assertThat(argumentParser.getStringValueForTag(ANY_TAG, EXCEPTION_MESSAGE)).isNull();
+ }
+
+ @Test
+ public void argumentParserShouldHandleMultipleTagsWithValue() throws Exception {
+ ArgumentParser argumentParser = new ArgumentParser(Lists.newArrayList(OTHER_TAG_ARGUMENT, SINGLE_STRING_ARGUMENT, ANY_TAG_ARGUMENT, NUMBER_ARGUMENT));
+ assertThat(argumentParser.getNumericValueForTag(ANY_TAG, EXCEPTION_MESSAGE)).isEqualTo(NUMERIC_VALUE);
+ assertThat(argumentParser.getStringValueForTag(OTHER_TAG, EXCEPTION_MESSAGE));
+ }
+
+ @Test
+ public void argumentParserShouldHandleTagWithValueAndRemainingString() throws Exception {
+ ArgumentParser argumentParser = new ArgumentParser(Lists.newArrayList(ANY_TAG_ARGUMENT, NUMBER_ARGUMENT, SINGLE_STRING_ARGUMENT));
+ assertThat(argumentParser.getNumericValueForTag(ANY_TAG, EXCEPTION_MESSAGE)).isEqualTo(NUMERIC_VALUE);
+ assertThat(argumentParser.getRemainingStringValue(EXCEPTION_MESSAGE)).isEqualTo(STRING_VALUE);
+ }
+
+ @Test
+ public void argumentParserShouldHandleMultipleSingleTagArguments() throws Exception {
+ ArgumentParser argumentParser = new ArgumentParser(Lists.<Argument>newArrayList(ANY_TAG_ARGUMENT, OTHER_TAG_ARGUMENT));
+ assertThat(argumentParser.getSingleTags()).containsExactly(ANY_TAG, OTHER_TAG);
+ }
+
+}