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