001    package org.openstreetmap.josm.data.osm.visitor.paint.relations;
002    
003    import java.util.ArrayList;
004    import java.util.Collection;
005    import java.util.HashMap;
006    import java.util.Iterator;
007    import java.util.List;
008    import java.util.Map;
009    
010    import org.openstreetmap.josm.Main;
011    import org.openstreetmap.josm.data.SelectionChangedListener;
012    import org.openstreetmap.josm.data.osm.DataSet;
013    import org.openstreetmap.josm.data.osm.Node;
014    import org.openstreetmap.josm.data.osm.OsmPrimitive;
015    import org.openstreetmap.josm.data.osm.Relation;
016    import org.openstreetmap.josm.data.osm.Way;
017    import org.openstreetmap.josm.data.osm.event.AbstractDatasetChangedEvent;
018    import org.openstreetmap.josm.data.osm.event.DataChangedEvent;
019    import org.openstreetmap.josm.data.osm.event.DataSetListener;
020    import org.openstreetmap.josm.data.osm.event.NodeMovedEvent;
021    import org.openstreetmap.josm.data.osm.event.PrimitivesAddedEvent;
022    import org.openstreetmap.josm.data.osm.event.PrimitivesRemovedEvent;
023    import org.openstreetmap.josm.data.osm.event.RelationMembersChangedEvent;
024    import org.openstreetmap.josm.data.osm.event.TagsChangedEvent;
025    import org.openstreetmap.josm.data.osm.event.WayNodesChangedEvent;
026    import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon.PolyData;
027    import org.openstreetmap.josm.data.projection.Projection;
028    import org.openstreetmap.josm.data.projection.ProjectionChangeListener;
029    import org.openstreetmap.josm.gui.MapView;
030    import org.openstreetmap.josm.gui.MapView.LayerChangeListener;
031    import org.openstreetmap.josm.gui.NavigatableComponent;
032    import org.openstreetmap.josm.gui.layer.Layer;
033    import org.openstreetmap.josm.gui.layer.OsmDataLayer;
034    
035    /*
036     * A memory cache for Multipolygon objects.
037     * 
038     */
039    public class MultipolygonCache implements DataSetListener, LayerChangeListener, ProjectionChangeListener, SelectionChangedListener {
040    
041        private static final MultipolygonCache instance = new MultipolygonCache(); 
042        
043        private final Map<NavigatableComponent, Map<DataSet, Map<Relation, Multipolygon>>> cache;
044        
045        private final Collection<PolyData> selectedPolyData;
046        
047        private MultipolygonCache() {
048            this.cache = new HashMap<NavigatableComponent, Map<DataSet, Map<Relation, Multipolygon>>>();
049            this.selectedPolyData = new ArrayList<Multipolygon.PolyData>();
050            Main.addProjectionChangeListener(this);
051            DataSet.addSelectionListener(this);
052            MapView.addLayerChangeListener(this);
053        }
054    
055        public static final MultipolygonCache getInstance() {
056            return instance;
057        }
058    
059        public final Multipolygon get(NavigatableComponent nc, Relation r) {
060            return get(nc, r, false);
061        }
062    
063        public final Multipolygon get(NavigatableComponent nc, Relation r, boolean forceRefresh) {
064            Multipolygon multipolygon = null;
065            if (nc != null && r != null) {
066                Map<DataSet, Map<Relation, Multipolygon>> map1 = cache.get(nc);
067                if (map1 == null) {
068                    cache.put(nc, map1 = new HashMap<DataSet, Map<Relation, Multipolygon>>());
069                }
070                Map<Relation, Multipolygon> map2 = map1.get(r.getDataSet());
071                if (map2 == null) {
072                    map1.put(r.getDataSet(), map2 = new HashMap<Relation, Multipolygon>());
073                }
074                multipolygon = map2.get(r);
075                if (multipolygon == null || forceRefresh) {
076                    map2.put(r, multipolygon = new Multipolygon(r));
077                    for (PolyData pd : multipolygon.getCombinedPolygons()) {
078                        if (pd.selected) {
079                            selectedPolyData.add(pd);
080                        }
081                    }
082                }
083            }
084            return multipolygon;
085        }
086        
087        public final void clear(NavigatableComponent nc) {
088            Map<DataSet, Map<Relation, Multipolygon>> map = cache.remove(nc);
089            if (map != null) {
090                map.clear();
091                map = null;
092            }
093        }
094    
095        public final void clear(DataSet ds) {
096            for (Map<DataSet, Map<Relation, Multipolygon>> map1 : cache.values()) {
097                Map<Relation, Multipolygon> map2 = map1.remove(ds);
098                if (map2 != null) {
099                    map2.clear();
100                    map2 = null;
101                }
102            }
103        }
104    
105        public final void clear() {
106            cache.clear();
107        }
108        
109        private final Collection<Map<Relation, Multipolygon>> getMapsFor(DataSet ds) {
110            List<Map<Relation, Multipolygon>> result = new ArrayList<Map<Relation, Multipolygon>>();
111            for (Map<DataSet, Map<Relation, Multipolygon>> map : cache.values()) {
112                Map<Relation, Multipolygon> map2 = map.get(ds);
113                if (map2 != null) {
114                    result.add(map2);
115                }
116            }
117            return result;
118        }
119        
120        private static final boolean isMultipolygon(OsmPrimitive p) {
121            return p instanceof Relation && ((Relation) p).isMultipolygon();
122        }
123        
124        private final void updateMultipolygonsReferringTo(AbstractDatasetChangedEvent event) {
125            updateMultipolygonsReferringTo(event, event.getPrimitives(), event.getDataset());
126        }
127    
128        private final void updateMultipolygonsReferringTo(
129                final AbstractDatasetChangedEvent event, Collection<? extends OsmPrimitive> primitives, DataSet ds) {
130            updateMultipolygonsReferringTo(event, primitives, ds, null);
131        }
132        
133        private final Collection<Map<Relation, Multipolygon>> updateMultipolygonsReferringTo(
134                AbstractDatasetChangedEvent event, Collection<? extends OsmPrimitive> primitives, 
135                DataSet ds, Collection<Map<Relation, Multipolygon>> initialMaps) {
136            Collection<Map<Relation, Multipolygon>> maps = initialMaps;
137            if (primitives != null) {
138                for (OsmPrimitive p : primitives) {
139                    if (isMultipolygon(p)) {
140                        if (maps == null) {
141                            maps = getMapsFor(ds);
142                        }
143                        processEvent(event, (Relation) p, maps);
144                        
145                    } else if (p instanceof Way && p.getDataSet() != null) {
146                        for (OsmPrimitive ref : p.getReferrers()) {
147                            if (isMultipolygon(ref)) {
148                                if (maps == null) {
149                                    maps = getMapsFor(ds);
150                                }
151                                processEvent(event, (Relation) ref, maps);
152                            }
153                        }
154                    } else if (p instanceof Node && p.getDataSet() != null) {
155                        maps = updateMultipolygonsReferringTo(event, p.getReferrers(), ds, maps);
156                    }
157                }
158            }
159            return maps;
160        }
161        
162        private final void processEvent(AbstractDatasetChangedEvent event, Relation r, Collection<Map<Relation, Multipolygon>> maps) {
163            if (event instanceof NodeMovedEvent || event instanceof WayNodesChangedEvent) {
164                dispatchEvent(event, r, maps);
165            } else if (event instanceof PrimitivesRemovedEvent) {
166                if (event.getPrimitives().contains(r)) {
167                    removeMultipolygonFrom(r, maps);
168                }
169            } else {
170                // Default (non-optimal) action: remove multipolygon from cache 
171                removeMultipolygonFrom(r, maps);
172            }
173        }
174        
175        private final void dispatchEvent(AbstractDatasetChangedEvent event, Relation r, Collection<Map<Relation, Multipolygon>> maps) {
176            for (Map<Relation, Multipolygon> map : maps) {
177                Multipolygon m = map.get(r);
178                if (m != null) {
179                    for (PolyData pd : m.getCombinedPolygons()) {
180                        if (event instanceof NodeMovedEvent) {
181                            pd.nodeMoved((NodeMovedEvent) event);
182                        } else if (event instanceof WayNodesChangedEvent) {
183                            pd.wayNodesChanged((WayNodesChangedEvent)event);
184                        }
185                    }
186                }
187            }
188        }
189        
190        private final void removeMultipolygonFrom(Relation r, Collection<Map<Relation, Multipolygon>> maps) {
191            for (Map<Relation, Multipolygon> map : maps) {
192                map.remove(r);
193            }
194        }
195    
196        @Override
197        public void primitivesAdded(PrimitivesAddedEvent event) {
198            // Do nothing
199        }
200    
201        @Override
202        public void primitivesRemoved(PrimitivesRemovedEvent event) {
203            updateMultipolygonsReferringTo(event);
204        }
205    
206        @Override
207        public void tagsChanged(TagsChangedEvent event) {
208            // Do nothing
209        }
210    
211        @Override
212        public void nodeMoved(NodeMovedEvent event) {
213            updateMultipolygonsReferringTo(event);
214        }
215    
216        @Override
217        public void wayNodesChanged(WayNodesChangedEvent event) {
218            updateMultipolygonsReferringTo(event);
219        }
220    
221        @Override
222        public void relationMembersChanged(RelationMembersChangedEvent event) {
223            updateMultipolygonsReferringTo(event);
224        }
225    
226        @Override
227        public void otherDatasetChange(AbstractDatasetChangedEvent event) {
228            // Do nothing
229        }
230    
231        @Override
232        public void dataChanged(DataChangedEvent event) {
233            // Do not call updateMultipolygonsReferringTo as getPrimitives() 
234            // can return all the data set primitives for this event
235            Collection<Map<Relation, Multipolygon>> maps = null;
236            for (OsmPrimitive p : event.getPrimitives()) {
237                if (isMultipolygon(p)) {
238                    if (maps == null) {
239                        maps = getMapsFor(event.getDataset());
240                    }
241                    for (Map<Relation, Multipolygon> map : maps) {
242                        // DataChangedEvent is sent after downloading incomplete members (see #7131),
243                        // without having received RelationMembersChangedEvent or PrimitivesAddedEvent
244                        // OR when undoing a move of a large number of nodes (see #7195),
245                        // without having received NodeMovedEvent
246                        // This ensures concerned multipolygons will be correctly redrawn
247                        map.remove(p);
248                    }
249                }
250            }
251        }
252    
253        @Override
254        public void activeLayerChange(Layer oldLayer, Layer newLayer) {
255            // Do nothing
256        }
257    
258        @Override
259        public void layerAdded(Layer newLayer) {
260            // Do nothing
261        }
262    
263        @Override
264        public void layerRemoved(Layer oldLayer) {
265            if (oldLayer instanceof OsmDataLayer) {
266                clear(((OsmDataLayer) oldLayer).data);
267            }
268        }
269    
270        @Override
271        public void projectionChanged(Projection oldValue, Projection newValue) {
272            clear();
273        }
274    
275        @Override
276        public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) {
277            
278            for (Iterator<PolyData> it = selectedPolyData.iterator(); it.hasNext();) {
279                it.next().selected = false;
280                it.remove();
281            }
282            
283            DataSet ds = null;
284            Collection<Map<Relation, Multipolygon>> maps = null;
285            for (OsmPrimitive p : newSelection) {
286                if (p instanceof Way && p.getDataSet() != null) {
287                    if (ds == null) {
288                        ds = p.getDataSet();
289                    }
290                    for (OsmPrimitive ref : p.getReferrers()) {
291                        if (isMultipolygon(ref)) {
292                            if (maps == null) {
293                                maps = getMapsFor(ds);
294                            }
295                            for (Map<Relation, Multipolygon> map : maps) {
296                                Multipolygon multipolygon = map.get(ref);
297                                if (multipolygon != null) {
298                                    for (PolyData pd : multipolygon.getCombinedPolygons()) {
299                                        if (pd.getWayIds().contains(p.getUniqueId())) {
300                                            pd.selected = true;
301                                            selectedPolyData.add(pd);
302                                        }
303                                    }
304                                }
305                            }
306                        }
307                    }
308                }
309            }
310        }
311    }