Ticket #19312: 19312.2.patch
File 19312.2.patch, 11.8 KB (added by , 5 years ago) |
---|
-
src/org/openstreetmap/josm/data/validation/tests/RelationChecker.java
6 6 7 7 import java.util.ArrayList; 8 8 import java.util.Collection; 9 import java.util.Collections; 9 10 import java.util.EnumSet; 10 11 import java.util.HashMap; 12 import java.util.Iterator; 11 13 import java.util.LinkedHashMap; 14 import java.util.LinkedHashSet; 12 15 import java.util.LinkedList; 13 16 import java.util.List; 14 17 import java.util.Map; 15 18 import java.util.stream.Collectors; 16 19 20 import org.openstreetmap.josm.command.ChangeCommand; 17 21 import org.openstreetmap.josm.command.Command; 18 22 import org.openstreetmap.josm.command.DeleteCommand; 19 23 import org.openstreetmap.josm.data.osm.OsmPrimitive; … … 61 65 public static final int RELATION_EMPTY = 1708; 62 66 /** Type ''{0}'' of relation member with role ''{1}'' does not match accepted types ''{2}'' in preset {3} */ 63 67 public static final int WRONG_TYPE = 1709; 68 /** Relations build circular dependencies */ 69 public static final int RELATION_LOOP = 1710; 64 70 // CHECKSTYLE.ON: SingleSpaceSeparator 65 71 66 72 /** … … 70 76 public static final String ROLE_VERIF_PROBLEM_MSG = tr("Role verification problem"); 71 77 private boolean ignoreMultiPolygons; 72 78 private boolean ignoreTurnRestrictions; 73 79 private final List<List<Relation>> loops = new ArrayList<>(); 74 80 /** 75 81 * Constructor 76 82 */ … … 154 160 if (!map.isEmpty() && !allroles.isEmpty()) { 155 161 checkRoles(n, allroles, map); 156 162 } 163 checkLoop(n); 157 164 } 158 165 159 166 private static Map<String, RoleInfo> buildRoleInfoMap(Relation n) { … … 365 372 public Command fixError(TestError testError) { 366 373 Collection<? extends OsmPrimitive> primitives = testError.getPrimitives(); 367 374 if (isFixable(testError) && !primitives.iterator().next().isDeleted()) { 368 return new DeleteCommand(primitives); 375 if (testError.getCode() == RELATION_EMPTY) { 376 return new DeleteCommand(primitives); 377 } 378 if (testError.getCode() == RELATION_LOOP) { 379 Relation old = (Relation) primitives.iterator().next(); 380 Relation mod = new Relation(old); 381 mod.removeMembersFor(primitives); 382 return new ChangeCommand(old, mod); 383 } 369 384 } 370 385 return null; 371 386 } … … 373 388 @Override 374 389 public boolean isFixable(TestError testError) { 375 390 Collection<? extends OsmPrimitive> primitives = testError.getPrimitives(); 376 return testError.getCode() == RELATION_EMPTY && !primitives.isEmpty() && primitives.iterator().next().isNew(); 391 return testError.getCode() == RELATION_EMPTY && !primitives.isEmpty() && primitives.iterator().next().isNew() || 392 testError.getCode() == RELATION_LOOP && primitives.size() == 1; 377 393 } 378 394 379 395 @Override … … 381 397 relationpresets.clear(); 382 398 initializePresets(); 383 399 } 400 401 @Override 402 public void endTest() { 403 loops.forEach(loop -> errors.add(TestError.builder(this, Severity.ERROR, RELATION_LOOP) 404 .message(loop.size() == 2 ? tr("Relation contains itself as a member") : tr("Relations build circular dependencies")) 405 .primitives(new LinkedHashSet<>(loop)) 406 .build())); 407 loops.clear(); 408 super.endTest(); 409 } 410 411 /** 412 * Check if a given relation is part of a circular dependency loop. 413 * @param r the relation 414 */ 415 private void checkLoop(Relation r) { 416 checkLoop(r, new LinkedList<>()); 417 } 418 419 private void checkLoop(Relation parent, List<Relation> path) { 420 if (path.contains(parent)) { 421 Iterator<List<Relation>> iter = loops.iterator(); 422 while (iter.hasNext()) { 423 List<Relation> loop = iter.next(); 424 if (loop.size() > path.size() && loop.containsAll(path)) { 425 // remove same loop with irrelevant parent 426 iter.remove(); 427 } else if (path.size() >= loop.size() && path.containsAll(loop)) { 428 // same or smaller loop is already known 429 return; 430 } 431 } 432 if (path.get(0).equals(parent)) { 433 path.add(parent); 434 loops.add(path); 435 } 436 return; 437 } 438 path.add(parent); 439 for (Relation sub : parent.getMemberPrimitives(Relation.class)) { 440 if (sub.isUsable() && !sub.isIncomplete()) { 441 checkLoop(sub, new LinkedList<>(path)); 442 } 443 } 444 } 445 446 /** 447 * Check if adding one relation to another would produce a circular dependency. 448 * @param parent the relation which would be changed 449 * @param child the child relation which should be added to parent 450 * @return An unmodifiable list of relations which is empty when no circular dependency was found, 451 * else it contains the relations that form circular dependencies. 452 * The list then contains at least two items. Normally first and last item are both {@code parent}, 453 * but if child is already part of a circular dependency the returned list may not end with {@code parent}. 454 */ 455 public static List<Relation> checkAddMember(Relation parent, Relation child) { 456 if (parent == null || child == null || child.isIncomplete()) 457 return Collections.emptyList(); 458 RelationChecker test = new RelationChecker(); 459 LinkedList<Relation> path = new LinkedList<>(); 460 path.add(parent); 461 test.checkLoop(child, path); 462 if (test.loops.isEmpty()) 463 return Collections.emptyList(); 464 else 465 return Collections.unmodifiableList(test.loops.iterator().next()); 466 } 467 384 468 } -
src/org/openstreetmap/josm/gui/dialogs/relation/GenericRelationEditor.java
55 55 import org.openstreetmap.josm.data.osm.Relation; 56 56 import org.openstreetmap.josm.data.osm.RelationMember; 57 57 import org.openstreetmap.josm.data.osm.Tag; 58 import org.openstreetmap.josm.data.validation.tests.RelationChecker; 58 59 import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil; 59 60 import org.openstreetmap.josm.gui.MainApplication; 60 61 import org.openstreetmap.josm.gui.MainMenu; … … 884 885 * @param primitive the concerned primitive 885 886 */ 886 887 public static void warnOfCircularReferences(OsmPrimitive primitive) { 887 String msg = tr("<html>You are trying to add a relation to itself.<br>" 888 + "<br>" 889 + "This creates circular references and is therefore discouraged.<br>" 890 + "Skipping relation ''{0}''.</html>", 891 Utils.escapeReservedCharactersHTML(primitive.getDisplayName(DefaultNameFormatter.getInstance()))); 888 warnOfCircularReferences(primitive, Collections.emptyList()); 889 } 890 891 /** 892 * Warn about circular references. 893 * @param primitive the concerned primitive 894 * @param loop list of relation that form the circular dependencies 895 * @since xxx 896 */ 897 898 public static void warnOfCircularReferences(OsmPrimitive primitive, List<Relation> loop) { 899 final String msg; 900 if (loop.size() <= 2) { 901 msg = tr("<html>You are trying to add a relation to itself.<br>" 902 + "<br>" 903 + "This creates a circular dependency and is therefore discouraged.<br>" 904 + "Skipping relation ''{0}''.</html>", 905 Utils.escapeReservedCharactersHTML(primitive.getDisplayName(DefaultNameFormatter.getInstance()))); 906 907 } else { 908 msg = tr("<html>You are trying to add a relation which refers to the relation in the editor.<br>" 909 + "<br>" 910 + "This creates a circular dependency and is therefore discouraged.<br>" 911 + "Skipping relation ''{0}''." 912 + "<br>" 913 + "Relations that would build the circular dependency:<br>{1}</html>", 914 Utils.escapeReservedCharactersHTML(primitive.getDisplayName(DefaultNameFormatter.getInstance())), 915 loop.stream().map( 916 p -> Utils.escapeReservedCharactersHTML(p.getDisplayName(DefaultNameFormatter.getInstance()))) 917 .collect(Collectors.joining(" -> <br>")) 918 ); 919 } 892 920 JOptionPane.showMessageDialog( 893 921 MainApplication.getMainFrame(), 894 922 msg, … … 895 923 tr("Warning"), 896 924 JOptionPane.WARNING_MESSAGE); 897 925 } 898 899 926 /** 900 927 * Adds primitives to a given relation. 901 928 * @param orig The relation to modify … … 911 938 Relation relation = new Relation(orig); 912 939 boolean modified = false; 913 940 for (OsmPrimitive p : primitivesToAdd) { 914 if (p instanceof Relation && orig.equals(p)) { 915 warnOfCircularReferences(p); 916 continue; 941 if (p instanceof Relation) { 942 List<Relation> loop = RelationChecker.checkAddMember(relation, (Relation) p); 943 if (!loop.isEmpty() && loop.get(0).equals(loop.get(loop.size() - 1))) { 944 warnOfCircularReferences(p, loop); 945 continue; 946 } 917 947 } else if (MemberTableModel.hasMembersReferringTo(relation.getMembers(), Collections.singleton(p)) 918 948 && !confirmAddingPrimitive(p)) { 919 949 continue; -
src/org/openstreetmap/josm/gui/dialogs/relation/actions/AddFromSelectionAction.java
7 7 8 8 import org.openstreetmap.josm.data.osm.OsmPrimitive; 9 9 import org.openstreetmap.josm.data.osm.Relation; 10 import org.openstreetmap.josm.data.validation.tests.RelationChecker; 10 11 import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil; 11 12 import org.openstreetmap.josm.gui.dialogs.relation.GenericRelationEditor; 12 13 import org.openstreetmap.josm.gui.dialogs.relation.GenericRelationEditor.AddAbortException; … … 33 34 List<OsmPrimitive> ret = new ArrayList<>(); 34 35 ConditionalOptionPaneUtil.startBulkOperation("add_primitive_to_relation"); 35 36 for (OsmPrimitive primitive : primitives) { 36 if (primitive instanceof Relation 37 && editorAccess.getEditor().getRelation() != null && editorAccess.getEditor().getRelation().equals(primitive)) { 38 GenericRelationEditor.warnOfCircularReferences(primitive); 39 continue; 37 if (primitive instanceof Relation) { 38 List<Relation> loop = RelationChecker.checkAddMember(editorAccess.getEditor().getRelation(), (Relation) primitive); 39 if (!loop.isEmpty() && loop.get(0).equals(loop.get(loop.size() - 1))) { 40 GenericRelationEditor.warnOfCircularReferences(primitive, loop); 41 continue; 42 } 40 43 } 41 44 if (isPotentialDuplicate(primitive)) { 42 45 if (GenericRelationEditor.confirmAddingPrimitive(primitive)) {