Ticket #17268: clear_ignored_errors_v31.patch

File clear_ignored_errors_v31.patch, 25.1 KB (added by taylor.smock, 5 years ago)

Remove the Clear and Restore buttons and most attendant methods. The same effect as Clear can now be done without a specific button (select all, delete). There is no longer a restore, and changed the text on the popup from Delete to Don't ignore as suggested -- it still deletes from the list, but should be clearer to an end user.

  • src/org/openstreetmap/josm/data/preferences/sources/ValidatorPrefHelper.java

     
    4444    /** The preferences for ignored severity other */
    4545    public static final BooleanProperty PREF_OTHER = new BooleanProperty(PREFIX + ".other", false);
    4646
     47    /** The preferences key for the ignorelist */
     48    public static final String PREF_IGNORELIST = PREFIX + ".ignorelist";
     49
    4750    /**
    4851     * The preferences key for enabling the permanent filtering
    4952     * of the displayed errors in the tree regarding the current selection
  • src/org/openstreetmap/josm/data/validation/OsmValidator.java

     
    77import java.io.File;
    88import java.io.FileNotFoundException;
    99import java.io.IOException;
    10 import java.io.PrintWriter;
    1110import java.nio.charset.StandardCharsets;
    1211import java.nio.file.Files;
    1312import java.nio.file.Path;
     
    1716import java.util.Collection;
    1817import java.util.Collections;
    1918import java.util.EnumMap;
     19import java.util.Enumeration;
    2020import java.util.HashMap;
     21import java.util.Iterator;
    2122import java.util.List;
    2223import java.util.Map;
     24import java.util.Map.Entry;
    2325import java.util.SortedMap;
    2426import java.util.TreeMap;
    2527import java.util.TreeSet;
     
    2729import java.util.stream.Collectors;
    2830
    2931import javax.swing.JOptionPane;
     32import javax.swing.JTree;
     33import javax.swing.tree.DefaultMutableTreeNode;
     34import javax.swing.tree.TreeModel;
     35import javax.swing.tree.TreeNode;
    3036
    3137import org.openstreetmap.josm.data.preferences.sources.ValidatorPrefHelper;
    3238import org.openstreetmap.josm.data.projection.ProjectionRegistry;
     
    8894    /** Grid detail, multiplier of east,north values for valuable cell sizing */
    8995    private static double griddetail;
    9096
    91     private static final Collection<String> ignoredErrors = new TreeSet<>();
    92 
     97    private static final SortedMap<String, String> ignoredErrors = new TreeMap<>();
    9398    /**
    9499     * All registered tests
    95100     */
     
    169174    public static void initialize() {
    170175        checkValidatorDir();
    171176        initializeGridDetail();
    172         loadIgnoredErrors(); //FIXME: load only when needed
     177        loadIgnoredErrors();
    173178    }
    174179
    175180    /**
     
    204209    private static void loadIgnoredErrors() {
    205210        ignoredErrors.clear();
    206211        if (ValidatorPrefHelper.PREF_USE_IGNORE.get()) {
     212            Config.getPref().getListOfMaps(ValidatorPrefHelper.PREF_IGNORELIST).forEach(ignoredErrors::putAll);
    207213            Path path = Paths.get(getValidatorDir()).resolve("ignorederrors");
    208214            try {
    209215                if (path.toFile().exists()) {
    210216                    try {
    211                         ignoredErrors.addAll(Files.readAllLines(path, StandardCharsets.UTF_8));
     217                        TreeSet<String> treeSet = new TreeSet<>();
     218                        treeSet.addAll(Files.readAllLines(path, StandardCharsets.UTF_8));
     219                        treeSet.forEach(ignore -> ignoredErrors.putIfAbsent(ignore, ""));
     220
     221                        saveIgnoredErrors();
     222                        Files.deleteIfExists(path);
     223
    212224                    } catch (FileNotFoundException e) {
    213225                        Logging.debug(Logging.getErrorMessage(e));
    214226                    } catch (IOException e) {
     
    228240     * @see TestError#getIgnoreSubGroup()
    229241     */
    230242    public static void addIgnoredError(String s) {
    231         ignoredErrors.add(s);
     243        addIgnoredError(s, "");
    232244    }
    233245
    234246    /**
     247     * Adds an ignored error
     248     * @param s The ignore group / sub group name
     249     * @param description What the error actually is
     250     * @see TestError#getIgnoreGroup()
     251     * @see TestError#getIgnoreSubGroup()
     252     */
     253    public static void addIgnoredError(String s, String description) {
     254        if (description == null) description = "";
     255        ignoredErrors.put(s, description);
     256    }
     257
     258    /**
     259     *  Make sure that we don't keep single entries for a "group ignore" or
     260     *  multiple different entries for the single entries that are in the same group.
     261     */
     262    private static void cleanupIgnoredErrors() {
     263        if (ignoredErrors.size() > 1) {
     264            List<String> toRemove = new ArrayList<>();
     265
     266            Iterator<Entry<String, String>> iter = ignoredErrors.entrySet().iterator();
     267            Entry<String, String> last = iter.next();
     268            while (iter.hasNext()) {
     269                Entry<String, String> entry = iter.next();
     270                if (entry.getKey().startsWith(last.getKey())) {
     271                    toRemove.add(entry.getKey());
     272                } else {
     273                    last = entry;
     274                }
     275            }
     276            toRemove.forEach(ignoredErrors::remove);
     277        }
     278
     279        Map<String, String> tmap = buildIgnore(buildJTreeList());
     280        if (tmap != null && !tmap.isEmpty()) {
     281            ignoredErrors.clear();
     282            ignoredErrors.putAll(tmap);
     283        }
     284    }
     285
     286    /**
    235287     * Check if a error should be ignored
    236288     * @param s The ignore group / sub group name
    237289     * @return <code>true</code> to ignore that error
    238290     */
    239291    public static boolean hasIgnoredError(String s) {
    240         return ignoredErrors.contains(s);
     292        return ignoredErrors.containsKey(s);
    241293    }
    242294
    243295    /**
    244      * Saves the names of the ignored errors to a file
     296     * Get the list of all ignored errors
     297     * @return The <code>Collection&ltString&gt</code> of errors that are ignored
    245298     */
     299    public static SortedMap<String, String> getIgnoredErrors() {
     300        return ignoredErrors;
     301    }
     302
     303    /**
     304     * Build a JTree with a list
     305     * @return &lttype&gtlist as a {@code JTree}
     306     */
     307    public static JTree buildJTreeList() {
     308        DefaultMutableTreeNode root = new DefaultMutableTreeNode(tr("Ignore list"));
     309        for (Entry<String, String> e: ignoredErrors.entrySet()) {
     310            String key = e.getKey();
     311            String value = e.getValue();
     312            ArrayList<String> ignoredWayList = new ArrayList<>();
     313            String[] osmobjects = key.split(":(r|w|n)_");
     314            for (int i = 1; i < osmobjects.length; i++) {
     315                String osmid = osmobjects[i];
     316                if (osmid.matches("^[0-9]+$")) {
     317                    osmid = '_' + osmid;
     318                    int index = key.indexOf(osmid);
     319                    if (index < key.lastIndexOf(']')) continue;
     320                    char type = key.charAt(index - 1);
     321                    ignoredWayList.add(type + osmid);
     322                }
     323            }
     324            for (String osmignore : ignoredWayList) {
     325                key = key.replace(':' + osmignore, "");
     326            }
     327
     328            DefaultMutableTreeNode trunk;
     329            DefaultMutableTreeNode branch;
     330
     331            if (value != null && !value.isEmpty()) {
     332                trunk = inTree(root, value);
     333                branch = inTree(trunk, key);
     334                trunk.add(branch);
     335            } else {
     336                trunk = inTree(root, key);
     337                branch = trunk;
     338            }
     339            ignoredWayList.forEach(osmignore -> branch.add(new DefaultMutableTreeNode(osmignore)));
     340
     341            root.add(trunk);
     342        }
     343        return new JTree(root);
     344    }
     345
     346    private static DefaultMutableTreeNode inTree(DefaultMutableTreeNode root, String name) {
     347        @SuppressWarnings("unchecked")
     348        Enumeration<TreeNode> trunks = root.children();
     349        while (trunks.hasMoreElements()) {
     350            TreeNode ttrunk = trunks.nextElement();
     351            if (ttrunk instanceof DefaultMutableTreeNode) {
     352                DefaultMutableTreeNode trunk = (DefaultMutableTreeNode) ttrunk;
     353                if (name.equals(trunk.getUserObject())) {
     354                    return trunk;
     355                }
     356            }
     357        }
     358        return new DefaultMutableTreeNode(name);
     359    }
     360
     361    /**
     362     * Build a {@code HashMap} from a tree of ignored errors
     363     * @param tree The JTree of ignored errors
     364     * @return A {@code HashMap} of the ignored errors for comparison
     365     */
     366    public static Map<String, String> buildIgnore(JTree tree) {
     367        TreeModel model = tree.getModel();
     368        DefaultMutableTreeNode root = (DefaultMutableTreeNode) model.getRoot();
     369        return buildIgnore(model, root);
     370    }
     371
     372    private static Map<String, String> buildIgnore(TreeModel model, DefaultMutableTreeNode node) {
     373        HashMap<String, String> rHashMap = new HashMap<>();
     374
     375        String osmids = node.getUserObject().toString();
     376        String description = "";
     377
     378        if (!model.getRoot().equals(node)) {
     379            description = ((DefaultMutableTreeNode) node.getParent()).getUserObject().toString();
     380        } else {
     381            description = node.getUserObject().toString();
     382        }
     383        if (tr("Ignore list").equals(description)) description = "";
     384        if (!osmids.matches("^[0-9]+(_.*|$)")) {
     385            description = osmids;
     386            osmids = "";
     387        }
     388
     389
     390        for (int i = 0; i < model.getChildCount(node); i++) {
     391            DefaultMutableTreeNode child = (DefaultMutableTreeNode) model.getChild(node, i);
     392            if (model.getChildCount(child) == 0) {
     393                String ignoreName = child.getUserObject().toString();
     394                if (ignoreName.matches("^(r|w|n)_.*")) {
     395                    osmids += ":" + child.getUserObject().toString();
     396                } else if (ignoreName.matches("^[0-9]+(_.*|)$")) {
     397                    rHashMap.put(ignoreName, description);
     398                }
     399            } else {
     400                rHashMap.putAll(buildIgnore(model, child));
     401            }
     402        }
     403        if (!osmids.isEmpty() && osmids.indexOf(':') != 0) rHashMap.put(osmids, description);
     404        return rHashMap;
     405    }
     406
     407    /**
     408     * Reset the error list by deleting {@code validator.ignorelist}
     409     */
     410    public static void resetErrorList() {
     411        saveIgnoredErrors();
     412        Config.getPref().putListOfMaps(ValidatorPrefHelper.PREF_IGNORELIST, null);
     413        OsmValidator.initialize();
     414    }
     415
     416    /**
     417     * Saves the names of the ignored errors to a preference
     418     */
    246419    public static void saveIgnoredErrors() {
    247         try (PrintWriter out = new PrintWriter(new File(getValidatorDir(), "ignorederrors"), StandardCharsets.UTF_8.name())) {
    248             for (String e : ignoredErrors) {
    249                 out.println(e);
     420        List<Map<String, String>> list = new ArrayList<>();
     421        cleanupIgnoredErrors();
     422        list.add(ignoredErrors);
     423        int i = 0;
     424        while (i < list.size()) {
     425            if (list.get(i) == null || list.get(i).isEmpty()) {
     426                list.remove(i);
     427                continue;
    250428            }
    251         } catch (IOException e) {
    252             Logging.error(e);
     429            i++;
    253430        }
     431        if (list.isEmpty()) list = null;
     432        Config.getPref().putListOfMaps(ValidatorPrefHelper.PREF_IGNORELIST, list);
    254433    }
    255434
    256435    /**
  • src/org/openstreetmap/josm/gui/dialogs/ValidatorDialog.java

     
    6363import org.openstreetmap.josm.tools.ImageProvider;
    6464import org.openstreetmap.josm.tools.InputMapUtils;
    6565import org.openstreetmap.josm.tools.JosmRuntimeException;
     66import org.openstreetmap.josm.tools.Pair;
    6667import org.openstreetmap.josm.tools.Shortcut;
    6768import org.xml.sax.SAXException;
    6869
     
    8586    private final SideButton fixButton;
    8687    /** The ignore button */
    8788    private final SideButton ignoreButton;
     89    /** The reset ignorelist button */
     90    private final SideButton ignorelistManagement;
    8891    /** The select button */
    8992    private final SideButton selectButton;
    9093    /** The lookup button */
     
    174177            });
    175178            ignoreButton.setEnabled(false);
    176179            buttons.add(ignoreButton);
     180
     181            ignorelistManagement = new SideButton(new AbstractAction() {
     182                {
     183                    putValue(NAME, tr("Manage Ignore"));
     184                    putValue(SHORT_DESCRIPTION, tr("Manage the ignore list"));
     185                    new ImageProvider("dialogs", "fix").getResource().attachImageIcon(this, true);
     186                }
     187
     188                @Override
     189                public void actionPerformed(ActionEvent e) {
     190                    ValidatorListManagementDialog dialog = new ValidatorListManagementDialog("Ignore");
     191                    if (dialog.getValue() == 1) {
     192                        // TODO save
     193                    }
     194                }
     195            });
     196            buttons.add(ignorelistManagement);
    177197        } else {
    178198            ignoreButton = null;
     199            ignorelistManagement = null;
    179200        }
     201
    180202        createLayout(tree, true, buttons);
    181203    }
    182204
     
    245267
    246268            Object mainNodeInfo = node.getUserObject();
    247269            if (!(mainNodeInfo instanceof TestError)) {
    248                 Set<String> state = new HashSet<>();
     270                Set<Pair<String, String>> state = new HashSet<>();
    249271                // ask if the whole set should be ignored
    250272                if (asked == JOptionPane.DEFAULT_OPTION) {
    251273                    String[] a = new String[] {tr("Whole group"), tr("Single elements"), tr("Nothing")};
     
    257279                    ValidatorTreePanel.visitTestErrors(node, err -> {
    258280                        err.setIgnored(true);
    259281                        changed.set(true);
    260                         state.add(node.getDepth() == 1 ? err.getIgnoreSubGroup() : err.getIgnoreGroup());
     282                        state.add(new Pair<>(node.getDepth() == 1 ? err.getIgnoreSubGroup() : err.getIgnoreGroup(), err.getMessage()));
    261283                    }, processedNodes);
    262                     for (String s : state) {
    263                         OsmValidator.addIgnoredError(s);
     284                    for (Pair<String, String> s : state) {
     285                        OsmValidator.addIgnoredError(s.a, s.b);
    264286                    }
    265287                    continue;
    266288                } else if (asked == JOptionPane.CANCEL_OPTION || asked == JOptionPane.CLOSED_OPTION) {
     
    271293            ValidatorTreePanel.visitTestErrors(node, error -> {
    272294                String state = error.getIgnoreState();
    273295                if (state != null) {
    274                     OsmValidator.addIgnoredError(state);
     296                    OsmValidator.addIgnoredError(state, error.getMessage());
    275297                }
    276298                changed.set(true);
    277299                error.setIgnored(true);
     
    287309    /**
    288310     * Sets the selection of the map to the current selected items.
    289311     */
    290     @SuppressWarnings("unchecked")
    291312    private void setSelectedItems() {
    292313        DataSet ds = MainApplication.getLayerManager().getActiveDataSet();
    293314        if (tree == null || ds == null)
  • src/org/openstreetmap/josm/gui/dialogs/ValidatorListManagementDialog.java

     
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.gui.dialogs;
     3
     4import static org.openstreetmap.josm.tools.I18n.tr;
     5
     6import java.awt.Dimension;
     7import java.awt.GridBagLayout;
     8import java.awt.Rectangle;
     9import java.awt.event.ActionEvent;
     10import java.awt.event.KeyEvent;
     11import java.awt.event.KeyListener;
     12import java.awt.event.MouseAdapter;
     13import java.awt.event.MouseEvent;
     14import java.util.List;
     15import java.util.Locale;
     16import java.util.Map;
     17
     18import javax.swing.AbstractAction;
     19import javax.swing.ImageIcon;
     20import javax.swing.JMenuItem;
     21import javax.swing.JOptionPane;
     22import javax.swing.JPanel;
     23import javax.swing.JPopupMenu;
     24import javax.swing.JScrollPane;
     25import javax.swing.JTree;
     26import javax.swing.tree.DefaultMutableTreeNode;
     27import javax.swing.tree.TreePath;
     28
     29import org.openstreetmap.josm.actions.ValidateAction;
     30import org.openstreetmap.josm.data.validation.OsmValidator;
     31import org.openstreetmap.josm.data.validation.TestError;
     32import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil;
     33import org.openstreetmap.josm.gui.ExtendedDialog;
     34import org.openstreetmap.josm.gui.MainApplication;
     35import org.openstreetmap.josm.gui.MapFrame;
     36import org.openstreetmap.josm.gui.util.GuiHelper;
     37import org.openstreetmap.josm.tools.GBC;
     38import org.openstreetmap.josm.tools.ImageProvider;
     39import org.openstreetmap.josm.tools.Logging;
     40
     41
     42/**
     43 * A management window for the validator's ignorelist
     44 * @author Taylor Smock
     45 * @since xxx
     46 */
     47public class ValidatorListManagementDialog extends ExtendedDialog {
     48    enum BUTTONS {
     49        OK(0, tr("OK"), new ImageProvider("ok")),
     50        CANCEL(1, tr("Cancel"), new ImageProvider("cancel"));
     51
     52        private int index;
     53        private String name;
     54        private ImageIcon icon;
     55
     56        BUTTONS(int index, String name, ImageProvider image) {
     57            this.index = index;
     58            this.name = name;
     59            Dimension dim = new Dimension();
     60            ImageIcon sizeto = new ImageProvider("ok").getResource().getImageIcon();
     61            dim.setSize(-1, sizeto.getIconHeight());
     62            this.icon = image.getResource().getImageIcon(dim);
     63        }
     64
     65        public ImageIcon getImageIcon() {
     66            return icon;
     67        }
     68
     69        public int getIndex() {
     70            return index;
     71        }
     72
     73        public String getName() {
     74            return name;
     75        }
     76    }
     77
     78    private static final String[] BUTTON_TEXTS = {BUTTONS.OK.getName(), BUTTONS.CANCEL.getName()};
     79
     80    private static final ImageIcon[] BUTTON_IMAGES = {BUTTONS.OK.getImageIcon(), BUTTONS.CANCEL.getImageIcon()};
     81
     82    private final JPanel panel = new JPanel(new GridBagLayout());
     83
     84    private final JTree ignoreErrors;
     85
     86    private final String type;
     87
     88    /**
     89     * Create a new {@link ValidatorListManagementDialog}
     90     * @param type The type of list to create (first letter may or may not be
     91     * capitalized, it is put into all lowercase after building the title)
     92     */
     93    public ValidatorListManagementDialog(String type) {
     94        super(MainApplication.getMainFrame(), tr("Validator {0} List Management", type), BUTTON_TEXTS, false);
     95        this.type = type.toLowerCase(Locale.ENGLISH);
     96        setButtonIcons(BUTTON_IMAGES);
     97
     98        ignoreErrors = buildList();
     99        JScrollPane scroll = GuiHelper.embedInVerticalScrollPane(ignoreErrors);
     100
     101        panel.add(scroll, GBC.eol().fill(GBC.BOTH).anchor(GBC.CENTER));
     102        setContent(panel);
     103        setDefaultButton(1);
     104        setupDialog();
     105        setModal(true);
     106        showDialog();
     107    }
     108
     109    @Override
     110    public void buttonAction(int buttonIndex, ActionEvent evt) {
     111        // Currently OK/Cancel buttons do nothing
     112        final int answer;
     113        if (buttonIndex == BUTTONS.OK.getIndex()) {
     114            Map<String, String> errors = OsmValidator.getIgnoredErrors();
     115            Map<String, String> tree = OsmValidator.buildIgnore(ignoreErrors);
     116            if (!errors.equals(tree)) {
     117                answer = rerunValidatorPrompt();
     118                if (answer == JOptionPane.YES_OPTION || answer == JOptionPane.NO_OPTION) {
     119                    OsmValidator.resetErrorList();
     120                    tree.forEach((ignore, description) -> {
     121                        OsmValidator.addIgnoredError(ignore, description);
     122                    });
     123                    OsmValidator.saveIgnoredErrors();
     124                    OsmValidator.initialize();
     125                }
     126            }
     127            dispose();
     128        } else {
     129            super.buttonAction(buttonIndex, evt);
     130        }
     131    }
     132
     133    /**
     134     * Build a JTree with a list
     135     * @return &lttype&gtlist as a {@code JTree}
     136     */
     137    public JTree buildList() {
     138        JTree tree;
     139
     140        if ("ignore".equals(type)) {
     141            tree = OsmValidator.buildJTreeList();
     142        } else {
     143            Logging.error(tr("Cannot understand the following type: {0}", type));
     144            return null;
     145        }
     146        tree.setRootVisible(false);
     147        tree.setShowsRootHandles(true);
     148        tree.addMouseListener(new MouseAdapter() {
     149            @Override
     150            public void mousePressed(MouseEvent e) {
     151                process(e);
     152            }
     153
     154            @Override
     155            public void mouseReleased(MouseEvent e) {
     156                process(e);
     157            }
     158
     159            private void process(MouseEvent e) {
     160                if (e.isPopupTrigger()) {
     161                    TreePath[] paths = tree.getSelectionPaths();
     162                    if (paths == null) return;
     163                    Rectangle bounds = tree.getUI().getPathBounds(tree, paths[0]);
     164                    if (bounds != null) {
     165                        JPopupMenu menu = new JPopupMenu();
     166                        JMenuItem delete = new JMenuItem(new AbstractAction(tr("Don't ignore")) {
     167                            @Override
     168                            public void actionPerformed(ActionEvent e1) {
     169                                deleteAction(tree, paths);
     170                            }
     171                        });
     172                        menu.add(delete);
     173                        menu.show(e.getComponent(), e.getX(), e.getY());
     174                    }
     175                }
     176            }
     177        });
     178
     179        tree.addKeyListener(new KeyListener() {
     180
     181            @Override
     182            public void keyTyped(KeyEvent e) {
     183                // Do nothing
     184            }
     185
     186            @Override
     187            public void keyPressed(KeyEvent e) {
     188                // Do nothing
     189            }
     190
     191            @Override
     192            public void keyReleased(KeyEvent e) {
     193                TreePath[] paths = tree.getSelectionPaths();
     194                if (e.getKeyCode() == KeyEvent.VK_DELETE && paths != null) {
     195                    deleteAction(tree, paths);
     196                }
     197            }
     198        });
     199        return tree;
     200    }
     201
     202    private void deleteAction(JTree tree, TreePath[] paths) {
     203        for (TreePath path : paths) {
     204            tree.clearSelection();
     205            tree.addSelectionPath(path);
     206            DefaultMutableTreeNode node = (DefaultMutableTreeNode) tree.getLastSelectedPathComponent();
     207            DefaultMutableTreeNode parent = (DefaultMutableTreeNode) node.getParent();
     208            node.removeAllChildren();
     209            while (node.getChildCount() == 0) {
     210                node.removeFromParent();
     211                node = parent;
     212                if (parent == null || parent.isRoot()) break;
     213                parent = (DefaultMutableTreeNode) node.getParent();
     214            }
     215        }
     216        tree.updateUI();
     217    }
     218
     219
     220    /**
     221     * Prompt to rerun the validator when the ignore list changes
     222     * @return {@code JOptionPane.YES_OPTION}, {@code JOptionPane.NO_OPTION},
     223     *  or {@code JOptionPane.CANCEL_OPTION}
     224     */
     225    public int rerunValidatorPrompt() {
     226        MapFrame map = MainApplication.getMap();
     227        List<TestError> errors = map.validatorDialog.tree.getErrors();
     228        ValidateAction validateAction = ValidatorDialog.validateAction;
     229        if (!validateAction.isEnabled() || errors == null || errors.isEmpty()) return JOptionPane.NO_OPTION;
     230        final int answer = ConditionalOptionPaneUtil.showOptionDialog(
     231                "rerun_validation_when_ignorelist_changed",
     232                MainApplication.getMainFrame(),
     233                tr("{0}Should the validation be rerun?{1}", "<hmtl><h3>", "</h3></html>"),
     234                tr("Ignored error filter changed"),
     235                JOptionPane.YES_NO_CANCEL_OPTION,
     236                JOptionPane.QUESTION_MESSAGE,
     237                null,
     238                null);
     239        if (answer == JOptionPane.YES_OPTION) {
     240            validateAction.doValidate(true);
     241        }
     242        return answer;
     243    }
     244}
  • src/org/openstreetmap/josm/spi/preferences/MapListSetting.java

     
    66import java.util.LinkedHashMap;
    77import java.util.List;
    88import java.util.Map;
     9import java.util.SortedMap;
    910
    1011/**
    1112 * Setting containing a {@link List} of {@link Map}s of {@link String} values.
     
    4041        if (value.contains(null))
    4142            throw new IllegalArgumentException("Error: Null as list element in preference setting");
    4243        for (Map<String, String> map : value) {
    43             if (map.containsKey(null))
     44            if (!(map instanceof SortedMap) && map.containsKey(null))
    4445                throw new IllegalArgumentException("Error: Null as map key in preference setting");
    4546            if (map.containsValue(null))
    4647                throw new IllegalArgumentException("Error: Null as map value in preference setting");