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}