/* * Copyright 2012 NGDATA nv * * 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 org.lilyproject.indexer.model.indexerconf; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import com.google.common.base.Splitter; /** * A pattern matching system for {@link org.lilyproject.repository.api.ValueType} names. * * <p>ValueType itself does not define any structural requirements for the type argument, * i.e. it is up to each ValueType implementation to interpret the argument.</p> * * <p>The format for the patterns is described in the user documentation.</p> */ public class TypePattern { /** * Explanation of the pattern: it first matches a name which cannot contain <> * or (), and then it has a parameter either surrounded by <> or by (), or * actually they can be mixed like (> but that's not really intentional. */ private static final Pattern TYPE_PATTERN = Pattern.compile("([^<>\\(\\)]+)(?:[<\\(](.+)[>\\)])?"); private static final Splitter COMMA_SPLITTER = Splitter.on(',').trimResults().omitEmptyStrings(); private List<SubTypePattern> patterns = new ArrayList<SubTypePattern>(); public TypePattern(String pattern) { this.patterns = parseOrPattern(pattern); } public boolean matches(String type) { ParsedType parsedType = parseType(type); for (SubTypePattern pattern : patterns) { if (pattern.matches(parsedType)) { return true; } } return false; } private ParsedType parseType(String type) { return parseType(type, type); } private ParsedType parseType(String type, String completeType) { Matcher matcher = TYPE_PATTERN.matcher(type); if (matcher.matches()) { String name = matcher.group(1); String arg = matcher.group(2); ParsedType typeArg = null; if (arg != null) { typeArg = parseType(arg, completeType); } return new ParsedType(name, typeArg); } else { throw new IllegalArgumentException("Invalid type: '" + type + "' in '" + completeType + "'"); } } /** * Parse a comma-separated list of patterns. */ private List<SubTypePattern> parseOrPattern(String pattern) { List<SubTypePattern> result = new ArrayList<SubTypePattern>(); for (String typePattern : COMMA_SPLITTER.split(pattern)) { result.add(parsePattern(typePattern)); } return result; } private SubTypePattern parsePattern(String pattern) { return parsePattern(pattern, pattern); } /** * Parse a type pattern, including its recursively contained type patterns for the arguments. */ private SubTypePattern parsePattern(String pattern, String completePattern) { Matcher matcher = TYPE_PATTERN.matcher(pattern); if (matcher.matches()) { String name = matcher.group(1); TypeNameMatch typeNameMatch = new TypeNameMatch(name); String arg = matcher.group(2); TypeArgMatch typeArgMatch; if (arg == null) { typeArgMatch = new NoTypeArgumentMatch(); } else if (arg.equals("*")) { typeArgMatch = new OneOptionalArgMatch(); } else if (arg.equals("+")) { typeArgMatch = new ExactOneArgMatch(); } else if (arg.equals("**")) { typeArgMatch = new AnyArgMatch(); } else if (arg.equals("++")) { typeArgMatch = new AtLeastOneArgMatch(); } else { typeArgMatch = new NestedArgMatch(parsePattern(arg, completePattern)); } return new SubTypePattern(typeNameMatch, typeArgMatch); } else { throw new IllegalArgumentException("Invalid type pattern: '" + pattern + "' in '" + completePattern + "'"); } } private static class ParsedType { String name; ParsedType arg; ParsedType(String name, ParsedType arg) { this.name = name; this.arg = arg; } } private static class SubTypePattern { TypeNameMatch nameMatch; TypeArgMatch argMatch; SubTypePattern(TypeNameMatch nameMatch, TypeArgMatch argMatch) { this.nameMatch = nameMatch; this.argMatch = argMatch; } public boolean matches(ParsedType type) { return nameMatch.matches(type.name) && argMatch.matches(type.arg); } } private static class TypeNameMatch { private WildcardPattern pattern; TypeNameMatch(String name) { this.pattern = new WildcardPattern(name); } boolean matches(String name) { return pattern.lightMatch(name); } } private static interface TypeArgMatch { /** * @param type the argument type object to be checked, is null if there is no argument */ boolean matches(ParsedType type); } private static class NoTypeArgumentMatch implements TypeArgMatch { @Override public boolean matches(ParsedType type) { return type == null; } } private static class ExactOneArgMatch implements TypeArgMatch { @Override public boolean matches(ParsedType type) { return type != null && type.arg == null; } } private static class OneOptionalArgMatch implements TypeArgMatch { @Override public boolean matches(ParsedType type) { return type == null || type.arg == null; } } private static class AtLeastOneArgMatch implements TypeArgMatch { @Override public boolean matches(ParsedType type) { return type != null; } } private static class AnyArgMatch implements TypeArgMatch { @Override public boolean matches(ParsedType type) { return true; } } private static class NestedArgMatch implements TypeArgMatch { private SubTypePattern pattern; NestedArgMatch(SubTypePattern pattern) { this.pattern = pattern; } @Override public boolean matches(ParsedType type) { return type != null && pattern.matches(type); } } }