package net.osmand.render; import gnu.trove.map.hash.TIntObjectHashMap; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.PrintStream; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Stack; import net.osmand.PlatformUtil; import org.apache.commons.logging.Log; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; public class RenderingRulesStorage { private final static Log log = PlatformUtil.getLog(RenderingRulesStorage.class); static boolean STORE_ATTTRIBUTES = false; // keep sync ! // keep sync ! not change values public final static int MULTY_POLYGON_TYPE = 0; public final static int POINT_RULES = 1; public final static int LINE_RULES = 2; public final static int POLYGON_RULES = 3; public final static int TEXT_RULES = 4; public final static int ORDER_RULES = 5; public final static int LENGTH_RULES = 6; private final static int SHIFT_TAG_VAL = 16; // C++ List<String> dictionary = new ArrayList<String>(); Map<String, Integer> dictionaryMap = new LinkedHashMap<String, Integer>(); public RenderingRuleStorageProperties PROPS = new RenderingRuleStorageProperties(); @SuppressWarnings("unchecked") public TIntObjectHashMap<RenderingRule>[] tagValueGlobalRules = new TIntObjectHashMap[LENGTH_RULES]; protected Map<String, RenderingRule> renderingAttributes = new LinkedHashMap<String, RenderingRule>(); protected Map<String, String> renderingConstants = new LinkedHashMap<String, String>(); protected String renderingName; protected String internalRenderingName; public static interface RenderingRulesStorageResolver { RenderingRulesStorage resolve(String name, RenderingRulesStorageResolver ref) throws XmlPullParserException, IOException; } public RenderingRulesStorage(String name, Map<String, String> renderingConstants){ getDictionaryValue(""); this.renderingName = name; if(renderingConstants != null) { this.renderingConstants.putAll(renderingConstants); } } public int getDictionaryValue(String val) { if(dictionaryMap.containsKey(val)){ return dictionaryMap.get(val); } int nextInd = dictionaryMap.size(); dictionaryMap.put(val, nextInd); dictionary.add(val); return nextInd; } public String getStringValue(int i){ return dictionary.get(i); } public String getName() { return renderingName; } public String getInternalRenderingName() { return internalRenderingName; } public void parseRulesFromXmlInputStream(InputStream is, RenderingRulesStorageResolver resolver) throws XmlPullParserException, IOException { XmlPullParser parser = PlatformUtil.newXMLPullParser(); RenderingRulesHandler handler = new RenderingRulesHandler(parser, resolver); handler.parse(is); RenderingRulesStorage depends = handler.getDependsStorage(); if (depends != null) { // merge results // dictionary and props are already merged Iterator<Entry<String, RenderingRule>> it = depends.renderingAttributes.entrySet().iterator(); while (it.hasNext()) { Entry<String, RenderingRule> e = it.next(); if (renderingAttributes.containsKey(e.getKey())) { RenderingRule root = renderingAttributes.get(e.getKey()); List<RenderingRule> list = e.getValue().getIfElseChildren(); for (RenderingRule every : list) { root.addIfElseChildren(every); } e.getValue().addToBeginIfElseChildren(root); } else { renderingAttributes.put(e.getKey(), e.getValue()); } } for (int i = 0; i < LENGTH_RULES; i++) { if (depends.tagValueGlobalRules[i] == null || depends.tagValueGlobalRules[i].isEmpty()) { continue; } if (tagValueGlobalRules[i] != null) { int[] keys = depends.tagValueGlobalRules[i].keys(); for (int j = 0; j < keys.length; j++) { RenderingRule rule = tagValueGlobalRules[i].get(keys[j]); RenderingRule dependsRule = depends.tagValueGlobalRules[i].get(keys[j]); if (dependsRule != null) { if (rule != null) { RenderingRule toInsert = createTagValueRootWrapperRule(keys[j], rule); toInsert.addIfElseChildren(dependsRule); tagValueGlobalRules[i].put(keys[j], toInsert); } else { tagValueGlobalRules[i].put(keys[j], dependsRule); } } } } else { tagValueGlobalRules[i] = depends.tagValueGlobalRules[i]; } } } } public static String colorToString(int color) { if ((0xFF000000 & color) == 0xFF000000) { return "#" + Integer.toHexString(color & 0x00FFFFFF); //$NON-NLS-1$ } else { return "#" + Integer.toHexString(color); //$NON-NLS-1$ } } private void registerGlobalRule(RenderingRule rr, int state, String tagS, String valueS) throws XmlPullParserException { if(tagS == null || valueS == null){ throw new XmlPullParserException("Attribute tag should be specified for root filter " + rr.toString()); } int tag = getDictionaryValue(tagS); int value = getDictionaryValue(valueS); int key = (tag << SHIFT_TAG_VAL) + value; RenderingRule insert = tagValueGlobalRules[state].get(key); if (insert != null) { // all root rules should have at least tag/value insert = createTagValueRootWrapperRule(key, insert); insert.addIfElseChildren(rr); } else { insert = rr; } tagValueGlobalRules[state].put(key, insert); } private RenderingRule createTagValueRootWrapperRule(int tagValueKey, RenderingRule previous) { if (previous.getProperties().length > 0) { Map<String, String> m = new HashMap<String, String>(); RenderingRule toInsert = new RenderingRule(m, true, RenderingRulesStorage.this); toInsert.addIfElseChildren(previous); return toInsert; } else { return previous; } } private class RenderingRulesHandler { private final XmlPullParser parser; private int state; Stack<RenderingRule> stack = new Stack<RenderingRule>(); Map<String, String> attrsMap = new LinkedHashMap<String, String>(); private final RenderingRulesStorageResolver resolver; private RenderingRulesStorage dependsStorage; public RenderingRulesHandler(XmlPullParser parser, RenderingRulesStorageResolver resolver){ this.parser = parser; this.resolver = resolver; } public void parse(InputStream is) throws XmlPullParserException, IOException { parser.setInput(is, "UTF-8"); int tok; while ((tok = parser.next()) != XmlPullParser.END_DOCUMENT) { if (tok == XmlPullParser.START_TAG) { startElement(parser.getName()); } else if (tok == XmlPullParser.END_TAG) { endElement(parser.getName()); } } } public RenderingRulesStorage getDependsStorage() { return dependsStorage; } private boolean isTopCase() { for(int i = 0; i < stack.size(); i++) { if(!stack.get(i).isGroup()) { return false; } } return true; } public void startElement(String name) throws XmlPullParserException, IOException { boolean stateChanged = false; final boolean isCase = isCase(name); final boolean isSwitch = isSwitch(name); if(isCase || isSwitch){ //$NON-NLS-1$ attrsMap.clear(); boolean top = stack.size() == 0 || isTopCase(); parseAttributes(attrsMap); RenderingRule renderingRule = new RenderingRule(attrsMap, isSwitch, RenderingRulesStorage.this); if(top || STORE_ATTTRIBUTES){ renderingRule.storeAttributes(attrsMap); } if (stack.size() > 0 && stack.peek() instanceof RenderingRule) { RenderingRule parent = ((RenderingRule) stack.peek()); parent.addIfElseChildren(renderingRule); } stack.push(renderingRule); } else if(isApply(name)){ //$NON-NLS-1$ attrsMap.clear(); parseAttributes(attrsMap); RenderingRule renderingRule = new RenderingRule(attrsMap, false, RenderingRulesStorage.this); if(STORE_ATTTRIBUTES) { renderingRule.storeAttributes(attrsMap); } if (stack.size() > 0 && stack.peek() instanceof RenderingRule) { ((RenderingRule) stack.peek()).addIfChildren(renderingRule); } else { throw new XmlPullParserException("Apply (groupFilter) without parent"); } stack.push(renderingRule); } else if("order".equals(name)){ //$NON-NLS-1$ state = ORDER_RULES; stateChanged = true; } else if("text".equals(name)){ //$NON-NLS-1$ state = TEXT_RULES; stateChanged = true; } else if("point".equals(name)){ //$NON-NLS-1$ state = POINT_RULES; stateChanged = true; } else if("line".equals(name)){ //$NON-NLS-1$ state = LINE_RULES; stateChanged = true; } else if("polygon".equals(name)){ //$NON-NLS-1$ state = POLYGON_RULES; stateChanged = true; } else if("renderingAttribute".equals(name)){ //$NON-NLS-1$ String attr = parser.getAttributeValue("", "name"); RenderingRule root = new RenderingRule(new HashMap<String, String>(), false, RenderingRulesStorage.this); renderingAttributes.put(attr, root); stack.push(root); } else if("renderingProperty".equals(name)){ //$NON-NLS-1$ String attr = parser.getAttributeValue("", "attr"); RenderingRuleProperty prop; String type = parser.getAttributeValue("", "type"); if("boolean".equalsIgnoreCase(type)){ prop = RenderingRuleProperty.createInputBooleanProperty(attr); } else if("string".equalsIgnoreCase(type)){ prop = RenderingRuleProperty.createInputStringProperty(attr); } else { prop = RenderingRuleProperty.createInputIntProperty(attr); } prop.setDescription(parser.getAttributeValue("", "description")); prop.setDefaultValueDescription(parser.getAttributeValue("", "defaultValueDescription")); prop.setCategory(parser.getAttributeValue("", "category")); prop.setName(parser.getAttributeValue("", "name")); if(parser.getAttributeValue("", "possibleValues") != null){ prop.setPossibleValues(parser.getAttributeValue("", "possibleValues").split(",")); } PROPS.registerRule(prop); } else if("renderingConstant".equals(name)){ //$NON-NLS-1$ if(!renderingConstants.containsKey(parser.getAttributeValue("", "name"))){ renderingConstants.put(parser.getAttributeValue("", "name"), parser.getAttributeValue("", "value")); } } else if("renderingStyle".equals(name)){ //$NON-NLS-1$ String depends = parser.getAttributeValue("", "depends"); if(depends != null && depends.length()> 0){ this.dependsStorage = resolver.resolve(depends, resolver); } if(dependsStorage != null){ // copy dictionary dictionary = new ArrayList<String>(dependsStorage.dictionary); dictionaryMap = new LinkedHashMap<String, Integer>(dependsStorage.dictionaryMap); PROPS = new RenderingRuleStorageProperties(dependsStorage.PROPS); } internalRenderingName = parser.getAttributeValue("", "name"); } else if("renderer".equals(name)){ //$NON-NLS-1$ throw new XmlPullParserException("Rendering style is deprecated and no longer supported."); } else { log.warn("Unknown tag : " + name); //$NON-NLS-1$ } if(stateChanged){ tagValueGlobalRules[state] = new TIntObjectHashMap<RenderingRule>(); } } protected boolean isCase(String name) { return "filter".equals(name) || "case".equals(name); } protected boolean isApply(String name) { return "groupFilter".equals(name) || "apply".equals(name) || "apply_if".equals(name); } protected boolean isSwitch(String name) { return "group".equals(name) || "switch".equals(name); } private Map<String, String> parseAttributes(Map<String, String> m) { for (int i = 0; i < parser.getAttributeCount(); i++) { String name = parser.getAttributeName(i); String vl = parser.getAttributeValue(i); if (vl != null && vl.startsWith("$")) { String cv = vl.substring(1); if (!renderingConstants.containsKey(cv) && !renderingAttributes.containsKey(cv)) { throw new IllegalStateException("Rendering constant or attribute '" + cv + "' was not specified."); } if(renderingConstants.containsKey(cv)){ vl = renderingConstants.get(cv); } } m.put(name, vl); } return m; } public void endElement(String name) throws XmlPullParserException { if (isCase(name) || isSwitch(name)) { RenderingRule renderingRule = (RenderingRule) stack.pop(); if(stack.size() == 0) { registerTopLevel(renderingRule, null, Collections.EMPTY_MAP); } } else if(isApply(name)){ stack.pop(); } else if("renderingAttribute".equals(name)){ //$NON-NLS-1$ stack.pop(); } } protected void registerTopLevel(RenderingRule renderingRule, List<RenderingRule> applyRules, Map<String, String> attrs) throws XmlPullParserException { if(renderingRule.isGroup() && (renderingRule.getIntPropertyValue(RenderingRuleStorageProperties.TAG) == -1 || renderingRule.getIntPropertyValue(RenderingRuleStorageProperties.VALUE) == -1)){ List<RenderingRule> caseChildren = renderingRule.getIfElseChildren(); for(RenderingRule ch : caseChildren) { List<RenderingRule> apply = applyRules; if(!renderingRule.getIfChildren().isEmpty()) { apply = new ArrayList<RenderingRule>(); apply.addAll(renderingRule.getIfChildren()); if(applyRules != null) { apply.addAll(applyRules); } } Map<String, String> cattrs = new HashMap<String, String>(attrs); cattrs.putAll(renderingRule.getAttributes()); registerTopLevel(ch, apply, cattrs); } } else { String tg = null; String vl = null; HashMap<String, String> ns = new HashMap<String, String>(attrs); ns.putAll(renderingRule.getAttributes()); tg = ns.remove("tag"); vl = ns.remove("value"); // reset rendering rule attributes renderingRule.init(ns); if(STORE_ATTTRIBUTES) { renderingRule.storeAttributes(ns); } registerGlobalRule(renderingRule, state, tg, vl); if (applyRules != null) { for (RenderingRule apply : applyRules) { renderingRule.addIfChildren(apply); } } } } } public int getTagValueKey(String tag, String value){ int itag = getDictionaryValue(tag); int ivalue = getDictionaryValue(value); return (itag << SHIFT_TAG_VAL) | ivalue; } public String getValueString(int tagValueKey){ return getStringValue(tagValueKey & ((1 << SHIFT_TAG_VAL) - 1)); } public String getTagString(int tagValueKey){ return getStringValue(tagValueKey >> SHIFT_TAG_VAL); } protected RenderingRule getRule(int state, int itag, int ivalue){ if(tagValueGlobalRules[state] != null){ return tagValueGlobalRules[state].get((itag << SHIFT_TAG_VAL) | ivalue); } return null; } public RenderingRule getRenderingAttributeRule(String attribute){ return renderingAttributes.get(attribute); } public String[] getRenderingAttributeNames() { return renderingAttributes.keySet().toArray(new String[renderingAttributes.size()]); } public RenderingRule[] getRenderingAttributeValues() { return renderingAttributes.values().toArray(new RenderingRule[renderingAttributes.size()]); } public RenderingRule[] getRules(int state){ if(state >= tagValueGlobalRules.length || tagValueGlobalRules[state] == null) { return new RenderingRule[0]; } return tagValueGlobalRules[state].values(new RenderingRule[tagValueGlobalRules[state].size()]); } public int getRuleTagValueKey(int state, int ind){ return tagValueGlobalRules[state].keys()[ind]; } public void printDebug(int state, PrintStream out){ for(int key : tagValueGlobalRules[state].keys()) { RenderingRule rr = tagValueGlobalRules[state].get(key); out.print("\n\n"+getTagString(key) + " : " + getValueString(key) + "\n "); printRenderingRule(" ", rr, out); } } private static void printRenderingRule(String indent, RenderingRule rr, PrintStream out){ out.print(rr.toString(indent, new StringBuilder()).toString()); } public static void main(String[] args) throws XmlPullParserException, IOException { STORE_ATTTRIBUTES = true; // InputStream is = RenderingRulesStorage.class.getResourceAsStream("default.render.xml"); final String loc = "/Users/victorshcherb/osmand/repos/resources/rendering_styles/"; String defaultFile = loc + "UniRS.render.xml"; if(args.length > 0) { defaultFile = args[0]; } final Map<String, String> renderingConstants = new LinkedHashMap<String, String>(); InputStream is = new FileInputStream(loc + "default.render.xml"); try { XmlPullParser parser = PlatformUtil.newXMLPullParser(); parser.setInput(is, "UTF-8"); int tok; while ((tok = parser.next()) != XmlPullParser.END_DOCUMENT) { if (tok == XmlPullParser.START_TAG) { String tagName = parser.getName(); if (tagName.equals("renderingConstant")) { if (!renderingConstants.containsKey(parser.getAttributeValue("", "name"))) { renderingConstants.put(parser.getAttributeValue("", "name"), parser.getAttributeValue("", "value")); } } } } } finally { is.close(); } RenderingRulesStorage storage = new RenderingRulesStorage("default", renderingConstants); final RenderingRulesStorageResolver resolver = new RenderingRulesStorageResolver() { @Override public RenderingRulesStorage resolve(String name, RenderingRulesStorageResolver ref) throws XmlPullParserException, IOException { RenderingRulesStorage depends = new RenderingRulesStorage(name, renderingConstants); // depends.parseRulesFromXmlInputStream(RenderingRulesStorage.class.getResourceAsStream(name + ".render.xml"), ref); depends.parseRulesFromXmlInputStream(new FileInputStream(loc + name + ".render.xml"), ref); return depends; } }; is = new FileInputStream(defaultFile); storage.parseRulesFromXmlInputStream(is, resolver); // storage = new RenderingRulesStorage("", null); // new DefaultRenderingRulesStorage().createStyle(storage); for (RenderingRuleProperty p : storage.PROPS.getCustomRules()) { System.out.println(p.getCategory() + " " + p.getName() + " " + p.getAttrName()); } // printAllRules(storage); // testSearch(storage); } protected static void testSearch(RenderingRulesStorage storage) { // long tm = System.nanoTime(); // int count = 100000; // for (int i = 0; i < count; i++) { RenderingRuleSearchRequest searchRequest = new RenderingRuleSearchRequest(storage); searchRequest.setStringFilter(storage.PROPS.R_TAG, "highway"); searchRequest.setStringFilter(storage.PROPS.R_VALUE, "residential"); // searchRequest.setStringFilter(storage.PROPS.R_ADDITIONAL, "leaf_type=broadleaved"); // searchRequest.setIntFilter(storage.PROPS.R_LAYER, 1); searchRequest.setIntFilter(storage.PROPS.R_MINZOOM, 13); searchRequest.setIntFilter(storage.PROPS.R_MAXZOOM, 13); // searchRequest.setBooleanFilter(storage.PROPS.R_NIGHT_MODE, true); // for (RenderingRuleProperty customProp : storage.PROPS.getCustomRules()) { // if (customProp.isBoolean()) { // searchRequest.setBooleanFilter(customProp, false); // } else { // searchRequest.setStringFilter(customProp, ""); // } // } // searchRequest.setBooleanFilter(storage.PROPS.get("noPolygons"), true); boolean res = searchRequest.search(LINE_RULES); System.out.println("Result " + res); printResult(searchRequest, System.out); // } // System.out.println((System.nanoTime()- tm)/ (1e6f * count) ); } protected static void printAllRules(RenderingRulesStorage storage) { System.out.println("\n\n--------- POINTS ----- "); storage.printDebug(POINT_RULES, System.out); System.out.println("\n\n--------- POLYGON ----- "); storage.printDebug(POLYGON_RULES, System.out); System.out.println("\n\n--------- LINES ----- "); storage.printDebug(LINE_RULES, System.out); System.out.println("\n\n--------- ORDER ----- "); storage.printDebug(ORDER_RULES, System.out); System.out.println("\n\n--------- TEXT ----- "); storage.printDebug(TEXT_RULES, System.out); } private static void printResult(RenderingRuleSearchRequest searchRequest, PrintStream out) { if(searchRequest.isFound()){ out.print(" Found : "); for (RenderingRuleProperty rp : searchRequest.getProperties()) { if(rp.isOutputProperty() && searchRequest.isSpecified(rp)){ out.print(" " + rp.getAttrName() + "= "); if(rp.isString()){ out.print("\"" + searchRequest.getStringPropertyValue(rp) + "\""); } else if(rp.isFloat()){ out.print(searchRequest.getFloatPropertyValue(rp)); } else if(rp.isColor()){ out.print(searchRequest.getColorStringPropertyValue(rp)); } else if(rp.isIntParse()){ out.print(searchRequest.getIntPropertyValue(rp)); } } } } else { out.println("Not found"); } } }