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    }