Index: trunk/src/org/openstreetmap/josm/data/validation/tests/RelationChecker.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/validation/tests/RelationChecker.java	(revision 11988)
+++ trunk/src/org/openstreetmap/josm/data/validation/tests/RelationChecker.java	(revision 11989)
@@ -95,14 +95,4 @@
     }
 
-    private static class RolePreset {
-        private final List<Role> roles;
-        private final String name;
-
-        RolePreset(List<Role> roles, String name) {
-            this.roles = roles;
-            this.name = name;
-        }
-    }
-
     private static class RoleInfo {
         private int total;
@@ -111,5 +101,5 @@
     @Override
     public void visit(Relation n) {
-        Map<String, RolePreset> allroles = buildAllRoles(n);
+        List<Role> allroles = buildAllRoles(n);
         if (allroles.isEmpty() && n.hasTag("type", "route")
                 && n.hasTag("route", "train", "subway", "monorail", "tram", "bus", "trolleybus", "aerialway", "ferry")) {
@@ -151,6 +141,6 @@
 
     // return Roles grouped by key
-    private static Map<String, RolePreset> buildAllRoles(Relation n) {
-        Map<String, RolePreset> allroles = new HashMap<>();
+    private static List<Role> buildAllRoles(Relation n) {
+        List<Role> allroles = new LinkedList<>();
 
         for (TaggingPreset p : relationpresets) {
@@ -158,15 +148,5 @@
             final Roles r = Utils.find(p.data, Roles.class);
             if (matches && r != null) {
-                for (Role role: r.roles) {
-                    String key = role.key;
-                    List<Role> roleGroup;
-                    if (allroles.containsKey(key)) {
-                        roleGroup = allroles.get(key).roles;
-                    } else {
-                        roleGroup = new LinkedList<>();
-                        allroles.put(key, new RolePreset(roleGroup, p.name));
-                    }
-                    roleGroup.add(role);
-                }
+                allroles.addAll(r.roles);
             }
         }
@@ -199,5 +179,5 @@
      * get all role definition for specified key and check, if some definition matches
      *
-     * @param rolePreset containing preset for role of the member
+     * @param allroles containing list of possible role presets of the member
      * @param member to be verified
      * @param n relation to be verified
@@ -205,13 +185,18 @@
      *
      */
-    private boolean checkMemberExpressionAndType(RolePreset rolePreset, RelationMember member, Relation n) {
-        if (rolePreset == null || rolePreset.roles == null) {
-            // no restrictions on role types
-            return true;
-        }
+    private boolean checkMemberExpressionAndType(List<Role> allroles, RelationMember member, Relation n) {
+        String role = member.getRole();
+        String name = null;
+        // Set of all accepted types in template
+        Collection<TaggingPresetType> types = EnumSet.noneOf(TaggingPresetType.class);
         TestError possibleMatchError = null;
         // iterate through all of the role definition within preset
         // and look for any matching definition
-        for (Role r: rolePreset.roles) {
+        for (Role r: allroles) {
+            if (!r.isRole(role)) {
+                continue;
+            }
+            name = r.key;
+            types.addAll(r.types);
             if (checkMemberType(r, member)) {
                 // member type accepted by role definition
@@ -238,5 +223,5 @@
                                     .message(ROLE_VERIF_PROBLEM_MSG,
                                             marktr("Role of relation member does not match expression ''{0}'' in template {1}"),
-                                            r.memberExpression, rolePreset.name)
+                                            r.memberExpression, name)
                                     .primitives(member.getMember().isUsable() ? member.getMember() : n)
                                     .build();
@@ -251,5 +236,8 @@
         }
 
-        if (possibleMatchError != null) {
+        if (name == null) {
+           return true;
+        }
+        else if (possibleMatchError != null) {
             // if any error found, then assume that member type was correct
             // and complain about not matching the memberExpression
@@ -260,10 +248,4 @@
             // it could also fail at memberExpression, but we can't guess at which
 
-            // prepare Set of all accepted types in template
-            Collection<TaggingPresetType> types = EnumSet.noneOf(TaggingPresetType.class);
-            for (Role r: rolePreset.roles) {
-                types.addAll(r.types);
-            }
-
             // convert in localization friendly way to string of accepted types
             String typesStr = types.stream().map(x -> tr(x.getName())).collect(Collectors.joining("/"));
@@ -272,5 +254,5 @@
                     .message(ROLE_VERIF_PROBLEM_MSG,
                             marktr("Type ''{0}'' of relation member with role ''{1}'' does not match accepted types ''{2}'' in template {3}"),
-                            member.getType(), member.getRole(), typesStr, rolePreset.name)
+                            member.getType(), member.getRole(), typesStr, name)
                     .primitives(member.getMember().isUsable() ? member.getMember() : n)
                     .build());
@@ -285,27 +267,34 @@
      * @param map contains statistics of occurances of specified role types in relation
      */
-    private void checkRoles(Relation n, Map<String, RolePreset> allroles, Map<String, RoleInfo> map) {
+    private void checkRoles(Relation n, List<Role> allroles, Map<String, RoleInfo> map) {
         // go through all members of relation
         for (RelationMember member: n.getMembers()) {
-            String role = member.getRole();
-
             // error reporting done inside
-            checkMemberExpressionAndType(allroles.get(role), member, n);
+            checkMemberExpressionAndType(allroles, member, n);
         }
 
         // verify role counts based on whole role sets
-        for (RolePreset rp: allroles.values()) {
-            for (Role r: rp.roles) {
-                String keyname = r.key;
-                if (keyname.isEmpty()) {
-                    keyname = tr("<empty>");
-                }
-                checkRoleCounts(n, r, keyname, map.get(r.key));
-            }
+        for (Role r: allroles) {
+            String keyname = r.key;
+            if (keyname.isEmpty()) {
+                keyname = tr("<empty>");
+            }
+            checkRoleCounts(n, r, keyname, map.get(r.key));
+        }
+        if("network".equals(n.get("type")) && !"bicycle".equals(n.get("route"))) {
+            return;
         }
         // verify unwanted members
         for (String key : map.keySet()) {
-            if (!allroles.containsKey(key) && !"network".equals(n.get("type")) && !"bicycle".equals(n.get("route"))) {
-                String templates = allroles.keySet().stream().collect(Collectors.joining("/"));
+            boolean found = false;
+            for (Role r: allroles) {
+                if (r.isRole(key)) {
+                    found = true;
+                    break;
+                }
+            }
+            
+            if (!found) {
+                String templates = allroles.stream().map(r -> r.key).collect(Collectors.joining("/"));
 
                 if (!key.isEmpty()) {
Index: trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/Roles.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/Roles.java	(revision 11988)
+++ trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/Roles.java	(revision 11989)
@@ -29,4 +29,6 @@
         /** Role name used in a relation */
         public String key; // NOSONAR
+        /** Is the role name a regular expression */
+        public boolean regexp; // NOSONAR
         /** The text to display */
         public String text; // NOSONAR
@@ -37,6 +39,7 @@
         /** An expression (cf. search dialog) for objects of this role */
         public SearchCompiler.Match memberExpression; // NOSONAR
-
+        /** Is this role required at least once in the relation? */
         public boolean required; // NOSONAR
+        /** How often must the element appear */
         private long count;
 
@@ -50,4 +53,11 @@
             } else if (!"optional".equals(str))
                 throw new SAXException(tr("Unknown requisite: {0}", str));
+        }
+
+        public void setRegexp(String str) throws SAXException {
+            if ("true".equals(str)) {
+                regexp = true;
+            } else if (!"false".equals(str))
+                throw new SAXException(tr("Unknown regexp value: {0}", str));
         }
 
@@ -83,4 +93,18 @@
             else
                 return c != 0 ? c : 1;
+        }
+
+        /**
+         * Check if the given role matches this class (required to check regexp role types)
+         * @param role role to check
+         * @return <code>true</code> if role matches
+         * @since 11989
+         */
+        public boolean isRole(String role) {
+            if (regexp && role != null) { // pass null through, it will anyway fail
+                
+                return role.matches(this.key);
+            }
+            return this.key.equals(role);
         }
 
