source: josm/trunk/src/org/openstreetmap/josm/command/AddPrimitivesCommand.java@ 17534

Last change on this file since 17534 was 17240, checked in by GerdP, 4 years ago

fix #14620: DataIntegrityProblemException: Deleted node referenced while using conflation
The plugin shows an error in the core class AddPrimitivesCommand.

  • AddPrimitivesCommand undo crashes with existing data with status "deleted" Patch by Tyndare, modified
  • add stability for primitives with the same unique id but different types, like a way and a node with the same id.
  • add unit tests for both
  • Property svn:eol-style set to native
File size: 6.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.List;
9import java.util.Objects;
10import java.util.Optional;
11import java.util.stream.Collectors;
12
13import javax.swing.Icon;
14
15import org.openstreetmap.josm.data.osm.DataSet;
16import org.openstreetmap.josm.data.osm.Node;
17import org.openstreetmap.josm.data.osm.NodeData;
18import org.openstreetmap.josm.data.osm.OsmPrimitive;
19import org.openstreetmap.josm.data.osm.PrimitiveData;
20import org.openstreetmap.josm.tools.CheckParameterUtil;
21
22/**
23 * Add primitives to a data layer.
24 * @since 2305
25 */
26public class AddPrimitivesCommand extends Command {
27
28 private List<PrimitiveData> data;
29 private Collection<PrimitiveData> toSelect;
30 private List<PrimitiveData> preExistingData;
31
32 // only filled on undo
33 private List<OsmPrimitive> createdPrimitives;
34
35 /**
36 * Constructs a new {@code AddPrimitivesCommand} to add data to the given data set.
37 * @param data The OSM primitives data to add. Must not be {@code null}
38 * @param toSelect The OSM primitives to select at the end. Can be {@code null}
39 * @param ds The target data set. Must not be {@code null}
40 * @since 12718
41 */
42 public AddPrimitivesCommand(List<PrimitiveData> data, List<PrimitiveData> toSelect, DataSet ds) {
43 super(ds);
44 init(data, toSelect);
45 }
46
47 /**
48 * Constructs a new {@code AddPrimitivesCommand} to add data to the given data set.
49 * @param data The OSM primitives data to add and select. Must not be {@code null}
50 * @param ds The target data set. Must not be {@code null}
51 * @since 12726
52 */
53 public AddPrimitivesCommand(List<PrimitiveData> data, DataSet ds) {
54 this(data, data, ds);
55 }
56
57 private void init(List<PrimitiveData> data, List<PrimitiveData> toSelect) {
58 CheckParameterUtil.ensureParameterNotNull(data, "data");
59 this.data = new ArrayList<>(data);
60 if (toSelect == data) {
61 this.toSelect = this.data;
62 } else if (toSelect != null) {
63 this.toSelect = new ArrayList<>(toSelect);
64 }
65 }
66
67 @Override
68 public boolean executeCommand() {
69 DataSet ds = getAffectedDataSet();
70 if (createdPrimitives == null) { // first time execution
71 List<OsmPrimitive> newPrimitives = new ArrayList<>(data.size());
72 preExistingData = new ArrayList<>();
73
74 for (PrimitiveData pd : data) {
75 OsmPrimitive primitive = ds.getPrimitiveById(pd);
76 boolean created = primitive == null;
77 if (primitive == null) {
78 primitive = pd.getType().newInstance(pd.getUniqueId(), true);
79 } else {
80 preExistingData.add(primitive.save());
81 }
82 if (pd instanceof NodeData) { // Load nodes immediately because they can't be added to dataset without coordinates
83 primitive.load(pd);
84 }
85 if (created) {
86 ds.addPrimitive(primitive);
87 }
88 newPrimitives.add(primitive);
89 }
90
91 // Then load ways and relations
92 for (int i = 0; i < newPrimitives.size(); i++) {
93 if (!(newPrimitives.get(i) instanceof Node)) {
94 newPrimitives.get(i).load(data.get(i));
95 }
96 }
97 newPrimitives.forEach(p -> p.setModified(true));
98 } else { // redo
99 // When redoing this command, we have to add the same objects, otherwise
100 // a subsequent command (e.g. MoveCommand) cannot be redone.
101 for (OsmPrimitive osm : createdPrimitives) {
102 if (preExistingData.stream().anyMatch(pd -> pd.getPrimitiveId().equals(osm.getPrimitiveId()))) {
103 Optional<PrimitiveData> o = data.stream()
104 .filter(pd -> pd.getPrimitiveId().equals(osm.getPrimitiveId())).findAny();
105 if (o.isPresent()) {
106 osm.load(o.get());
107 }
108 } else {
109 ds.addPrimitive(osm);
110 }
111 }
112 }
113 if (toSelect != null) {
114 ds.setSelected(toSelect.stream().map(ds::getPrimitiveById).collect(Collectors.toList()));
115 }
116 return true;
117 }
118
119 @Override public void undoCommand() {
120 DataSet ds = getAffectedDataSet();
121 if (createdPrimitives == null) {
122 createdPrimitives = new ArrayList<>(data.size());
123 for (PrimitiveData pd : data) {
124 OsmPrimitive p = ds.getPrimitiveById(pd);
125 createdPrimitives.add(p);
126 }
127 createdPrimitives = PurgeCommand.topoSort(createdPrimitives);
128 }
129 // reversed order, see #14620
130 for (int i = createdPrimitives.size() - 1; i >= 0; i--) {
131 OsmPrimitive osm = createdPrimitives.get(i);
132 Optional<PrimitiveData> previous = preExistingData.stream()
133 .filter(pd -> pd.getPrimitiveId().equals(osm.getPrimitiveId())).findAny();
134 if (previous.isPresent()) {
135 osm.load(previous.get());
136 } else {
137 ds.removePrimitive(osm);
138 }
139 }
140 }
141
142 @Override
143 public String getDescriptionText() {
144 int size = data != null ? data.size() : createdPrimitives.size();
145 return trn("Added {0} object", "Added {0} objects", size, size);
146 }
147
148 @Override
149 public Icon getDescriptionIcon() {
150 return null;
151 }
152
153 @Override
154 public void fillModifiedData(Collection<OsmPrimitive> modified, Collection<OsmPrimitive> deleted,
155 Collection<OsmPrimitive> added) {
156 // Does nothing because we don't want to create OsmPrimitives.
157 }
158
159 @Override
160 public Collection<? extends OsmPrimitive> getParticipatingPrimitives() {
161 if (createdPrimitives != null)
162 return createdPrimitives;
163
164 return data.stream()
165 .map(d -> Objects.requireNonNull(getAffectedDataSet().getPrimitiveById(d), () -> "No primitive found for " + d))
166 .collect(Collectors.toSet());
167 }
168
169 @Override
170 public int hashCode() {
171 return Objects.hash(super.hashCode(), data, toSelect, preExistingData, createdPrimitives);
172 }
173
174 @Override
175 public boolean equals(Object obj) {
176 if (this == obj) return true;
177 if (obj == null || getClass() != obj.getClass()) return false;
178 if (!super.equals(obj)) return false;
179 AddPrimitivesCommand that = (AddPrimitivesCommand) obj;
180 return Objects.equals(data, that.data) &&
181 Objects.equals(toSelect, that.toSelect) &&
182 Objects.equals(preExistingData, that.preExistingData) &&
183 Objects.equals(createdPrimitives, that.createdPrimitives);
184 }
185}
Note: See TracBrowser for help on using the repository browser.