Changeset 18935 in josm for trunk


Ignore:
Timestamp:
2024-01-15T17:20:18+01:00 (11 months ago)
Author:
taylor.smock
Message:

Fix #23399: Simplify way crashes

This was primarily caused by selection updates occurring while the commands were generated. The fixes could be any of the following:

  • Synchronized collections
  • New list
  • Avoiding selection updates

The last option was taken for the following reasons:

  • Avoiding a StackOverflow exception when many ways are being simplified
  • Reduced memory allocations (>11GB to <250MB)
  • Reduced CPU cycles
Location:
trunk
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/actions/SimplifyWayAction.java

    r18883 r18935  
    341341     */
    342342    private static SequenceCommand buildSimplifyWaysCommand(List<Way> ways, double threshold) {
    343         Collection<Command> allCommands = ways.stream()
    344                 .map(way -> createSimplifyCommand(way, threshold))
     343        List<Command> allCommands = ways.stream()
     344                .map(way -> createSimplifyCommand(way, threshold, false))
    345345                .filter(Objects::nonNull)
    346346                .collect(StreamUtils.toUnmodifiableList());
    347347        if (allCommands.isEmpty())
    348348            return null;
     349        final List<OsmPrimitive> deletedPrimitives = allCommands.stream()
     350                .map(Command::getChildren)
     351                .flatMap(Collection::stream)
     352                .filter(DeleteCommand.class::isInstance)
     353                .map(DeleteCommand.class::cast)
     354                .map(DeleteCommand::getParticipatingPrimitives)
     355                .flatMap(Collection::stream)
     356                .collect(Collectors.toList());
     357        allCommands.get(0).getAffectedDataSet().clearSelection(deletedPrimitives);
    349358        return new SequenceCommand(
    350359                trn("Simplify {0} way", "Simplify {0} ways", allCommands.size(), allCommands.size()),
     
    372381     */
    373382    public static SequenceCommand createSimplifyCommand(Way w, double threshold) {
     383        return createSimplifyCommand(w, threshold, true);
     384    }
     385
     386    /**
     387     * Creates the SequenceCommand to simplify a way with a given threshold.
     388     *
     389     * @param w the way to simplify
     390     * @param threshold the max error threshold
     391     * @param deselect {@code true} if we want to deselect the deleted nodes
     392     * @return The sequence of commands to run
     393     */
     394    private static SequenceCommand createSimplifyCommand(Way w, double threshold, boolean deselect) {
    374395        int lower = 0;
    375396        int i = 0;
     
    418439        cmds.add(new ChangeNodesCommand(w, newNodes));
    419440        cmds.add(new DeleteCommand(w.getDataSet(), delNodes));
    420         w.getDataSet().clearSelection(delNodes);
     441        if (deselect) {
     442            w.getDataSet().clearSelection(delNodes);
     443        }
    421444        return new SequenceCommand(
    422445                trn("Simplify Way (remove {0} node)", "Simplify Way (remove {0} nodes)", delNodes.size(), delNodes.size()), cmds);
  • trunk/test/unit/org/openstreetmap/josm/actions/SimplifyWayActionTest.java

    r18870 r18935  
    22package org.openstreetmap.josm.actions;
    33
     4import static org.junit.jupiter.api.Assertions.assertAll;
     5import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
    46import static org.junit.jupiter.api.Assertions.assertEquals;
     7import static org.junit.jupiter.api.Assertions.assertFalse;
    58import static org.junit.jupiter.api.Assertions.assertNotNull;
     9import static org.openstreetmap.josm.tools.I18n.tr;
    610
    711import java.io.IOException;
     
    2529import org.openstreetmap.josm.data.osm.Node;
    2630import org.openstreetmap.josm.data.osm.Way;
     31import org.openstreetmap.josm.gui.ExtendedDialog;
    2732import org.openstreetmap.josm.gui.MainApplication;
     33import org.openstreetmap.josm.gui.layer.OsmDataLayer;
     34import org.openstreetmap.josm.gui.util.GuiHelper;
    2835import org.openstreetmap.josm.io.IllegalDataException;
    2936import org.openstreetmap.josm.io.OsmReader;
    3037import org.openstreetmap.josm.testutils.annotations.Main;
    3138import org.openstreetmap.josm.testutils.annotations.Projection;
     39import org.openstreetmap.josm.testutils.mockers.ExtendedDialogMocker;
     40import org.openstreetmap.josm.testutils.mockers.HelpAwareOptionPaneMocker;
    3241import org.openstreetmap.josm.tools.Utils;
    3342
     
    100109        assertEquals(Collections.singleton(n1), deleteCommands.iterator().next().getParticipatingPrimitives());
    101110    }
     111
     112    /**
     113     * Non-regression test for #23399
     114     */
     115    @Test
     116    void testNonRegression23399() {
     117        TestUtils.assumeWorkingJMockit();
     118        new ExtendedDialogMocker(Collections.singletonMap("Simplify way", "Simplify")) {
     119            @Override
     120            protected String getString(ExtendedDialog instance) {
     121                return instance.getTitle();
     122            }
     123        };
     124        new HelpAwareOptionPaneMocker(Collections.singletonMap(
     125                tr("The selection contains {0} ways. Are you sure you want to simplify them all?", 1000), "Yes"));
     126        final ArrayList<Way> ways = new ArrayList<>(1000);
     127        final DataSet ds = new DataSet();
     128        for (int i = 0; i < 1000; i++) {
     129            final Way way = TestUtils.newWay("", new Node(new LatLon(0, 0)), new Node(new LatLon(0, 0.001)),
     130                    new Node(new LatLon(0, 0.002)));
     131            ways.add(way);
     132            ds.addPrimitiveRecursive(way);
     133        }
     134        MainApplication.getLayerManager().addLayer(new OsmDataLayer(ds, "SimplifyWayActionTest#testNonRegression23399", null));
     135        GuiHelper.runInEDTAndWait(() -> ds.setSelected(ds.allPrimitives()));
     136        assertEquals(ds.allPrimitives().size(), ds.getAllSelected().size());
     137        assertDoesNotThrow(() -> GuiHelper.runInEDTAndWaitWithException(() -> action.actionPerformed(null)));
     138        assertAll(ways.stream().map(way -> () -> assertEquals(2, way.getNodesCount())));
     139        assertAll(ds.getAllSelected().stream().map(p -> () -> assertFalse(p.isDeleted())));
     140        assertEquals(3000, ds.getAllSelected().size());
     141    }
    102142}
Note: See TracChangeset for help on using the changeset viewer.