Ticket #9844: RelationChecker.java-1.1.patch

File RelationChecker.java-1.1.patch, 13.7 KB (added by wiktorn, 10 years ago)

corrected version

  • 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;
    1111import java.util.HashSet;
    1212import java.util.LinkedList;
    13 import java.util.List;
    1413import java.util.Set;
    1514
    1615import org.openstreetmap.josm.command.Command;
    1716import org.openstreetmap.josm.command.DeleteCommand;
    18 import org.openstreetmap.josm.data.osm.Node;
    1917import org.openstreetmap.josm.data.osm.OsmPrimitive;
    2018import org.openstreetmap.josm.data.osm.Relation;
    2119import org.openstreetmap.josm.data.osm.RelationMember;
    22 import org.openstreetmap.josm.data.osm.Way;
    2320import org.openstreetmap.josm.data.validation.Severity;
    2421import org.openstreetmap.josm.data.validation.Test;
    2522import org.openstreetmap.josm.data.validation.TestError;
     
    3027import org.openstreetmap.josm.gui.tagging.TaggingPresetItems.Roles;
    3128import org.openstreetmap.josm.gui.tagging.TaggingPresetType;
    3229import org.openstreetmap.josm.gui.tagging.TaggingPresets;
     30import org.openstreetmap.josm.tools.Utils;
    3331
    3432/**
    3533 * Check for wrong relations.
     
    8280        }
    8381    }
    8482
     83    private static class RolePreset {
     84        public RolePreset(LinkedList<Role> roles, String name) {
     85            this.roles = roles;
     86            this.name = name;
     87        }
     88        private LinkedList<Role> roles;
     89        private String name;
     90    }
     91
    8592    private static class RoleInfo {
    8693        private int total = 0;
    87         private Collection<Node> nodes = new LinkedList<>();
    88         private Collection<Way> ways = new LinkedList<>();
    89         private Collection<Way> openways = new LinkedList<>();
    90         private Collection<Relation> relations = new LinkedList<>();
    9194    }
    9295
    9396    @Override
    9497    public void visit(Relation n) {
    95         LinkedList<Role> allroles = buildAllRoles(n);
     98        HashMap<String, RolePreset> allroles = buildAllRoles(n);
    9699        if (allroles.isEmpty() && n.hasTag("type", "route")
    97100                && n.hasTag("route", "train", "subway", "monorail", "tram", "bus", "trolleybus", "aerialway", "ferry")) {
    98101            errors.add(new TestError(this, Severity.WARNING,
     
    117120            RoleInfo ri = map.get(role);
    118121            if (ri == null) {
    119122                ri = new RoleInfo();
     123                map.put(role, ri);
    120124            }
    121125            ri.total++;
    122             if (m.isRelation()) {
    123                 ri.relations.add(m.getRelation());
    124             } else if(m.isWay()) {
    125                 ri.ways.add(m.getWay());
    126                 if (!m.getWay().isClosed()) {
    127                     ri.openways.add(m.getWay());
    128                 }
    129             }
    130             else if (m.isNode()) {
    131                 ri.nodes.add(m.getNode());
    132             }
    133             map.put(role, ri);
    134126        }
    135127        return map;
    136128    }
    137129
    138     private LinkedList<Role> buildAllRoles(Relation n) {
    139         LinkedList<Role> allroles = new LinkedList<>();
     130    // return Roles grouped by key
     131    private HashMap<String, RolePreset> buildAllRoles(Relation n) {
     132        HashMap<String, RolePreset> allroles = new HashMap<>();
     133
    140134        for (TaggingPreset p : relationpresets) {
    141135            boolean matches = true;
    142136            Roles r = null;
     
    152146                }
    153147            }
    154148            if (matches && r != null) {
    155                 allroles.addAll(r.roles);
     149                for(Role role: r.roles) {
     150                    String key = role.key;
     151                    LinkedList<Role> roleGroup = null;
     152                    if (allroles.containsKey(key)) {
     153                        roleGroup = allroles.get(key).roles;
     154                    } else {
     155                        roleGroup = new LinkedList<>();
     156                        allroles.put(key, new RolePreset(roleGroup, p.name));
     157                    }
     158                    roleGroup.add(role);
     159                }
    156160            }
    157161        }
    158162        return allroles;
    159163    }
    160164
    161     private void checkRoles(Relation n, LinkedList<Role> allroles, HashMap<String, RoleInfo> map) {
    162         List<String> done = new LinkedList<>();
    163         // Remove empty roles if several exist (like in route=hiking, see #9844)
    164         List<Role> emptyRoles = new LinkedList<>();
    165         for (Role r : allroles) {
    166             if ("".equals(r.key)) {
    167                 emptyRoles.add(r);
    168             }
     165    private boolean checkMemberType(Role r, RelationMember member) {
     166        if (r.types != null) {
     167            return r.types.contains(member.getDisplayType());
     168        } else {
     169            // if no types specified, then test is passed
     170            return true;
     171        }
     172    }
     173
     174    //
     175    /**
     176     * get all role definition for specified key and check, if some definition matches
     177     *
     178     * @param rolePreset containing preset for role of the member
     179     * @param member to be verified
     180     * @param n relation to be verified
     181     * @return <tt>true</tt> if member passed any of definition within preset
     182     *
     183     */
     184    private boolean checkMemberExpressionAndType(RolePreset rolePreset, RelationMember member, Relation n) {
     185        TestError possibleMatchError = null;
     186        if (rolePreset == null || rolePreset.roles == null) {
     187            // no restrictions on role types
     188            return true;
    169189        }
    170         if (emptyRoles.size() > 1) {
    171             allroles.removeAll(emptyRoles);
     190        // iterate through all of the role definition within preset
     191        // and look for any matching definition
     192        for (Role r: rolePreset.roles) {
     193            if (checkMemberType(r, member)) {
     194                // member type accepted by role definition
     195                if (r.memberExpression == null) {
     196                    // no member expression - so all requirements met
     197                    return true;
     198                } else {
     199                    // verify if preset accepts such member
     200                    OsmPrimitive primitive = member.getMember();
     201                    if(!primitive.isUsable()) {
     202                        // if member is not usable (i.e. not present in working set)
     203                        // we can't verify expression - so we just skip it
     204                        return true;
     205                    } else {
     206                        // verify expression
     207                        if(r.memberExpression.match(primitive)) {
     208                            return true;
     209                        } else {
     210                            // possible match error
     211                            // we still need to iterate further, as we might have
     212                            // different present, for which memberExpression will match
     213                            // but stash the error in case no better reason will be found later
     214                            String s = marktr("Role member did not match expression {0} in template {1}");
     215                            possibleMatchError = new TestError(this, Severity.WARNING, ROLE_VERIF_PROBLEM_MSG,
     216                                    tr(s, r.memberExpression, rolePreset.name), s, WRONG_TYPE,
     217                                        member.getMember().isUsable() ? member.getMember() : n);
     218
     219                        }
     220                    }
     221                }
     222            }
    172223        }
    173         for (Role r : allroles) {
    174             done.add(r.key);
    175             String keyname = r.key;
    176             if ("".equals(keyname)) {
    177                 keyname = tr("<empty>");
     224
     225        if( possibleMatchError != null) {
     226            // if any error found, then assume that member type was correct
     227            // and complain about not matching the memberExpression
     228            // (the only failure, that we could gather)
     229            errors.add(possibleMatchError);
     230        } else {
     231            // no errors found till now. So member at least failed at matching the type
     232            // it could also fail at memberExpression, but we can't guess at which
     233            String s = marktr("Role member type {0} did not match accepted list of {1} in template {2}");
     234
     235            // prepare Set of all accepted types in template
     236            EnumSet<TaggingPresetType> types = EnumSet.noneOf(TaggingPresetType.class);
     237            for (Role r: rolePreset.roles) {
     238                types.addAll(r.types);
    178239            }
    179             RoleInfo ri = map.get(r.key);
    180             checkRoleCounts(n, r, keyname, ri);
    181             if (ri != null) {
    182                 if (r.types != null) {
    183                     checkRoleTypes(n, r, keyname, ri);
     240
     241            // convert in localization friendly way to string of accepted types
     242            String typesStr = Utils.join("/", Utils.transform(types, new Utils.Function<TaggingPresetType, Object>() {
     243                public Object apply(TaggingPresetType x) {
     244                    return tr(x.getName());
    184245                }
    185                 if (r.memberExpression != null) {
    186                     checkRoleMemberExpressions(n, r, keyname, ri);
     246            }));
     247
     248            errors.add(new TestError(this, Severity.WARNING, ROLE_VERIF_PROBLEM_MSG,
     249                    tr(s, member.getType(), typesStr, rolePreset.name), s, WRONG_TYPE,
     250                        member.getMember().isUsable() ? member.getMember() : n));
     251        }
     252        return false;
     253    }
     254
     255
     256    private void checkRoles(Relation n, HashMap<String, RolePreset> allroles, HashMap<String, RoleInfo> map) {
     257        Set<String> done = new HashSet<>();
     258
     259        // go through all members of relation
     260        for (RelationMember member: n.getMembers()) {
     261            String role = member.getRole();
     262            done.add(role);
     263
     264            // error reporting done inside
     265            checkMemberExpressionAndType(allroles.get(role), member, n);
     266        }
     267
     268        // verify role counts based on whole role sets
     269        for(RolePreset rp: allroles.values()) {
     270            for (Role r: rp.roles) {
     271                String keyname = r.key;
     272                if ("".equals(keyname)) {
     273                    keyname = tr("<empty>");
    187274                }
     275                checkRoleCounts(n, r, keyname, map.get(r.key));
    188276            }
    189277        }
     278        // verify unwanted members
    190279        for (String key : map.keySet()) {
    191280            if (!done.contains(key)) {
     281                StringBuilder templates = new StringBuilder();
     282                for (RolePreset rp: allroles.values()) {
     283                    templates.append(rp.name);
     284                }
     285
    192286                if (key.length() > 0) {
    193                     String s = marktr("Role {0} unknown");
     287                    String s = marktr("Role {0} unknown in templates {1}");
     288
    194289                    errors.add(new TestError(this, Severity.WARNING, ROLE_VERIF_PROBLEM_MSG,
    195                             tr(s, key), MessageFormat.format(s, key), ROLE_UNKNOWN, n));
     290                            tr(s, key, templates.toString()), MessageFormat.format(s, key), ROLE_UNKNOWN, n));
    196291                } else {
    197                     String s = marktr("Empty role found");
     292                    String s = marktr("Empty role found in templates {0}");
    198293                    errors.add(new TestError(this, Severity.WARNING, ROLE_VERIF_PROBLEM_MSG,
    199                             tr(s), s, ROLE_EMPTY, n));
     294                            tr(s, templates), s, ROLE_EMPTY, n));
    200295                }
    201296            }
    202297        }
    203298    }
    204299
    205     private void checkRoleMemberExpressions(Relation n, Role r, String keyname, RoleInfo ri) {
    206         Set<OsmPrimitive> notMatching = new HashSet<>();
    207         Collection<OsmPrimitive> allPrimitives = new ArrayList<>();
    208         allPrimitives.addAll(ri.nodes);
    209         allPrimitives.addAll(ri.ways);
    210         allPrimitives.addAll(ri.relations);
    211         for (OsmPrimitive p : allPrimitives) {
    212             if (p.isUsable() && !r.memberExpression.match(p)) {
    213                 notMatching.add(p);
    214             }
    215         }
    216         if (!notMatching.isEmpty()) {
    217             String s = marktr("Member for role ''{0}'' does not match ''{1}''");
    218             LinkedList<OsmPrimitive> highlight = new LinkedList<>(notMatching);
    219             highlight.addFirst(n);
    220             errors.add(new TestError(this, Severity.WARNING, ROLE_VERIF_PROBLEM_MSG,
    221                     tr(s, keyname, r.memberExpression), MessageFormat.format(s, keyname, r.memberExpression), WRONG_TYPE,
    222                     highlight, notMatching));
    223         }
    224     }
    225 
    226     private void checkRoleTypes(Relation n, Role r, String keyname, RoleInfo ri) {
    227         Set<OsmPrimitive> wrongTypes = new HashSet<>();
    228         if (!r.types.contains(TaggingPresetType.WAY)) {
    229             wrongTypes.addAll(r.types.contains(TaggingPresetType.CLOSEDWAY) ? ri.openways : ri.ways);
    230         }
    231         if (!r.types.contains(TaggingPresetType.NODE)) {
    232             wrongTypes.addAll(ri.nodes);
    233         }
    234         if (!r.types.contains(TaggingPresetType.RELATION)) {
    235             wrongTypes.addAll(ri.relations);
    236         }
    237         if (!wrongTypes.isEmpty()) {
    238             String s = marktr("Member for role {0} of wrong type");
    239             LinkedList<OsmPrimitive> highlight = new LinkedList<>(wrongTypes);
    240             highlight.addFirst(n);
    241             errors.add(new TestError(this, Severity.WARNING, ROLE_VERIF_PROBLEM_MSG,
    242                     tr(s, keyname), MessageFormat.format(s, keyname), WRONG_TYPE,
    243                     highlight, wrongTypes));
    244         }
    245     }
    246300
    247301    private void checkRoleCounts(Relation n, Role r, String keyname, RoleInfo ri) {
    248302        long count = (ri == null) ? 0 : ri.total;