source: josm/trunk/src/org/openstreetmap/josm/command/PurgeCommand.java @ 5241

Revision 4918, 9.5 KB checked in by simon04, 3 months ago (diff)

fix #7370 - Refactor Command.getDescription

  • Property svn:eol-style set to native
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.command;
3
4import static org.openstreetmap.josm.tools.I18n.trn;
5
6import java.util.ArrayList;
7import java.util.Collection;
8import java.util.HashMap;
9import java.util.HashSet;
10import java.util.Iterator;
11import java.util.List;
12import java.util.Map;
13import java.util.Set;
14
15import javax.swing.Icon;
16
17import org.openstreetmap.josm.data.osm.DataSet;
18import org.openstreetmap.josm.data.osm.Hash;
19import org.openstreetmap.josm.data.osm.Node;
20import org.openstreetmap.josm.data.osm.NodeData;
21import org.openstreetmap.josm.data.osm.OsmPrimitive;
22import org.openstreetmap.josm.data.osm.PrimitiveData;
23import org.openstreetmap.josm.data.osm.PrimitiveId;
24import org.openstreetmap.josm.data.osm.Relation;
25import org.openstreetmap.josm.data.osm.RelationData;
26import org.openstreetmap.josm.data.osm.Storage;
27import org.openstreetmap.josm.data.osm.Way;
28import org.openstreetmap.josm.data.osm.WayData;
29import org.openstreetmap.josm.gui.layer.OsmDataLayer;
30import org.openstreetmap.josm.tools.ImageProvider;
31
32/**
33 * Command, to purge a list of primitives.
34 */
35public class PurgeCommand extends Command {
36    protected List<OsmPrimitive> toPurge;
37    protected Storage<PrimitiveData> makeIncompleteData;
38
39    protected Map<PrimitiveId, PrimitiveData> makeIncompleteData_byPrimId;
40
41    final protected DataSet ds;
42
43    /**
44     * This command relies on a number of consistency conditions:
45     *  - makeIncomplete must be a subset of toPurge.
46     *  - Each primitive, that is in toPurge but not in makeIncomplete, must
47     *      have all its referrers in toPurge.
48     *  - Each element of makeIncomplete must not be new and must have only
49     *      referrers that are either a relation or included in toPurge.
50     */
51    public PurgeCommand(OsmDataLayer layer, Collection<OsmPrimitive> toPurge, Collection<OsmPrimitive> makeIncomplete) {
52        super(layer);
53        this.ds = layer.data;
54        /**
55         * The topological sort is to avoid missing way nodes and missing
56         * relation members when adding primitives back to the dataset on undo.
57         *
58         * The same should hold for normal execution, but at time of writing
59         * there seem to be no such consistency checks when removing primitives.
60         * (It is done in a save manner, anyway.)
61         */
62        this.toPurge = topoSort(toPurge);
63        saveIncomplete(makeIncomplete);
64    }
65
66    protected void saveIncomplete(Collection<OsmPrimitive> makeIncomplete) {
67        makeIncompleteData = new Storage<PrimitiveData>(new Hash<PrimitiveData,PrimitiveData>() {
68            public int getHashCode(PrimitiveData k) {
69                return (int) k.getUniqueId();
70            }
71
72            public boolean equals(PrimitiveData k, PrimitiveData t) {
73                return k.getUniqueId() == t.getUniqueId() && k.getType() == t.getType();
74            }
75        });
76        makeIncompleteData_byPrimId = makeIncompleteData.foreignKey(new Hash<PrimitiveId, PrimitiveData>() {
77            public int getHashCode(PrimitiveId k) {
78                return (int) k.getUniqueId();
79            }
80
81            public boolean equals(PrimitiveId k, PrimitiveData t) {
82                return k.getUniqueId() == t.getUniqueId() && k.getType() == t.getType();
83            }
84        });
85
86        for (OsmPrimitive osm : makeIncomplete) {
87            makeIncompleteData.add(osm.save());
88        }
89    }
90
91    @Override
92    public boolean executeCommand() {
93        ds.beginUpdate();
94        try {
95            /**
96             * Loop from back to front to keep referential integrity.
97             */
98            for (int i=toPurge.size()-1; i>=0; --i) {
99                OsmPrimitive osm = toPurge.get(i);
100                if (makeIncompleteData_byPrimId.containsKey(osm)) {
101                    // we could simply set the incomplete flag
102                    // but that would not free memory in case the
103                    // user clears undo/redo buffer after purge
104                    PrimitiveData empty;
105                    switch(osm.getType()) {
106                    case NODE: empty = new NodeData(); break;
107                    case WAY: empty = new WayData(); break;
108                    case RELATION: empty = new RelationData(); break;
109                    default: throw new AssertionError();
110                    }
111                    empty.setId(osm.getUniqueId());
112                    empty.setIncomplete(true);
113                    osm.load(empty);
114                } else {
115                    ds.removePrimitive(osm);
116                }
117            }
118        } finally {
119            ds.endUpdate();
120        }
121        return true;
122    }
123
124    @Override
125    public void undoCommand() {
126        if (ds == null)
127            return;
128
129        for (OsmPrimitive osm : toPurge) {
130            PrimitiveData data = makeIncompleteData_byPrimId.get(osm);
131            if (data != null) {
132                if (ds.getPrimitiveById(osm) != osm)
133                    throw new AssertionError(String.format("Primitive %s has been made incomplete when purging, but it cannot be found on undo.", osm));
134                osm.load(data);
135            } else {
136                if (ds.getPrimitiveById(osm) != null)
137                    throw new AssertionError(String.format("Primitive %s was removed when purging, but is still there on undo", osm));
138                ds.addPrimitive(osm);
139            }
140        }
141    }
142
143    /**
144     * Sorts a collection of primitives such that for each object
145     * its referrers come later in the sorted collection.
146     */
147    public static List<OsmPrimitive> topoSort(Collection<OsmPrimitive> sel) {
148        Set<OsmPrimitive> in = new HashSet<OsmPrimitive>(sel);
149
150        List<OsmPrimitive> out = new ArrayList<OsmPrimitive>(in.size());
151
152        /**
153         *  First add nodes that have no way referrer.
154         */
155        outer:
156            for (Iterator<OsmPrimitive> it = in.iterator(); it.hasNext();) {
157                OsmPrimitive u = it.next();
158                if (u instanceof Node) {
159                    Node n = (Node) u;
160                    for (OsmPrimitive ref : n.getReferrers()) {
161                        if (ref instanceof Way && in.contains(ref)) {
162                            continue outer;
163                        }
164                    }
165                    it.remove();
166                    out.add(n);
167                }
168            }
169
170        /**
171         * Then add all ways, each preceded by its (remaining) nodes.
172         */
173        top:
174            while (!in.isEmpty()) {
175                for (Iterator<OsmPrimitive> it = in.iterator(); it.hasNext();) {
176                    OsmPrimitive u = it.next();
177                    if (u instanceof Way) {
178                        Way w = (Way) u;
179                        it.remove();
180                        for (Node n : w.getNodes()) {
181                            if (in.contains(n)) {
182                                in.remove(n);
183                                out.add(n);
184                            }
185                        }
186                        out.add(w);
187                        continue top;
188                    }
189                }
190                break; // no more ways left
191            }
192
193        /**
194         * Rest are relations. Do topological sorting on a DAG where each
195         * arrow points from child to parent. (Because it is faster to
196         * loop over getReferrers() than getMembers().)
197         */
198        Set<Relation> inR = (Set) in;
199        Set<Relation> childlessR = new HashSet<Relation>();
200        List<Relation> outR = new ArrayList<Relation>(inR.size());
201
202        HashMap<Relation, Integer> numChilds = new HashMap<Relation, Integer>();
203
204        // calculate initial number of childs
205        for (Relation r : inR) {
206            numChilds.put(r, 0);
207        }
208        for (Relation r : inR) {
209            for (OsmPrimitive parent : r.getReferrers()) {
210                if (!(parent instanceof Relation))
211                    throw new AssertionError();
212                Integer i = numChilds.get(parent);
213                if (i != null) {
214                    numChilds.put((Relation)parent, i+1);
215                }
216            }
217        }
218        for (Relation r : inR) {
219            if (numChilds.get(r).equals(0)) {
220                childlessR.add(r);
221            }
222        }
223
224        while (!childlessR.isEmpty()) {
225            // Identify one childless Relation and
226            // let it virtually die. This makes other
227            // relations childless.
228            Iterator<Relation> it  = childlessR.iterator();
229            Relation next = it.next();
230            it.remove();
231            outR.add(next);
232
233            for (OsmPrimitive parentPrim : next.getReferrers()) {
234                Relation parent = (Relation) parentPrim;
235                Integer i = numChilds.get(parent);
236                if (i != null) {
237                    numChilds.put(parent, i-1);
238                    if (i-1 == 0) {
239                        childlessR.add(parent);
240                    }
241                }
242            }
243        }
244
245        if (outR.size() != inR.size())
246            throw new AssertionError("topo sort algorithm failed");
247
248        out.addAll(outR);
249
250        return out;
251    }
252
253    @Override
254    public String getDescriptionText() {
255        return trn("Purged {0} object", "Purged {0} objects", toPurge.size(), toPurge.size());
256    }
257
258    @Override
259    public Icon getDescriptionIcon() {
260        return ImageProvider.get("data", "purge");
261    }
262
263    @Override
264    public Collection<? extends OsmPrimitive> getParticipatingPrimitives() {
265        return toPurge;
266    }
267
268    @Override
269    public void fillModifiedData(Collection<OsmPrimitive> modified, Collection<OsmPrimitive> deleted, Collection<OsmPrimitive> added) {
270    }
271}
Note: See TracBrowser for help on using the repository browser.