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.List;
009import java.util.Objects;
010import java.util.Optional;
011import java.util.stream.Collectors;
012
013import javax.swing.Icon;
014
015import org.openstreetmap.josm.data.osm.DataSet;
016import org.openstreetmap.josm.data.osm.Node;
017import org.openstreetmap.josm.data.osm.NodeData;
018import org.openstreetmap.josm.data.osm.OsmPrimitive;
019import org.openstreetmap.josm.data.osm.PrimitiveData;
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;
029    private Collection<PrimitiveData> toSelect;
030    private List<PrimitiveData> preExistingData;
031
032    // only filled on undo
033    private List<OsmPrimitive> createdPrimitives;
034
035    /**
036     * Constructs a new {@code AddPrimitivesCommand} to add data to the given data set.
037     * @param data The OSM primitives data to add. Must not be {@code null}
038     * @param toSelect The OSM primitives to select at the end. Can be {@code null}
039     * @param ds The target data set. Must not be {@code null}
040     * @since 12718
041     */
042    public AddPrimitivesCommand(List<PrimitiveData> data, List<PrimitiveData> toSelect, DataSet ds) {
043        super(ds);
044        init(data, toSelect);
045    }
046
047    /**
048     * Constructs a new {@code AddPrimitivesCommand} to add data to the given data set.
049     * @param data The OSM primitives data to add and select. Must not be {@code null}
050     * @param ds The target data set. Must not be {@code null}
051     * @since 12726
052     */
053    public AddPrimitivesCommand(List<PrimitiveData> data, DataSet ds) {
054        this(data, data, ds);
055    }
056
057    private void init(List<PrimitiveData> data, List<PrimitiveData> toSelect) {
058        CheckParameterUtil.ensureParameterNotNull(data, "data");
059        this.data = new ArrayList<>(data);
060        if (toSelect == data) {
061            this.toSelect = this.data;
062        } else if (toSelect != null) {
063            this.toSelect = new ArrayList<>(toSelect);
064        }
065    }
066
067    @Override
068    public boolean executeCommand() {
069        DataSet ds = getAffectedDataSet();
070        if (createdPrimitives == null) { // first time execution
071            List<OsmPrimitive> newPrimitives = new ArrayList<>(data.size());
072            preExistingData = new ArrayList<>();
073
074            for (PrimitiveData pd : data) {
075                OsmPrimitive primitive = ds.getPrimitiveById(pd);
076                boolean created = primitive == null;
077                if (primitive == null) {
078                    primitive = pd.getType().newInstance(pd.getUniqueId(), true);
079                } else {
080                    preExistingData.add(primitive.save());
081                }
082                if (pd instanceof NodeData) { // Load nodes immediately because they can't be added to dataset without coordinates
083                    primitive.load(pd);
084                }
085                if (created) {
086                    ds.addPrimitive(primitive);
087                }
088                newPrimitives.add(primitive);
089            }
090
091            // Then load ways and relations
092            for (int i = 0; i < newPrimitives.size(); i++) {
093                if (!(newPrimitives.get(i) instanceof Node)) {
094                    newPrimitives.get(i).load(data.get(i));
095                }
096            }
097            newPrimitives.forEach(p -> p.setModified(true));
098        } else { // redo
099            // 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}