/* * Copyright 2014 Hannes Janetzek * * This file is part of the OpenScienceMap project (http://www.opensciencemap.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.oscim.theme.rule; import java.util.List; import org.oscim.core.Tag; import org.oscim.theme.rule.RuleBuilder.RuleType; import org.oscim.theme.styles.RenderStyle; public class Rule { public final class Element { public static final int NODE = 1 << 0; public static final int LINE = 1 << 1; public static final int POLY = 1 << 2; public static final int WAY = LINE | POLY; public static final int ANY = NODE | WAY; } public final class Closed { public static final int NO = 1 << 0; public static final int YES = 1 << 1; public static final int ANY = NO | YES; } public final class Selector { public static final int ANY = 0; public static final int FIRST = 1 << 0; public static final int WHEN_MATCHED = 1 << 1; } public final static RenderStyle[] EMPTY_STYLE = new RenderStyle[0]; public final static Rule[] EMPTY_RULES = new Rule[0]; public final Rule[] subRules; public final RenderStyle[] styles; public final int zoom; public final int element; public final boolean selectFirstMatch; public final boolean selectWhenMatched; Rule(int element, int zoom, int selector, Rule[] subRules, RenderStyle[] styles) { this.element = element; this.zoom = zoom; this.subRules = (subRules == null) ? EMPTY_RULES : subRules; this.styles = (styles == null) ? EMPTY_STYLE : styles; selectFirstMatch = (selector & Selector.FIRST) != 0; selectWhenMatched = (selector & Selector.WHEN_MATCHED) != 0; } public boolean matchesTags(Tag[] tags) { return true; } public boolean matchElement(int type, Tag[] tags, int zoomLevel, List<RenderStyle> result) { if (((element & type) == 0) || ((zoom & zoomLevel) == 0) || !matchesTags(tags)) return false; boolean matched = false; if (subRules != EMPTY_RULES) { if (selectFirstMatch) { /* only add first matching rule and when-matched rules iff a * previous rule matched */ for (Rule r : subRules) { /* continue if matched xor selectWhenMatch */ if (matched ^ r.selectWhenMatched) continue; if (r.matchElement(type, tags, zoomLevel, result)) matched = true; } } else { /* add all rules and when-matched rules iff a previous rule * matched */ for (Rule r : subRules) { if (r.selectWhenMatched && !matched) continue; if (r.matchElement(type, tags, zoomLevel, result)) matched = true; } } } if (styles == EMPTY_STYLE) /* matched if styles where added */ return matched; /* add instructions for this rule */ for (RenderStyle ri : styles) result.add(ri); /* this rule did match */ return true; } public void dispose() { for (RenderStyle ri : styles) ri.dispose(); for (Rule subRule : subRules) subRule.dispose(); } public void scaleTextSize(float scaleFactor) { for (RenderStyle ri : styles) ri.scaleTextSize(scaleFactor); for (Rule subRule : subRules) subRule.scaleTextSize(scaleFactor); } public void updateStyles() { for (RenderStyle ri : styles) ri.update(); for (Rule subRule : subRules) subRule.updateStyles(); } public static class RuleVisitor { public void apply(Rule r) { for (Rule subRule : r.subRules) this.apply(subRule); } } public static class TextSizeVisitor extends RuleVisitor { float scaleFactor = 1; public void setScaleFactor(float scaleFactor) { this.scaleFactor = scaleFactor; } @Override public void apply(Rule r) { for (RenderStyle ri : r.styles) ri.scaleTextSize(scaleFactor); super.apply(r); } } public static class UpdateVisitor extends RuleVisitor { @Override public void apply(Rule r) { for (RenderStyle ri : r.styles) ri.update(); super.apply(r); } } public void apply(RuleVisitor v) { v.apply(this); } static class PositiveRuleK extends Rule { private final String mKey; PositiveRuleK(int element, int zoom, int selector, String key, Rule[] subRules, RenderStyle[] styles) { super(element, zoom, selector, subRules, styles); mKey = key; } @Override public boolean matchesTags(Tag[] tags) { for (Tag tag : tags) if (mKey == tag.key) return true; return false; } } static class PositiveRuleV extends Rule { private final String mValue; PositiveRuleV(int element, int zoom, int selector, String value, Rule[] subRules, RenderStyle[] styles) { super(element, zoom, selector, subRules, styles); mValue = value; } @Override public boolean matchesTags(Tag[] tags) { for (Tag tag : tags) if (mValue == tag.value) return true; return false; } } static class PositiveRuleKV extends Rule { private final String mKey; private final String mValue; PositiveRuleKV(int element, int zoom, int selector, String key, String value, Rule[] subRules, RenderStyle[] styles) { super(element, zoom, selector, subRules, styles); mKey = key; mValue = value; } @Override public boolean matchesTags(Tag[] tags) { for (Tag tag : tags) if (mKey == tag.key) return (mValue == tag.value); return false; } } static class PositiveRuleMultiKV extends Rule { private final String mKeys[]; private final String mValues[]; PositiveRuleMultiKV(int element, int zoom, int selector, String keys[], String values[], Rule[] subRules, RenderStyle[] styles) { super(element, zoom, selector, subRules, styles); if (keys.length == 0) mKeys = null; else mKeys = keys; if (values.length == 0) mValues = null; else mValues = values; } @Override public boolean matchesTags(Tag[] tags) { if (mKeys == null) { for (Tag tag : tags) { for (String value : mValues) { if (value == tag.value) return true; } } return false; } for (Tag tag : tags) for (String key : mKeys) { if (key == tag.key) { if (mValues == null) return true; for (String value : mValues) { if (value == tag.value) return true; } } } return false; } } static class NegativeRule extends Rule { public final String[] keys; public final String[] values; /* (-) 'exclusive negation' matches when either KEY is not present * or KEY is present and any VALUE is NOT present * * (\) 'except negation' matches when KEY is present * none items of VALUE is present (TODO). * (can be emulated by <m k="a"><m k=a v="-|b|c">...</m></m>) * * (~) 'non-exclusive negation' matches when either KEY is not present * or KEY is present and any VALUE is present */ public final boolean exclusive; NegativeRule(RuleType type, int element, int zoom, int selector, String[] keys, String[] values, Rule[] subRules, RenderStyle[] styles) { super(element, zoom, selector, subRules, styles); this.keys = keys; this.values = values; this.exclusive = type == RuleType.EXCLUDE; } @Override public boolean matchesTags(Tag[] tags) { if (!containsKeys(tags)) return true; for (Tag tag : tags) for (String value : values) if (value == tag.value) return !exclusive; return exclusive; } private boolean containsKeys(Tag[] tags) { for (Tag tag : tags) for (String key : keys) if (key == tag.key) return true; return false; } } public static RuleBuilder builder() { return new RuleBuilder(); } }