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); + } + +}