001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.mappaint.mapcss; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.text.MessageFormat; 007import java.util.Collections; 008import java.util.Iterator; 009import java.util.Map; 010import java.util.Set; 011import java.util.stream.Collectors; 012import java.util.stream.Stream; 013 014import org.openstreetmap.josm.data.osm.INode; 015import org.openstreetmap.josm.data.osm.IPrimitive; 016import org.openstreetmap.josm.data.osm.IRelation; 017import org.openstreetmap.josm.data.osm.IWay; 018import org.openstreetmap.josm.data.osm.OsmUtils; 019import org.openstreetmap.josm.tools.JosmRuntimeException; 020import org.openstreetmap.josm.tools.Logging; 021 022/** 023 * Store indexes of {@link MapCSSRule}s using {@link MapCSSRuleIndex} differentiated by {@linkplain Selector#getBase() base} 024 */ 025public final class MapCSSStyleIndex { 026 027 /** 028 * Rules for nodes 029 */ 030 final MapCSSRuleIndex nodeRules = new MapCSSRuleIndex(); 031 /** 032 * Rules for ways without tag area=no 033 */ 034 final MapCSSRuleIndex wayRules = new MapCSSRuleIndex(); 035 /** 036 * Rules for ways with tag area=no 037 */ 038 final MapCSSRuleIndex wayNoAreaRules = new MapCSSRuleIndex(); 039 /** 040 * Rules for relations that are not multipolygon relations 041 */ 042 final MapCSSRuleIndex relationRules = new MapCSSRuleIndex(); 043 /** 044 * Rules for multipolygon relations 045 */ 046 final MapCSSRuleIndex multipolygonRules = new MapCSSRuleIndex(); 047 /** 048 * rules to apply canvas properties 049 */ 050 final MapCSSRuleIndex canvasRules = new MapCSSRuleIndex(); 051 052 /** 053 * Clear the index. 054 * <p> 055 * You must own the write lock STYLE_SOURCE_LOCK when calling this method. 056 */ 057 public void clear() { 058 nodeRules.clear(); 059 wayRules.clear(); 060 wayNoAreaRules.clear(); 061 relationRules.clear(); 062 multipolygonRules.clear(); 063 canvasRules.clear(); 064 } 065 066 /** 067 * Builds and initializes the index. 068 * <p> 069 * You must own the write lock of STYLE_SOURCE_LOCK when calling this method. 070 * @param ruleStream the rules to index 071 */ 072 public void buildIndex(Stream<MapCSSRule> ruleStream) { 073 clear(); 074 // optimization: filter rules for different primitive types 075 ruleStream.forEach(rule -> { 076 final Map<String, MapCSSRule> selectorsByBase; 077 final Set<String> bases = rule.selectors.stream().map(Selector::getBase).collect(Collectors.toSet()); 078 if (bases.size() == 1) { 079 // reuse rule 080 selectorsByBase = Collections.singletonMap(bases.iterator().next(), rule); 081 } else { 082 selectorsByBase = rule.selectors.stream() 083 .collect(Collectors.groupingBy(Selector::getBase, 084 Collectors.collectingAndThen(Collectors.toList(), selectors -> new MapCSSRule(selectors, rule.declaration)))); 085 } 086 selectorsByBase.forEach((base, optRule) -> { 087 switch (base) { 088 case Selector.BASE_NODE: 089 nodeRules.add(optRule); 090 break; 091 case Selector.BASE_WAY: 092 wayNoAreaRules.add(optRule); 093 wayRules.add(optRule); 094 break; 095 case Selector.BASE_AREA: 096 wayRules.add(optRule); 097 multipolygonRules.add(optRule); 098 break; 099 case Selector.BASE_RELATION: 100 relationRules.add(optRule); 101 multipolygonRules.add(optRule); 102 break; 103 case Selector.BASE_ANY: 104 nodeRules.add(optRule); 105 wayRules.add(optRule); 106 wayNoAreaRules.add(optRule); 107 relationRules.add(optRule); 108 multipolygonRules.add(optRule); 109 break; 110 case Selector.BASE_CANVAS: 111 canvasRules.add(optRule); 112 break; 113 case Selector.BASE_META: 114 case Selector.BASE_SETTING: 115 case Selector.BASE_SETTINGS: 116 break; 117 default: 118 final RuntimeException e = new JosmRuntimeException(MessageFormat.format("Unknown MapCSS base selector {0}", base)); 119 Logging.warn(tr("Failed to index validator rules. Error was: {0}", e.getMessage())); 120 Logging.error(e); 121 } 122 }); 123 }); 124 initIndex(); 125 } 126 127 private void initIndex() { 128 nodeRules.initIndex(); 129 wayRules.initIndex(); 130 wayNoAreaRules.initIndex(); 131 relationRules.initIndex(); 132 multipolygonRules.initIndex(); 133 canvasRules.initIndex(); 134 } 135 136 /** 137 * Get the index of rules for the given primitive. 138 * @param p the primitive 139 * @return index of rules for the given primitive 140 */ 141 public MapCSSRuleIndex get(IPrimitive p) { 142 if (p instanceof INode) { 143 return nodeRules; 144 } else if (p instanceof IWay) { 145 if (OsmUtils.isFalse(p.get("area"))) { 146 return wayNoAreaRules; 147 } else { 148 return wayRules; 149 } 150 } else if (p instanceof IRelation) { 151 if (((IRelation<?>) p).isMultipolygon()) { 152 return multipolygonRules; 153 } else if (p.hasKey("#canvas")) { 154 return canvasRules; 155 } else { 156 return relationRules; 157 } 158 } else { 159 throw new IllegalArgumentException("Unsupported type: " + p); 160 } 161 } 162 163 /** 164 * Get a subset of all rules that might match the primitive. Rules not included in the result are guaranteed to 165 * not match this primitive. 166 * <p> 167 * You must have a read lock of STYLE_SOURCE_LOCK when calling this method. 168 * 169 * @param osm the primitive to match 170 * @return An iterator over possible rules in the right order. 171 */ 172 public Iterator<MapCSSRule> getRuleCandidates(IPrimitive osm) { 173 return get(osm).getRuleCandidates(osm); 174 } 175 176 /** 177 * Check if this index is empty. 178 * @return true if this index is empty. 179 * @since 16784 180 */ 181 public boolean isEmpty() { 182 return nodeRules.isEmpty() && wayRules.isEmpty() && wayNoAreaRules.isEmpty() && relationRules.isEmpty() 183 && multipolygonRules.isEmpty() && canvasRules.isEmpty(); 184 } 185}