source: josm/trunk/src/org/openstreetmap/josm/gui/mappaint/StyleCache.java@ 8575

Last change on this file since 8575 was 8575, checked in by bastiK, 9 years ago

see #11607 - more debugging output for error in StyleCache

  • Property svn:eol-style set to native
File size: 7.5 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.mappaint;
3
4import java.util.ArrayList;
5import java.util.Arrays;
6import java.util.Collection;
7import java.util.Iterator;
8import java.util.List;
9import java.util.Objects;
10
11import org.openstreetmap.josm.data.osm.Storage;
12import org.openstreetmap.josm.tools.Pair;
13
14/**
15 * Caches styles for a single primitive.
16 * Splits the range of possible scale values (0 < scale < +Infinity) into multiple
17 * subranges, for each scale range it keeps a list of styles.
18 * Immutable class, equals & hashCode is required (the same for StyleList, ElemStyle
19 * and its subclasses).
20 */
21public final class StyleCache {
22 /* list of boundaries for the scale ranges */
23 private final List<Double> bd;
24 /* styles for each scale range */
25 private final List<StyleList> data;
26
27 // TODO: clean up the intern pool from time to time (after purge or layer removal)
28 private static final Storage<StyleCache> internPool = new Storage<>();
29
30 public static final StyleCache EMPTY_STYLECACHE = (new StyleCache()).intern();
31
32 private StyleCache() {
33 bd = new ArrayList<>();
34 bd.add(0.0);
35 bd.add(Double.POSITIVE_INFINITY);
36 data = new ArrayList<>();
37 data.add(null);
38 }
39
40 private StyleCache(StyleCache s) {
41 bd = new ArrayList<>(s.bd);
42 data = new ArrayList<>(s.data);
43 }
44
45 /**
46 * List of Styles, immutable
47 */
48 public static class StyleList implements Iterable<ElemStyle> {
49 private List<ElemStyle> lst;
50
51 /**
52 * Constructs a new {@code StyleList}.
53 */
54 public StyleList() {
55 lst = new ArrayList<>();
56 }
57
58 public StyleList(ElemStyle... init) {
59 lst = new ArrayList<>(Arrays.asList(init));
60 }
61
62 public StyleList(Collection<ElemStyle> sl) {
63 lst = new ArrayList<>(sl);
64 }
65
66 public StyleList(StyleList sl, ElemStyle s) {
67 lst = new ArrayList<>(sl.lst);
68 lst.add(s);
69 }
70
71 @Override
72 public Iterator<ElemStyle> iterator() {
73 return lst.iterator();
74 }
75
76 public boolean isEmpty() {
77 return lst.isEmpty();
78 }
79
80 public int size() {
81 return lst.size();
82 }
83
84 @Override
85 public String toString() {
86 return lst.toString();
87 }
88
89 @Override
90 public boolean equals(Object obj) {
91 if (obj == null || getClass() != obj.getClass())
92 return false;
93 final StyleList other = (StyleList) obj;
94 return Objects.equals(lst, other.lst);
95 }
96
97 @Override
98 public int hashCode() {
99 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}
Note: See TracBrowser for help on using the repository browser.