/*
* Copyright 2011 Edmunds.com, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.edmunds.etm.common.xml;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.StringTokenizer;
import java.util.regex.Pattern;
import static com.edmunds.etm.common.xml.UrlRuleValidator.State.ASTERISK;
import static com.edmunds.etm.common.xml.UrlRuleValidator.State.START;
import static com.edmunds.etm.common.xml.UrlRuleValidator.State.SYMBOL;
/**
* Parses the ETM client configuration file.
*/
public class UrlRuleValidator {
private static final String SLASH = "/";
/**
* Precompiled pattern for keywords 'make', 'year', 'zipcode' and 'state'.
*/
private static final Pattern KEYWORDS_PATTERN = Pattern.compile("^[(make|model|year|zipcode|state)]$",
Pattern.CASE_INSENSITIVE);
/**
* Precompiled pattern for escaped words 'make', 'year', 'zipcode' and 'state'.
*/
private static final Pattern ESCAPED_KEYWORDS_PATTERN = Pattern.compile("^\\\\[(make|model|year|zipcode|state)]$",
Pattern.CASE_INSENSITIVE);
/**
* Precompiled pattern for zero or more directories.
*/
private static final Pattern DIRECTORIES_PATTERN = Pattern.compile("^\\*{2}$");
private final Collection<String> xmlRules;
public UrlRuleValidator(final Collection<String> xmlRules) {
this.xmlRules = xmlRules;
}
public List<UrlRuleValidationError> validate() {
List<UrlRuleValidationError> errors = new ArrayList<UrlRuleValidationError>();
for (String rule : xmlRules) {
errors.addAll(validateRule(rule));
}
return errors;
}
/**
* Validate single URL rule.
*
* @param rule the URL rule to validate
* @return list of validation errors
*/
private List<UrlRuleValidationError> validateRule(String rule) {
List<UrlRuleValidationError> errors = new ArrayList<UrlRuleValidationError>();
if (rule.startsWith(SLASH)) {
int charPos = 0;
for (StringTokenizer stz = new StringTokenizer(rule, SLASH, true); stz.hasMoreTokens();) {
String s = stz.nextToken();
if (!SLASH.equals(s) && !isTokenValid(s)) {
errors.add(new UrlRuleValidationError(rule, s, charPos, null));
}
charPos += s.length();
}
} else {
errors.add(new UrlRuleValidationError(rule, rule, 0, SLASH));
}
return errors;
}
/**
* Check syntax of single token.
*
* @param token token.
* @return true if sintax is valid, otherwise - false.
*/
private boolean isTokenValid(String token) {
if (KEYWORDS_PATTERN.matcher(token).matches()
|| ESCAPED_KEYWORDS_PATTERN.matcher(token).matches()
|| DIRECTORIES_PATTERN.matcher(token).matches()) {
return true;
}
State state = START;
for (int i = 0; i < token.length(); i++) {
char ch = token.charAt(i);
if (ch == '*') {
if (state.equals(ASTERISK)) {
return false;
} else {
state = ASTERISK;
}
} else if (ch == '\\' && token.length() == i + 1) {
return false;
} else if (ch == '.' && token.length() == 1) {
return false;
} else {
state = SYMBOL;
}
}
return true;
}
/**
* Finite machine state.
*/
protected enum State {
/**
* Last symbol is any symbol except asterisk.
*/
SYMBOL,
/**
* Last symbol is Asterisk.
*/
ASTERISK,
/**
* Start state.
*/
START
}
}