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}