001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.mappaint; 003 004import java.util.ArrayList; 005import java.util.Arrays; 006import java.util.Collection; 007import java.util.Iterator; 008import java.util.List; 009import java.util.Objects; 010 011import org.openstreetmap.josm.data.osm.Storage; 012import org.openstreetmap.josm.tools.Pair; 013 014/** 015 * Caches styles for a single primitive. 016 * Splits the range of possible scale values (0 < scale < +Infinity) into multiple 017 * subranges, for each scale range it keeps a list of styles. 018 * Immutable class, equals & hashCode is required (the same for StyleList, ElemStyle 019 * and its subclasses). 020 */ 021public final class StyleCache { 022 /* list of boundaries for the scale ranges */ 023 private final List<Double> bd; 024 /* styles for each scale range */ 025 private final List<StyleList> data; 026 027 private static final Storage<StyleCache> internPool = new Storage<>(); // TODO: clean up the intern pool from time to time (after purge or layer removal) 028 029 public static final StyleCache EMPTY_STYLECACHE = (new StyleCache()).intern(); 030 031 private StyleCache() { 032 bd = new ArrayList<>(); 033 bd.add(0.0); 034 bd.add(Double.POSITIVE_INFINITY); 035 data = new ArrayList<>(); 036 data.add(null); 037 } 038 039 private StyleCache(StyleCache s) { 040 bd = new ArrayList<>(s.bd); 041 data = new ArrayList<>(s.data); 042 } 043 044 /** 045 * List of Styles, immutable 046 */ 047 public static class StyleList implements Iterable<ElemStyle> { 048 private List<ElemStyle> lst; 049 050 /** 051 * Constructs a new {@code StyleList}. 052 */ 053 public StyleList() { 054 lst = new ArrayList<>(); 055 } 056 057 public StyleList(ElemStyle... init) { 058 lst = new ArrayList<>(Arrays.asList(init)); 059 } 060 061 public StyleList(Collection<ElemStyle> sl) { 062 lst = new ArrayList<>(sl); 063 } 064 065 public StyleList(StyleList sl, ElemStyle s) { 066 lst = new ArrayList<>(sl.lst); 067 lst.add(s); 068 } 069 070 @Override 071 public Iterator<ElemStyle> iterator() { 072 return lst.iterator(); 073 } 074 075 public boolean isEmpty() { 076 return lst.isEmpty(); 077 } 078 079 public int size() { 080 return lst.size(); 081 } 082 083 @Override 084 public String toString() { 085 return lst.toString(); 086 } 087 088 @Override 089 public boolean equals(Object obj) { 090 if (obj == null || getClass() != obj.getClass()) 091 return false; 092 final StyleList other = (StyleList) obj; 093 return Objects.equals(lst, other.lst); 094 } 095 096 @Override 097 public int hashCode() { 098 return lst.hashCode(); 099 } 100 } 101 102 /** 103 * looks up styles for a certain scale value 104 */ 105 public StyleList get(double scale) { 106 if (scale <= 0) 107 throw new IllegalArgumentException(); 108 for (int i=0; i<data.size(); ++i) { 109 if (bd.get(i) < scale && scale <= bd.get(i+1)) { 110 return data.get(i); 111 } 112 } 113 throw new AssertionError(); 114 } 115 116 /** 117 * looks up styles for a certain scale value and additionally returns 118 * the scale range for the returned styles 119 */ 120 public Pair<StyleList, Range> getWithRange(double scale) { 121 if (scale <= 0) 122 throw new IllegalArgumentException(); 123 for (int i=0; i<data.size(); ++i) { 124 if (bd.get(i) < scale && scale <= bd.get(i+1)) { 125 return new Pair<>(data.get(i), new Range(bd.get(i), bd.get(i+1))); 126 } 127 } 128 throw new AssertionError(); 129 } 130 131 public StyleCache put(StyleList sl, Range r) { 132 return put(sl, r.getLower(), r.getUpper()); 133 } 134 135 /** 136 * add a new styles to the cache. this is only possible, if 137 * for this scale range, there is nothing in the cache yet. 138 */ 139 public StyleCache put(StyleList sl, double lower, double upper) { 140 StyleCache s = new StyleCache(this); 141 s.putImpl(sl, lower, upper); 142 s.consistencyTest(); 143 return s.intern(); 144 } 145 146 // this exception type is for debugging #8997 and can later be replaced 147 // by AssertionError 148 public static class RangeViolatedError extends Error { 149 } 150 151 /** 152 * ASCII-art explanation: 153 * 154 * data[i] 155 * --|-------|---------|-- 156 * bd[i-1] bd[i] bd[i+1] 157 * 158 * (--------] 159 * lower upper 160 */ 161 private void putImpl(StyleList sl, double lower, double upper) { 162 int i=0; 163 while (bd.get(i) < lower) { 164 ++i; 165 } 166 if (bd.get(i) == lower) { 167 if (upper > bd.get(i+1)) 168 throw new RangeViolatedError(); 169 if (data.get(i) != null) 170 throw new AssertionError("the new range must be within a subrange that has no data"); 171 172 // --|-------|--------|-- 173 // i-1 i i+1 174 // (--------] 175 if (bd.get(i+1) == upper) { 176 data.set(i, sl); 177 } 178 // --|-------|--------|-- 179 // i-1 i i+1 180 // (-----] 181 else { 182 bd.add(i+1, upper); 183 data.add(i, sl); 184 } 185 return; 186 } else { 187 if (bd.get(i) < upper) 188 throw new AssertionError("the new range must be within a single subrange"); 189 if (data.get(i-1) != null) 190 throw new AssertionError(); 191 192 // --|-------|--------|-- 193 // i-1 i i+1 194 // (--] or 195 // (----] 196 bd.add(i, lower); 197 data.add(i, sl); 198 199 // --|--|----|--------|-- 200 // i-1 i i+1 i+2 201 // (--] 202 if (bd.get(i+1) > upper) { 203 bd.add(i+1, upper); 204 data.add(i+1, null); 205 } 206 return; 207 } 208 } 209 210 public void consistencyTest() { 211 if (bd.size() < 2) throw new AssertionError(); 212 if (data.size() < 1) throw new AssertionError(); 213 if (bd.size() != data.size() + 1) throw new AssertionError(); 214 if (bd.get(0) != 0) throw new AssertionError(); 215 if (bd.get(bd.size() - 1) != Double.POSITIVE_INFINITY) throw new AssertionError(); 216 for (int i=0; i<data.size() - 1; ++i) { 217 if (bd.get(i) >= bd.get(i + 1)) throw new AssertionError(); 218 } 219 } 220 221 /** 222 * Like String.intern() (reduce memory consumption). 223 * StyleCache must not be changed after it has 224 * been added to the intern pool. 225 */ 226 public StyleCache intern() { 227 return internPool.putUnique(this); 228 } 229 230 @Override 231 public boolean equals(Object obj) { 232 if (obj == null || getClass() != obj.getClass()) 233 return false; 234 final StyleCache other = (StyleCache) obj; 235 return bd.equals(other.bd) && data.equals(other.data); 236 } 237 238 @Override 239 public int hashCode() { 240 int hash = 7; 241 hash = 23 * hash + bd.hashCode(); 242 hash = 23 * hash + data.hashCode(); 243 return hash; 244 } 245 246 @Override 247 public String toString() { 248 return "SC{" + bd + ' ' + data + '}'; 249 } 250}