001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.mappaint;
003
004import java.util.Arrays;
005import java.util.Optional;
006
007import org.openstreetmap.josm.data.osm.Storage;
008import org.openstreetmap.josm.tools.Pair;
009
010/**
011 * Caches styles for a single primitive.
012 * <p>
013 * This object is immutable.
014 */
015public final class StyleCache {
016
017    // TODO: clean up the intern pool from time to time (after purge or layer removal)
018    private static final Storage<StyleCache> internPool = new Storage<>();
019
020    /**
021     * An empty style cache entry
022     */
023    public static final StyleCache EMPTY_STYLECACHE = (new StyleCache()).intern();
024
025    private static final int PLAIN = 0;
026    private static final int SELECTED = 1;
027
028    @SuppressWarnings("unchecked")
029    private final DividedScale<StyleElementList>[] states = new DividedScale[2];
030
031    private StyleCache(StyleCache sc) {
032        states[0] = sc.states[0];
033        states[1] = sc.states[1];
034    }
035
036    private StyleCache() {
037    }
038
039    /**
040     * Creates a new copy of this style cache with a new entry added.
041     * @param o The style to cache.
042     * @param r The range the style is for.
043     * @param selected The style list we should use (selected/unselected)
044     * @return The new object.
045     */
046    public StyleCache put(StyleElementList o, Range r, boolean selected) {
047        StyleCache s = new StyleCache(this);
048
049        int idx = getIndex(selected);
050        s.states[idx] = Optional.ofNullable(s.states[idx]).orElseGet(DividedScale::new).put(o, r);
051        return s.intern();
052    }
053
054    /**
055     * Get the style for a specific style. Returns the range as well.
056     * @param scale The current scale
057     * @param selected true to get the state for a selected element,
058     * @return The style and the range it is valid for.
059     */
060    public Pair<StyleElementList, Range> getWithRange(double scale, boolean selected) {
061        int idx = getIndex(selected);
062        if (states[idx] == null) {
063            return Pair.create(null, Range.ZERO_TO_INFINITY);
064        }
065        return states[idx].getWithRange(scale);
066    }
067
068    private static int getIndex(boolean selected) {
069        return selected ? SELECTED : PLAIN;
070    }
071
072    @Override
073    public String toString() {
074        return "StyleCache{PLAIN: " + this.states[PLAIN] + " SELECTED: " + this.states[SELECTED] + "}";
075    }
076
077    @Override
078    public int hashCode() {
079        return Arrays.deepHashCode(this.states);
080    }
081
082    @Override
083    public boolean equals(Object obj) {
084        if (obj == null) {
085            return false;
086        }
087        if (getClass() != obj.getClass()) {
088            return false;
089        }
090        final StyleCache other = (StyleCache) obj;
091        return Arrays.deepEquals(this.states, other.states);
092    }
093
094    /**
095     * Like String.intern() (reduce memory consumption).
096     * StyleCache must not be changed after it has been added to the intern pool.
097     * @return style cache
098     */
099    private StyleCache intern() {
100        return internPool.putUnique(this);
101    }
102
103    /**
104     * Clears the style cache. This should only be used for testing.
105     * It may be removed some day and replaced by a WeakReference implementation that automatically forgets old entries.
106     */
107    static void clearStyleCachePool() {
108        internPool.clear();
109    }
110
111    /**
112     * Get the size of the intern pool. Only for tests!
113     * @return size of the intern pool
114     */
115    public static int getInternPoolSize() {
116        return internPool.size();
117    }
118}