Ticket #25021: #25021-Fixed-Sonar-issues-in-VectorDataSet.patch
| File #25021-Fixed-Sonar-issues-in-VectorDataSet.patch, 10.5 KB (added by , 2 days ago) |
|---|
-
src/org/openstreetmap/josm/data/vector/VectorDataSet.java
### Eclipse Workspace Patch 1.0 #P josm diff --git src/org/openstreetmap/josm/data/vector/VectorDataSet.java src/org/openstreetmap/josm/data/vector/VectorDataSet.java index 86d5383..7988be8 100644
252 252 253 253 @Override 254 254 public <T extends VectorPrimitive> Collection<T> getPrimitives(Predicate<? super VectorPrimitive> predicate) { 255 Collection<VectorPrimitive> primitives = tryRead(this.readWriteLock, () -> {255 final Collection<VectorPrimitive> primitives = tryRead(this.readWriteLock, () -> { 256 256 final Storage<MVTTile> dataStore = this.getBestZoomDataStore().orElse(null); 257 257 final Stream<VectorDataStore> dataStoreStream = dataStore != null ? dataStore.stream().map(MVTTile::getData) : Stream.empty(); 258 258 return Stream.concat(dataStoreStream, Stream.of(this.customDataStore)) … … 363 363 public Collection<VectorPrimitive> getAllSelected() { 364 364 return tryRead(this.readWriteLock, () -> { 365 365 final Storage<MVTTile> dataStore = this.getBestZoomDataStore().orElse(null); 366 Stream<VectorDataStore> dataStoreStream = dataStore != null ? dataStore.stream().map(MVTTile::getData) : Stream.empty(); 367 return Stream.concat(dataStoreStream, Stream.of(this.customDataStore)).map(VectorDataStore::getPrimitivesMap) 368 .flatMap(dataMap -> { 369 // Synchronize on dataMap to avoid concurrent modification errors 370 synchronized (dataMap) { 371 return this.currentSelectedPrimitives.stream().map(dataMap::get).filter(Objects::nonNull); 372 } 373 }).collect(Collectors.toList()); 366 final Stream<VectorDataStore> dataStoreStream = dataStore != null 367 ? dataStore.stream().map(MVTTile::getData) 368 : Stream.empty(); 369 370 return Stream.concat(dataStoreStream, Stream.ofNullable(this.customDataStore)) 371 .map(VectorDataStore::getPrimitivesMap) 372 .filter(Objects::nonNull) 373 .flatMap(dataMap -> { 374 final Map<PrimitiveId, VectorPrimitive> lockObject = dataMap; 375 synchronized (lockObject) { 376 final List<VectorPrimitive> selectedInMap = new ArrayList<>(); 377 for (PrimitiveId id : this.currentSelectedPrimitives) { 378 final VectorPrimitive primitive = lockObject.get(id); 379 if (primitive != null) { 380 selectedInMap.add(primitive); 381 } 382 } 383 return selectedInMap.stream(); 384 } 385 }) 386 .collect(Collectors.toList()); 374 387 }).orElseGet(Collections::emptyList); 375 388 } 376 389 … … 490 503 private boolean doSelectionChange(final Function<Set<VectorPrimitive>, 491 504 SelectionChangeEvent<VectorPrimitive, VectorNode, VectorWay, VectorRelation, VectorDataSet>> command) { 492 505 synchronized (this.selectionLock) { 493 SelectionChangeEvent<VectorPrimitive, VectorNode, VectorWay, VectorRelation, VectorDataSet> event =506 final SelectionChangeEvent<VectorPrimitive, VectorNode, VectorWay, VectorRelation, VectorDataSet> event = 494 507 command.apply(currentSelectedPrimitives.stream().map(this::getPrimitiveById).collect(Collectors.toSet())); 495 508 if (event.isNop()) { 496 509 return false; … … 635 648 * @param invisibleLayers The layer to not show 636 649 */ 637 650 public void setInvisibleLayers(Collection<String> invisibleLayers) { 638 String[] currentInvisibleLayers = invisibleLayers.stream().filter(Objects::nonNull).toArray(String[]::new);639 List<String> temporaryList = Arrays.asList(currentInvisibleLayers);651 final String[] currentInvisibleLayers = invisibleLayers.stream().filter(Objects::nonNull).toArray(String[]::new); 652 final List<String> temporaryList = Arrays.asList(currentInvisibleLayers); 640 653 this.dataStoreMap.values().stream().flatMap(Collection::stream).map(MVTTile::getData) 641 654 .forEach(dataStore -> dataStore.getAllPrimitives().parallelStream() 642 655 .forEach(primitive -> primitive.setVisible(!temporaryList.contains(primitive.getLayer())))); -
test/unit/org/openstreetmap/josm/data/vector/VectorDataSetTest.java
diff --git test/unit/org/openstreetmap/josm/data/vector/VectorDataSetTest.java test/unit/org/openstreetmap/josm/data/vector/VectorDataSetTest.java index 39f1b65..9b66b6b 100644
6 6 7 7 import java.nio.file.Paths; 8 8 import java.util.ArrayList; 9 import java.util.Arrays; 9 10 import java.util.Collection; 10 11 import java.util.Collections; 11 12 import java.util.HashSet; 13 import java.util.List; 14 import java.util.concurrent.CountDownLatch; 15 import java.util.concurrent.ExecutorService; 16 import java.util.concurrent.Executors; 17 import java.util.concurrent.Future; 18 import java.util.concurrent.TimeUnit; 19 import java.util.stream.Collectors; 12 20 13 21 import org.awaitility.Awaitility; 14 22 import org.awaitility.Durations; 15 23 import org.junit.jupiter.api.BeforeEach; 16 24 import org.junit.jupiter.api.RepeatedTest; 25 import org.junit.jupiter.api.Test; 17 26 import org.openstreetmap.josm.TestUtils; 27 import org.openstreetmap.josm.data.coor.LatLon; 18 28 import org.openstreetmap.josm.data.imagery.ImageryInfo; 19 29 import org.openstreetmap.josm.data.imagery.vectortile.mapbox.MVTTile; 20 30 import org.openstreetmap.josm.data.imagery.vectortile.mapbox.MapboxVectorCachedTileLoader; … … 73 83 throw new IllegalArgumentException("Tiles come with a {z}, {x}, and {y} component"); 74 84 } 75 85 final MapboxVectorTileSource tileSource = layer.getTileSource(); 76 MapboxVectorCachedTileLoader tileLoader = layer.getTileLoader();77 Collection<MVTTile> tilesCollection = new ArrayList<>();86 final MapboxVectorCachedTileLoader tileLoader = layer.getTileLoader(); 87 final Collection<MVTTile> tilesCollection = new ArrayList<>(); 78 88 for (int i = 0; i < tiles.length / 3; i++) { 79 89 final MVTTile tile = (MVTTile) layer.createTile(tileSource, tiles[3 * i + 1], tiles[3 * i + 2], tiles[3 * i]); 80 90 tileLoader.createTileLoaderJob(tile).submit(); … … 124 134 assertEquals(1, dataSet.getWays().size()); 125 135 assertEquals(0, dataSet.getRelations().size()); 126 136 } 137 138 139 /** 140 * Test that {@link VectorDataSet#getAllSelected()} returns the expected selected primitives 141 * and returns an empty collection once the selection is cleared. 142 */ 143 @Test 144 void testGetAllSelected() { 145 final VectorDataSet dataSet = this.layer.getData(); 146 assertTrue(dataSet.getAllSelected().isEmpty()); 147 148 dataSet.setZoom(14); 149 loadTile(this.layer, 14, 3248, 6258); 150 151 // Select two arbitrary nodes from the loaded tile 152 final List<VectorNode> nodesToSelect = dataSet.getNodes().stream() 153 .limit(2) 154 .collect(Collectors.toList()); 155 assertEquals(2, nodesToSelect.size()); 156 157 dataSet.setSelected(nodesToSelect); 158 159 final Collection<VectorPrimitive> selected = dataSet.getAllSelected(); 160 assertEquals(2, selected.size()); 161 assertTrue(selected.containsAll(nodesToSelect)); 162 163 // Clear selection again 164 dataSet.clearSelection(); 165 assertTrue(dataSet.getAllSelected().isEmpty()); 166 } 167 168 /** 169 * Test that {@link VectorDataSet#getAllSelected()} properly combines selected primitives 170 * from both loaded vector tiles and the custom data store. 171 */ 172 @Test 173 void testGetAllSelectedWithCustomDataStore() { 174 final VectorDataSet dataSet = this.layer.getData(); 175 dataSet.setZoom(14); 176 loadTile(this.layer, 14, 3248, 6258); 177 178 // Combine a primitive from the tile and a custom primitive (customDataStore) 179 final VectorPrimitive tilePrimitive = dataSet.getNodes().iterator().next(); 180 final VectorNode customNode = new VectorNode("custom-layer"); 181 customNode.setCoor(new LatLon(0, 0)); 182 dataSet.addPrimitive(customNode); 183 184 dataSet.setSelected(Arrays.asList(tilePrimitive, customNode)); 185 186 final Collection<VectorPrimitive> selected = dataSet.getAllSelected(); 187 assertEquals(2, selected.size()); 188 assertTrue(selected.contains(tilePrimitive)); 189 assertTrue(selected.contains(customNode)); 190 } 191 192 /** 193 * Test that {@link VectorDataSet#getAllSelected()} is thread-safe when primitives are 194 * concurrently added and removed while reading the current selection. 195 * 196 * @throws Exception if any thread execution or timeout error occurs 197 */ 198 @Test 199 void testGetAllSelectedThreadSafety() throws Exception { 200 final VectorDataSet dataSet = this.layer.getData(); 201 dataSet.setZoom(14); 202 loadTile(this.layer, 14, 3248, 6258); 203 204 final List<VectorNode> nodesToSelect = dataSet.getNodes().stream() 205 .limit(5) 206 .collect(Collectors.toList()); 207 dataSet.setSelected(nodesToSelect); 208 209 final int iterations = 200; 210 final ExecutorService executor = Executors.newFixedThreadPool(2); 211 final CountDownLatch startLatch = new CountDownLatch(1); 212 213 // Thread 1: Continuously add and remove primitives (modifies underlying maps) 214 final Future<?> writer = executor.submit(() -> { 215 startLatch.await(); 216 for (int i = 0; i < iterations; i++) { 217 final VectorNode tempNode = new VectorNode("temp-layer"); 218 tempNode.setCoor(new LatLon(i * 0.001, i * 0.001)); 219 dataSet.addPrimitive(tempNode); 220 dataSet.removePrimitive(tempNode); 221 } 222 return null; 223 }); 224 225 // Thread 2: Concurrently read getAllSelected() 226 final Future<?> reader = executor.submit(() -> { 227 startLatch.await(); 228 for (int i = 0; i < iterations; i++) { 229 final Collection<VectorPrimitive> selected = dataSet.getAllSelected(); 230 assertEquals(5, selected.size()); 231 } 232 return null; 233 }); 234 235 startLatch.countDown(); 236 writer.get(5, TimeUnit.SECONDS); 237 reader.get(5, TimeUnit.SECONDS); 238 executor.shutdown(); 239 } 127 240 }
