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

Last change on this file since 5903 was 5681, checked in by bastiK, 11 years ago

some simple refactoring

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