Ticket #9844: RelationChecker.java-1.2.patch

File RelationChecker.java-1.2.patch, 17.8 KB (added by wiktorn, 11 years ago)

patch version 1.2

  • data/defaultpresets.xml

     
    68536853            </optional>
    68546854            <roles>
    68556855                <role key="from" text="from way" requisite="required" count="1" type="way" />
    6856                 <role key="via" text="via node or ways" requisite="required" type="way,node" />
     6856                <role key="via" text="via node or ways" requisite="required" count="1" type="way,node" />
    68576857                <role key="to" text="to way" requisite="required" count="1" type="way" />
    68586858            </roles>
    68596859        </item>
  • src/org/openstreetmap/josm/data/validation/tests/RelationChecker.java

     
    55import static org.openstreetmap.josm.tools.I18n.tr;
    66
    77import java.text.MessageFormat;
    8 import java.util.ArrayList;
    98import java.util.Collection;
     9import java.util.EnumSet;
    1010import java.util.HashMap;
    11 import java.util.HashSet;
    1211import java.util.LinkedList;
    13 import java.util.List;
    14 import java.util.Set;
    1512
    1613import org.openstreetmap.josm.command.Command;
    1714import org.openstreetmap.josm.command.DeleteCommand;
    18 import org.openstreetmap.josm.data.osm.Node;
    1915import org.openstreetmap.josm.data.osm.OsmPrimitive;
     16import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
    2017import org.openstreetmap.josm.data.osm.Relation;
    2118import org.openstreetmap.josm.data.osm.RelationMember;
    22 import org.openstreetmap.josm.data.osm.Way;
    2319import org.openstreetmap.josm.data.validation.Severity;
    2420import org.openstreetmap.josm.data.validation.Test;
    2521import org.openstreetmap.josm.data.validation.TestError;
     
    3026import org.openstreetmap.josm.gui.tagging.TaggingPresetItems.Roles;
    3127import org.openstreetmap.josm.gui.tagging.TaggingPresetType;
    3228import org.openstreetmap.josm.gui.tagging.TaggingPresets;
     29import org.openstreetmap.josm.tools.Utils;
    3330
    3431/**
    3532 * Check for wrong relations.
     
    8582        }
    8683    }
    8784
     85    private static class RolePreset {
     86        public RolePreset(LinkedList<Role> roles, String name) {
     87            this.roles = roles;
     88            this.name = name;
     89        }
     90        private LinkedList<Role> roles;
     91        private String name;
     92    }
     93
    8894    private static class RoleInfo {
    8995        private int total = 0;
    90         private Collection<Node> nodes = new LinkedList<>();
    91         private Collection<Way> ways = new LinkedList<>();
    92         private Collection<Way> openways = new LinkedList<>();
    93         private Collection<Relation> relations = new LinkedList<>();
    9496    }
    9597
    9698    @Override
    9799    public void visit(Relation n) {
    98         LinkedList<Role> allroles = buildAllRoles(n);
     100        HashMap<String, RolePreset> allroles = buildAllRoles(n);
    99101        if (allroles.isEmpty() && n.hasTag("type", "route")
    100102                && n.hasTag("route", "train", "subway", "monorail", "tram", "bus", "trolleybus", "aerialway", "ferry")) {
    101103            errors.add(new TestError(this, Severity.WARNING,
     
    120122            RoleInfo ri = map.get(role);
    121123            if (ri == null) {
    122124                ri = new RoleInfo();
     125                map.put(role, ri);
    123126            }
    124127            ri.total++;
    125             if (m.isRelation()) {
    126                 ri.relations.add(m.getRelation());
    127             } else if(m.isWay()) {
    128                 ri.ways.add(m.getWay());
    129                 if (!m.getWay().isClosed()) {
    130                     ri.openways.add(m.getWay());
    131                 }
    132             }
    133             else if (m.isNode()) {
    134                 ri.nodes.add(m.getNode());
    135             }
    136             map.put(role, ri);
    137128        }
    138129        return map;
    139130    }
    140131
    141     private LinkedList<Role> buildAllRoles(Relation n) {
    142         LinkedList<Role> allroles = new LinkedList<>();
     132    // return Roles grouped by key
     133    private HashMap<String, RolePreset> buildAllRoles(Relation n) {
     134        HashMap<String, RolePreset> allroles = new HashMap<>();
     135
    143136        for (TaggingPreset p : relationpresets) {
    144137            boolean matches = true;
    145138            Roles r = null;
     
    155148                }
    156149            }
    157150            if (matches && r != null) {
    158                 allroles.addAll(r.roles);
     151                for(Role role: r.roles) {
     152                    String key = role.key;
     153                    LinkedList<Role> roleGroup = null;
     154                    if (allroles.containsKey(key)) {
     155                        roleGroup = allroles.get(key).roles;
     156                    } else {
     157                        roleGroup = new LinkedList<>();
     158                        allroles.put(key, new RolePreset(roleGroup, p.name));
     159                    }
     160                    roleGroup.add(role);
     161                }
    159162            }
    160163        }
    161164        return allroles;
    162165    }
    163166
    164     private void checkRoles(Relation n, LinkedList<Role> allroles, HashMap<String, RoleInfo> map) {
    165         List<String> done = new LinkedList<>();
    166         // Remove empty roles if several exist (like in route=hiking, see #9844)
    167         List<Role> emptyRoles = new LinkedList<>();
    168         for (Role r : allroles) {
    169             if ("".equals(r.key)) {
    170                 emptyRoles.add(r);
     167    private boolean checkMemberType(Role r, RelationMember member) {
     168        if (r.types != null) {
     169            if (member.getDisplayType().equals(OsmPrimitiveType.NODE)) {
     170                return r.types.contains(TaggingPresetType.NODE);
    171171            }
    172         }
    173         if (emptyRoles.size() > 1) {
    174             allroles.removeAll(emptyRoles);
    175         }
    176         for (Role r : allroles) {
    177             done.add(r.key);
    178             String keyname = r.key;
    179             if ("".equals(keyname)) {
    180                 keyname = tr("<empty>");
     172            if (member.getDisplayType().equals(OsmPrimitiveType.CLOSEDWAY)) {
     173                return r.types.contains(TaggingPresetType.CLOSEDWAY);
    181174            }
    182             RoleInfo ri = map.get(r.key);
    183             checkRoleCounts(n, r, keyname, ri);
    184             if (ri != null) {
    185                 if (r.types != null) {
    186                     checkRoleTypes(n, r, keyname, ri);
    187                 }
    188                 if (r.memberExpression != null) {
    189                     checkRoleMemberExpressions(n, r, keyname, ri);
    190                 }
     175            if (member.getDisplayType().equals(OsmPrimitiveType.WAY)) {
     176                return r.types.contains(TaggingPresetType.WAY);
     177            }
     178            if (member.getDisplayType().equals(OsmPrimitiveType.RELATION)) {
     179                return r.types.contains(TaggingPresetType.RELATION);
    191180            }
     181            // not matching type
     182            return false;
     183        } else {
     184            // if no types specified, then test is passed
     185            return true;
    192186        }
    193         for (String key : map.keySet()) {
    194             if (!done.contains(key)) {
    195                 if (key.length() > 0) {
    196                     String s = marktr("Role {0} unknown");
    197                     errors.add(new TestError(this, Severity.WARNING, ROLE_VERIF_PROBLEM_MSG,
    198                             tr(s, key), MessageFormat.format(s, key), ROLE_UNKNOWN, n));
     187    }
     188
     189    //
     190    /**
     191     * get all role definition for specified key and check, if some definition matches
     192     *
     193     * @param rolePreset containing preset for role of the member
     194     * @param member to be verified
     195     * @param n relation to be verified
     196     * @return <tt>true</tt> if member passed any of definition within preset
     197     *
     198     */
     199    private boolean checkMemberExpressionAndType(RolePreset rolePreset, RelationMember member, Relation n) {
     200        TestError possibleMatchError = null;
     201        if (rolePreset == null || rolePreset.roles == null) {
     202            // no restrictions on role types
     203            return true;
     204        }
     205        // iterate through all of the role definition within preset
     206        // and look for any matching definition
     207        for (Role r: rolePreset.roles) {
     208            if (checkMemberType(r, member)) {
     209                // member type accepted by role definition
     210                if (r.memberExpression == null) {
     211                    // no member expression - so all requirements met
     212                    return true;
    199213                } else {
    200                     String s = marktr("Empty role found");
    201                     errors.add(new TestError(this, Severity.WARNING, ROLE_VERIF_PROBLEM_MSG,
    202                             tr(s), s, ROLE_EMPTY, n));
     214                    // verify if preset accepts such member
     215                    OsmPrimitive primitive = member.getMember();
     216                    if(!primitive.isUsable()) {
     217                        // if member is not usable (i.e. not present in working set)
     218                        // we can't verify expression - so we just skip it
     219                        return true;
     220                    } else {
     221                        // verify expression
     222                        if(r.memberExpression.match(primitive)) {
     223                            return true;
     224                        } else {
     225                            // possible match error
     226                            // we still need to iterate further, as we might have
     227                            // different present, for which memberExpression will match
     228                            // but stash the error in case no better reason will be found later
     229                            String s = marktr("Role member does not match expression {0} in template {1}");
     230                            possibleMatchError = new TestError(this, Severity.WARNING, ROLE_VERIF_PROBLEM_MSG,
     231                                    tr(s, r.memberExpression, rolePreset.name), s, WRONG_TYPE,
     232                                    member.getMember().isUsable() ? member.getMember() : n);
     233
     234                        }
     235                    }
    203236                }
    204237            }
    205238        }
    206     }
    207239
    208     private void checkRoleMemberExpressions(Relation n, Role r, String keyname, RoleInfo ri) {
    209         Set<OsmPrimitive> notMatching = new HashSet<>();
    210         Collection<OsmPrimitive> allPrimitives = new ArrayList<>();
    211         allPrimitives.addAll(ri.nodes);
    212         allPrimitives.addAll(ri.ways);
    213         allPrimitives.addAll(ri.relations);
    214         for (OsmPrimitive p : allPrimitives) {
    215             if (p.isUsable() && !r.memberExpression.match(p)) {
    216                 notMatching.add(p);
     240        if( possibleMatchError != null) {
     241            // if any error found, then assume that member type was correct
     242            // and complain about not matching the memberExpression
     243            // (the only failure, that we could gather)
     244            errors.add(possibleMatchError);
     245        } else {
     246            // no errors found till now. So member at least failed at matching the type
     247            // it could also fail at memberExpression, but we can't guess at which
     248            String s = marktr("Role member type {0} does not match accepted list of {1} in template {2}");
     249
     250            // prepare Set of all accepted types in template
     251            EnumSet<TaggingPresetType> types = EnumSet.noneOf(TaggingPresetType.class);
     252            for (Role r: rolePreset.roles) {
     253                types.addAll(r.types);
    217254            }
    218         }
    219         if (!notMatching.isEmpty()) {
    220             String s = marktr("Member for role ''{0}'' does not match ''{1}''");
    221             LinkedList<OsmPrimitive> highlight = new LinkedList<>(notMatching);
    222             highlight.addFirst(n);
     255
     256            // convert in localization friendly way to string of accepted types
     257            String typesStr = Utils.join("/", Utils.transform(types, new Utils.Function<TaggingPresetType, Object>() {
     258                public Object apply(TaggingPresetType x) {
     259                    return tr(x.getName());
     260                }
     261            }));
     262
    223263            errors.add(new TestError(this, Severity.WARNING, ROLE_VERIF_PROBLEM_MSG,
    224                     tr(s, keyname, r.memberExpression), MessageFormat.format(s, keyname, r.memberExpression), WRONG_TYPE,
    225                     highlight, notMatching));
     264                    tr(s, member.getType(), typesStr, rolePreset.name), s, WRONG_TYPE,
     265                    member.getMember().isUsable() ? member.getMember() : n));
    226266        }
     267        return false;
    227268    }
    228269
    229     private void checkRoleTypes(Relation n, Role r, String keyname, RoleInfo ri) {
    230         Set<OsmPrimitive> wrongTypes = new HashSet<>();
    231         if (!r.types.contains(TaggingPresetType.WAY)) {
    232             wrongTypes.addAll(r.types.contains(TaggingPresetType.CLOSEDWAY) ? ri.openways : ri.ways);
    233         }
    234         if (!r.types.contains(TaggingPresetType.NODE)) {
    235             wrongTypes.addAll(ri.nodes);
     270
     271    /**
     272     *
     273     * @param n relation to validate
     274     * @param allroles contains presets for specified relation
     275     * @param map contains statistics of occurances of specified role types in relation
     276     */
     277    private void checkRoles(Relation n, HashMap<String, RolePreset> allroles, HashMap<String, RoleInfo> map) {
     278        // go through all members of relation
     279        for (RelationMember member: n.getMembers()) {
     280            String role = member.getRole();
     281
     282
     283            // error reporting done inside
     284            checkMemberExpressionAndType(allroles.get(role), member, n);
     285
    236286        }
    237         if (!r.types.contains(TaggingPresetType.RELATION)) {
    238             wrongTypes.addAll(ri.relations);
     287
     288        // verify role counts based on whole role sets
     289        for(RolePreset rp: allroles.values()) {
     290            for (Role r: rp.roles) {
     291                String keyname = r.key;
     292                if ("".equals(keyname)) {
     293                    keyname = tr("<empty>");
     294                }
     295                checkRoleCounts(n, r, keyname, map.get(r.key));
     296            }
    239297        }
    240         if (!wrongTypes.isEmpty()) {
    241             String s = marktr("Member for role {0} of wrong type");
    242             LinkedList<OsmPrimitive> highlight = new LinkedList<>(wrongTypes);
    243             highlight.addFirst(n);
    244             errors.add(new TestError(this, Severity.WARNING, ROLE_VERIF_PROBLEM_MSG,
    245                     tr(s, keyname), MessageFormat.format(s, keyname), WRONG_TYPE,
    246                     highlight, wrongTypes));
     298        // verify unwanted members
     299        for (String key : map.keySet()) {
     300            if (!allroles.containsKey(key)) {
     301                String templates = Utils.join("/", Utils.transform(allroles.keySet(), new Utils.Function<String, Object>() {
     302                    public Object apply(String x) {
     303                        return tr(x);
     304                    }
     305                }));
     306
     307                if (key.length() > 0) {
     308                    String s = marktr("Role {0} unknown in templates {1}");
     309
     310                    errors.add(new TestError(this, Severity.WARNING, ROLE_VERIF_PROBLEM_MSG,
     311                            tr(s, key, templates.toString()), MessageFormat.format(s, key), ROLE_UNKNOWN, n));
     312                } else {
     313                    String s = marktr("Empty role type found when expecting one of {0}");
     314                    errors.add(new TestError(this, Severity.WARNING, ROLE_VERIF_PROBLEM_MSG,
     315                            tr(s, templates), s, ROLE_EMPTY, n));
     316                }
     317            }
    247318        }
    248319    }
    249320
     321
    250322    private void checkRoleCounts(Relation n, Role r, String keyname, RoleInfo ri) {
    251323        long count = (ri == null) ? 0 : ri.total;
    252324        long vc = r.getValidCount(count);
  • test/unit/org/openstreetmap/josm/data/validation/tests/RelationCheckerTest.groovy

     
    11// License: GPL. For details, see LICENSE file.
    22package org.openstreetmap.josm.data.validation.tests
    33
     4import static org.openstreetmap.josm.TestUtils.createPrimitive
     5
    46import org.openstreetmap.josm.JOSMFixture
    57import org.openstreetmap.josm.data.osm.Relation
    68import org.openstreetmap.josm.data.osm.RelationMember
     
    810import org.openstreetmap.josm.data.validation.TestError
    911import org.openstreetmap.josm.gui.tagging.TaggingPresets
    1012
    11 import static org.openstreetmap.josm.TestUtils.createPrimitive
    12 
    1313class RelationCheckerTest extends GroovyTestCase {
    1414
    1515    @Override
     
    5454
    5555        def errors = testRelation(r)
    5656        assert errors.size() == 1
    57         assert errors.get(0).getDescription() == "Role outer2 unknown"
     57        assert errors.get(0).getDescription() == "Role outer2 unknown in templates outer/inner"
    5858    }
    5959
    6060    void testRestrictionViaMissing() {
     
    7575
    7676        def errors = testRelation(r)
    7777        assert errors.size() == 1
    78         assert errors.get(0).getDescription() == "Member for role via of wrong type"
     78        assert errors.get(0).getDescription() == "Role member type relation does not match accepted list of node/way in template Turn restriction"
    7979    }
    8080
    8181    void testRestrictionTwoFrom() {
     
    9999
    100100        def errors = testRelation(r)
    101101        assert errors.size() == 1
    102         assert errors.get(0).getDescription() == "Empty role found"
     102        assert errors.get(0).getDescription().startsWith("Empty role type found when expecting one of")
    103103    }
    104104
    105105    void testPowerMemberExpression() {
     
    108108
    109109        def errors = testRelation(r)
    110110        assert errors.size() == 1
    111         assert errors.get(0).getDescription() == "Member for role '<empty>' does not match 'power'"
     111        assert errors.get(0).getDescription() == "Role member does not match expression power in template Power route"
    112112    }
    113113}