/* * Copyright 2010, 2011, 2012 mapsforge.org * * This program is free software: you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License as published by the Free Software * Foundation, either version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License along with * this program. If not, see <http://www.gnu.org/licenses/>. */ package org.mapsforge.map.rendertheme.rule; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Locale; import java.util.Stack; import java.util.regex.Pattern; import org.mapsforge.map.rendertheme.XmlUtils; import org.xml.sax.Attributes; import org.xml.sax.SAXException; /** * A builder for {@link Rule} instances. */ public class RuleBuilder { private static final String CLOSED = "closed"; private static final String E = "e"; private static final String K = "k"; private static final Pattern SPLIT_PATTERN = Pattern.compile("\\|"); private static final String STRING_NEGATION = "~"; private static final String STRING_WILDCARD = "*"; private static final String V = "v"; private static final String ZOOM_MAX = "zoom-max"; private static final String ZOOM_MIN = "zoom-min"; private static ClosedMatcher getClosedMatcher(Closed closed) { switch (closed) { case YES: return ClosedWayMatcher.INSTANCE; case NO: return LinearWayMatcher.INSTANCE; case ANY: return AnyMatcher.INSTANCE; } throw new IllegalArgumentException("unknown closed value: " + closed); } private static ElementMatcher getElementMatcher(Element element) { switch (element) { case NODE: return ElementNodeMatcher.INSTANCE; case WAY: return ElementWayMatcher.INSTANCE; case ANY: return AnyMatcher.INSTANCE; } throw new IllegalArgumentException("unknown element value: " + element); } private static AttributeMatcher getKeyMatcher(List<String> keyList) { if (STRING_WILDCARD.equals(keyList.get(0))) { return AnyMatcher.INSTANCE; } AttributeMatcher attributeMatcher = Rule.MATCHERS_CACHE_KEY.get(keyList); if (attributeMatcher == null) { attributeMatcher = new KeyMatcher(keyList); Rule.MATCHERS_CACHE_KEY.put(keyList, attributeMatcher); } return attributeMatcher; } private static AttributeMatcher getValueMatcher(List<String> valueList) { if (STRING_WILDCARD.equals(valueList.get(0))) { return AnyMatcher.INSTANCE; } AttributeMatcher attributeMatcher = Rule.MATCHERS_CACHE_VALUE.get(valueList); if (attributeMatcher == null) { attributeMatcher = new ValueMatcher(valueList); Rule.MATCHERS_CACHE_VALUE.put(valueList, attributeMatcher); } return attributeMatcher; } private Closed closed; private Element element; private List<String> keyList; private String keys; private final Stack<Rule> ruleStack; private List<String> valueList; private String values; ClosedMatcher closedMatcher; ElementMatcher elementMatcher; byte zoomMax; byte zoomMin; public RuleBuilder(String elementName, Attributes attributes, Stack<Rule> ruleStack) throws SAXException { this.ruleStack = ruleStack; this.closed = Closed.ANY; this.zoomMin = 0; this.zoomMax = Byte.MAX_VALUE; extractValues(elementName, attributes); } /** * @return a new {@code Rule} instance. */ public Rule build() { if (this.valueList.remove(STRING_NEGATION)) { AttributeMatcher attributeMatcher = new NegativeMatcher(this.keyList, this.valueList); return new NegativeRule(this, attributeMatcher); } AttributeMatcher keyMatcher = getKeyMatcher(this.keyList); AttributeMatcher valueMatcher = getValueMatcher(this.valueList); keyMatcher = RuleOptimizer.optimize(keyMatcher, this.ruleStack); valueMatcher = RuleOptimizer.optimize(valueMatcher, this.ruleStack); return new PositiveRule(this, keyMatcher, valueMatcher); } private void extractValues(String elementName, Attributes attributes) throws SAXException { for (int i = 0; i < attributes.getLength(); ++i) { String name = attributes.getQName(i); String value = attributes.getValue(i); if (E.equals(name)) { this.element = Element.valueOf(value.toUpperCase(Locale.ENGLISH)); } else if (K.equals(name)) { this.keys = value; } else if (V.equals(name)) { this.values = value; } else if (CLOSED.equals(name)) { this.closed = Closed.valueOf(value.toUpperCase(Locale.ENGLISH)); } else if (ZOOM_MIN.equals(name)) { this.zoomMin = XmlUtils.parseNonNegativeByte(name, value); } else if (ZOOM_MAX.equals(name)) { this.zoomMax = XmlUtils.parseNonNegativeByte(name, value); } else { throw XmlUtils.createSAXException(elementName, name, value, i); } } validate(elementName); this.keyList = new ArrayList<String>(Arrays.asList(SPLIT_PATTERN.split(this.keys))); this.valueList = new ArrayList<String>(Arrays.asList(SPLIT_PATTERN.split(this.values))); this.elementMatcher = getElementMatcher(this.element); this.closedMatcher = getClosedMatcher(this.closed); this.elementMatcher = RuleOptimizer.optimize(this.elementMatcher, this.ruleStack); this.closedMatcher = RuleOptimizer.optimize(this.closedMatcher, this.ruleStack); } private void validate(String elementName) throws SAXException { XmlUtils.checkMandatoryAttribute(elementName, E, this.element); XmlUtils.checkMandatoryAttribute(elementName, K, this.keys); XmlUtils.checkMandatoryAttribute(elementName, V, this.values); if (this.zoomMin > this.zoomMax) { throw new SAXException('\'' + ZOOM_MIN + "' > '" + ZOOM_MAX + "': " + this.zoomMin + ' ' + this.zoomMax); } } }