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