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

Last change on this file since 5674 was 5674, checked in by jttt, 11 years ago

Move IdHash to Storage class and rename to PrimitiveIdHash to make it easier for other classes to use Storage class

  • Property svn:eol-style set to native
File size: 9.1 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:
157 while (!in.isEmpty()) {
158 for (Iterator<OsmPrimitive> it = in.iterator(); it.hasNext();) {
159 OsmPrimitive u = it.next();
160 if (u instanceof Way) {
161 Way w = (Way) u;
162 it.remove();
163 for (Node n : w.getNodes()) {
164 if (in.contains(n)) {
165 in.remove(n);
166 out.add(n);
167 }
168 }
169 out.add(w);
170 continue top;
171 }
172 }
173 break; // no more ways left
174 }
175
176 /**
177 * Rest are relations. Do topological sorting on a DAG where each
178 * arrow points from child to parent. (Because it is faster to
179 * loop over getReferrers() than getMembers().)
180 */
181 Set<Relation> inR = (Set) in;
182 Set<Relation> childlessR = new HashSet<Relation>();
183 List<Relation> outR = new ArrayList<Relation>(inR.size());
184
185 HashMap<Relation, Integer> numChilds = new HashMap<Relation, Integer>();
186
187 // calculate initial number of childs
188 for (Relation r : inR) {
189 numChilds.put(r, 0);
190 }
191 for (Relation r : inR) {
192 for (OsmPrimitive parent : r.getReferrers()) {
193 if (!(parent instanceof Relation))
194 throw new AssertionError();
195 Integer i = numChilds.get(parent);
196 if (i != null) {
197 numChilds.put((Relation)parent, i+1);
198 }
199 }
200 }
201 for (Relation r : inR) {
202 if (numChilds.get(r).equals(0)) {
203 childlessR.add(r);
204 }
205 }
206
207 while (!childlessR.isEmpty()) {
208 // Identify one childless Relation and
209 // let it virtually die. This makes other
210 // relations childless.
211 Iterator<Relation> it = childlessR.iterator();
212 Relation next = it.next();
213 it.remove();
214 outR.add(next);
215
216 for (OsmPrimitive parentPrim : next.getReferrers()) {
217 Relation parent = (Relation) parentPrim;
218 Integer i = numChilds.get(parent);
219 if (i != null) {
220 numChilds.put(parent, i-1);
221 if (i-1 == 0) {
222 childlessR.add(parent);
223 }
224 }
225 }
226 }
227
228 if (outR.size() != inR.size())
229 throw new AssertionError("topo sort algorithm failed");
230
231 out.addAll(outR);
232
233 return out;
234 }
235
236 @Override
237 public String getDescriptionText() {
238 return trn("Purged {0} object", "Purged {0} objects", toPurge.size(), toPurge.size());
239 }
240
241 @Override
242 public Icon getDescriptionIcon() {
243 return ImageProvider.get("data", "purge");
244 }
245
246 @Override
247 public Collection<? extends OsmPrimitive> getParticipatingPrimitives() {
248 return toPurge;
249 }
250
251 @Override
252 public void fillModifiedData(Collection<OsmPrimitive> modified, Collection<OsmPrimitive> deleted, Collection<OsmPrimitive> added) {
253 }
254}
Note: See TracBrowser for help on using the repository browser.