Ignore:
Timestamp:
2020-04-19T14:54:56+02:00 (4 years ago)
Author:
simon04
Message:

Extract inner class MapCSSRuleIndex

Location:
trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss
Files:
1 added
2 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/MapCSSStyleIndex.java

    r16172 r16357  
    1717import org.openstreetmap.josm.data.osm.IWay;
    1818import org.openstreetmap.josm.data.osm.OsmUtils;
    19 import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSStyleSource.MapCSSRuleIndex;
    2019import org.openstreetmap.josm.tools.JosmRuntimeException;
    2120import org.openstreetmap.josm.tools.Logging;
  • trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/MapCSSStyleSource.java

    r16000 r16357  
    1515import java.nio.charset.StandardCharsets;
    1616import java.util.ArrayList;
    17 import java.util.BitSet;
    18 import java.util.Collections;
    19 import java.util.HashMap;
    2017import java.util.HashSet;
    2118import java.util.Iterator;
    2219import java.util.List;
    2320import java.util.Locale;
    24 import java.util.Map;
    2521import java.util.Map.Entry;
    26 import java.util.NoSuchElementException;
    27 import java.util.Optional;
    2822import java.util.Set;
    2923import java.util.concurrent.locks.ReadWriteLock;
     
    3529import org.openstreetmap.josm.data.Version;
    3630import org.openstreetmap.josm.data.osm.IPrimitive;
    37 import org.openstreetmap.josm.data.osm.KeyValueVisitor;
    3831import org.openstreetmap.josm.data.osm.Node;
    39 import org.openstreetmap.josm.data.osm.Tagged;
    4032import org.openstreetmap.josm.data.preferences.sources.SourceEntry;
    4133import org.openstreetmap.josm.gui.mappaint.Cascade;
     
    4840import org.openstreetmap.josm.gui.mappaint.StyleSettingFactory;
    4941import org.openstreetmap.josm.gui.mappaint.StyleSource;
    50 import org.openstreetmap.josm.gui.mappaint.mapcss.ConditionFactory.KeyCondition;
    51 import org.openstreetmap.josm.gui.mappaint.mapcss.ConditionFactory.KeyMatchType;
    52 import org.openstreetmap.josm.gui.mappaint.mapcss.ConditionFactory.KeyValueCondition;
    53 import org.openstreetmap.josm.gui.mappaint.mapcss.ConditionFactory.SimpleKeyValueCondition;
    5442import org.openstreetmap.josm.gui.mappaint.mapcss.Selector.GeneralSelector;
    5543import org.openstreetmap.josm.gui.mappaint.mapcss.parsergen.MapCSSParser;
     
    131119            SUPPORTED_KEYS.add(lt.prefix + StyleKeys.REAL_WIDTH);
    132120            SUPPORTED_KEYS.add(lt.prefix + StyleKeys.WIDTH);
    133         }
    134     }
    135 
    136     /**
    137      * A collection of {@link MapCSSRule}s, that are indexed by tag key and value.
    138      *
    139      * Speeds up the process of finding all rules that match a certain primitive.
    140      *
    141      * Rules with a {@link SimpleKeyValueCondition} [key=value] or rules that require a specific key to be set are
    142      * indexed. Now you only need to loop the tags of a primitive to retrieve the possibly matching rules.
    143      *
    144      * To use this index, you need to {@link #add(MapCSSRule)} all rules to it. You then need to call
    145      * {@link #initIndex()}. Afterwards, you can use {@link #getRuleCandidates(IPrimitive)} to get an iterator over
    146      * all rules that might be applied to that primitive.
    147      */
    148     public static class MapCSSRuleIndex {
    149         /**
    150          * This is an iterator over all rules that are marked as possible in the bitset.
    151          *
    152          * @author Michael Zangl
    153          */
    154         private final class RuleCandidatesIterator implements Iterator<MapCSSRule>, KeyValueVisitor {
    155             private final BitSet ruleCandidates;
    156             private int next;
    157 
    158             private RuleCandidatesIterator(BitSet ruleCandidates) {
    159                 this.ruleCandidates = ruleCandidates;
    160             }
    161 
    162             @Override
    163             public boolean hasNext() {
    164                 return next >= 0 && next < rules.size();
    165             }
    166 
    167             @Override
    168             public MapCSSRule next() {
    169                 if (!hasNext())
    170                     throw new NoSuchElementException();
    171                 MapCSSRule rule = rules.get(next);
    172                 next = ruleCandidates.nextSetBit(next + 1);
    173                 return rule;
    174             }
    175 
    176             @Override
    177             public void remove() {
    178                 throw new UnsupportedOperationException();
    179             }
    180 
    181             @Override
    182             public void visitKeyValue(Tagged p, String key, String value) {
    183                 MapCSSKeyRules v = index.get(key);
    184                 if (v != null) {
    185                     BitSet rs = v.get(value);
    186                     ruleCandidates.or(rs);
    187                 }
    188             }
    189 
    190             /**
    191              * Call this before using the iterator.
    192              */
    193             public void prepare() {
    194                 next = ruleCandidates.nextSetBit(0);
    195             }
    196         }
    197 
    198         /**
    199          * This is a map of all rules that are only applied if the primitive has a given key (and possibly value)
    200          *
    201          * @author Michael Zangl
    202          */
    203         private static final class MapCSSKeyRules {
    204             /**
    205              * The indexes of rules that might be applied if this tag is present and the value has no special handling.
    206              */
    207             BitSet generalRules = new BitSet();
    208 
    209             /**
    210              * A map that sores the indexes of rules that might be applied if the key=value pair is present on this
    211              * primitive. This includes all key=* rules.
    212              */
    213             Map<String, BitSet> specialRules = new HashMap<>();
    214 
    215             public void addForKey(int ruleIndex) {
    216                 generalRules.set(ruleIndex);
    217                 for (BitSet r : specialRules.values()) {
    218                     r.set(ruleIndex);
    219                 }
    220             }
    221 
    222             public void addForKeyAndValue(String value, int ruleIndex) {
    223                 BitSet forValue = specialRules.get(value);
    224                 if (forValue == null) {
    225                     forValue = new BitSet();
    226                     forValue.or(generalRules);
    227                     specialRules.put(value.intern(), forValue);
    228                 }
    229                 forValue.set(ruleIndex);
    230             }
    231 
    232             public BitSet get(String value) {
    233                 BitSet forValue = specialRules.get(value);
    234                 if (forValue != null) return forValue; else return generalRules;
    235             }
    236         }
    237 
    238         /**
    239          * All rules this index is for. Once this index is built, this list is sorted.
    240          */
    241         private final List<MapCSSRule> rules = new ArrayList<>();
    242         /**
    243          * All rules that only apply when the given key is present.
    244          */
    245         private final Map<String, MapCSSKeyRules> index = new HashMap<>();
    246         /**
    247          * Rules that do not require any key to be present. Only the index in the {@link #rules} array is stored.
    248          */
    249         private final BitSet remaining = new BitSet();
    250 
    251         /**
    252          * Add a rule to this index. This needs to be called before {@link #initIndex()} is called.
    253          * @param rule The rule to add.
    254          */
    255         public void add(MapCSSRule rule) {
    256             rules.add(rule);
    257         }
    258 
    259         /**
    260          * Initialize the index.
    261          * <p>
    262          * You must own the write lock of STYLE_SOURCE_LOCK when calling this method.
    263          */
    264         public void initIndex() {
    265             Collections.sort(rules);
    266             for (int ruleIndex = 0; ruleIndex < rules.size(); ruleIndex++) {
    267                 MapCSSRule r = rules.get(ruleIndex);
    268                 for (Selector selector : r.selectors) {
    269                     Selector selRightmost = selector;
    270                     while (selRightmost instanceof Selector.ChildOrParentSelector) {
    271                         selRightmost = ((Selector.ChildOrParentSelector) selRightmost).right;
    272                     }
    273                     final List<Condition> conditions = selRightmost.getConditions();
    274                     if (conditions == null || conditions.isEmpty()) {
    275                         remaining.set(ruleIndex);
    276                         continue;
    277                     }
    278                     Optional<SimpleKeyValueCondition> lastCondition = Utils.filteredCollection(conditions, SimpleKeyValueCondition.class)
    279                             .stream()
    280                             .reduce((first, last) -> last);
    281                     if (lastCondition.isPresent()) {
    282                         getEntryInIndex(lastCondition.get().k).addForKeyAndValue(lastCondition.get().v, ruleIndex);
    283                     } else {
    284                         String key = findAnyRequiredKey(conditions);
    285                         if (key != null) {
    286                             getEntryInIndex(key).addForKey(ruleIndex);
    287                         } else {
    288                             remaining.set(ruleIndex);
    289                         }
    290                     }
    291                 }
    292             }
    293         }
    294 
    295         /**
    296          * Search for any key that condition might depend on.
    297          *
    298          * @param conds The conditions to search through.
    299          * @return An arbitrary key this rule depends on or <code>null</code> if there is no such key.
    300          */
    301         private static String findAnyRequiredKey(List<Condition> conds) {
    302             String key = null;
    303             for (Condition c : conds) {
    304                 if (c instanceof KeyCondition) {
    305                     KeyCondition keyCondition = (KeyCondition) c;
    306                     if (!keyCondition.negateResult && conditionRequiresKeyPresence(keyCondition.matchType)) {
    307                         key = keyCondition.label;
    308                     }
    309                 } else if (c instanceof KeyValueCondition) {
    310                     KeyValueCondition keyValueCondition = (KeyValueCondition) c;
    311                     if (keyValueCondition.requiresExactKeyMatch()) {
    312                         key = keyValueCondition.k;
    313                     }
    314                 }
    315             }
    316             return key;
    317         }
    318 
    319         private static boolean conditionRequiresKeyPresence(KeyMatchType matchType) {
    320             return matchType != KeyMatchType.REGEX;
    321         }
    322 
    323         private MapCSSKeyRules getEntryInIndex(String key) {
    324             MapCSSKeyRules rulesWithMatchingKey = index.get(key);
    325             if (rulesWithMatchingKey == null) {
    326                 rulesWithMatchingKey = new MapCSSKeyRules();
    327                 index.put(key.intern(), rulesWithMatchingKey);
    328             }
    329             return rulesWithMatchingKey;
    330         }
    331 
    332         /**
    333          * Get a subset of all rules that might match the primitive. Rules not included in the result are guaranteed to
    334          * not match this primitive.
    335          * <p>
    336          * You must have a read lock of STYLE_SOURCE_LOCK when calling this method.
    337          *
    338          * @param osm the primitive to match
    339          * @return An iterator over possible rules in the right order.
    340          * @since 13810 (signature)
    341          */
    342         public Iterator<MapCSSRule> getRuleCandidates(IPrimitive osm) {
    343             final BitSet ruleCandidates = new BitSet(rules.size());
    344             ruleCandidates.or(remaining);
    345 
    346             final RuleCandidatesIterator candidatesIterator = new RuleCandidatesIterator(ruleCandidates);
    347             osm.visitKeys(candidatesIterator);
    348             candidatesIterator.prepare();
    349             return candidatesIterator;
    350         }
    351 
    352         /**
    353          * Clear the index.
    354          * <p>
    355          * You must own the write lock STYLE_SOURCE_LOCK when calling this method.
    356          */
    357         public void clear() {
    358             rules.clear();
    359             index.clear();
    360             remaining.clear();
    361121        }
    362122    }
Note: See TracChangeset for help on using the changeset viewer.