From ca1a7b90d7e35888654c74046cb515ec567cd17f Mon Sep 17 00:00:00 2001 From: Hannes Janetzek Date: Mon, 10 Mar 2014 01:13:21 +0100 Subject: [PATCH] refactor: extract theme RulerBuilder from Rule --- .../org/oscim/theme/carto/RenderTheme.java | 2 +- vtm/src/org/oscim/theme/DebugTheme.java | 2 +- vtm/src/org/oscim/theme/IRenderTheme.java | 2 +- vtm/src/org/oscim/theme/RenderTheme.java | 29 +- .../org/oscim/theme/RenderThemeHandler.java | 66 +++-- vtm/src/org/oscim/theme/rule/Element.java | 4 +- .../org/oscim/theme/rule/NegativeRule.java | 6 +- .../org/oscim/theme/rule/PositiveRule.java | 7 +- vtm/src/org/oscim/theme/rule/Rule.java | 266 +++--------------- vtm/src/org/oscim/theme/rule/RuleBuilder.java | 236 ++++++++++++++++ .../org/oscim/theme/rule/RuleOptimizer.java | 23 +- 11 files changed, 342 insertions(+), 301 deletions(-) create mode 100644 vtm/src/org/oscim/theme/rule/RuleBuilder.java diff --git a/vtm-jeo/src/org/oscim/theme/carto/RenderTheme.java b/vtm-jeo/src/org/oscim/theme/carto/RenderTheme.java index 92c7f398..e34aac64 100644 --- a/vtm-jeo/src/org/oscim/theme/carto/RenderTheme.java +++ b/vtm-jeo/src/org/oscim/theme/carto/RenderTheme.java @@ -258,7 +258,7 @@ public class RenderTheme implements IRenderTheme { } @Override - public void updateInstructions() { + public void updateStyles() { // TODO Auto-generated method stub } diff --git a/vtm/src/org/oscim/theme/DebugTheme.java b/vtm/src/org/oscim/theme/DebugTheme.java index 9caaae8d..5349dd59 100644 --- a/vtm/src/org/oscim/theme/DebugTheme.java +++ b/vtm/src/org/oscim/theme/DebugTheme.java @@ -41,7 +41,7 @@ public class DebugTheme implements IRenderTheme { } @Override - public void updateInstructions() { + public void updateStyles() { } diff --git a/vtm/src/org/oscim/theme/IRenderTheme.java b/vtm/src/org/oscim/theme/IRenderTheme.java index ce8de7e6..95f3d293 100644 --- a/vtm/src/org/oscim/theme/IRenderTheme.java +++ b/vtm/src/org/oscim/theme/IRenderTheme.java @@ -56,7 +56,7 @@ public interface IRenderTheme { */ public abstract int getMapBackground(); - public void updateInstructions(); + public void updateStyles(); /** * Scales the text size of this RenderTheme by the given factor. diff --git a/vtm/src/org/oscim/theme/RenderTheme.java b/vtm/src/org/oscim/theme/RenderTheme.java index 71e51ecd..60971bd9 100644 --- a/vtm/src/org/oscim/theme/RenderTheme.java +++ b/vtm/src/org/oscim/theme/RenderTheme.java @@ -41,8 +41,8 @@ public class RenderTheme implements IRenderTheme { private final float mBaseTextSize; private final int mMapBackground; - private int mLevels; - private Rule[] mRules; + private final int mLevels; + private final Rule[] mRules; class RenderStyleCache { final int matchType; @@ -75,9 +75,11 @@ public class RenderTheme implements IRenderTheme { private final RenderStyleCache[] mStyleCache; - public RenderTheme(int mapBackground, float baseStrokeWidth, float baseTextSize) { + public RenderTheme(int mapBackground, float baseTextSize, Rule[] rules, int levels) { mMapBackground = mapBackground; mBaseTextSize = baseTextSize; + mRules = rules; + mLevels = levels; mStyleCache = new RenderStyleCache[3]; mStyleCache[0] = new RenderStyleCache(Element.NODE); @@ -240,27 +242,16 @@ public class RenderTheme implements IRenderTheme { return ri.list; } - void complete(List rulesList, int levels) { - mLevels = levels; - - mRules = new Rule[rulesList.size()]; - rulesList.toArray(mRules); - - for (int i = 0, n = mRules.length; i < n; i++) { - mRules[i].onComplete(); - } - } - @Override public void scaleTextSize(float scaleFactor) { - for (int i = 0, n = mRules.length; i < n; i++) - mRules[i].scaleTextSize(scaleFactor * mBaseTextSize); + for (Rule rule : mRules) + rule.scaleTextSize(scaleFactor * mBaseTextSize); } @Override - public void updateInstructions() { - for (int i = 0, n = mRules.length; i < n; i++) - mRules[i].updateInstructions(); + public void updateStyles() { + for (Rule rule : mRules) + rule.updateStyles(); } } diff --git a/vtm/src/org/oscim/theme/RenderThemeHandler.java b/vtm/src/org/oscim/theme/RenderThemeHandler.java index 0c1363d5..c78a3ecd 100644 --- a/vtm/src/org/oscim/theme/RenderThemeHandler.java +++ b/vtm/src/org/oscim/theme/RenderThemeHandler.java @@ -36,6 +36,7 @@ import org.oscim.renderer.atlas.TextureRegion; import org.oscim.renderer.elements.TextureItem; import org.oscim.theme.IRenderTheme.ThemeException; import org.oscim.theme.rule.Rule; +import org.oscim.theme.rule.RuleBuilder; import org.oscim.theme.styles.Area; import org.oscim.theme.styles.Circle; import org.oscim.theme.styles.Extrusion; @@ -60,7 +61,7 @@ public class RenderThemeHandler extends DefaultHandler { RENDER_THEME, RENDERING_INSTRUCTION, RULE, STYLE, ATLAS; } - //private static final String ELEMENT_NAME_RENDER_THEME = "rendertheme"; + private static final String ELEMENT_NAME_RENDER_THEME = "rendertheme"; private static final String ELEMENT_NAME_MATCH = "m"; private static final String UNEXPECTED_ELEMENT = "unexpected element: "; @@ -118,26 +119,30 @@ public class RenderThemeHandler extends DefaultHandler { log.debug(sb.toString()); } - private ArrayList mRulesList = new ArrayList(); - private Rule mCurrentRule; + private ArrayList mRulesList = new ArrayList(); + private RuleBuilder mCurrentRule; private Stack mElementStack = new Stack(); - private Stack mRuleStack = new Stack(); + private Stack mRuleStack = new Stack(); private HashMap mStyles = new HashMap(10); private TextureAtlas mTextureAtlas; - private int mLevel; + private int mLevels = 0; + private int mMapBackground = 0xffffffff; + private float mBaseTextSize = 1; + private RenderTheme mRenderTheme; @Override public void endDocument() { - if (mRenderTheme == null) { - throw new IllegalArgumentException("missing element: rules"); - } - mRenderTheme.complete(mRulesList, mLevel); + Rule[] rules = new Rule[mRulesList.size()]; + for (int i = 0, n = rules.length; i < n; i++) + rules[i] = mRulesList.get(i).onComplete(); + + mRenderTheme = new RenderTheme(mMapBackground, mBaseTextSize, rules, mLevels); mRulesList.clear(); mStyles.clear(); @@ -179,13 +184,13 @@ public class RenderThemeHandler extends DefaultHandler { public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { try { - if ("rendertheme".equals(localName)) { + if (ELEMENT_NAME_RENDER_THEME.equals(localName)) { checkState(localName, Element.RENDER_THEME); - mRenderTheme = createRenderTheme(localName, attributes); + createRenderTheme(localName, attributes); } else if (ELEMENT_NAME_MATCH.equals(localName)) { checkState(localName, Element.RULE); - Rule rule = Rule.create(localName, attributes, mRuleStack); + RuleBuilder rule = RuleBuilder.create(localName, attributes, mRuleStack); if (!mRuleStack.empty()) { mCurrentRule.addSubRule(rule); } @@ -207,7 +212,7 @@ public class RenderThemeHandler extends DefaultHandler { } else if ("outline-layer".equals(localName)) { checkState(localName, Element.RENDERING_INSTRUCTION); - Line line = createLine(null, localName, attributes, mLevel++, true); + Line line = createLine(null, localName, attributes, mLevels++, true); mStyles.put(OUTLINE_STYLE + line.style, line); } else if ("area".equals(localName)) { @@ -217,11 +222,11 @@ public class RenderThemeHandler extends DefaultHandler { } else if ("caption".equals(localName)) { checkState(localName, Element.RENDERING_INSTRUCTION); Text text = createText(localName, attributes, true); - mCurrentRule.addRenderingInstruction(text); + mCurrentRule.addStyle(text); } else if ("circle".equals(localName)) { checkState(localName, Element.RENDERING_INSTRUCTION); - Circle circle = createCircle(localName, attributes, mLevel++); - mCurrentRule.addRenderingInstruction(circle); + Circle circle = createCircle(localName, attributes, mLevels++); + mCurrentRule.addStyle(circle); } else if ("line".equals(localName)) { checkState(localName, Element.RENDERING_INSTRUCTION); @@ -230,18 +235,18 @@ public class RenderThemeHandler extends DefaultHandler { } else if ("lineSymbol".equals(localName)) { checkState(localName, Element.RENDERING_INSTRUCTION); LineSymbol lineSymbol = createLineSymbol(localName, attributes); - mCurrentRule.addRenderingInstruction(lineSymbol); + mCurrentRule.addStyle(lineSymbol); } else if ("text".equals(localName)) { checkState(localName, Element.RENDERING_INSTRUCTION); String style = attributes.getValue("use"); if (style == null) { Text text = createText(localName, attributes, false); - mCurrentRule.addRenderingInstruction(text); + mCurrentRule.addStyle(text); } else { Text pt = (Text) mStyles.get(TEXT_STYLE + style); if (pt != null) - mCurrentRule.addRenderingInstruction(pt); + mCurrentRule.addStyle(pt); else log.debug("BUG not a path text style: " + style); } @@ -249,7 +254,7 @@ public class RenderThemeHandler extends DefaultHandler { } else if ("symbol".equals(localName)) { checkState(localName, Element.RENDERING_INSTRUCTION); Symbol symbol = createSymbol(localName, attributes); - mCurrentRule.addRenderingInstruction(symbol); + mCurrentRule.addStyle(symbol); } else if ("outline".equals(localName)) { checkState(localName, Element.RENDERING_INSTRUCTION); @@ -257,8 +262,8 @@ public class RenderThemeHandler extends DefaultHandler { } else if ("extrusion".equals(localName)) { checkState(localName, Element.RENDERING_INSTRUCTION); - Extrusion extrusion = createExtrusion(localName, attributes, mLevel++); - mCurrentRule.addRenderingInstruction(extrusion); + Extrusion extrusion = createExtrusion(localName, attributes, mLevels++); + mCurrentRule.addStyle(extrusion); } else if ("atlas".equals(localName)) { checkState(localName, Element.ATLAS); @@ -305,12 +310,12 @@ public class RenderThemeHandler extends DefaultHandler { } } - Line line = createLine(style, localName, attributes, mLevel++, false); + Line line = createLine(style, localName, attributes, mLevels++, false); if (isStyle) { mStyles.put(LINE_STYLE + line.style, line); } else { - mCurrentRule.addRenderingInstruction(line); + mCurrentRule.addStyle(line); // Note 'outline' will not be inherited, it's just a // shorcut to add the outline RenderInstruction. addOutline(attributes.getValue("outline")); @@ -452,13 +457,13 @@ public class RenderThemeHandler extends DefaultHandler { } } - Area area = createArea(style, localName, attributes, mLevel); - mLevel += 2; + Area area = createArea(style, localName, attributes, mLevels); + mLevels += 2; if (isStyle) { mStyles.put(AREA_STYLE + area.style, area); } else { - mCurrentRule.addRenderingInstruction(area); + mCurrentRule.addStyle(area); } } @@ -550,7 +555,7 @@ public class RenderThemeHandler extends DefaultHandler { if (style != null) { Line line = (Line) mStyles.get(OUTLINE_STYLE + style); if (line != null && line.outline) - mCurrentRule.addRenderingInstruction(line); + mCurrentRule.addStyle(line); else log.debug("BUG not an outline style: " + style); } @@ -652,7 +657,7 @@ public class RenderThemeHandler extends DefaultHandler { mElementStack.push(element); } - static RenderTheme createRenderTheme(String elementName, Attributes attributes) { + private void createRenderTheme(String elementName, Attributes attributes) { Integer version = null; int mapBackground = Color.WHITE; float baseStrokeWidth = 1; @@ -691,7 +696,8 @@ public class RenderThemeHandler extends DefaultHandler { else if (baseTextSize < 0) throw new ThemeException("base-text-size must not be negative: " + baseTextSize); - return new RenderTheme(mapBackground, baseStrokeWidth, baseTextSize); + mMapBackground = mapBackground; + mBaseTextSize = baseTextSize; } /** diff --git a/vtm/src/org/oscim/theme/rule/Element.java b/vtm/src/org/oscim/theme/rule/Element.java index 5b961953..2a91a24d 100644 --- a/vtm/src/org/oscim/theme/rule/Element.java +++ b/vtm/src/org/oscim/theme/rule/Element.java @@ -1,6 +1,4 @@ -/* - * Copyright 2010, 2011, 2012 mapsforge.org - * Copyright 2013 Hannes Janetzek +/* Copyright 2013 Hannes Janetzek * * This file is part of the OpenScienceMap project (http://www.opensciencemap.org). * diff --git a/vtm/src/org/oscim/theme/rule/NegativeRule.java b/vtm/src/org/oscim/theme/rule/NegativeRule.java index 054575ef..ce750aaa 100644 --- a/vtm/src/org/oscim/theme/rule/NegativeRule.java +++ b/vtm/src/org/oscim/theme/rule/NegativeRule.java @@ -18,12 +18,14 @@ package org.oscim.theme.rule; import org.oscim.core.Tag; +import org.oscim.theme.styles.RenderStyle; class NegativeRule extends Rule { final AttributeMatcher mAttributeMatcher; - NegativeRule(int element, int zoom, boolean matchFirst, AttributeMatcher attributeMatcher) { - super(element, zoom, matchFirst); + NegativeRule(int element, int zoom, int selector, AttributeMatcher attributeMatcher, + Rule[] subRules, RenderStyle[] styles) { + super(element, zoom, selector, subRules, styles); mAttributeMatcher = attributeMatcher; } diff --git a/vtm/src/org/oscim/theme/rule/PositiveRule.java b/vtm/src/org/oscim/theme/rule/PositiveRule.java index fb6b15ff..370bcb24 100644 --- a/vtm/src/org/oscim/theme/rule/PositiveRule.java +++ b/vtm/src/org/oscim/theme/rule/PositiveRule.java @@ -18,14 +18,15 @@ package org.oscim.theme.rule; import org.oscim.core.Tag; +import org.oscim.theme.styles.RenderStyle; class PositiveRule extends Rule { final AttributeMatcher mKeyMatcher; final AttributeMatcher mValueMatcher; - PositiveRule(int element, int zoom, boolean matchFirst, AttributeMatcher keyMatcher, - AttributeMatcher valueMatcher) { - super(element, zoom, matchFirst); + PositiveRule(int element, int zoom, int selector, AttributeMatcher keyMatcher, + AttributeMatcher valueMatcher, Rule[] subRules, RenderStyle[] styles) { + super(element, zoom, selector, subRules, styles); if (keyMatcher instanceof AnyMatcher) mKeyMatcher = null; diff --git a/vtm/src/org/oscim/theme/rule/Rule.java b/vtm/src/org/oscim/theme/rule/Rule.java index 16902973..80c075d0 100644 --- a/vtm/src/org/oscim/theme/rule/Rule.java +++ b/vtm/src/org/oscim/theme/rule/Rule.java @@ -1,5 +1,4 @@ /* - * Copyright 2010, 2011, 2012 mapsforge.org * Copyright 2013 Hannes Janetzek * * This file is part of the OpenScienceMap project (http://www.opensciencemap.org). @@ -17,211 +16,26 @@ */ package org.oscim.theme.rule; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; import java.util.List; -import java.util.Map; -import java.util.Stack; import org.oscim.core.Tag; -import org.oscim.theme.IRenderTheme.ThemeException; -import org.oscim.theme.RenderThemeHandler; import org.oscim.theme.styles.RenderStyle; -import org.xml.sax.Attributes; public 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 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, byte zoomMin, byte zoomMax, boolean matchFirst) { + private final Rule[] subRules; + private final RenderStyle[] styles; - // zoom-level bitmask - int zoom = 0; - for (int z = zoomMin; z <= zoomMax && z < 32; z++) - zoom |= (1 << z); + private final int zoom; + private final int element; - List keyList = null, valueList = null; - boolean negativeRule = false; - boolean exclusionRule = false; + Rule(int element, int zoom, int selector, Rule[] subRules, RenderStyle[] styles) { + this.element = element; + this.zoom = zoom; + this.subRules = subRules; + this.styles = styles; - AttributeMatcher keyMatcher, valueMatcher = null; - - if (values == null) { - valueMatcher = AnyMatcher.getInstance(); - } else { - valueList = new ArrayList(Arrays.asList(values.split("\\|"))); - if (valueList.remove(STRING_NEGATION)) - negativeRule = true; - else if (valueList.remove(STRING_EXCLUSIVE)) - exclusionRule = true; - else { - valueMatcher = getValueMatcher(valueList); - valueMatcher = RuleOptimizer.optimize(valueMatcher, ruleStack); - } - } - - if (keys == null) { - if (negativeRule || exclusionRule) { - throw new ThemeException("negative rule requires key"); - } - keyMatcher = AnyMatcher.getInstance(); - } else { - keyList = new ArrayList(Arrays.asList(keys.split("\\|"))); - keyMatcher = getKeyMatcher(keyList); - - if ((keyMatcher instanceof AnyMatcher) && (negativeRule || exclusionRule)) { - throw new ThemeException("negative rule requires key"); - } - - if (negativeRule) { - AttributeMatcher m = new NegativeMatcher(keyList, valueList, false); - return new NegativeRule(element, zoom, matchFirst, m); - } else if (exclusionRule) { - AttributeMatcher m = new NegativeMatcher(keyList, valueList, true); - return new NegativeRule(element, zoom, matchFirst, m); - } - - keyMatcher = RuleOptimizer.optimize(keyMatcher, ruleStack); - } - - return new PositiveRule(element, zoom, matchFirst, 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(byte zoomMin, byte zoomMax) { - if (zoomMin < 0) - throw new ThemeException("zoom-min must not be negative: " + zoomMin); - else if (zoomMax < 0) - throw new ThemeException("zoom-max must not be negative: " + zoomMax); - else if (zoomMin > zoomMax) - throw new ThemeException("zoom-min must be less or equal zoom-max: " + zoomMin); - } - - public static Rule create(String elementName, Attributes attributes, Stack ruleStack) { - int element = Element.ANY; - int closed = Closed.ANY; - String keys = null; - String values = null; - byte zoomMin = 0; - byte zoomMax = Byte.MAX_VALUE; - boolean matchFirst = false; - - 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(); - 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(); - 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 if ("select".equals(name)) { - matchFirst = "first".equals(value); - } else { - RenderThemeHandler.logUnknownAttribute(elementName, name, value, i); - } - } - - if (closed == Closed.YES) - element = Element.POLY; - else if (closed == Closed.NO) - element = Element.LINE; - - validate(zoomMin, zoomMax); - - return createRule(ruleStack, element, keys, values, zoomMin, zoomMax, matchFirst); - } - - private Rule[] mSubRules; - private RenderStyle[] mRenderInstructions; - - final int mZoom; - final int mElement; - final boolean mMatchFirst; - - static class Builder { - ArrayList renderInstructions = new ArrayList(4); - ArrayList subRules = new ArrayList(4); - - public void clear() { - renderInstructions.clear(); - renderInstructions = null; - subRules.clear(); - subRules = null; - } - } - - private Builder builder; - - Rule(int type, int zoom, boolean matchFirst) { - builder = new Builder(); - mElement = type; - mZoom = zoom; - mMatchFirst = matchFirst; - } - - public void addRenderingInstruction(RenderStyle renderInstruction) { - builder.renderInstructions.add(renderInstruction); - } - - public void addSubRule(Rule rule) { - builder.subRules.add(rule); } abstract boolean matchesTags(Tag[] tags); @@ -255,50 +69,41 @@ public abstract class Rule { return false; } - public void onComplete() { - MATCHERS_CACHE_KEY.clear(); - MATCHERS_CACHE_VALUE.clear(); - - mRenderInstructions = new RenderStyle[builder.renderInstructions.size()]; - builder.renderInstructions.toArray(mRenderInstructions); - - mSubRules = new Rule[builder.subRules.size()]; - builder.subRules.toArray(mSubRules); - - builder.clear(); - builder = null; - - for (Rule subRule : mSubRules) - subRule.onComplete(); - } - public void onDestroy() { - for (RenderStyle ri : mRenderInstructions) - ri.destroy(); + if (styles != null) + for (RenderStyle ri : styles) + ri.destroy(); - for (Rule subRule : mSubRules) - subRule.onDestroy(); + if (subRules != null) + for (Rule subRule : subRules) + subRule.onDestroy(); } public void scaleTextSize(float scaleFactor) { - for (RenderStyle ri : mRenderInstructions) - ri.scaleTextSize(scaleFactor); - for (Rule subRule : mSubRules) - subRule.scaleTextSize(scaleFactor); + if (styles != null) + for (RenderStyle ri : styles) + ri.scaleTextSize(scaleFactor); + + if (subRules != null) + for (Rule subRule : subRules) + subRule.scaleTextSize(scaleFactor); } - public void updateInstructions() { - for (RenderStyle ri : mRenderInstructions) - ri.update(); - for (Rule subRule : mSubRules) - subRule.updateInstructions(); + public void updateStyles() { + if (styles != null) + for (RenderStyle ri : styles) + ri.update(); + + if (subRules != null) + for (Rule subRule : subRules) + subRule.updateStyles(); } public static class RuleVisitor { boolean apply(Rule r) { - - for (Rule subRule : r.mSubRules) - this.apply(subRule); + if (r.subRules != null) + for (Rule subRule : r.subRules) + this.apply(subRule); return true; } @@ -307,8 +112,9 @@ public abstract class Rule { public static class UpdateVisitor extends RuleVisitor { @Override boolean apply(Rule r) { - for (RenderStyle ri : r.mRenderInstructions) - ri.update(); + if (r.styles != null) + for (RenderStyle ri : r.styles) + ri.update(); return super.apply(r); } diff --git a/vtm/src/org/oscim/theme/rule/RuleBuilder.java b/vtm/src/org/oscim/theme/rule/RuleBuilder.java new file mode 100644 index 00000000..03143849 --- /dev/null +++ b/vtm/src/org/oscim/theme/rule/RuleBuilder.java @@ -0,0 +1,236 @@ +package org.oscim.theme.rule; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Stack; + +import org.oscim.theme.IRenderTheme.ThemeException; +import org.oscim.theme.RenderThemeHandler; +import org.oscim.theme.styles.RenderStyle; +import org.xml.sax.Attributes; + +public class RuleBuilder { + boolean positiveRule; + + int zoom; + int element; + int selector; + + AttributeMatcher keyMatcher; + AttributeMatcher valueMatcher; + + ArrayList renderStyles = new ArrayList(4); + ArrayList subRules = new ArrayList(4); + + 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 String STRING_NEGATION = "~"; + private static final String STRING_EXCLUSIVE = "-"; + private static final String STRING_WILDCARD = "*"; + + private static final int SELECT_FIRST = 1 << 0; + private static final int SELECT_WHEN_MATCHED = 1 << 1; + + public RuleBuilder(boolean positive, int element, int zoom, int selector, + AttributeMatcher keyMatcher, AttributeMatcher valueMatcher) { + this.positiveRule = positive; + this.element = element; + this.zoom = zoom; + this.selector = selector; + this.keyMatcher = keyMatcher; + this.valueMatcher = valueMatcher; + } + + private static RuleBuilder createRule(Stack ruleStack, int element, String keys, + String values, byte zoomMin, byte zoomMax, int selector) { + + // zoom-level bitmask + int zoom = 0; + for (int z = zoomMin; z <= zoomMax && z < 32; z++) + zoom |= (1 << z); + + List keyList = null, valueList = null; + boolean negativeRule = false; + boolean exclusionRule = false; + + AttributeMatcher keyMatcher, valueMatcher = null; + + if (values == null) { + valueMatcher = AnyMatcher.getInstance(); + } else { + valueList = new ArrayList(Arrays.asList(values.split("\\|"))); + if (valueList.remove(STRING_NEGATION)) + negativeRule = true; + else if (valueList.remove(STRING_EXCLUSIVE)) + exclusionRule = true; + else { + valueMatcher = getValueMatcher(valueList); + valueMatcher = RuleOptimizer.optimize(valueMatcher, ruleStack); + } + } + + if (keys == null) { + if (negativeRule || exclusionRule) { + throw new ThemeException("negative rule requires key"); + } + keyMatcher = AnyMatcher.getInstance(); + } else { + keyList = new ArrayList(Arrays.asList(keys.split("\\|"))); + keyMatcher = getKeyMatcher(keyList); + + if ((keyMatcher instanceof AnyMatcher) && (negativeRule || exclusionRule)) { + throw new ThemeException("negative rule requires key"); + } + + if (negativeRule) { + AttributeMatcher m = new NegativeMatcher(keyList, valueList, false); + return new RuleBuilder(false, element, zoom, selector, m, null); + } else if (exclusionRule) { + AttributeMatcher m = new NegativeMatcher(keyList, valueList, true); + return new RuleBuilder(false, element, zoom, selector, m, null); + } + + keyMatcher = RuleOptimizer.optimize(keyMatcher, ruleStack); + } + + return new RuleBuilder(true, element, zoom, selector, 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(byte zoomMin, byte zoomMax) { + if (zoomMin < 0) + throw new ThemeException("zoom-min must not be negative: " + zoomMin); + else if (zoomMax < 0) + throw new ThemeException("zoom-max must not be negative: " + zoomMax); + else if (zoomMin > zoomMax) + throw new ThemeException("zoom-min must be less or equal zoom-max: " + zoomMin); + } + + public static RuleBuilder create(String elementName, Attributes attributes, + Stack ruleStack) { + int element = Element.ANY; + int closed = Closed.ANY; + String keys = null; + String values = null; + byte zoomMin = 0; + byte zoomMax = Byte.MAX_VALUE; + int selector = 0; + + 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(); + 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(); + 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 if ("select".equals(name)) { + if ("first".equals(value)) + selector |= SELECT_FIRST; + if ("when-matched".equals(value)) + selector |= SELECT_WHEN_MATCHED; + } else { + RenderThemeHandler.logUnknownAttribute(elementName, name, value, i); + } + } + + if (closed == Closed.YES) + element = Element.POLY; + else if (closed == Closed.NO) + element = Element.LINE; + + validate(zoomMin, zoomMax); + + return createRule(ruleStack, element, keys, values, zoomMin, zoomMax, selector); + } + + public Rule onComplete() { + MATCHERS_CACHE_KEY.clear(); + MATCHERS_CACHE_VALUE.clear(); + + RenderStyle[] styles = null; + Rule[] rules = null; + + if (renderStyles.size() > 0) { + styles = new RenderStyle[renderStyles.size()]; + renderStyles.toArray(styles); + } + + if (subRules.size() > 0) { + rules = new Rule[subRules.size()]; + for (int i = 0; i < rules.length; i++) + rules[i] = subRules.get(i).onComplete(); + } + + if (positiveRule) + return new PositiveRule(element, zoom, selector, keyMatcher, + valueMatcher, rules, styles); + else + return new NegativeRule(element, zoom, selector, keyMatcher, + rules, styles); + } + + public void addStyle(RenderStyle style) { + renderStyles.add(style); + } + + public void addSubRule(RuleBuilder rule) { + subRules.add(rule); + } + +} diff --git a/vtm/src/org/oscim/theme/rule/RuleOptimizer.java b/vtm/src/org/oscim/theme/rule/RuleOptimizer.java index e6bae924..5b74f873 100644 --- a/vtm/src/org/oscim/theme/rule/RuleOptimizer.java +++ b/vtm/src/org/oscim/theme/rule/RuleOptimizer.java @@ -22,12 +22,13 @@ import java.util.Stack; final class RuleOptimizer { private static AttributeMatcher optimizeKeyMatcher(AttributeMatcher attributeMatcher, - Stack ruleStack) { + Stack ruleStack) { for (int i = 0, n = ruleStack.size(); i < n; ++i) { - if (ruleStack.get(i) instanceof PositiveRule) { - PositiveRule positiveRule = (PositiveRule) ruleStack.get(i); - if (positiveRule.mKeyMatcher != null - && positiveRule.mKeyMatcher.isCoveredBy(attributeMatcher)) { + if (ruleStack.get(i).positiveRule) { + RuleBuilder positiveRule = ruleStack.get(i); + + if (positiveRule.keyMatcher != null + && positiveRule.keyMatcher.isCoveredBy(attributeMatcher)) { return null; } } @@ -37,13 +38,13 @@ final class RuleOptimizer { } private static AttributeMatcher optimizeValueMatcher( - AttributeMatcher attributeMatcher, Stack ruleStack) { + AttributeMatcher attributeMatcher, Stack ruleStack) { for (int i = 0, n = ruleStack.size(); i < n; ++i) { - if (ruleStack.get(i) instanceof PositiveRule) { - PositiveRule positiveRule = (PositiveRule) ruleStack.get(i); + if (ruleStack.get(i).positiveRule) { + RuleBuilder positiveRule = ruleStack.get(i); - if (positiveRule.mValueMatcher != null - && positiveRule.mValueMatcher.isCoveredBy(attributeMatcher)) { + if (positiveRule.valueMatcher != null + && positiveRule.valueMatcher.isCoveredBy(attributeMatcher)) { return null; } } @@ -53,7 +54,7 @@ final class RuleOptimizer { } static AttributeMatcher optimize(AttributeMatcher attributeMatcher, - Stack ruleStack) { + Stack ruleStack) { if (attributeMatcher instanceof AnyMatcher) return attributeMatcher;// return null; else if (attributeMatcher instanceof NegativeMatcher) {