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