Changeset 18636 in josm for trunk/src/org/openstreetmap


Ignore:
Timestamp:
2023-01-17T14:41:59+01:00 (2 years ago)
Author:
taylor.smock
Message:

Fix #21423: Prevent error codes from clashing

This works by creating a unique code using the test class name. The new
format for ignore entries will look like UNIQUECODE_CODE_MESSAGE_STATE.
The switchover date for the new entries is set at 2024-01-01 in order to
give users sufficient time to update, such that if a user has multiple
installations of JOSM, all versions will be able to use the same ignore
list. The compatibility code is intended to be removed in 2025.

Location:
trunk/src/org/openstreetmap/josm
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/data/validation/OsmValidator.java

    r18211 r18636  
    113113    private static final Class<Test>[] CORE_TEST_CLASSES = new Class[] {// NOPMD
    114114        /* FIXME - unique error numbers for tests aren't properly unique - ignoring will not work as expected */
     115        /* Error codes are class.getName().hashCode() + "_" + oldCode. There should almost never be a collision. */
    115116        DuplicateNode.class, // ID    1 ..   99
    116117        OverlappingWays.class, // ID  101 ..  199
     
    218219    private static void loadIgnoredErrors() {
    219220        ignoredErrors.clear();
    220         if (ValidatorPrefHelper.PREF_USE_IGNORE.get()) {
     221        if (Boolean.TRUE.equals(ValidatorPrefHelper.PREF_USE_IGNORE.get())) {
    221222            Config.getPref().getListOfMaps(ValidatorPrefHelper.PREF_IGNORELIST).forEach(ignoredErrors::putAll);
    222223            Path path = Paths.get(getValidatorDir()).resolve("ignorederrors");
     
    224225                if (path.toFile().exists()) {
    225226                    try {
    226                         TreeSet<String> treeSet = new TreeSet<>();
    227                         treeSet.addAll(Files.readAllLines(path, StandardCharsets.UTF_8));
     227                        TreeSet<String> treeSet = new TreeSet<>(Files.readAllLines(path, StandardCharsets.UTF_8));
    228228                        treeSet.forEach(ignore -> ignoredErrors.putIfAbsent(ignore, ""));
    229229                        removeLegacyEntries(true);
     
    247247    private static void removeLegacyEntries(boolean force) {
    248248        // see #19053:
     249        boolean wasChanged = removeLegacyEntry(force, true, "3000");
     250        // see #18230 (pt_assistant, RightAngleBuildingTest)
     251        wasChanged |= removeLegacyEntry(force, false, "3701");
     252
     253        if (wasChanged) {
     254            saveIgnoredErrors();
     255        }
     256    }
     257
     258    private static boolean removeLegacyEntry(boolean force, boolean keep, String prefix) {
    249259        boolean wasChanged = false;
    250260        if (force) {
     
    252262            while (iter.hasNext()) {
    253263                Entry<String, String> entry = iter.next();
    254                 if (entry.getKey().startsWith("3000_")) {
     264                if (entry.getKey().startsWith(prefix + "_")) {
    255265                    Logging.warn(tr("Cannot handle ignore list entry {0}", entry));
    256266                    iter.remove();
     
    259269            }
    260270        }
    261         String legacyEntry = ignoredErrors.remove("3000");
    262         if (legacyEntry != null) {
     271        String legacyEntry = ignoredErrors.remove(prefix);
     272        if (keep && legacyEntry != null) {
    263273            if (!legacyEntry.isEmpty()) {
    264                 addIgnoredError("3000_" + legacyEntry, legacyEntry);
     274                addIgnoredError(prefix + "_" + legacyEntry, legacyEntry);
    265275            }
    266276            wasChanged = true;
    267277        }
    268         if (wasChanged) {
    269             saveIgnoredErrors();
    270         }
     278        return wasChanged;
    271279    }
    272280
     
    503511        cleanupIgnoredErrors();
    504512        ignoredErrors.remove("3000"); // see #19053
     513        ignoredErrors.remove("3701"); // see #18230
    505514        list.add(ignoredErrors);
    506515        int i = 0;
     
    608617
    609618    /**
    610      * Initialize grid details based on current projection system. Values based on
     619     * Initialize grid details based on the current projection system. Values based on
    611620     * the original value fixed for EPSG:4326 (10000) using heuristics (that is, test&amp;error
    612621     * until most bugs were discovered while keeping the processing time reasonable)
     
    637646
    638647    /**
    639      * Initializes all tests if this operations hasn't been performed already.
     648     * Initializes all tests if this operation hasn't been performed already.
    640649     */
    641650    public static synchronized void initializeTests() {
  • trunk/src/org/openstreetmap/josm/data/validation/TestError.java

    r17622 r18636  
    55import java.awt.geom.PathIterator;
    66import java.text.MessageFormat;
     7import java.time.Instant;
    78import java.util.ArrayList;
    89import java.util.Arrays;
     
    1112import java.util.List;
    1213import java.util.Locale;
     14import java.util.Map;
    1315import java.util.TreeSet;
    1416import java.util.function.Supplier;
     
    3436 */
    3537public class TestError implements Comparable<TestError> {
     38    /**
     39     * Used to switch users over to new ignore system, UNIQUE_CODE_MESSAGE_STATE
     40     * 1_704_067_200L -> 2024-01-01
     41     * We can probably remove this and the supporting code in 2025.
     42     */
     43    private static boolean switchOver = Instant.now().isAfter(Instant.ofEpochMilli(1_704_067_200L));
    3644    /** is this error on the ignore list */
    3745    private boolean ignored;
     
    5159    /** Internal code used by testers to classify errors */
    5260    private final int code;
     61    /** Internal code used by testers to classify errors. Used for moving between JOSM versions. */
     62    private final int uniqueCode;
    5363    /** If this error is selected */
    5464    private boolean selected;
     
    6474        private final Severity severity;
    6575        private final int code;
     76        private final int uniqueCode;
    6677        private String message;
    6778        private String description;
     
    7586            this.severity = severity;
    7687            this.code = code;
     88            this.uniqueCode = this.tester != null ? this.tester.getClass().getName().hashCode() : code;
    7789        }
    7890
     
    232244            return new TestError(this);
    233245        }
     246    }
     247
     248    /**
     249     * Update error codes on read and save. Used for tests.
     250     * @param updateErrorCodes {@code true} to update error codes. See {@link #switchOver} for default.
     251     */
     252    static void setUpdateErrorCodes(boolean updateErrorCodes) {
     253        switchOver = updateErrorCodes;
    234254    }
    235255
     
    255275        this.highlighted = builder.highlighted;
    256276        this.code = builder.code;
     277        this.uniqueCode = builder.uniqueCode;
    257278        this.fixingCommand = builder.fixingCommand;
    258279    }
     
    307328     */
    308329    public String getIgnoreState() {
     330        return getIgnoreState(false);
     331    }
     332
     333    /**
     334     * Get the ignore state
     335     * @param useOriginal if {@code true}, use the original code to get the ignore state
     336     * @return The ignore state ({@link #getIgnoreGroup} + ignored object list)
     337     */
     338    private String getIgnoreState(boolean useOriginal) {
    309339        Collection<String> strings = new TreeSet<>();
    310340        for (OsmPrimitive o : primitives) {
     
    322352            strings.add(type + '_' + o.getId());
    323353        }
    324         return strings.stream().map(o -> ':' + o).collect(Collectors.joining("", getIgnoreSubGroup(), ""));
     354        return strings.stream().map(o -> ':' + o).collect(Collectors.joining("", getIgnoreSubGroup(useOriginal), ""));
    325355    }
    326356
     
    336366
    337367    private boolean calcIgnored() {
     368        // Begin code removal section (backwards compatibility)
     369        if (OsmValidator.hasIgnoredError(getIgnoreGroup(true))) {
     370            updateIgnoreList(getIgnoreGroup(true), getIgnoreGroup(false));
     371            return true;
     372        }
     373        if (OsmValidator.hasIgnoredError(getIgnoreSubGroup(true))) {
     374            updateIgnoreList(getIgnoreSubGroup(true), getIgnoreSubGroup(false));
     375            return true;
     376        }
     377        String oldState = getIgnoreState(true);
     378        String state = getIgnoreState(false);
     379        if (oldState != null && OsmValidator.hasIgnoredError(oldState)) {
     380            updateIgnoreList(oldState, state);
     381            return true;
     382        }
     383        // End code removal section
    338384        if (OsmValidator.hasIgnoredError(getIgnoreGroup()))
    339385            return true;
    340386        if (OsmValidator.hasIgnoredError(getIgnoreSubGroup()))
    341387            return true;
    342         String state = getIgnoreState();
    343388        return state != null && OsmValidator.hasIgnoredError(state);
     389    }
     390
     391    /**
     392     * Convert old keys to new keys. Only takes effect when {@link #switchOver} is true
     393     * @param oldKey The key to replace
     394     * @param newKey The new key
     395     */
     396    private static void updateIgnoreList(String oldKey, String newKey) {
     397        if (switchOver) {
     398            Map<String, String> errors = OsmValidator.getIgnoredErrors();
     399            if (errors.containsKey(oldKey)) {
     400                String value = errors.remove(oldKey);
     401                errors.put(newKey, value);
     402            }
     403        }
    344404    }
    345405
     
    349409     */
    350410    public String getIgnoreSubGroup() {
     411        return getIgnoreSubGroup(false);
     412    }
     413
     414    /**
     415     * Get the subgroup for the error
     416     * @param useOriginal if {@code true}, use the original code instead of the new unique codes.
     417     * @return The ignore subgroup
     418     */
     419    private String getIgnoreSubGroup(boolean useOriginal) {
    351420        if (code == 3000) {
    352421            // see #19053
    353422            return "3000_" + (description == null ? message : description);
    354423        }
    355         String ignorestring = getIgnoreGroup();
     424        String ignorestring = getIgnoreGroup(useOriginal);
    356425        if (descriptionEn != null) {
    357426            ignorestring += '_' + descriptionEn;
     
    366435     */
    367436    public String getIgnoreGroup() {
     437        return getIgnoreGroup(false);
     438    }
     439
     440    /**
     441     * Get the ignore group
     442     * @param useOriginal if {@code true}, use the original code instead of a unique code + original code.
     443     *                    Used for reading and understanding old ignore groups.
     444     * @return The ignore group.
     445     */
     446    private String getIgnoreGroup(boolean useOriginal) {
    368447        if (code == 3000) {
    369448            // see #19053
    370449            return "3000_" + getMessage();
    371450        }
    372         return Integer.toString(code);
     451        if (useOriginal) {
     452            return Integer.toString(this.code);
     453        }
     454        return this.uniqueCode + "_" + this.code;
    373455    }
    374456
     
    403485    public int getCode() {
    404486        return code;
     487    }
     488
     489    /**
     490     * Get the unique code for this test. Used for ignore lists.
     491     * @return The unique code (generated with {@code tester.getClass().getName().hashCode() + code}).
     492     * @since xxx
     493     */
     494    public int getUniqueCode() {
     495        return this.uniqueCode;
    405496    }
    406497
     
    547638     */
    548639    public boolean isSimilar(TestError other) {
    549         return getCode() == other.getCode()
     640        return getUniqueCode() == other.getUniqueCode()
     641                && getCode() == other.getCode()
    550642                && getMessage().equals(other.getMessage())
    551643                && getPrimitives().size() == other.getPrimitives().size()
     
    571663    @Override
    572664    public String toString() {
    573         return "TestError [tester=" + tester + ", code=" + code + ", message=" + message + ']';
     665        return "TestError [tester=" + tester + ", unique code=" + this.uniqueCode +
     666                ", code=" + code + ", message=" + message + ']';
    574667    }
    575668
  • trunk/src/org/openstreetmap/josm/io/GeoJSONMapRouletteWriter.java

    r18365 r18636  
    4646        propertiesBuilder.add("message", testError.getMessage());
    4747        Optional.ofNullable(testError.getDescription()).ifPresent(description -> propertiesBuilder.add("description", description));
    48         propertiesBuilder.add("code", testError.getCode());
     48        propertiesBuilder.add("code", testError.getUniqueCode());
    4949        propertiesBuilder.add("fixable", testError.isFixable());
    5050        propertiesBuilder.add("severity", testError.getSeverity().toString());
Note: See TracChangeset for help on using the changeset viewer.