001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.command;
003
004import static org.openstreetmap.josm.tools.I18n.trn;
005
006import java.util.ArrayList;
007import java.util.Collection;
008import java.util.HashSet;
009import java.util.List;
010import java.util.Objects;
011
012import javax.swing.Icon;
013
014import org.openstreetmap.josm.data.osm.DataSet;
015import org.openstreetmap.josm.data.osm.Node;
016import org.openstreetmap.josm.data.osm.NodeData;
017import org.openstreetmap.josm.data.osm.OsmPrimitive;
018import org.openstreetmap.josm.data.osm.PrimitiveData;
019import org.openstreetmap.josm.gui.layer.OsmDataLayer;
020import org.openstreetmap.josm.tools.CheckParameterUtil;
021import org.openstreetmap.josm.tools.JosmRuntimeException;
022
023/**
024 * Add primitives to a data layer.
025 * @since 2305
026 */
027public class AddPrimitivesCommand extends Command {
028
029    private List<PrimitiveData> data = new ArrayList<>();
030    private Collection<PrimitiveData> toSelect = new ArrayList<>();
031
032    // only filled on undo
033    private List<OsmPrimitive> createdPrimitives;
034    private Collection<OsmPrimitive> createdPrimitivesToSelect;
035
036    /**
037     * Constructs a new {@code AddPrimitivesCommand} to add data to the current edit layer.
038     * @param data The OSM primitives data to add. Must not be {@code null}
039     */
040    public AddPrimitivesCommand(List<PrimitiveData> data) {
041        this(data, data);
042    }
043
044    /**
045     * Constructs a new {@code AddPrimitivesCommand} to add data to the current edit layer.
046     * @param data The OSM primitives to add. Must not be {@code null}
047     * @param toSelect The OSM primitives to select at the end. Can be {@code null}
048     * @since 5953
049     */
050    public AddPrimitivesCommand(List<PrimitiveData> data, List<PrimitiveData> toSelect) {
051        init(data, toSelect);
052    }
053
054    /**
055     * Constructs a new {@code AddPrimitivesCommand} to add data to the given layer.
056     * @param data The OSM primitives data to add. Must not be {@code null}
057     * @param toSelect The OSM primitives to select at the end. Can be {@code null}
058     * @param layer The target data layer. Must not be {@code null}
059     */
060    public AddPrimitivesCommand(List<PrimitiveData> data, List<PrimitiveData> toSelect, OsmDataLayer layer) {
061        super(layer);
062        init(data, toSelect);
063    }
064
065    private void init(List<PrimitiveData> data, List<PrimitiveData> toSelect) {
066        CheckParameterUtil.ensureParameterNotNull(data, "data");
067        this.data.addAll(data);
068        if (toSelect != null) {
069            this.toSelect.addAll(toSelect);
070        }
071    }
072
073    @Override
074    public boolean executeCommand() {
075        Collection<OsmPrimitive> primitivesToSelect;
076        if (createdPrimitives == null) { // first time execution
077            List<OsmPrimitive> newPrimitives = new ArrayList<>(data.size());
078            primitivesToSelect = new ArrayList<>(toSelect.size());
079
080            for (PrimitiveData pd : data) {
081                OsmPrimitive primitive = getAffectedDataSet().getPrimitiveById(pd);
082                boolean created = primitive == null;
083                if (created) {
084                    primitive = pd.getType().newInstance(pd.getUniqueId(), true);
085                }
086                if (pd instanceof NodeData) { // Load nodes immediately because they can't be added to dataset without coordinates
087                    primitive.load(pd);
088                }
089                if (created) {
090                    getAffectedDataSet().addPrimitive(primitive);
091                }
092                newPrimitives.add(primitive);
093                if (toSelect.contains(pd)) {
094                    primitivesToSelect.add(primitive);
095                }
096            }
097
098            // Then load ways and relations
099            for (int i = 0; i < newPrimitives.size(); i++) {
100                if (!(newPrimitives.get(i) instanceof Node)) {
101                    newPrimitives.get(i).load(data.get(i));
102                }
103            }
104            newPrimitives.stream().forEach(p -> p.setModified(true));
105        } else { // redo
106            // When redoing this command, we have to add the same objects, otherwise
107            // a subsequent command (e.g. MoveCommand) cannot be redone.
108            for (OsmPrimitive osm : createdPrimitives) {
109                getAffectedDataSet().addPrimitive(osm);
110            }
111            primitivesToSelect = createdPrimitivesToSelect;
112        }
113
114        getAffectedDataSet().setSelected(primitivesToSelect);
115        return true;
116    }
117
118    @Override public void undoCommand() {
119        DataSet ds = getAffectedDataSet();
120
121        if (createdPrimitives == null) {
122            createdPrimitives = new ArrayList<>(data.size());
123            createdPrimitivesToSelect = new ArrayList<>(toSelect.size());
124
125            for (PrimitiveData pd : data) {
126                OsmPrimitive p = ds.getPrimitiveById(pd);
127                createdPrimitives.add(p);
128                if (toSelect.contains(pd)) {
129                    createdPrimitivesToSelect.add(p);
130                }
131            }
132            createdPrimitives = PurgeCommand.topoSort(createdPrimitives);
133
134            for (PrimitiveData p : data) {
135                ds.removePrimitive(p);
136            }
137            data = null;
138            toSelect = null;
139
140        } else {
141            for (OsmPrimitive osm : createdPrimitives) {
142                ds.removePrimitive(osm);
143            }
144        }
145    }
146
147    @Override
148    public String getDescriptionText() {
149        int size = data != null ? data.size() : createdPrimitives.size();
150        return trn("Added {0} object", "Added {0} objects", size, size);
151    }
152
153    @Override
154    public Icon getDescriptionIcon() {
155        return null;
156    }
157
158    @Override
159    public void fillModifiedData(Collection<OsmPrimitive> modified, Collection<OsmPrimitive> deleted,
160            Collection<OsmPrimitive> added) {
161        // Does nothing because we don't want to create OsmPrimitives.
162    }
163
164    @Override
165    public Collection<? extends OsmPrimitive> getParticipatingPrimitives() {
166        if (createdPrimitives != null)
167            return createdPrimitives;
168
169        Collection<OsmPrimitive> prims = new HashSet<>();
170        for (PrimitiveData d : data) {
171            OsmPrimitive osm = getAffectedDataSet().getPrimitiveById(d);
172            if (osm == null)
173                throw new JosmRuntimeException("No primitive found for " + d);
174            prims.add(osm);
175        }
176        return prims;
177    }
178
179    @Override
180    public int hashCode() {
181        return Objects.hash(super.hashCode(), data, toSelect, createdPrimitives, createdPrimitivesToSelect);
182    }
183
184    @Override
185    public boolean equals(Object obj) {
186        if (this == obj) return true;
187        if (obj == null || getClass() != obj.getClass()) return false;
188        if (!super.equals(obj)) return false;
189        AddPrimitivesCommand that = (AddPrimitivesCommand) obj;
190        return Objects.equals(data, that.data) &&
191               Objects.equals(toSelect, that.toSelect) &&
192               Objects.equals(createdPrimitives, that.createdPrimitives) &&
193               Objects.equals(createdPrimitivesToSelect, that.createdPrimitivesToSelect);
194    }
195}