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 }