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