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 = getAffectedDataSet().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                    getAffectedDataSet().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            newPrimitives.stream().forEach(p -> p.setModified(true));
104        } else { // redo
105            // When redoing this command, we have to add the same objects, otherwise
106            // a subsequent command (e.g. MoveCommand) cannot be redone.
107            for (OsmPrimitive osm : createdPrimitives) {
108                getLayer().data.addPrimitive(osm);
109            }
110            primitivesToSelect = createdPrimitivesToSelect;
111        }
112
113        getLayer().data.setSelected(primitivesToSelect);
114        return true;
115    }
116
117    @Override public void undoCommand() {
118        DataSet ds = getAffectedDataSet();
119
120        if (createdPrimitives == null) {
121            createdPrimitives = new ArrayList<>(data.size());
122            createdPrimitivesToSelect = new ArrayList<>(toSelect.size());
123
124            for (PrimitiveData pd : data) {
125                OsmPrimitive p = ds.getPrimitiveById(pd);
126                createdPrimitives.add(p);
127                if (toSelect.contains(pd)) {
128                    createdPrimitivesToSelect.add(p);
129                }
130            }
131            createdPrimitives = PurgeCommand.topoSort(createdPrimitives);
132
133            for (PrimitiveData p : data) {
134                ds.removePrimitive(p);
135            }
136            data = null;
137            toSelect = null;
138
139        } else {
140            for (OsmPrimitive osm : createdPrimitives) {
141                ds.removePrimitive(osm);
142            }
143        }
144    }
145
146    @Override
147    public String getDescriptionText() {
148        int size = data != null ? data.size() : createdPrimitives.size();
149        return trn("Added {0} object", "Added {0} objects", size, size);
150    }
151
152    @Override
153    public Icon getDescriptionIcon() {
154        return null;
155    }
156
157    @Override
158    public void fillModifiedData(Collection<OsmPrimitive> modified, Collection<OsmPrimitive> deleted,
159            Collection<OsmPrimitive> added) {
160        // Does nothing because we don't want to create OsmPrimitives.
161    }
162
163    @Override
164    public Collection<? extends OsmPrimitive> getParticipatingPrimitives() {
165        if (createdPrimitives != null)
166            return createdPrimitives;
167
168        Collection<OsmPrimitive> prims = new HashSet<>();
169        for (PrimitiveData d : data) {
170            OsmPrimitive osm = getAffectedDataSet().getPrimitiveById(d);
171            if (osm == null)
172                throw new RuntimeException();
173            prims.add(osm);
174        }
175        return prims;
176    }
177
178    @Override
179    public int hashCode() {
180        return Objects.hash(super.hashCode(), data, toSelect, createdPrimitives, createdPrimitivesToSelect);
181    }
182
183    @Override
184    public boolean equals(Object obj) {
185        if (this == obj) return true;
186        if (obj == null || getClass() != obj.getClass()) return false;
187        if (!super.equals(obj)) return false;
188        AddPrimitivesCommand that = (AddPrimitivesCommand) obj;
189        return Objects.equals(data, that.data) &&
190                Objects.equals(toSelect, that.toSelect) &&
191                Objects.equals(createdPrimitives, that.createdPrimitives) &&
192                Objects.equals(createdPrimitivesToSelect, that.createdPrimitivesToSelect);
193    }
194}