/* * 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 . */ package org.oscim.theme; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Stack; import java.util.regex.Pattern; import org.oscim.core.Tag; import org.oscim.theme.renderinstruction.RenderInstruction; import org.xml.sax.Attributes; abstract class Rule { private static final Map, AttributeMatcher> MATCHERS_CACHE_KEY = new HashMap, AttributeMatcher>(); private static final Map, AttributeMatcher> MATCHERS_CACHE_VALUE = new HashMap, AttributeMatcher>(); private static final Pattern SPLIT_PATTERN = Pattern.compile("\\|"); private static final String STRING_NEGATION = "~"; private static final String STRING_EXCLUSIVE = "-"; private static final String STRING_WILDCARD = "*"; private static Rule createRule(Stack ruleStack, int element, String keys, String values, int closed, byte zoomMin, byte zoomMax) { List keyList = new ArrayList(Arrays.asList(SPLIT_PATTERN .split(keys))); List valueList = new ArrayList(Arrays.asList(SPLIT_PATTERN .split(values))); if (valueList.remove(STRING_NEGATION)) { AttributeMatcher attributeMatcher = new NegativeMatcher(keyList, valueList, false); return new NegativeRule(element, closed, zoomMin, zoomMax, attributeMatcher); } if (valueList.remove(STRING_EXCLUSIVE)) { AttributeMatcher attributeMatcher = new NegativeMatcher(keyList, valueList, true); return new NegativeRule(element, closed, zoomMin, zoomMax, attributeMatcher); } AttributeMatcher keyMatcher = getKeyMatcher(keyList); AttributeMatcher valueMatcher = getValueMatcher(valueList); keyMatcher = RuleOptimizer.optimize(keyMatcher, ruleStack); valueMatcher = RuleOptimizer.optimize(valueMatcher, ruleStack); return new PositiveRule(element, closed, zoomMin, zoomMax, keyMatcher, valueMatcher); } private static AttributeMatcher getKeyMatcher(List keyList) { if (STRING_WILDCARD.equals(keyList.get(0))) { return AnyMatcher.getInstance(); } AttributeMatcher attributeMatcher = MATCHERS_CACHE_KEY.get(keyList); if (attributeMatcher == null) { if (keyList.size() == 1) { attributeMatcher = new SingleKeyMatcher(keyList.get(0)); } else { attributeMatcher = new MultiKeyMatcher(keyList); } MATCHERS_CACHE_KEY.put(keyList, attributeMatcher); } return attributeMatcher; } private static AttributeMatcher getValueMatcher(List valueList) { if (STRING_WILDCARD.equals(valueList.get(0))) { return AnyMatcher.getInstance(); } AttributeMatcher attributeMatcher = MATCHERS_CACHE_VALUE.get(valueList); if (attributeMatcher == null) { if (valueList.size() == 1) { attributeMatcher = new SingleValueMatcher(valueList.get(0)); } else { attributeMatcher = new MultiValueMatcher(valueList); } MATCHERS_CACHE_VALUE.put(valueList, attributeMatcher); } return attributeMatcher; } private static void validate(String elementName, String keys, String values, byte zoomMin, byte zoomMax) { if (keys == null) { throw new IllegalArgumentException("missing attribute k for element: " + elementName); } else if (values == null) { throw new IllegalArgumentException("missing attribute v for element: " + elementName); } else if (zoomMin < 0) { throw new IllegalArgumentException("zoom-min must not be negative: " + zoomMin); } else if (zoomMax < 0) { throw new IllegalArgumentException("zoom-max must not be negative: " + zoomMax); } else if (zoomMin > zoomMax) { throw new IllegalArgumentException( "zoom-min must be less or equal zoom-max: " + zoomMin); } } static Rule create(String elementName, Attributes attributes, Stack ruleStack) { int element = Element.ANY; String keys = null; String values = null; int closed = Closed.ANY; byte zoomMin = 0; byte zoomMax = Byte.MAX_VALUE; for (int i = 0; i < attributes.getLength(); ++i) { String name = attributes.getLocalName(i); String value = attributes.getValue(i); if ("e".equals(name)) { String val = value.toUpperCase(Locale.ENGLISH); if ("WAY".equals(val)) element = Element.WAY; else if ("NODE".equals(val)) element = Element.NODE; } else if ("k".equals(name)) { keys = value; } else if ("v".equals(name)) { values = value; } else if ("closed".equals(name)) { String val = value.toUpperCase(Locale.ENGLISH); if ("YES".equals(val)) closed = Closed.YES; else if ("NO".equals(val)) closed = Closed.NO; } else if ("zoom-min".equals(name)) { zoomMin = Byte.parseByte(value); } else if ("zoom-max".equals(name)) { zoomMax = Byte.parseByte(value); } else { RenderThemeHandler.logUnknownAttribute(elementName, name, value, i); } } validate(elementName, keys, values, zoomMin, zoomMax); return createRule(ruleStack, element, keys, values, closed, zoomMin, zoomMax); } private ArrayList mRenderInstructions; private ArrayList mSubRules; private Rule[] mSubRuleArray; private RenderInstruction[] mRenderInstructionArray; final byte mZoomMax; final byte mZoomMin; final int mElement; final int mClosed; Rule(int element, int closed, byte zoomMin, byte zoomMax) { mClosed = closed; mElement = element; mZoomMin = zoomMin; mZoomMax = zoomMax; mRenderInstructions = new ArrayList(4); mSubRules = new ArrayList(4); } void addRenderingInstruction(RenderInstruction renderInstruction) { mRenderInstructions.add(renderInstruction); } void addSubRule(Rule rule) { mSubRules.add(rule); } abstract boolean matchesNode(Tag[] tags); abstract boolean matchesWay(Tag[] tags); void matchNode(IRenderCallback renderCallback, Tag[] tags, byte zoomLevel, List matchingList) { if ((mElement != Element.WAY) && mZoomMin <= zoomLevel && mZoomMax >= zoomLevel && matchesNode(tags)) { for (int i = 0, n = mRenderInstructionArray.length; i < n; i++) matchingList.add(mRenderInstructionArray[i]); for (int i = 0, n = mSubRuleArray.length; i < n; i++) mSubRuleArray[i].matchNode(renderCallback, tags, zoomLevel, matchingList); } } void matchWay(IRenderCallback renderCallback, Tag[] tags, byte zoomLevel, int closed, List matchingList) { if ((mElement != Element.NODE) && mZoomMin <= zoomLevel && mZoomMax >= zoomLevel && (mClosed == closed || mClosed == Closed.ANY) && (matchesWay(tags))) { // add instructions for this rule for (int i = 0, n = mRenderInstructionArray.length; i < n; i++) matchingList.add(mRenderInstructionArray[i]); // check subrules for (int i = 0, n = mSubRuleArray.length; i < n; i++) mSubRuleArray[i].matchWay(renderCallback, tags, zoomLevel, closed, matchingList); } } void onComplete() { MATCHERS_CACHE_KEY.clear(); MATCHERS_CACHE_VALUE.clear(); mRenderInstructionArray = new RenderInstruction[mRenderInstructions.size()]; mRenderInstructions.toArray(mRenderInstructionArray); mSubRuleArray = new Rule[mSubRules.size()]; mSubRules.toArray(mSubRuleArray); mRenderInstructions.clear(); mRenderInstructions = null; mSubRules.clear(); mSubRules = null; for (int i = 0, n = mSubRuleArray.length; i < n; i++) mSubRuleArray[i].onComplete(); } void onDestroy() { for (int i = 0, n = mRenderInstructionArray.length; i < n; i++) mRenderInstructionArray[i].destroy(); for (int i = 0, n = mSubRuleArray.length; i < n; i++) mSubRuleArray[i].onDestroy(); } void scaleStrokeWidth(float scaleFactor) { for (int i = 0, n = mRenderInstructionArray.length; i < n; i++) mRenderInstructionArray[i].scaleStrokeWidth(scaleFactor); for (int i = 0, n = mSubRuleArray.length; i < n; i++) mSubRuleArray[i].scaleStrokeWidth(scaleFactor); } void scaleTextSize(float scaleFactor) { for (int i = 0, n = mRenderInstructionArray.length; i < n; i++) mRenderInstructionArray[i].scaleTextSize(scaleFactor); for (int i = 0, n = mSubRuleArray.length; i < n; i++) mSubRuleArray[i].scaleTextSize(scaleFactor); } }