Index: applications/editors/josm/plugins/validator/build.xml
===================================================================
--- applications/editors/josm/plugins/validator/build.xml	(revision 21524)
+++ applications/editors/josm/plugins/validator/build.xml	(revision 21539)
@@ -27,5 +27,5 @@
 	-->
 	<property name="commit.message" value="don't break relations, when fixing duplicate ways" />
-	<property name="plugin.main.version" value="3118" />
+	<property name="plugin.main.version" value="3289" />
 
 
Index: applications/editors/josm/plugins/validator/src/org/openstreetmap/josm/plugins/validator/OSMValidatorPlugin.java
===================================================================
--- applications/editors/josm/plugins/validator/src/org/openstreetmap/josm/plugins/validator/OSMValidatorPlugin.java	(revision 21524)
+++ applications/editors/josm/plugins/validator/src/org/openstreetmap/josm/plugins/validator/OSMValidatorPlugin.java	(revision 21539)
@@ -44,4 +44,5 @@
 import org.openstreetmap.josm.plugins.validator.tests.NodesWithSameName;
 import org.openstreetmap.josm.plugins.validator.tests.OverlappingWays;
+import org.openstreetmap.josm.plugins.validator.tests.RelationChecker;
 import org.openstreetmap.josm.plugins.validator.tests.SelfIntersectingWay;
 import org.openstreetmap.josm.plugins.validator.tests.SimilarNamedWays;
@@ -83,5 +84,6 @@
      */
     @SuppressWarnings("unchecked")
-    public static Class<Test>[] allAvailableTests = new Class[] { DuplicateNode.class, // ID    1 ..   99
+    public static Class<Test>[] allAvailableTests = new Class[] {
+            DuplicateNode.class, // ID    1 ..   99
             OverlappingWays.class, // ID  101 ..  199
             UntaggedNode.class, // ID  201 ..  299
@@ -100,4 +102,5 @@
             NameMismatch.class, // ID  1501 ..  1599
             MultipolygonTest.class, // ID  1601 ..  1699
+            RelationChecker.class, // ID  1701 ..  1799
     };
 
Index: applications/editors/josm/plugins/validator/src/org/openstreetmap/josm/plugins/validator/ValidateAction.java
===================================================================
--- applications/editors/josm/plugins/validator/src/org/openstreetmap/josm/plugins/validator/ValidateAction.java	(revision 21524)
+++ applications/editors/josm/plugins/validator/src/org/openstreetmap/josm/plugins/validator/ValidateAction.java	(revision 21539)
@@ -106,8 +106,8 @@
 
     class ValidationTask extends PleaseWaitRunnable {
-    	private Collection<Test> tests;
-    	private Collection<OsmPrimitive> validatedPrimitmives;
-    	private Collection<OsmPrimitive> formerValidatedPrimitives;
-    	private boolean canceled;
+        private Collection<Test> tests;
+        private Collection<OsmPrimitive> validatedPrimitmives;
+        private Collection<OsmPrimitive> formerValidatedPrimitives;
+        private boolean canceled;
         private List<TestError> errors;
 
@@ -118,70 +118,70 @@
          * @param formerValidatedPrimitives the last collection of primitives being validates. May be null.
          */
-    	public ValidationTask(Collection<Test> tests, Collection<OsmPrimitive> validatedPrimitives, Collection<OsmPrimitive> formerValidatedPrimitives) {
-    		super(tr("Validating"), false /*don't ignore exceptions */);
-    		this.validatedPrimitmives  = validatedPrimitives;
-    		this.formerValidatedPrimitives = formerValidatedPrimitives;
-    		this.tests = tests;
-    	}
+        public ValidationTask(Collection<Test> tests, Collection<OsmPrimitive> validatedPrimitives, Collection<OsmPrimitive> formerValidatedPrimitives) {
+            super(tr("Validating"), false /*don't ignore exceptions */);
+            this.validatedPrimitmives  = validatedPrimitives;
+            this.formerValidatedPrimitives = formerValidatedPrimitives;
+            this.tests = tests;
+        }
 
-		@Override
-		protected void cancel() {
-			this.canceled = true;
-		}
+        @Override
+        protected void cancel() {
+            this.canceled = true;
+        }
 
-		@Override
-		protected void finish() {
-			if (canceled) return;
+        @Override
+        protected void finish() {
+            if (canceled) return;
 
-			// update GUI on Swing EDT
-			//
-			Runnable r = new Runnable()  {
-				public void run() {
-			        plugin.validationDialog.tree.setErrors(errors);
-			        plugin.validationDialog.setVisible(true);
-			        Main.main.getCurrentDataSet().fireSelectionChanged();
-				}
-			};
-			if (SwingUtilities.isEventDispatchThread()) {
-				r.run();
-			} else {
-				SwingUtilities.invokeLater(r);
-			}
-		}
+            // update GUI on Swing EDT
+            //
+            Runnable r = new Runnable()  {
+                public void run() {
+                    plugin.validationDialog.tree.setErrors(errors);
+                    plugin.validationDialog.setVisible(true);
+                    Main.main.getCurrentDataSet().fireSelectionChanged();
+                }
+            };
+            if (SwingUtilities.isEventDispatchThread()) {
+                r.run();
+            } else {
+                SwingUtilities.invokeLater(r);
+            }
+        }
 
-		@Override
-		protected void realRun() throws SAXException, IOException,
-				OsmTransferException {
-			if (tests == null || tests.isEmpty()) return;
-	        errors = new ArrayList<TestError>(200);
-	        getProgressMonitor().setTicksCount(tests.size() * validatedPrimitmives.size());
-	        int testCounter = 0;
-			for (Test test : tests) {
-				if (canceled) return;
-				testCounter++;
-				getProgressMonitor().setCustomText(tr("Test {0}/{1}: Starting {2}", testCounter, tests.size(),test.name));
-	            test.setPartialSelection(formerValidatedPrimitives != null);
-	            test.startTest(getProgressMonitor().createSubTaskMonitor(validatedPrimitmives.size(), false));
-	            test.visit(validatedPrimitmives);
-	            test.endTest();
-	            errors.addAll(test.getErrors());
-	        }
-			tests = null;
-	        if (Main.pref.getBoolean(PreferenceEditor.PREF_USE_IGNORE, true)) {
-				getProgressMonitor().subTask(tr("Updating ignored errors ..."));
-	            for (TestError error : errors) {
-	            	if (canceled) return;
-	                List<String> s = new ArrayList<String>();
-	                s.add(error.getIgnoreState());
-	                s.add(error.getIgnoreGroup());
-	                s.add(error.getIgnoreSubGroup());
-	                for (String state : s) {
-	                    if (state != null && plugin.ignoredErrors.contains(state)) {
-	                        error.setIgnored(true);
-	                    }
-	                }
-	            }
-	        }
-		}
+        @Override
+        protected void realRun() throws SAXException, IOException,
+                OsmTransferException {
+            if (tests == null || tests.isEmpty()) return;
+            errors = new ArrayList<TestError>(200);
+            getProgressMonitor().setTicksCount(tests.size() * validatedPrimitmives.size());
+            int testCounter = 0;
+            for (Test test : tests) {
+                if (canceled) return;
+                testCounter++;
+                getProgressMonitor().setCustomText(tr("Test {0}/{1}: Starting {2}", testCounter, tests.size(),test.name));
+                test.setPartialSelection(formerValidatedPrimitives != null);
+                test.startTest(getProgressMonitor().createSubTaskMonitor(validatedPrimitmives.size(), false));
+                test.visit(validatedPrimitmives);
+                test.endTest();
+                errors.addAll(test.getErrors());
+            }
+            tests = null;
+            if (Main.pref.getBoolean(PreferenceEditor.PREF_USE_IGNORE, true)) {
+                getProgressMonitor().subTask(tr("Updating ignored errors ..."));
+                for (TestError error : errors) {
+                    if (canceled) return;
+                    List<String> s = new ArrayList<String>();
+                    s.add(error.getIgnoreState());
+                    s.add(error.getIgnoreGroup());
+                    s.add(error.getIgnoreSubGroup());
+                    for (String state : s) {
+                        if (state != null && plugin.ignoredErrors.contains(state)) {
+                            error.setIgnored(true);
+                        }
+                    }
+                }
+            }
+        }
     }
 }
Index: applications/editors/josm/plugins/validator/src/org/openstreetmap/josm/plugins/validator/tests/RelationChecker.java
===================================================================
--- applications/editors/josm/plugins/validator/src/org/openstreetmap/josm/plugins/validator/tests/RelationChecker.java	(revision 21539)
+++ applications/editors/josm/plugins/validator/src/org/openstreetmap/josm/plugins/validator/tests/RelationChecker.java	(revision 21539)
@@ -0,0 +1,209 @@
+// License: GPL. See LICENSE file for details.
+package org.openstreetmap.josm.plugins.validator.tests;
+
+import static org.openstreetmap.josm.tools.I18n.marktr;
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.text.MessageFormat;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.Map;
+
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.Relation;
+import org.openstreetmap.josm.data.osm.RelationMember;
+import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.gui.preferences.TaggingPresetPreference;
+import org.openstreetmap.josm.gui.tagging.TaggingPreset;
+import org.openstreetmap.josm.plugins.validator.OSMValidatorPlugin;
+import org.openstreetmap.josm.plugins.validator.Severity;
+import org.openstreetmap.josm.plugins.validator.Test;
+import org.openstreetmap.josm.plugins.validator.TestError;
+
+/**
+ * Check for wrong relations
+ *
+ */
+public class RelationChecker extends Test
+{
+    protected static int ROLE_UNKNOWN      = 1701;
+    protected static int ROLE_EMPTY        = 1702;
+    protected static int WRONG_TYPE        = 1703;
+    protected static int HIGH_COUNT        = 1704;
+    protected static int LOW_COUNT         = 1705;
+    protected static int ROLE_MISSING      = 1706;
+    protected static int RELATION_UNKNOWN  = 1707;
+    protected static int RELATION_EMPTY    = 1708;
+
+    /**
+     * Constructor
+     */
+    public RelationChecker()
+    {
+        super(tr("Relation checker :"),
+                tr("This plugin checks for errors in relations."));
+    }
+
+    @Override
+    public void initialize(OSMValidatorPlugin plugin) throws Exception
+    {
+        initializePresets();
+    }
+
+    static Collection<TaggingPreset> relationpresets = new LinkedList<TaggingPreset>();
+    /**
+     * Reads the presets data.
+     *
+     */
+    public void initializePresets()
+    {
+        Collection<TaggingPreset> presets = TaggingPresetPreference.taggingPresets;
+        if(presets != null)
+        {
+            for(TaggingPreset p : presets)
+            {
+                for(TaggingPreset.Item i : p.data)
+                {
+                    if(i instanceof TaggingPreset.Roles)
+                    {
+                        relationpresets.add(p);
+                        break;
+                    }
+                }
+            }
+        }
+    }
+
+    public class RoleInfo
+    {
+        int total = 0;
+        int nodes = 0;
+        int ways = 0;
+        int closedways = 0;
+        int openways = 0;
+        int relations = 0;
+    }
+
+    @Override
+    public void visit(Relation n)
+    {
+        LinkedList<TaggingPreset.Role> allroles = new LinkedList<TaggingPreset.Role>();
+        for(TaggingPreset p : relationpresets)
+        {
+            boolean matches = true;
+            TaggingPreset.Roles r = null;
+            for(TaggingPreset.Item i : p.data)
+            {
+                if(i instanceof TaggingPreset.Key)
+                {
+                    TaggingPreset.Key k = (TaggingPreset.Key)i;
+                    if(!k.value.equals(n.get(k.key)))
+                    {
+                        matches = false;
+                        break;
+                    }
+                }
+                else if(i instanceof TaggingPreset.Roles)
+                    r = (TaggingPreset.Roles) i;
+            }
+            if(matches && r != null)
+                allroles.addAll(r.roles);
+        }
+        if(allroles.size() == 0)
+        {
+            errors.add( new TestError(this, Severity.WARNING, tr("Relation type is unknown"),
+            RELATION_UNKNOWN, n) );
+
+        }
+        else
+        {
+            HashMap<String,RoleInfo> map = new HashMap<String, RoleInfo>();
+            for(RelationMember m : n.getMembers()) {
+                String s = "";
+                if(m.hasRole())
+                    s = m.getRole();
+                RoleInfo ri = map.get(s);
+                if(ri == null)
+                    ri = new RoleInfo();
+                ri.total++;
+                if(m.isRelation())
+                    ri.relations++;
+                else if(m.isWay())
+                {
+                    ri.ways++;
+                    if(m.getWay().isClosed())
+                        ri.closedways++;
+                    else
+                        ri.openways++;
+                }
+                else if(m.isNode())
+                    ri.nodes++;
+                map.put(s, ri);
+            }
+            if(map.size() == 0)
+                errors.add( new TestError(this, Severity.ERROR, tr("Relation is empty"),
+                RELATION_EMPTY, n) );
+            else
+            {
+                LinkedList<String> done = new LinkedList<String>();
+                for(TaggingPreset.Role r : allroles)
+                {
+                    done.add(r.key);
+                    String keyname = r.key;
+                    if(keyname == "")
+                        keyname = tr("<empty>");
+                    RoleInfo ri = map.get(r.key);
+                    long count = (ri == null) ? 0 : ri.total;
+                    long vc = r.getValidCount(count);
+                    if(count != vc)
+                    {
+                        if(count == 0)
+                        {
+                            String s = marktr("Role {0} missing");
+                            errors.add( new TestError(this, Severity.WARNING, tr("Role verification problem"),
+                            tr(s, keyname), MessageFormat.format(s, keyname), ROLE_MISSING, n) );
+                        }
+                        else if(vc > count)
+                        {
+                            String s = marktr("Number of {0} roles too low ({1})");
+                            errors.add( new TestError(this, Severity.WARNING, tr("Role verification problem"),
+                            tr(s, keyname, count), MessageFormat.format(s, keyname, count), LOW_COUNT, n) );
+                        }
+                        else
+                        {
+                            String s = marktr("Number of {0} roles too high ({1})");
+                            errors.add( new TestError(this, Severity.WARNING, tr("Role verification problem"),
+                            tr(s, keyname, count), MessageFormat.format(s, keyname, count), HIGH_COUNT, n) );
+                        }
+                    }
+                    if(ri != null && ((!r.types.contains("way") && (r.types.contains("closedway") ? ri.openways > 0 : ri.ways > 0))
+                    || (!r.types.contains("node") && ri.nodes > 0) || (!r.types.contains("relation") && ri.relations > 0)))
+                    {
+                        String s = marktr("Member for role {0} of wrong type");
+                        errors.add( new TestError(this, Severity.WARNING, tr("Role verification problem"),
+                        tr(s, keyname), MessageFormat.format(s, keyname), WRONG_TYPE, n) );
+                    }
+                }
+                for(String key : map.keySet())
+                {
+                    if(!done.contains(key))
+                    {
+                        if(key.length() > 0)
+                        {
+                            String s = marktr("Role {0} unknown");
+                            errors.add( new TestError(this, Severity.WARNING, tr("Role verification problem"),
+                            tr(s, key), MessageFormat.format(s, key), ROLE_UNKNOWN, n) );
+                        }
+                        else
+                        {
+                            String s = marktr("Empty role found");
+                            errors.add( new TestError(this, Severity.WARNING, tr("Role verification problem"),
+                            tr(s), s, ROLE_EMPTY, n) );
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
