Changeset 18842 in josm


Ignore:
Timestamp:
2023-09-25T17:34:00+02:00 (16 months ago)
Author:
taylor.smock
Message:

Fix #23191: NPE in AddTagsDialog

This is caused when a new layer is added via Remote Control while the add tag
dialog is open.

Location:
trunk
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/gui/dialogs/properties/TagEditHelper.java

    r18692 r18842  
    7373import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
    7474import org.openstreetmap.josm.data.osm.Tag;
     75import org.openstreetmap.josm.data.osm.Tagged;
    7576import org.openstreetmap.josm.data.osm.search.SearchCompiler;
    7677import org.openstreetmap.josm.data.osm.search.SearchParseError;
     
    8283import org.openstreetmap.josm.data.preferences.StringProperty;
    8384import org.openstreetmap.josm.data.tagging.ac.AutoCompletionItem;
     85import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil;
    8486import org.openstreetmap.josm.gui.ExtendedDialog;
    8587import org.openstreetmap.josm.gui.IExtendedDialog;
     
    278280        try {
    279281            activeDataSet.beginUpdate();
    280             sel = OsmDataManager.getInstance().getInProgressSelection();
    281             if (Utils.isEmpty(sel))
     282            Collection<OsmPrimitive> selection = OsmDataManager.getInstance().getInProgressSelection();
     283            this.sel = selection;
     284            if (Utils.isEmpty(selection))
    282285                return;
    283286
     
    287290
    288291            addDialog.destroyActions();
    289             if (addDialog.getValue() == 1)
    290                 addDialog.performTagAdding();
    291             else
     292            // Remote control can cause the selection to change, see #23191.
     293            if (addDialog.getValue() == 1 && (selection == sel || warnSelectionChanged())) {
     294                addDialog.performTagAdding(selection);
     295            } else {
    292296                addDialog.undoAllTagsAdding();
     297            }
    293298        } finally {
    294299            activeDataSet.endUpdate();
     
    373378    public void loadTagsIfNeeded() {
    374379        loadTagsToIgnore();
    375         if (PROPERTY_REMEMBER_TAGS.get() && recentTags.isEmpty()) {
     380        if (Boolean.TRUE.equals(PROPERTY_REMEMBER_TAGS.get()) && recentTags.isEmpty()) {
    376381            recentTags.loadFromPreference(PROPERTY_RECENT_TAGS);
    377382        }
     
    407412     */
    408413    public void saveTagsIfNeeded() {
    409         if (PROPERTY_REMEMBER_TAGS.get() && !recentTags.isEmpty()) {
     414        if (Boolean.TRUE.equals(PROPERTY_REMEMBER_TAGS.get()) && !recentTags.isEmpty()) {
    410415            recentTags.saveToPreference(PROPERTY_RECENT_TAGS);
    411416        }
     
    449454            return selectedItem.toString();
    450455        return getEditItem(cb);
     456    }
     457
     458    /**
     459     * Warn user about a selection change
     460     * @return {@code true} if the user wants to apply the tag change to the old selection
     461     */
     462    private static boolean warnSelectionChanged() {
     463        return ConditionalOptionPaneUtil.showConfirmationDialog("properties.selection-changed",
     464                MainApplication.getMainFrame(),
     465                tr("Data selection has changed since the dialog was opened"),
     466                tr("Apply tag change to old selection?"),
     467                JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE, JOptionPane.YES_OPTION);
    451468    }
    452469
     
    506523                /**
    507524                 * This hack allows the comboboxes to have their own orientation.
    508                  *
     525                 * <p>
    509526                 * The problem is that
    510527                 * {@link org.openstreetmap.josm.gui.ExtendedDialog#showDialog ExtendedDialog} calls
    511528                 * {@code applyComponentOrientation} very late in the dialog construction process
    512529                 * thus overwriting the orientation the components have chosen for themselves.
    513                  *
     530                 * <p>
    514531                 * This stops the propagation of {@code applyComponentOrientation}, thus all
    515532                 * components may (and have to) set their own orientation.
     
    794811                /**
    795812                 * This hack allows the comboboxes to have their own orientation.
    796                  *
     813                 * <p>
    797814                 * The problem is that
    798815                 * {@link org.openstreetmap.josm.gui.ExtendedDialog#showDialog ExtendedDialog} calls
    799816                 * {@code applyComponentOrientation} very late in the dialog construction process
    800817                 * thus overwriting the orientation the components have chosen for themselves.
    801                  *
     818                 * <p>
    802819                 * This stops the propagation of {@code applyComponentOrientation}, thus all
    803820                 * components may (and have to) set their own orientation.
     
    11871204         */
    11881205        public final void performTagAdding() {
     1206            Collection<OsmPrimitive> selection = sel;
     1207            if (!Utils.isEmpty(selection)) {
     1208                performTagAdding(selection);
     1209            }
     1210        }
     1211
     1212        /**
     1213         * Read tags from comboboxes and add it to all selected objects
     1214         * @param selection The selection to perform tag adding on
     1215         * @since 18842
     1216         */
     1217        private void performTagAdding(Collection<OsmPrimitive> selection) {
    11891218            String key = getEditItem(keys);
    11901219            String value = getEditItem(values);
    11911220            if (key.isEmpty() || value.isEmpty())
    11921221                return;
    1193             for (OsmPrimitive osm : sel) {
     1222            for (Tagged osm : selection) {
    11941223                String val = osm.get(key);
    11951224                if (val != null && !val.equals(value)) {
     
    12031232            }
    12041233            recentTags.add(new Tag(key, value));
    1205             valueCount.put(key, new TreeMap<String, Integer>());
     1234            valueCount.put(key, new TreeMap<>());
    12061235            AutoCompletionManager.rememberUserInput(key, value, false);
    12071236            commandCount++;
    1208             UndoRedoHandler.getInstance().add(new ChangePropertyCommand(sel, key, value));
     1237            UndoRedoHandler.getInstance().add(new ChangePropertyCommand(selection, key, value));
    12091238            changedKey = key;
    12101239            clearEntries();
     
    12161245        }
    12171246
     1247        /**
     1248         * Undo all tag add commands that this dialog has created
     1249         */
    12181250        public void undoAllTagsAdding() {
    12191251            UndoRedoHandler.getInstance().undo(commandCount);
  • trunk/test/unit/org/openstreetmap/josm/gui/dialogs/properties/TagEditHelperTest.java

    r18799 r18842  
    22package org.openstreetmap.josm.gui.dialogs.properties;
    33
     4import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
    45import static org.junit.jupiter.api.Assertions.assertEquals;
    56import static org.junit.jupiter.api.Assertions.assertFalse;
    67import static org.junit.jupiter.api.Assertions.assertNotNull;
     8import static org.junit.jupiter.api.Assertions.assertTrue;
    79
    810import java.awt.GraphicsEnvironment;
     
    1517import java.util.List;
    1618import java.util.Map;
     19import java.util.concurrent.Future;
     20import java.util.concurrent.atomic.AtomicBoolean;
    1721import java.util.function.Function;
    1822import java.util.stream.Collectors;
    1923
     24import javax.swing.JOptionPane;
    2025import javax.swing.JTable;
    2126import javax.swing.table.DefaultTableModel;
    2227
     28import org.awaitility.Awaitility;
     29import org.awaitility.Durations;
    2330import org.junit.jupiter.api.Test;
    2431import org.openstreetmap.josm.TestUtils;
     
    3037import org.openstreetmap.josm.data.osm.Way;
    3138import org.openstreetmap.josm.data.tagging.ac.AutoCompletionItem;
     39import org.openstreetmap.josm.gui.ExtendedDialog;
    3240import org.openstreetmap.josm.gui.MainApplication;
    3341import org.openstreetmap.josm.gui.dialogs.properties.TagEditHelper.AddTagsDialog;
     
    3543import org.openstreetmap.josm.gui.mappaint.MapPaintStyles;
    3644import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSStyleSource;
     45import org.openstreetmap.josm.spi.preferences.Config;
    3746import org.openstreetmap.josm.testutils.annotations.Projection;
    3847import org.openstreetmap.josm.testutils.annotations.Territories;
    3948import org.openstreetmap.josm.testutils.mockers.WindowMocker;
     49import org.openstreetmap.josm.tools.JosmRuntimeException;
     50
     51import mockit.Mock;
     52import mockit.MockUp;
    4053
    4154/**
     
    104117            return node;
    105118        }, "junction", "roundabout");
     119    }
     120
     121    /**
     122     * Non-regression test for <a href="https://josm.openstreetmap.de/ticket/23191>#23191</a>
     123     */
     124    @Test
     125    void testTicket23191() {
     126        TestUtils.assumeWorkingJMockit();
     127        final TagEditHelper tagEditHelper = newTagEditHelper();
     128        final DataSet original = new DataSet();
     129        MainApplication.getLayerManager().addLayer(new OsmDataLayer(original, "TagEditHelperTest.testTicket23191_1", null));
     130        final Node toSelect = TestUtils.newNode("");
     131        original.addPrimitive(toSelect);
     132        original.setSelected(toSelect);
     133        assertEquals(1, OsmDataManager.getInstance().getInProgressISelection().size());
     134        assertTrue(OsmDataManager.getInstance().getInProgressISelection().contains(toSelect));
     135
     136        final AtomicBoolean canContinue = new AtomicBoolean();
     137        final AtomicBoolean showingDialog = new AtomicBoolean();
     138
     139        // Instantiate the AddTagsDialog where we don't have to worry about race conditions
     140        tagEditHelper.sel = OsmDataManager.getInstance().getInProgressSelection();
     141        final AddTagsDialog addTagsDialog = tagEditHelper.getAddTagsDialog();
     142        tagEditHelper.resetSelection();
     143        new MockUp<TagEditHelper>() {
     144            @Mock
     145            public AddTagsDialog getAddTagsDialog() {
     146                return addTagsDialog;
     147            }
     148        };
     149
     150        new MockUp<AddTagsDialog>() {
     151            @Mock
     152            public ExtendedDialog showDialog() {
     153                showingDialog.set(true);
     154                while (!canContinue.get()) {
     155                    synchronized (canContinue) {
     156                        try {
     157                            canContinue.wait();
     158                        } catch (InterruptedException e) {
     159                            throw new JosmRuntimeException(e);
     160                        }
     161                    }
     162                }
     163                return null;
     164            }
     165
     166            @Mock
     167            public int getValue() {
     168                return 1;
     169            }
     170        };
     171
     172        // Avoid showing the JOption pane
     173        Config.getPref().putBoolean("message.properties.selection-changed", false);
     174        Config.getPref().putInt("message.properties.selection-changed.value", JOptionPane.YES_OPTION);
     175
     176        // "Open" the tag edit dialog -- this should technically be in the EDT, but we are mocking the UI parts out,
     177        // since the EDT does allow new EDT runnables when showing the add tag dialog
     178        Future<?> tagFuture = MainApplication.worker.submit(tagEditHelper::addTag);
     179
     180        Awaitility.await().atMost(Durations.ONE_SECOND).untilTrue(showingDialog);
     181        // This is what remote control will effectively do
     182        MainApplication.getLayerManager().addLayer(new OsmDataLayer(new DataSet(), "TagEditHelperTest.testTicket23191_2", null));
     183        tagEditHelper.resetSelection();
     184
     185        // Enter key=value
     186        addTagsDialog.keys.setText("building");
     187        addTagsDialog.values.setText("yes");
     188
     189        // Close the tag edit dialog
     190        synchronized (canContinue) {
     191            canContinue.set(true);
     192            canContinue.notifyAll();
     193        }
     194
     195        assertDoesNotThrow(() -> tagFuture.get());
    106196    }
    107197
Note: See TracChangeset for help on using the changeset viewer.