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.stream().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}