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