001 // License: GPL. For details, see LICENSE file. 002 package org.openstreetmap.josm.gui.mappaint; 003 004 import java.util.ArrayList; 005 import java.util.Arrays; 006 import java.util.Collection; 007 import java.util.Iterator; 008 import java.util.List; 009 010 import org.openstreetmap.josm.data.osm.Storage; 011 import org.openstreetmap.josm.tools.Pair; 012 import 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 */ 021 public class StyleCache { 022 /* list of boundaries for the scale ranges */ 023 ArrayList<Double> bd; 024 /* styles for each scale range */ 025 ArrayList<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 /** 145 * ASCII-art explanation: 146 * 147 * data[i] 148 * --|-------|---------|-- 149 * bd[i-1] bd[i] bd[i+1] 150 * 151 * (--------] 152 * lower upper 153 */ 154 private void putImpl(StyleList sl, double lower, double upper) { 155 int i=0; 156 while (bd.get(i) < lower) { 157 ++i; 158 } 159 if (bd.get(i) == lower) { 160 if (upper > bd.get(i+1)) 161 throw new AssertionError("the new range must be within a single subrange"); 162 if (data.get(i) != null) 163 throw new AssertionError("the new range must be within a subrange that has no data"); 164 165 // --|-------|--------|-- 166 // i-1 i i+1 167 // (--------] 168 if (bd.get(i+1) == upper) { 169 data.set(i, sl); 170 } 171 // --|-------|--------|-- 172 // i-1 i i+1 173 // (-----] 174 else { 175 bd.add(i+1, upper); 176 data.add(i, sl); 177 } 178 return; 179 } else { 180 if (bd.get(i) < upper) 181 throw new AssertionError("the new range must be within a single subrange"); 182 if (data.get(i-1) != null) 183 throw new AssertionError(); 184 185 // --|-------|--------|-- 186 // i-1 i i+1 187 // (--] or 188 // (----] 189 bd.add(i, lower); 190 data.add(i, sl); 191 192 // --|--|----|--------|-- 193 // i-1 i i+1 i+2 194 // (--] 195 if (bd.get(i+1) > upper) { 196 bd.add(i+1, upper); 197 data.add(i+1, null); 198 } 199 return; 200 } 201 } 202 203 public void consistencyTest() { 204 if (bd.size() < 2) throw new AssertionError(); 205 if (data.size() < 1) throw new AssertionError(); 206 if (bd.size() != data.size() + 1) throw new AssertionError(); 207 if (bd.get(0) != 0) throw new AssertionError(); 208 if (bd.get(bd.size() - 1) != Double.POSITIVE_INFINITY) throw new AssertionError(); 209 for (int i=0; i<data.size() - 1; ++i) { 210 if (bd.get(i) >= bd.get(i + 1)) throw new AssertionError(); 211 } 212 } 213 214 /** 215 * Like String.intern() (reduce memory consumption). 216 * StyleCache must not be changed after it has 217 * been added to the intern pool. 218 */ 219 public StyleCache intern() { 220 return internPool.putUnique(this); 221 } 222 223 @Override 224 public boolean equals(Object obj) { 225 if (obj == null || getClass() != obj.getClass()) 226 return false; 227 final StyleCache other = (StyleCache) obj; 228 return bd.equals(other.bd) && data.equals(other.data); 229 } 230 231 @Override 232 public int hashCode() { 233 int hash = 7; 234 hash = 23 * hash + bd.hashCode(); 235 hash = 23 * hash + data.hashCode(); 236 return hash; 237 } 238 239 @Override 240 public String toString() { 241 return "SC{" + bd + ' ' + data + '}'; 242 } 243 }