Index: applications/editors/josm/plugins/licensechange/build.xml
===================================================================
--- applications/editors/josm/plugins/licensechange/build.xml	(revision 25955)
+++ applications/editors/josm/plugins/licensechange/build.xml	(revision 25955)
@@ -0,0 +1,178 @@
+remove this line if you want to build the plugin
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+** This is the build.xml for the licensechange plugin
+**
+** Usage
+** =====
+** To build it run
+**
+**    > ant  dist
+**
+** To install the generated plugin locally (in your default plugin directory) run
+**
+**    > ant  install
+**
+** To build against the core in ../../core, create a correct manifest and deploy to
+** SVN,
+**    set the properties commit.message and plugin.main.version
+** and run
+**    > ant  publish
+**
+**
+-->
+<project name="licensechange" default="dist" basedir=".">
+
+	<!--
+	  ** update before publishing
+	-->
+	<property name="commit.message" value="" />
+	<property name="plugin.main.version" value="4076" />
+
+
+	<property name="josm"                   location="../../core/dist/josm-custom.jar"/>
+	<property name="plugin.dist.dir"        value="../../dist"/>
+	<property name="plugin.build.dir"       value="build"/>
+	<property name="plugin.jar"             value="${plugin.dist.dir}/${ant.project.name}.jar"/>
+	<property name="ant.build.javac.target" value="1.5"/>
+	<target name="init">
+		<mkdir dir="${plugin.build.dir}"/>
+	</target>
+	<target name="compile" depends="init">
+		<echo message="creating ${plugin.jar}"/>
+		<javac srcdir="src" classpath="${josm}" debug="true" destdir="${plugin.build.dir}">
+			<compilerarg value="-Xlint:deprecation"/>
+			<compilerarg value="-Xlint:unchecked"/>
+		</javac>
+	</target>
+	<target name="dist" depends="compile,revision">
+		<copy todir="${plugin.build.dir}/images">
+			<fileset dir="images"/>
+		</copy>
+		<jar destfile="${plugin.jar}" basedir="${plugin.build.dir}">
+			<manifest>
+				<attribute name="Author" value="Frederik Ramm"/>
+				<attribute name="Plugin-Class" value="org.openstreetmap.josm.plugins.licensechange.LicenseChangePlugin"/>
+				<attribute name="Plugin-Date" value="${version.entry.commit.date}"/>
+				<attribute name="Plugin-Description" value="Checks if all users contributing to an object have agreed to the license change." />
+				<attribute name="Plugin-Icon" value="images/licensechange.png"/>
+				<attribute name="Plugin-Link" value="http://wiki.openstreetmap.org/index.php/JOSM/Plugins/LicenseChange"/>
+				<attribute name="Plugin-Mainversion" value="${plugin.main.version}"/>
+				<attribute name="Plugin-Version" value="${version.entry.commit.revision}"/>
+			</manifest>
+		</jar>
+	</target>
+	<target name="revision">
+		<exec append="false" error="/dev/null" output="REVISION" executable="svn" failifexecutionfails="false">
+			<env key="LANG" value="C"/>
+			<arg value="info"/>
+			<arg value="--xml"/>
+			<arg value="."/>
+		</exec>
+		<!--<xmlproperty file="REVISION" prefix="version" keepRoot="false" collapseAttributes="true"/>-->
+		<delete file="REVISION"/>
+	</target>
+	<target name="clean">
+		<delete dir="${plugin.build.dir}"/>
+		<delete file="${plugin.jar}"/>
+	</target>
+	<target name="install" depends="dist">
+		<property environment="env"/>
+		<condition property="josm.plugins.dir" value="${env.APPDATA}/JOSM/plugins" else="${user.home}/.josm/plugins">
+			<and>
+				<os family="windows"/>
+			</and>
+		</condition>
+		<copy file="${plugin.jar}" todir="${josm.plugins.dir}"/>
+	</target>
+
+	<!--
+	 ************************** Publishing the plugin ***********************************
+	-->
+	<!--
+	** extracts the JOSM release for the JOSM version in ../core and saves it in the
+	** property ${coreversion.info.entry.revision}
+	**
+	-->
+	<target name="core-info">
+		<exec append="false" output="core.info.xml" executable="svn" failifexecutionfails="false">
+			<env key="LANG" value="C"/>
+			<arg value="info"/>
+			<arg value="--xml"/>
+			<arg value="../../core"/>
+		</exec>
+		<xmlproperty file="core.info.xml" prefix="coreversion" keepRoot="true" collapseAttributes="true"/>
+		<echo>Building against core revision ${coreversion.info.entry.revision}.</echo>
+		<echo>Plugin-Mainversion is set to ${plugin.main.version}.</echo>
+		<delete file="core.info.xml" />
+	</target>
+
+	<!--
+	** commits the source tree for this plugin
+	-->
+	<target name="commit-current">
+		<echo>Commiting the plugin source with message '${commit.message}' ...</echo>
+		<exec append="true" output="svn.log" executable="svn" failifexecutionfails="false">
+			<env key="LANG" value="C"/>
+			<arg value="-m '${commit.message}'"/>
+			<arg value="commit"/>
+			<arg value="."/>
+		</exec>
+	</target>
+
+	<!--
+	** updates (svn up) the source tree for this plugin
+	-->
+	<target name="update-current">
+		<echo>Updating plugin source ...</echo>
+		<exec append="true" output="svn.log" executable="svn" failifexecutionfails="false">
+			<env key="LANG" value="C"/>
+			<arg value="up"/>
+			<arg value="."/>
+		</exec>
+		<echo>Updating ${plugin.jar} ...</echo>
+		<exec append="true" output="svn.log" executable="svn" failifexecutionfails="false">
+			<env key="LANG" value="C"/>
+			<arg value="up"/>
+			<arg value="../dist/${plugin.jar}"/>
+		</exec>
+	</target>
+
+	<!--
+	** commits the plugin.jar
+	-->
+	<target name="commit-dist">
+		<echo>
+***** Properties of published ${plugin.jar} *****
+Commit message    : '${commit.message}'
+Plugin-Mainversion: ${plugin.main.version}
+JOSM build version: ${coreversion.info.entry.revision}
+Plugin-Version    : ${version.entry.commit.revision}
+***** / Properties of published ${plugin.jar} *****
+
+Now commiting ${plugin.jar} ...
+</echo>
+		<exec append="true" output="svn.log" executable="svn" failifexecutionfails="false">
+			<env key="LANG" value="C"/>
+			<arg value="-m '${commit.message}'"/>
+			<arg value="commit"/>
+			<arg value="${plugin.jar}"/>
+		</exec>
+	</target>
+
+	<!-- ** make sure svn is present as a command line tool ** -->
+	<target name="ensure-svn-present">
+		<exec append="true" output="svn.log" executable="svn" failonerror="false" resultproperty="svn.exit.code">
+			<env key="LANG" value="C" />
+			<arg value="--version" />
+		</exec>
+		<fail message="Fatal: command 'svn' not found. Please make sure svn is installed on your system.">
+			<condition>
+				<isfailure code="${svn.exit.code}" />
+			</condition>
+		</fail>
+	</target>
+
+	<target name="publish" depends="ensure-svn-present,core-info,commit-current,update-current,clean,dist,commit-dist">
+	</target>
+</project>
Index: applications/editors/josm/plugins/licensechange/src/org/openstreetmap/josm/plugins/licensechange/BasicLicenseCheck.java
===================================================================
--- applications/editors/josm/plugins/licensechange/src/org/openstreetmap/josm/plugins/licensechange/BasicLicenseCheck.java	(revision 25955)
+++ applications/editors/josm/plugins/licensechange/src/org/openstreetmap/josm/plugins/licensechange/BasicLicenseCheck.java	(revision 25955)
@@ -0,0 +1,79 @@
+// License: GPL. See LICENSE file for details.
+package org.openstreetmap.josm.plugins.licensechange;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.osm.*;
+
+public class BasicLicenseCheck extends Check 
+{
+
+    private LicenseChangePlugin plugin; 
+
+    public BasicLicenseCheck(LicenseChangePlugin p) 
+    {
+        super(tr("Basic License Check."),
+            tr("Checks if all contributors have agreed to the new CT/License."));
+        plugin = p;
+    }
+
+    @Override public void visit(Node n) 
+    {
+        List<User> users = plugin.getUsers(n);
+        doCheck(n, users);
+    }
+    @Override public void visit(Way w) 
+    {
+        List<User> users = plugin.getUsers(w);
+        doCheck(w, users);
+    }
+    @Override public void visit(Relation r) 
+    {
+        List<User> users = plugin.getUsers(r);
+        doCheck(r, users);
+    }
+
+    private void doCheck(OsmPrimitive n, List<User> users)
+    {
+        Severity sev = null;
+        if (users != null)
+        {
+            int u0 = users.get(0).getRelicensingStatus();
+            String msg = null;
+            if (u0 == User.STATUS_NOT_AGREED || u0 == User.STATUS_ANONYMOUS)
+            {
+                sev = Severity.DATA_LOSS;
+                msg = (u0 == User.STATUS_NOT_AGREED) ? tr("Creator has rejected CT") : tr("Creator unknown");
+            }
+            else if (u0 == User.STATUS_UNDECIDED)
+            {
+                sev = Severity.POSSIBLE_DATA_LOSS;
+                msg = tr("Creator has not (yet) accepted CT");
+            }
+            else
+            {
+                for (int i=1; i<users.size(); i++)
+                {
+                    int ux = users.get(i).getRelicensingStatus();
+                    if (ux == User.STATUS_NOT_AGREED || ux == User.STATUS_ANONYMOUS || ux == User.STATUS_UNDECIDED)
+                    {
+                        sev = Severity.DATA_REDUCTION;
+                        msg = tr("Object modified by user(s) who have rejected, or not agreed to, CT");
+                        break;
+                    }
+                }
+            }
+
+            if (sev != null)
+            {
+                List<? extends org.openstreetmap.josm.data.osm.OsmPrimitive> x = Arrays.asList(n);
+                errors.add(new LicenseProblem(this, sev, msg, x, x));
+            }
+        }
+    }
+}
Index: applications/editors/josm/plugins/licensechange/src/org/openstreetmap/josm/plugins/licensechange/Check.java
===================================================================
--- applications/editors/josm/plugins/licensechange/src/org/openstreetmap/josm/plugins/licensechange/Check.java	(revision 25955)
+++ applications/editors/josm/plugins/licensechange/src/org/openstreetmap/josm/plugins/licensechange/Check.java	(revision 25955)
@@ -0,0 +1,180 @@
+// License: GPL. See LICENSE file for details.
+package org.openstreetmap.josm.plugins.licensechange;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.GridBagConstraints;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import javax.swing.JCheckBox;
+import javax.swing.JPanel;
+
+import org.openstreetmap.josm.command.Command;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.Relation;
+import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.data.osm.visitor.AbstractVisitor;
+import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
+import org.openstreetmap.josm.gui.progress.ProgressMonitor;
+import org.openstreetmap.josm.tools.GBC;
+
+/**
+ */
+public class Check extends AbstractVisitor
+{
+    /** Name of the test */
+    protected final String name;
+
+    /** Description of the test */
+    protected final String description;
+
+    /** Whether this test is enabled. Enabled by default */
+    protected boolean enabled = true;
+
+    /** The preferences check for validation */
+    protected JCheckBox checkEnabled;
+
+    /** The preferences check for validation on upload */
+    protected JCheckBox checkBeforeUpload;
+
+    /** Whether this test must check before upload. Enabled by default */
+    protected boolean testBeforeUpload = true;
+
+    /** Whether this test is performing just before an upload */
+    protected boolean isBeforeUpload;
+
+    /** The list of errors */
+    protected List<LicenseProblem> errors = new ArrayList<LicenseProblem>(30);
+
+    /** the progress monitor to use */
+    protected ProgressMonitor progressMonitor;
+    /**
+     * Constructor
+     * @param name Name of the test
+     * @param description Description of the test
+     */
+    public Check(String name, String description)
+    {
+        this.name = name;
+        this.description = description;
+    }
+
+    /**
+     * Constructor
+     * @param name Name of the test
+     */
+    public Check(String name)
+    {
+        this(name, null);
+    }
+
+    /**
+     * Initializes any global data used this tester.
+     * @param plugin The plugin
+     * @throws Exception When cannot initialize the test
+     */
+    public void initialize(LicenseChangePlugin plugin) throws Exception {}
+
+    /**
+     * Start the test using a given progress monitor
+     *
+     * @param progressMonitor  the progress monitor
+     */
+    public void startCheck(ProgressMonitor progressMonitor) {
+        if (progressMonitor == null) {
+                this.progressMonitor = NullProgressMonitor.INSTANCE;
+        } else {
+                this.progressMonitor = progressMonitor;
+        }
+        this.progressMonitor.beginTask(tr("Running test {0}", name));
+        errors = new ArrayList<LicenseProblem>(30);
+    }
+
+    /**
+     * Gets the validation errors accumulated until this moment.
+     * @return The list of errors
+     */
+    public List<LicenseProblem> getProblems()
+    {
+        return errors;
+    }
+
+    /**
+     * Notification of the end of the test. The tester may perform additional
+     * actions and destroy the used structures
+     */
+    public void endCheck() {
+        progressMonitor.finishTask();
+        progressMonitor = null;
+    }
+
+    /**
+     * Visits all primitives to be tested. These primitives are always visited
+     * in the same order: nodes first, then ways.
+     *
+     * @param selection The primitives to be tested
+     */
+    public void visit(Collection<OsmPrimitive> selection)
+    {
+        progressMonitor.setTicksCount(selection.size());
+        for (OsmPrimitive p : selection) {
+            if( p.isUsable() )
+                p.visit(this);
+            progressMonitor.worked(1);
+        }
+    }
+
+    public void visit(Node n) {}
+
+    public void visit(Way w) {}
+
+    public void visit(Relation r) {}
+
+    /**
+     * Allow the tester to manage its own preferences
+     * @param testPanel The panel to add any preferences component
+     */
+    public void addGui(JPanel testPanel)
+    {
+        checkEnabled = new JCheckBox(name, enabled);
+        checkEnabled.setToolTipText(description);
+        testPanel.add(checkEnabled, GBC.std());
+
+        GBC a = GBC.eol();
+        a.anchor = GridBagConstraints.EAST;
+        checkBeforeUpload = new JCheckBox();
+        checkBeforeUpload.setSelected(testBeforeUpload);
+        testPanel.add(checkBeforeUpload, a);
+    }
+
+    /**
+     * Called when the used submits the preferences
+     */
+    public boolean ok()
+    {
+        enabled = checkEnabled.isSelected();
+        testBeforeUpload = checkBeforeUpload.isSelected();
+        return false;
+    }
+
+    /**
+     * Returns true if this plugin must check the uploaded data before uploading
+     * @return true if this plugin must check the uploaded data before uploading
+     */
+    public boolean testBeforeUpload()
+    {
+        return testBeforeUpload;
+    }
+
+    /**
+     * Sets the flag that marks an upload check
+     * @param isUpload if true, the test is before upload
+     */
+    public void setBeforeUpload(boolean isUpload)
+    {
+        this.isBeforeUpload = isUpload;
+    }
+}
Index: applications/editors/josm/plugins/licensechange/src/org/openstreetmap/josm/plugins/licensechange/CheckAction.java
===================================================================
--- applications/editors/josm/plugins/licensechange/src/org/openstreetmap/josm/plugins/licensechange/CheckAction.java	(revision 25955)
+++ applications/editors/josm/plugins/licensechange/src/org/openstreetmap/josm/plugins/licensechange/CheckAction.java	(revision 25955)
@@ -0,0 +1,152 @@
+// License: GPL. See LICENSE file for details.
+package org.openstreetmap.josm.plugins.licensechange;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import javax.swing.SwingUtilities;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.JosmAction;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.gui.PleaseWaitRunnable;
+import org.openstreetmap.josm.io.OsmTransferException;
+import org.openstreetmap.josm.data.validation.util.AggregatePrimitivesVisitor;
+import org.openstreetmap.josm.tools.Shortcut;
+import org.xml.sax.SAXException;
+
+/**
+ * The action that starts the license check.
+ */
+public class CheckAction extends JosmAction 
+{
+    private LicenseChangePlugin plugin;
+
+    /** Last selection used to validate */
+    private Collection<OsmPrimitive> lastSelection;
+
+    /**
+     * Constructor
+     */
+    public CheckAction(LicenseChangePlugin plugin) 
+    {
+        super(tr("License Check"), "licensechange", tr("Performs the license check"),
+        Shortcut.registerShortcut("tools:licensechange", tr("Tool: {0}", tr("License Check")), KeyEvent.VK_C, Shortcut.GROUP_EDIT, Shortcut.SHIFT_DEFAULT), true);
+        this.plugin = plugin;
+    }
+
+    public void actionPerformed(ActionEvent ev) 
+    {
+        doCheck(ev);
+    }
+
+    /**
+     * Does the validation.
+     * <p>
+     *
+     * @param ev The event
+     */
+    public void doCheck(ActionEvent ev)
+    {
+        if (plugin.validateAction == null || Main.map == null || !Main.map.isVisible())
+            return;
+
+        plugin.initializeProblemLayer();
+
+        Collection<OsmPrimitive> selection;
+        selection = Main.main.getCurrentDataSet().getSelected();
+        if (selection.isEmpty()) 
+        {
+            selection = Main.main.getCurrentDataSet().allNonDeletedPrimitives();
+            lastSelection = null;
+        } 
+        else 
+        {
+            AggregatePrimitivesVisitor v = new AggregatePrimitivesVisitor();
+            selection = v.visit(selection);
+            lastSelection = selection;
+        }
+
+        CheckTask task = new CheckTask(selection, lastSelection);
+        Main.worker.submit(task);
+    }
+
+    @Override
+    public void updateEnabledState() 
+    {
+        setEnabled(getEditLayer() != null);
+    }
+
+    class CheckTask extends PleaseWaitRunnable 
+    {
+        private Check licenseCheck;
+        private Collection<OsmPrimitive> validatedPrimitives;
+        private Collection<OsmPrimitive> formerValidatedPrimitives;
+        private boolean cancelled;
+        private List<LicenseProblem> problems;
+
+        /**
+         *
+         * @param validatedPrimitives the collection of primitives to validate.
+         * @param formerValidatedPrimitives the last collection of primitives being validates. May be null.
+         */
+        public CheckTask(Collection<OsmPrimitive> validatedPrimitives, Collection<OsmPrimitive> formerValidatedPrimitives) 
+        {
+            super(tr("Loading from Quick History Service"), false /*don't ignore exceptions */);
+            this.validatedPrimitives  = validatedPrimitives;
+            this.formerValidatedPrimitives = formerValidatedPrimitives;
+            this.licenseCheck = new BasicLicenseCheck(plugin);
+        }
+
+        @Override
+        protected void cancel() 
+        {
+            this.cancelled = true;
+        }
+
+        @Override
+        protected void finish() 
+        {
+            if (cancelled) return;
+
+            // update GUI on Swing EDT
+            //
+            Runnable r = new Runnable()  {
+                public void run() {
+                    plugin.validationDialog.tree.setErrors(problems);
+                    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 
+        {
+            plugin.loadDataFromQuickHistoryService(validatedPrimitives);
+            problems = new ArrayList<LicenseProblem>(200);
+            getProgressMonitor().setTicksCount(validatedPrimitives.size());
+            int testCounter = 0;
+            getProgressMonitor().setCustomText(tr("Analyzing"));
+            licenseCheck.startCheck(getProgressMonitor().createSubTaskMonitor(validatedPrimitives.size(), false));
+            licenseCheck.visit(validatedPrimitives);
+            licenseCheck.endCheck();
+            problems.addAll(licenseCheck.getProblems());
+        }
+    }
+}
Index: applications/editors/josm/plugins/licensechange/src/org/openstreetmap/josm/plugins/licensechange/LicenseChangeDialog.java
===================================================================
--- applications/editors/josm/plugins/licensechange/src/org/openstreetmap/josm/plugins/licensechange/LicenseChangeDialog.java	(revision 25955)
+++ applications/editors/josm/plugins/licensechange/src/org/openstreetmap/josm/plugins/licensechange/LicenseChangeDialog.java	(revision 25955)
@@ -0,0 +1,327 @@
+// License: GPL. See LICENSE file for details.
+package org.openstreetmap.josm.plugins.licensechange;
+
+import static org.openstreetmap.josm.tools.I18n.marktr;
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.BorderLayout;
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyEvent;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+import javax.swing.JMenuItem;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JPopupMenu;
+import javax.swing.JScrollPane;
+import javax.swing.SwingUtilities;
+import javax.swing.event.TreeSelectionEvent;
+import javax.swing.event.TreeSelectionListener;
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.TreePath;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.AutoScaleAction;
+import org.openstreetmap.josm.command.Command;
+import org.openstreetmap.josm.data.SelectionChangedListener;
+import org.openstreetmap.josm.data.osm.DataSet;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.WaySegment;
+import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
+import org.openstreetmap.josm.gui.PleaseWaitRunnable;
+import org.openstreetmap.josm.gui.SideButton;
+import org.openstreetmap.josm.gui.dialogs.ToggleDialog;
+import org.openstreetmap.josm.gui.progress.ProgressMonitor;
+import org.openstreetmap.josm.io.OsmTransferException;
+import org.openstreetmap.josm.tools.Shortcut;
+import org.xml.sax.SAXException;
+
+/**
+ * A small tool dialog for displaying the current problems. The selection manager
+ * respects clicks into the selection list. Ctrl-click will remove entries from
+ * the list while single click will make the clicked entry the only selection.
+ */
+public class LicenseChangeDialog extends ToggleDialog implements ActionListener, SelectionChangedListener {
+    private LicenseChangePlugin plugin;
+
+    /** The display tree */
+    protected ProblemTreePanel tree;
+
+    private SideButton selectButton;
+    /** The select button */
+
+    private JPopupMenu popupMenu;
+    private LicenseProblem popupMenuError = null;
+
+    /** Last selected element */
+    private DefaultMutableTreeNode lastSelectedNode = null;
+
+    /**
+     * Constructor
+     */
+    public LicenseChangeDialog(LicenseChangePlugin plugin) 
+    {
+        super(tr("Relicensing problems"), "licensechange", tr("Open the relicensing window."),
+                Shortcut.registerShortcut("subwindow:licensechange", tr("Toggle: {0}", tr("Relicensing problems")),
+                        KeyEvent.VK_V, Shortcut.GROUP_LAYER, Shortcut.SHIFT_DEFAULT), 150);
+
+        this.plugin = plugin;
+        popupMenu = new JPopupMenu();
+
+        JMenuItem zoomTo = new JMenuItem(tr("Zoom to problem"));
+        zoomTo.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                zoomToProblem();
+            }
+        });
+        popupMenu.add(zoomTo);
+
+        tree = new ProblemTreePanel();
+        tree.addMouseListener(new ClickWatch());
+        tree.addTreeSelectionListener(new SelectionWatch());
+
+        add(new JScrollPane(tree), BorderLayout.CENTER);
+
+        JPanel buttonPanel = new JPanel(new GridLayout(1, 3));
+
+        selectButton = new SideButton(marktr("Select"), "select", "LicenseChange",
+                tr("Set the selected elements on the map to the selected items in the list above."), this);
+        selectButton.setEnabled(false);
+        buttonPanel.add(selectButton);
+        buttonPanel.add(new SideButton(plugin.validateAction), "refresh");
+        add(buttonPanel, BorderLayout.SOUTH);
+
+    }
+
+    @Override
+    public void showNotify() 
+    {
+        DataSet.addSelectionListener(this);
+        DataSet ds = Main.main.getCurrentDataSet();
+        if (ds != null) {
+            updateSelection(ds.getSelected());
+        }
+    }
+
+    @Override
+    public void hideNotify() 
+    {
+        DataSet.removeSelectionListener(this);
+    }
+
+    @Override
+    public void setVisible(boolean v) 
+    {
+        if (tree != null)
+            tree.setVisible(v);
+        super.setVisible(v);
+        Main.map.repaint();
+    }
+
+    @SuppressWarnings("unchecked")
+    private void showPopupMenu(MouseEvent e) 
+    {
+        if (!e.isPopupTrigger())
+            return;
+        popupMenuError = null;
+        TreePath selPath = tree.getPathForLocation(e.getX(), e.getY());
+        if (selPath == null)
+            return;
+        DefaultMutableTreeNode node = (DefaultMutableTreeNode) selPath.getPathComponent(selPath.getPathCount() - 1);
+        if (!(node.getUserObject() instanceof LicenseProblem))
+            return;
+        popupMenuError = (LicenseProblem) node.getUserObject();
+        popupMenu.show(e.getComponent(), e.getX(), e.getY());
+    }
+
+    private void zoomToProblem() 
+    {
+        if (popupMenuError == null)
+            return;
+        LicenseChangeBoundingXYVisitor bbox = new LicenseChangeBoundingXYVisitor();
+        popupMenuError.visitHighlighted(bbox);
+        if (bbox.getBounds() == null)
+            return;
+        bbox.enlargeBoundingBox();
+        Main.map.mapView.recalculateCenterScale(bbox);
+    }
+
+    /**
+     * Sets the selection of the map to the current selected items.
+     */
+    @SuppressWarnings("unchecked")
+    private void setSelectedItems() 
+    {
+        if (tree == null)
+            return;
+
+        Collection<OsmPrimitive> sel = new HashSet<OsmPrimitive>(40);
+
+        TreePath[] selectedPaths = tree.getSelectionPaths();
+        if (selectedPaths == null)
+            return;
+
+        for (TreePath path : selectedPaths) {
+            DefaultMutableTreeNode node = (DefaultMutableTreeNode) path.getLastPathComponent();
+            Enumeration<DefaultMutableTreeNode> children = node.breadthFirstEnumeration();
+            while (children.hasMoreElements()) {
+                DefaultMutableTreeNode childNode = children.nextElement();
+                Object nodeInfo = childNode.getUserObject();
+                if (nodeInfo instanceof LicenseProblem) {
+                    LicenseProblem error = (LicenseProblem) nodeInfo;
+                    sel.addAll(error.getPrimitives());
+                }
+            }
+        }
+
+        Main.main.getCurrentDataSet().setSelected(sel);
+    }
+
+    public void actionPerformed(ActionEvent e) 
+    {
+        String actionCommand = e.getActionCommand();
+        if (actionCommand.equals("Select"))
+            setSelectedItems();
+    }
+
+    /**
+     * @param sel
+     *            The collection where to add all selected elements
+     * @param addSelected
+     *            if true, add all selected elements to collection
+     */
+    @SuppressWarnings("unchecked")
+    private void setSelection(Collection<OsmPrimitive> sel, boolean addSelected) {
+
+        DefaultMutableTreeNode node = (DefaultMutableTreeNode) tree.getLastSelectedPathComponent();
+        if (lastSelectedNode != null && !lastSelectedNode.equals(node)) {
+            Enumeration<DefaultMutableTreeNode> children = lastSelectedNode.breadthFirstEnumeration();
+            while (children.hasMoreElements()) {
+                DefaultMutableTreeNode childNode = children.nextElement();
+                Object nodeInfo = childNode.getUserObject();
+                if (nodeInfo instanceof LicenseProblem) {
+                    LicenseProblem error = (LicenseProblem) nodeInfo;
+                    error.setSelected(false);
+                }
+            }
+        }
+
+        lastSelectedNode = node;
+        if (node == null) return;
+
+        Enumeration<DefaultMutableTreeNode> children = node.breadthFirstEnumeration();
+        while (children.hasMoreElements()) {
+            DefaultMutableTreeNode childNode = children.nextElement();
+            Object nodeInfo = childNode.getUserObject();
+            if (nodeInfo instanceof LicenseProblem) {
+                LicenseProblem error = (LicenseProblem) nodeInfo;
+                error.setSelected(true);
+
+                if (addSelected) {
+                    sel.addAll(error.getPrimitives());
+                }
+            }
+        }
+        selectButton.setEnabled(true);
+    }
+
+    /**
+     * Watches for clicks.
+     */
+    public class ClickWatch extends MouseAdapter 
+    {
+        @Override
+        public void mouseClicked(MouseEvent e) {
+            selectButton.setEnabled(false);
+
+            boolean isDblClick = e.getClickCount() > 1;
+
+            Collection<OsmPrimitive> sel = isDblClick ? new HashSet<OsmPrimitive>(40) : null;
+
+            setSelection(sel, isDblClick);
+
+            if (isDblClick) {
+                Main.main.getCurrentDataSet().setSelected(sel);
+                if(Main.pref.getBoolean("licensechange.autozoom", false))
+                    AutoScaleAction.zoomTo(sel);
+            }
+        }
+
+        @Override
+        public void mousePressed(MouseEvent e) {
+            showPopupMenu(e);
+        }
+
+        @Override
+        public void mouseReleased(MouseEvent e) {
+            showPopupMenu(e);
+        }
+
+    }
+
+    /**
+     * Watches for tree selection.
+     */
+    public class SelectionWatch implements TreeSelectionListener 
+    {
+        public void valueChanged(TreeSelectionEvent e) {
+            selectButton.setEnabled(false);
+
+            if (e.getSource() instanceof JScrollPane) {
+                System.out.println(e.getSource());
+                return;
+            }
+
+            setSelection(null, false);
+            Main.map.repaint();
+        }
+    }
+
+    public static class LicenseChangeBoundingXYVisitor extends BoundingXYVisitor implements LicenseChangeVisitor 
+    {
+        public void visit(OsmPrimitive p) {
+            if (p.isUsable()) {
+                p.visit(this);
+            }
+        }
+
+        public void visit(WaySegment ws) {
+            if (ws.lowerIndex < 0 || ws.lowerIndex + 1 >= ws.way.getNodesCount())
+                return;
+            visit(ws.way.getNodes().get(ws.lowerIndex));
+            visit(ws.way.getNodes().get(ws.lowerIndex + 1));
+        }
+
+        public void visit(List<Node> nodes) {
+            for (Node n: nodes) {
+                visit(n);
+            }
+        }
+    }
+
+    public void updateSelection(Collection<? extends OsmPrimitive> newSelection) 
+    {
+        if (newSelection.isEmpty())
+            tree.setFilter(null);
+        HashSet<OsmPrimitive> filter = new HashSet<OsmPrimitive>(newSelection);
+        tree.setFilter(filter);
+    }
+
+    public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) 
+    {
+        updateSelection(newSelection);
+    }
+}
Index: applications/editors/josm/plugins/licensechange/src/org/openstreetmap/josm/plugins/licensechange/LicenseChangePlugin.java
===================================================================
--- applications/editors/josm/plugins/licensechange/src/org/openstreetmap/josm/plugins/licensechange/LicenseChangePlugin.java	(revision 25955)
+++ applications/editors/josm/plugins/licensechange/src/org/openstreetmap/josm/plugins/licensechange/LicenseChangePlugin.java	(revision 25955)
@@ -0,0 +1,270 @@
+// License: GPL. See LICENSE file for details.
+package org.openstreetmap.josm.plugins.licensechange;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeSet;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.OutputStream;
+import java.io.BufferedWriter;
+
+import javax.swing.JOptionPane;
+
+import org.xml.sax.helpers.DefaultHandler;
+import org.xml.sax.Attributes;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import javax.xml.parsers.SAXParserFactory;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.UploadAction;
+import org.openstreetmap.josm.data.osm.*;
+import org.openstreetmap.josm.data.osm.visitor.Visitor;
+import org.openstreetmap.josm.data.projection.Epsg4326;
+import org.openstreetmap.josm.data.projection.Lambert;
+import org.openstreetmap.josm.data.projection.Mercator;
+import org.openstreetmap.josm.gui.MapFrame;
+import org.openstreetmap.josm.gui.MapView;
+import org.openstreetmap.josm.gui.MapView.LayerChangeListener;
+import org.openstreetmap.josm.gui.layer.Layer;
+import org.openstreetmap.josm.gui.layer.OsmDataLayer;
+import org.openstreetmap.josm.gui.preferences.PreferenceSetting;
+import org.openstreetmap.josm.plugins.Plugin;
+import org.openstreetmap.josm.plugins.PluginInformation;
+
+/**
+ *
+ * Plugin that highlights data with unclear re-licensing status
+ *
+ * @author Frederik Ramm <frederik@remote.org>
+ * based on old Validator plugin by Francisco R. Santos and Dirk Stoecker
+ */
+public class LicenseChangePlugin extends Plugin implements LayerChangeListener 
+{
+
+    protected static ProblemLayer problemLayer = null;
+
+    /** The validate action */
+    CheckAction validateAction = new CheckAction(this);
+
+    /** The validation dialog */
+    LicenseChangeDialog validationDialog;
+
+    /** The list of errors per layer*/
+    Map<Layer, List<LicenseProblem>> layerProblems = new HashMap<Layer, List<LicenseProblem>>();
+
+    /** Database of users who have edited something. */
+    private final Map<Long, List<User>> nodeUsers = new HashMap<Long, List<User>>();
+    private final Map<Long, List<User>> wayUsers = new HashMap<Long, List<User>>();
+    private final Map<Long, List<User>> relationUsers = new HashMap<Long, List<User>>();
+
+    public List<User> getUsers(Node n) { return nodeUsers.get(n.getId()); }
+    public List<User> getUsers(Way n) { return wayUsers.get(n.getId()); }
+    public List<User> getUsers(Relation n) { return relationUsers.get(n.getId()); }
+
+    /**
+     * Creates the plugin
+     */
+    public LicenseChangePlugin(PluginInformation info) 
+    {
+        super(info);
+    }
+
+    @Override
+    public void mapFrameInitialized(MapFrame oldFrame, MapFrame newFrame) 
+    {
+        if (newFrame != null) 
+        {
+            validationDialog = new LicenseChangeDialog(this);
+            newFrame.addToggleDialog(validationDialog);
+            initializeProblemLayer();
+            MapView.addLayerChangeListener(this);
+        } 
+        else
+        {
+            MapView.removeLayerChangeListener(this);
+        }
+    }
+
+    public void initializeProblemLayer() 
+    {
+        if (problemLayer == null) 
+        {
+            problemLayer = new ProblemLayer(this);
+            Main.main.addLayer(problemLayer);
+        }
+    }
+
+    /* -------------------------------------------------------------------------- */
+    /* interface LayerChangeListener                                              */
+    /* -------------------------------------------------------------------------- */
+
+    public void activeLayerChange(Layer oldLayer, Layer newLayer) 
+    {
+        if (newLayer instanceof OsmDataLayer) {
+            List<LicenseProblem> errors = layerProblems.get(newLayer);
+            validationDialog.tree.setErrorList(errors);
+            Main.map.repaint();
+        }
+    }
+
+    public void layerAdded(Layer newLayer) 
+    {
+        if (newLayer instanceof OsmDataLayer) {
+            layerProblems.put(newLayer, new ArrayList<LicenseProblem>());
+        }
+    }
+
+    public void layerRemoved(Layer oldLayer) 
+    {
+        if (oldLayer == problemLayer) {
+            problemLayer = null;
+            return;
+        }
+        layerProblems.remove(oldLayer);
+        if (Main.map.mapView.getLayersOfType(OsmDataLayer.class).isEmpty()) {
+            if (problemLayer != null) {
+                Main.map.mapView.removeLayer(problemLayer);
+            }
+        }
+    }
+
+    private class QhsParser extends DefaultHandler 
+    {
+        List<User> theList = null;
+
+        @Override
+        public void startDocument() throws SAXException {
+        }
+
+        @Override public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException 
+        {
+            if ("node".equals(qName) || "way".equals(qName) || "relation".equals(qName)) 
+            {
+                 Map<Long, List<User>> theMap = ("node".equals(qName)) ? nodeUsers : ("way".equals(qName)) ? wayUsers : relationUsers;
+                 // we always overwrite a list that might already exist
+                 theMap.put(Long.decode(atts.getValue("id")), theList = new ArrayList<User>());
+            }
+            else if ("user".equals(qName))
+            {
+                String v = atts.getValue("version");
+                String i = atts.getValue("id");
+                String d = atts.getValue("decision");
+                User u = User.createOsmUser(Long.parseLong(i), null);
+                if ("first".equals(v)) theList.add(0, u); else theList.add(u);
+                u.setRelicensingStatus(
+                    "undecided".equals(d) ? User.STATUS_UNDECIDED :
+                    "auto".equals(d) ? User.STATUS_AUTO_AGREED :
+                    "yes".equals(d) ? User.STATUS_AGREED :
+                    "no".equals(d) ? User.STATUS_NOT_AGREED :
+                    "anonymous".equals(d) ? User.STATUS_ANONYMOUS :
+                    User.STATUS_UNKNOWN);
+            }
+        }
+    }
+
+    public void loadDataFromQuickHistoryService(Collection<OsmPrimitive> objectList)
+    {
+        final StringBuffer nodesToLoad = new StringBuffer();
+        final StringBuffer waysToLoad = new StringBuffer();
+        final StringBuffer relationsToLoad = new StringBuffer();
+
+        Visitor v = new Visitor() {
+            public void visit(Node n) {
+                if (!nodeUsers.containsKey(n.getId())) {
+                    nodesToLoad.append(",");
+                    nodesToLoad.append(n.getId());
+                }
+            }
+            public void visit(Way n) {
+                if (!wayUsers.containsKey(n.getId())) {
+                    waysToLoad.append(",");
+                    waysToLoad.append(n.getId());
+                }
+            }
+            public void visit(Relation n) {
+                if (!relationUsers.containsKey(n.getId())) {
+                    relationsToLoad.append(",");
+                    relationsToLoad.append(n.getId());
+                }
+            }
+            public void visit(Changeset c) {};
+        };
+
+        for (OsmPrimitive p : objectList) if (p.getId() > 0) p.visit(v);
+
+
+        if (nodesToLoad.length()==0 && waysToLoad.length()==0 && relationsToLoad.length()==0) return;
+
+        try {
+            URL qhs = new URL("http://wtfe.gryph.de/api/0.6/userlist");
+            HttpURLConnection activeConnection = (HttpURLConnection)qhs.openConnection();
+            activeConnection.setRequestMethod("POST");
+            activeConnection.setDoOutput(true);
+            activeConnection.setRequestProperty("Content-type", "text/xml");
+            OutputStream out = activeConnection.getOutputStream();
+            BufferedWriter bwr = new BufferedWriter(new OutputStreamWriter(out, "UTF-8"));
+            if (nodesToLoad.length() > 0) 
+            {
+                bwr.write("&nodes=0");
+                bwr.write(nodesToLoad.toString());
+            }
+            if (waysToLoad.length() > 0) 
+            {
+                bwr.write("&ways=0");
+                bwr.write(waysToLoad.toString());
+            }
+            if (relationsToLoad.length() > 0) 
+            {
+                bwr.write("&relations=0");
+                bwr.write(relationsToLoad.toString());
+            }
+            bwr.flush();
+
+            activeConnection.connect();
+            System.out.println(activeConnection.getResponseMessage());
+            int retCode = activeConnection.getResponseCode();
+
+            InputStream i = null;
+            try {
+                i = activeConnection.getInputStream();
+            } catch (IOException ioe) {
+                i = activeConnection.getErrorStream();
+            }
+
+            StringBuffer responseBody = new StringBuffer();
+
+
+            if (i != null) {
+                InputSource inputSource = new InputSource(i);
+                SAXParserFactory.newInstance().newSAXParser().parse(inputSource, new QhsParser());
+            }
+
+            activeConnection.disconnect();
+        }
+        catch (Exception ex)
+        {
+            ex.printStackTrace();
+        }
+    }
+}
+
Index: applications/editors/josm/plugins/licensechange/src/org/openstreetmap/josm/plugins/licensechange/LicenseChangeVisitor.java
===================================================================
--- applications/editors/josm/plugins/licensechange/src/org/openstreetmap/josm/plugins/licensechange/LicenseChangeVisitor.java	(revision 25955)
+++ applications/editors/josm/plugins/licensechange/src/org/openstreetmap/josm/plugins/licensechange/LicenseChangeVisitor.java	(revision 25955)
@@ -0,0 +1,14 @@
+// License: GPL. See LICENSE file for details.
+package org.openstreetmap.josm.plugins.licensechange;
+
+import java.util.List;
+
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.WaySegment;
+
+public interface LicenseChangeVisitor {
+    void visit(OsmPrimitive p);
+    void visit(WaySegment ws);
+    void visit(List<Node> nodes);
+}
Index: applications/editors/josm/plugins/licensechange/src/org/openstreetmap/josm/plugins/licensechange/LicenseProblem.java
===================================================================
--- applications/editors/josm/plugins/licensechange/src/org/openstreetmap/josm/plugins/licensechange/LicenseProblem.java	(revision 25955)
+++ applications/editors/josm/plugins/licensechange/src/org/openstreetmap/josm/plugins/licensechange/LicenseProblem.java	(revision 25955)
@@ -0,0 +1,292 @@
+// License: GPL. See LICENSE file for details.
+package org.openstreetmap.josm.plugins.licensechange;
+
+import java.awt.Color;
+import java.awt.Graphics;
+import java.awt.Point;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.TreeSet;
+
+import org.openstreetmap.josm.command.Command;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.Relation;
+import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.data.osm.WaySegment;
+import org.openstreetmap.josm.data.osm.visitor.AbstractVisitor;
+import org.openstreetmap.josm.gui.MapView;
+
+/**
+ */
+public class LicenseProblem 
+{
+    /** Severity */
+    private Severity severity;
+    /** The problem message */
+    private String message;
+    /** The affected primitives */
+    private List<? extends OsmPrimitive> primitives;
+    /** The primitives to be highlighted */
+    private List<?> highlighted;
+    /** The tester that raised this error */
+    private Check tester;
+    /** Whether this is selected */
+    private boolean selected;
+
+    /**
+     * Constructors
+     * @param tester The tester
+     * @param severity The severity of this problem
+     * @param message The error message
+     * @param primitive The affected primitive
+     * @param primitives The affected primitives
+     */
+    public LicenseProblem(Check tester, Severity severity, String message, List<? extends OsmPrimitive> primitives, List<?> highlighted) {
+        this.tester = tester;
+        this.severity = severity;
+        this.message = message;
+        this.primitives = primitives;
+        this.highlighted = highlighted;
+    }
+
+    /**
+     * Gets the error message
+     * @return the error message
+     */
+    public String getMessage() {
+        return message;
+    }
+
+    /**
+     * Sets the error message
+     * @param message The error message
+     */
+    public void setMessage(String message) {
+        this.message = message;
+    }
+
+    /**
+     * Gets the list of primitives affected by this error
+     * @return the list of primitives affected by this error
+     */
+    public List<? extends OsmPrimitive> getPrimitives() {
+        return primitives;
+    }
+
+    /**
+     * Sets the list of primitives affected by this error
+     * @param primitives the list of primitives affected by this error
+     */
+
+    public void setPrimitives(List<OsmPrimitive> primitives) {
+        this.primitives = primitives;
+    }
+
+    /**
+     * Gets the severity of this error
+     * @return the severity of this error
+     */
+    public Severity getSeverity() {
+        return severity;
+    }
+
+    /**
+     * Sets the severity of this error
+     * @param severity the severity of this error
+     */
+    public void setSeverity(Severity severity) {
+        this.severity = severity;
+    }
+
+
+    /**
+     * Gets the tester that raised this error
+     * @return the tester that raised this error
+     */
+    public Check getTester() {
+        return tester;
+    }
+
+    /**
+     * Paints the error on affected primitives
+     *
+     * @param g The graphics
+     * @param mv The MapView
+     */
+    public void paint(Graphics g, MapView mv) {
+        PaintVisitor v = new PaintVisitor(g, mv);
+        visitHighlighted(v);
+    }
+
+    @SuppressWarnings("unchecked")
+    public void visitHighlighted(LicenseChangeVisitor v) {
+        for (Object o : highlighted) {
+            if (o instanceof OsmPrimitive)
+                v.visit((OsmPrimitive) o);
+            else if (o instanceof WaySegment)
+                v.visit((WaySegment) o);
+            else if (o instanceof List<?>) {
+                v.visit((List<Node>)o);
+            }
+        }
+    }
+
+    /**
+     * Visitor that highlights the primitives affected by this error
+     * @author frsantos
+     */
+    class PaintVisitor extends AbstractVisitor implements LicenseChangeVisitor {
+        /** The graphics */
+        private final Graphics g;
+        /** The MapView */
+        private final MapView mv;
+
+        /**
+         * Constructor
+         * @param g The graphics
+         * @param mv The Mapview
+         */
+        public PaintVisitor(Graphics g, MapView mv) {
+            this.g = g;
+            this.mv = mv;
+        }
+
+        public void visit(OsmPrimitive p) {
+            if (p.isUsable()) {
+                p.visit(this);
+            }
+        }
+
+        /**
+         * Draws a circle around the node
+         * @param n The node
+         * @param color The circle color
+         */
+        public void drawNode(Node n, Color color) {
+            Point p = mv.getPoint(n);
+            g.setColor(color);
+            if (selected) {
+                g.fillOval(p.x - 5, p.y - 5, 10, 10);
+            } else
+                g.drawOval(p.x - 5, p.y - 5, 10, 10);
+        }
+
+        public void drawSegment(Point p1, Point p2, Color color) {
+            g.setColor(color);
+
+            double t = Math.atan2(p2.x - p1.x, p2.y - p1.y);
+            double cosT = Math.cos(t);
+            double sinT = Math.sin(t);
+            int deg = (int) Math.toDegrees(t);
+            if (selected) {
+                int[] x = new int[] { (int) (p1.x + 5 * cosT), (int) (p2.x + 5 * cosT), (int) (p2.x - 5 * cosT),
+                        (int) (p1.x - 5 * cosT) };
+                int[] y = new int[] { (int) (p1.y - 5 * sinT), (int) (p2.y - 5 * sinT), (int) (p2.y + 5 * sinT),
+                        (int) (p1.y + 5 * sinT) };
+                g.fillPolygon(x, y, 4);
+                g.fillArc(p1.x - 5, p1.y - 5, 10, 10, deg, 180);
+                g.fillArc(p2.x - 5, p2.y - 5, 10, 10, deg, -180);
+            } else {
+                g.drawLine((int) (p1.x + 5 * cosT), (int) (p1.y - 5 * sinT), (int) (p2.x + 5 * cosT),
+                        (int) (p2.y - 5 * sinT));
+                g.drawLine((int) (p1.x - 5 * cosT), (int) (p1.y + 5 * sinT), (int) (p2.x - 5 * cosT),
+                        (int) (p2.y + 5 * sinT));
+                g.drawArc(p1.x - 5, p1.y - 5, 10, 10, deg, 180);
+                g.drawArc(p2.x - 5, p2.y - 5, 10, 10, deg, -180);
+            }
+        }
+
+        /**
+         * Draws a line around the segment
+         *
+         * @param s The segment
+         * @param color The color
+         */
+        public void drawSegment(Node n1, Node n2, Color color) {
+            drawSegment(mv.getPoint(n1), mv.getPoint(n2), color);
+        }
+
+        /**
+         * Draw a small rectangle.
+         * White if selected (as always) or red otherwise.
+         *
+         * @param n The node to draw.
+         */
+        public void visit(Node n) {
+            if (isNodeVisible(n))
+                drawNode(n, severity.getColor());
+        }
+
+        public void visit(Way w) {
+            visit(w.getNodes());
+        }
+
+        public void visit(WaySegment ws) {
+            if (ws.lowerIndex < 0 || ws.lowerIndex + 1 >= ws.way.getNodesCount())
+                return;
+            Node a = ws.way.getNodes().get(ws.lowerIndex), b = ws.way.getNodes().get(ws.lowerIndex + 1);
+            if (isSegmentVisible(a, b)) {
+                drawSegment(a, b, severity.getColor());
+            }
+        }
+
+        public void visit(Relation r) {
+            /* No idea how to draw a relation. */
+        }
+
+        /**
+         * Checks if the given node is in the visible area.
+         * @param n The node to check for visibility
+         * @return true if the node is visible
+         */
+        protected boolean isNodeVisible(Node n) {
+            Point p = mv.getPoint(n);
+            return !((p.x < 0) || (p.y < 0) || (p.x > mv.getWidth()) || (p.y > mv.getHeight()));
+        }
+
+        /**
+         * Checks if the given segment is in the visible area.
+         * NOTE: This will return true for a small number of non-visible
+         *       segments.
+         * @param ls The segment to check
+         * @return true if the segment is visible
+         */
+        protected boolean isSegmentVisible(Node n1, Node n2) {
+            Point p1 = mv.getPoint(n1);
+            Point p2 = mv.getPoint(n2);
+            if ((p1.x < 0) && (p2.x < 0))
+                return false;
+            if ((p1.y < 0) && (p2.y < 0))
+                return false;
+            if ((p1.x > mv.getWidth()) && (p2.x > mv.getWidth()))
+                return false;
+            if ((p1.y > mv.getHeight()) && (p2.y > mv.getHeight()))
+                return false;
+            return true;
+        }
+
+        public void visit(List<Node> nodes) {
+            Node lastN = null;
+            for (Node n : nodes) {
+                if (lastN == null) {
+                    lastN = n;
+                    continue;
+                }
+                if (n.isDrawable() && isSegmentVisible(lastN, n)) {
+                    drawSegment(lastN, n, severity.getColor());
+                }
+                lastN = n;
+            }
+        }
+    }
+
+    /**
+     * Sets the selection flag of this error
+     * @param selected if this error is selected
+     */
+    public void setSelected(boolean selected) {
+        this.selected = selected;
+    }
+}
Index: applications/editors/josm/plugins/licensechange/src/org/openstreetmap/josm/plugins/licensechange/ProblemLayer.java
===================================================================
--- applications/editors/josm/plugins/licensechange/src/org/openstreetmap/josm/plugins/licensechange/ProblemLayer.java	(revision 25955)
+++ applications/editors/josm/plugins/licensechange/src/org/openstreetmap/josm/plugins/licensechange/ProblemLayer.java	(revision 25955)
@@ -0,0 +1,174 @@
+// License: GPL. See LICENSE file for details.
+package org.openstreetmap.josm.plugins.licensechange;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.Graphics2D;
+import java.util.Enumeration;
+import java.util.List;
+
+import javax.swing.Action;
+import javax.swing.Icon;
+import javax.swing.tree.DefaultMutableTreeNode;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.RenameLayerAction;
+import org.openstreetmap.josm.data.Bounds;
+import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
+import org.openstreetmap.josm.gui.MapView;
+import org.openstreetmap.josm.gui.MapView.LayerChangeListener;
+import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
+import org.openstreetmap.josm.gui.dialogs.LayerListPopup;
+import org.openstreetmap.josm.gui.layer.Layer;
+import org.openstreetmap.josm.gui.layer.OsmDataLayer;
+import org.openstreetmap.josm.plugins.licensechange.util.Bag;
+import org.openstreetmap.josm.tools.ImageProvider;
+
+/**
+ * A layer showing problem messages.
+ */
+public class ProblemLayer extends Layer implements LayerChangeListener 
+{
+
+    private LicenseChangePlugin plugin;
+
+    private int updateCount = -1;
+
+
+    public ProblemLayer(LicenseChangePlugin plugin) 
+    {
+        super(tr("Relicensing Problems"));
+        this.plugin = plugin;
+        MapView.addLayerChangeListener(this);
+    }
+
+    /**
+     * Return a static icon.
+     */
+    @Override
+    public Icon getIcon() 
+    {
+        return ImageProvider.get("layer", "licensechange");
+    }
+
+    /**
+     * Draw all primitives in this layer but do not draw modified ones (they
+     * are drawn by the edit layer).
+     * Draw nodes last to overlap the ways they belong to.
+     */
+    @SuppressWarnings("unchecked")
+    @Override
+    public void paint(final Graphics2D g, final MapView mv, Bounds bounds) 
+    {
+        updateCount = plugin.validationDialog.tree.getUpdateCount();
+        DefaultMutableTreeNode root = plugin.validationDialog.tree.getRoot();
+        if (root == null || root.getChildCount() == 0)
+            return;
+
+        DefaultMutableTreeNode severity = (DefaultMutableTreeNode) root.getLastChild();
+        while (severity != null) 
+        {
+            Enumeration<DefaultMutableTreeNode> problemMessages = severity.breadthFirstEnumeration();
+            while (problemMessages.hasMoreElements()) 
+            {
+                Object tn = problemMessages.nextElement().getUserObject();
+                if (tn instanceof LicenseProblem)
+                    ((LicenseProblem) tn).paint(g, mv);
+            }
+
+            // Severities in inverse order
+            severity = severity.getPreviousSibling();
+        }
+    }
+
+    @Override
+    public String getToolTipText() 
+    {
+        Bag<Severity, LicenseProblem> problemTree = new Bag<Severity, LicenseProblem>();
+        List<LicenseProblem> problems = plugin.validationDialog.tree.getErrors();
+        for (LicenseProblem e : problems) {
+            problemTree.add(e.getSeverity(), e);
+        }
+
+        StringBuilder b = new StringBuilder();
+        for (Severity s : Severity.values()) 
+        {
+            if (problemTree.containsKey(s))
+                b.append(tr(s.toString())).append(": ").append(problemTree.get(s).size()).append("<br>");
+        }
+
+        if (b.length() == 0)
+            return "<html>" + tr("No relicensing problems") + "</html>";
+        else
+            return "<html>" + tr("Relicensing problems") + ":<br>" + b + "</html>";
+    }
+
+    @Override
+    public void mergeFrom(Layer from) 
+    {
+    }
+
+    @Override
+    public boolean isMergable(Layer other) 
+    {
+        return false;
+    }
+
+    @Override
+    public boolean isChanged() 
+    {
+        return updateCount != plugin.validationDialog.tree.getUpdateCount();
+    }
+
+    @Override
+    public void visitBoundingBox(BoundingXYVisitor v) 
+    {
+    }
+
+    @Override
+    public Object getInfoComponent() 
+    {
+        return getToolTipText();
+    }
+
+    @Override
+    public Action[] getMenuEntries() 
+    {
+        return new Action[] {
+                LayerListDialog.getInstance().createShowHideLayerAction(),
+                LayerListDialog.getInstance().createDeleteLayerAction(),
+                SeparatorLayerAction.INSTANCE,
+                new RenameLayerAction(null, this),
+                SeparatorLayerAction.INSTANCE,
+                new LayerListPopup.InfoAction(this) };
+    }
+
+    @Override
+    public void destroy() 
+    {
+    }
+
+    public void activeLayerChange(Layer oldLayer, Layer newLayer) 
+    {
+    }
+
+    public void layerAdded(Layer newLayer) 
+    {
+    }
+
+    /**
+     * If layer is the OSM Data layer, remove all problems
+     */
+    public void layerRemoved(Layer oldLayer) 
+    {
+        if (oldLayer instanceof OsmDataLayer &&  Main.map.mapView.getEditLayer() == null) 
+        {
+            Main.map.mapView.removeLayer(this);
+        } 
+        else if (oldLayer == this) 
+        {
+            MapView.removeLayerChangeListener(this);
+            LicenseChangePlugin.problemLayer = null;
+        }
+    }
+}
Index: applications/editors/josm/plugins/licensechange/src/org/openstreetmap/josm/plugins/licensechange/ProblemTreePanel.java
===================================================================
--- applications/editors/josm/plugins/licensechange/src/org/openstreetmap/josm/plugins/licensechange/ProblemTreePanel.java	(revision 25955)
+++ applications/editors/josm/plugins/licensechange/src/org/openstreetmap/josm/plugins/licensechange/ProblemTreePanel.java	(revision 25955)
@@ -0,0 +1,315 @@
+// License: GPL. See LICENSE file for details.
+package org.openstreetmap.josm.plugins.licensechange;
+
+import java.awt.event.MouseEvent;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.Map.Entry;
+
+import javax.swing.JTree;
+import javax.swing.ToolTipManager;
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.DefaultTreeModel;
+import javax.swing.tree.TreePath;
+import javax.swing.tree.TreeSelectionModel;
+
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.plugins.licensechange.util.Bag;
+import org.openstreetmap.josm.data.validation.util.MultipleNameVisitor;
+
+/**
+ * A panel that displays the tree of license change problems.
+ */
+
+public class ProblemTreePanel extends JTree {
+
+    /**
+     * The validation data.
+     */
+    protected DefaultTreeModel treeModel = new DefaultTreeModel(new DefaultMutableTreeNode());
+
+    /** The list of errors shown in the tree */
+    private List<LicenseProblem> errors;
+
+    /**
+     * If {@link #filter} is not <code>null</code> only errors are displayed
+     * that refer to one of the primitives in the filter.
+     */
+    private Set<OsmPrimitive> filter = null;
+
+    private int updateCount;
+
+    /**
+     * Constructor
+     * @param errors The list of errors
+     */
+    public ProblemTreePanel(List<LicenseProblem> errors) {
+        ToolTipManager.sharedInstance().registerComponent(this);
+        this.setModel(treeModel);
+        this.setRootVisible(false);
+        this.setShowsRootHandles(true);
+        this.expandRow(0);
+        this.setVisibleRowCount(8);
+        this.setCellRenderer(new ProblemTreeRenderer());
+        this.getSelectionModel().setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION);
+        setErrorList(errors);
+    }
+
+    @Override
+    public String getToolTipText(MouseEvent e) {
+        String res = null;
+        TreePath path = getPathForLocation(e.getX(), e.getY());
+        if (path != null) {
+            DefaultMutableTreeNode node = (DefaultMutableTreeNode) path.getLastPathComponent();
+            Object nodeInfo = node.getUserObject();
+
+            if (nodeInfo instanceof LicenseProblem) {
+                LicenseProblem error = (LicenseProblem) nodeInfo;
+                MultipleNameVisitor v = new MultipleNameVisitor();
+                v.visit(error.getPrimitives());
+                res = "<html>" + v.getText() + "<br>" + error.getMessage();
+                res += "</html>";
+            } else
+                res = node.toString();
+        }
+        return res;
+    }
+
+    /** Constructor */
+    public ProblemTreePanel() {
+        this(null);
+    }
+
+    @Override
+    public void setVisible(boolean v) {
+        if (v)
+            buildTree();
+        else
+            treeModel.setRoot(new DefaultMutableTreeNode());
+        super.setVisible(v);
+    }
+
+    /**
+     * Builds the errors tree
+     */
+    public void buildTree() {
+        updateCount++;
+        DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode();
+
+        if (errors == null || errors.isEmpty()) {
+            treeModel.setRoot(rootNode);
+            return;
+        }
+
+        // Remember the currently expanded rows
+        Set<Object> oldSelectedRows = new HashSet<Object>();
+        Enumeration<TreePath> expanded = getExpandedDescendants(new TreePath(getRoot()));
+        if (expanded != null) {
+            while (expanded.hasMoreElements()) {
+                TreePath path = expanded.nextElement();
+                DefaultMutableTreeNode node = (DefaultMutableTreeNode) path.getLastPathComponent();
+                Object userObject = node.getUserObject();
+                if (userObject instanceof Severity)
+                    oldSelectedRows.add(userObject);
+                else if (userObject instanceof String) {
+                    String msg = (String) userObject;
+                    msg = msg.substring(0, msg.lastIndexOf(" ("));
+                    oldSelectedRows.add(msg);
+                }
+            }
+        }
+
+        Map<Severity, Bag<String, LicenseProblem>> errorTree = new HashMap<Severity, Bag<String, LicenseProblem>>();
+        Map<Severity, HashMap<String, Bag<String, LicenseProblem>>> errorTreeDeep = new HashMap<Severity, HashMap<String, Bag<String, LicenseProblem>>>();
+        for (Severity s : Severity.values()) {
+            errorTree.put(s, new Bag<String, LicenseProblem>(20));
+            errorTreeDeep.put(s, new HashMap<String, Bag<String, LicenseProblem>>());
+        }
+
+        for (LicenseProblem e : errors) {
+            Severity s = e.getSeverity();
+            String m = e.getMessage();
+            if (filter != null) {
+                boolean found = false;
+                for (OsmPrimitive p : e.getPrimitives()) {
+                    if (filter.contains(p)) {
+                        found = true;
+                        break;
+                    }
+                }
+                if (!found)
+                    continue;
+            } else
+                errorTree.get(s).add(m, e);
+        }
+
+        List<TreePath> expandedPaths = new ArrayList<TreePath>();
+        for (Severity s : Severity.values()) {
+            Bag<String, LicenseProblem> severityErrors = errorTree.get(s);
+            Map<String, Bag<String, LicenseProblem>> severityErrorsDeep = errorTreeDeep.get(s);
+            if (severityErrors.isEmpty() && severityErrorsDeep.isEmpty())
+                continue;
+
+            // Severity node
+            DefaultMutableTreeNode severityNode = new DefaultMutableTreeNode(s);
+            rootNode.add(severityNode);
+
+            if (oldSelectedRows.contains(s))
+                expandedPaths.add(new TreePath(new Object[] { rootNode, severityNode }));
+
+            for (Entry<String, List<LicenseProblem>> msgErrors : severityErrors.entrySet()) {
+                // Message node
+                List<LicenseProblem> errors = msgErrors.getValue();
+                String msg = msgErrors.getKey() + " (" + errors.size() + ")";
+                DefaultMutableTreeNode messageNode = new DefaultMutableTreeNode(msg);
+                severityNode.add(messageNode);
+
+                if (oldSelectedRows.contains(msgErrors.getKey())) {
+                    expandedPaths.add(new TreePath(new Object[] { rootNode, severityNode, messageNode }));
+                }
+
+                for (LicenseProblem error : errors) {
+                    // Error node
+                    DefaultMutableTreeNode errorNode = new DefaultMutableTreeNode(error);
+                    messageNode.add(errorNode);
+                }
+            }
+            for (Entry<String, Bag<String, LicenseProblem>> bag : severityErrorsDeep.entrySet()) {
+                // Group node
+                Bag<String, LicenseProblem> errorlist = bag.getValue();
+                DefaultMutableTreeNode groupNode = null;
+                if (errorlist.size() > 1) {
+                    String nmsg = bag.getKey() + " (" + errorlist.size() + ")";
+                    groupNode = new DefaultMutableTreeNode(nmsg);
+                    severityNode.add(groupNode);
+                    if (oldSelectedRows.contains(bag.getKey())) {
+                        expandedPaths.add(new TreePath(new Object[] { rootNode, severityNode, groupNode }));
+                    }
+                }
+
+                for (Entry<String, List<LicenseProblem>> msgErrors : errorlist.entrySet()) {
+                    // Message node
+                    List<LicenseProblem> errors = msgErrors.getValue();
+                    String msg;
+                    if (groupNode != null)
+                        msg = msgErrors.getKey() + " (" + errors.size() + ")";
+                    else
+                        msg = bag.getKey() + " - " + msgErrors.getKey() + " (" + errors.size() + ")";
+                    DefaultMutableTreeNode messageNode = new DefaultMutableTreeNode(msg);
+                    if (groupNode != null)
+                        groupNode.add(messageNode);
+                    else
+                        severityNode.add(messageNode);
+
+                    if (oldSelectedRows.contains(msgErrors.getKey())) {
+                        if (groupNode != null) {
+                            expandedPaths.add(new TreePath(new Object[] { rootNode, severityNode, groupNode,
+                                    messageNode }));
+                        } else {
+                            expandedPaths.add(new TreePath(new Object[] { rootNode, severityNode, messageNode }));
+                        }
+                    }
+
+                    for (LicenseProblem error : errors) {
+                        // Error node
+                        DefaultMutableTreeNode errorNode = new DefaultMutableTreeNode(error);
+                        messageNode.add(errorNode);
+                    }
+                }
+            }
+        }
+
+        treeModel.setRoot(rootNode);
+        for (TreePath path : expandedPaths) {
+            this.expandPath(path);
+        }
+    }
+
+    /**
+     * Sets the errors list used by a data layer
+     * @param errors The error list that is used by a data layer
+     */
+    public void setErrorList(List<LicenseProblem> errors) {
+        this.errors = errors;
+        if (isVisible())
+            buildTree();
+    }
+
+    /**
+     * Clears the current error list and adds these errors to it
+     * @param errors The validation errors
+     */
+    public void setErrors(List<LicenseProblem> newerrors) {
+        if (errors == null)
+            return;
+        errors.clear();
+        for (LicenseProblem error : newerrors) {
+            errors.add(error);
+        }
+        if (isVisible())
+            buildTree();
+    }
+
+    /**
+     * Returns the errors of the tree
+     * @return  the errors of the tree
+     */
+    public List<LicenseProblem> getErrors() {
+        return errors != null ? errors : Collections.<LicenseProblem> emptyList();
+    }
+
+    public Set<OsmPrimitive> getFilter() {
+        return filter;
+    }
+
+    public void setFilter(Set<OsmPrimitive> filter) {
+        if (filter != null && filter.size() == 0)
+            this.filter = null;
+        else
+            this.filter = filter;
+        if (isVisible())
+            buildTree();
+    }
+
+    /**
+     * Updates the current errors list
+     * @param errors The validation errors
+     */
+    public void resetErrors() {
+        List<LicenseProblem> e = new ArrayList<LicenseProblem>(errors);
+        setErrors(e);
+    }
+
+    /**
+     * Expands all tree
+     */
+    @SuppressWarnings("unchecked")
+    public void expandAll() {
+        DefaultMutableTreeNode root = getRoot();
+
+        int row = 0;
+        Enumeration<DefaultMutableTreeNode> children = root.breadthFirstEnumeration();
+        while (children.hasMoreElements()) {
+            children.nextElement();
+            expandRow(row++);
+        }
+    }
+
+    /**
+     * Returns the root node model.
+     * @return The root node model
+     */
+    public DefaultMutableTreeNode getRoot() {
+        return (DefaultMutableTreeNode) treeModel.getRoot();
+    }
+
+    public int getUpdateCount() {
+        return updateCount;
+    }
+}
Index: applications/editors/josm/plugins/licensechange/src/org/openstreetmap/josm/plugins/licensechange/ProblemTreeRenderer.java
===================================================================
--- applications/editors/josm/plugins/licensechange/src/org/openstreetmap/josm/plugins/licensechange/ProblemTreeRenderer.java	(revision 25955)
+++ applications/editors/josm/plugins/licensechange/src/org/openstreetmap/josm/plugins/licensechange/ProblemTreeRenderer.java	(revision 25955)
@@ -0,0 +1,46 @@
+// License: GPL. See LICENSE file for details.
+package org.openstreetmap.josm.plugins.licensechange;
+
+import java.awt.Component;
+
+import javax.swing.JTree;
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.DefaultTreeCellRenderer;
+
+import org.openstreetmap.josm.data.validation.util.MultipleNameVisitor;
+import org.openstreetmap.josm.tools.ImageProvider;
+
+/**
+ * Tree renderer for displaying errors
+ * @author frsantos
+ */
+public class ProblemTreeRenderer extends DefaultTreeCellRenderer
+{
+
+    @Override
+    public Component getTreeCellRendererComponent(JTree tree, Object value,
+            boolean selected, boolean expanded, boolean leaf, int row,
+            boolean hasFocus)
+    {
+        super.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus);
+
+        DefaultMutableTreeNode node = (DefaultMutableTreeNode)value;
+        Object nodeInfo = node.getUserObject();
+
+        if (nodeInfo instanceof Severity)
+        {
+            Severity s = (Severity)nodeInfo;
+            setIcon(ImageProvider.get("data", s.getIcon()));
+        }
+        else if (nodeInfo instanceof LicenseProblem)
+        {
+            LicenseProblem error = (LicenseProblem)nodeInfo;
+            MultipleNameVisitor v = new MultipleNameVisitor();
+            v.visit(error.getPrimitives());
+            setText(v.getText());
+            setIcon(v.getIcon());
+        }
+
+        return this;
+    }
+}
Index: applications/editors/josm/plugins/licensechange/src/org/openstreetmap/josm/plugins/licensechange/Severity.java
===================================================================
--- applications/editors/josm/plugins/licensechange/src/org/openstreetmap/josm/plugins/licensechange/Severity.java	(revision 25955)
+++ applications/editors/josm/plugins/licensechange/src/org/openstreetmap/josm/plugins/licensechange/Severity.java	(revision 25955)
@@ -0,0 +1,74 @@
+// License: GPL. See LICENSE file for details.
+package org.openstreetmap.josm.plugins.licensechange;
+
+import static org.openstreetmap.josm.tools.I18n.marktr;
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.Color;
+
+import org.openstreetmap.josm.Main;
+
+/** The error severity */
+public enum Severity {
+
+    /** Error messages */
+    DATA_LOSS(tr("Data loss"), "error.gif",                     
+        Main.pref.getColor(marktr("validation error"), Color.RED)),
+
+    /** Warning messages */
+    POSSIBLE_DATA_LOSS(tr("Possible data loss"), "warning.gif", 
+        Main.pref.getColor(marktr("validation warning"), Color.ORANGE)),
+
+    /** Other messages */
+    DATA_REDUCTION(tr("Data reduction"), "other.gif",           
+        Main.pref.getColor(marktr("validation other"), Color.YELLOW));
+
+    /** Description of the severity code */
+    private final String message;
+
+    /** Associated icon */
+    private final String icon;
+
+    /** Associated color */
+    private final Color color;
+
+    /**
+     * Constructor
+     *
+     * @param message Description
+     * @param icon Associated icon
+     * @param color The color of this severity
+     */
+    Severity(String message, String icon, Color color)
+    {
+        this.message = message;
+        this.icon = icon;
+        this.color = color;
+    }
+
+    @Override
+    public String toString()
+    {
+        return message;
+    }
+
+    /**
+     * Gets the associated icon
+     * @return the associated icon
+     */
+    public String getIcon()
+    {
+        return icon;
+    }
+
+    /**
+     * Gets the associated color
+     * @return The associated color
+     */
+    public Color getColor()
+    {
+        return color;
+    }
+
+
+}
Index: applications/editors/josm/plugins/licensechange/src/org/openstreetmap/josm/plugins/licensechange/util/Bag.java
===================================================================
--- applications/editors/josm/plugins/licensechange/src/org/openstreetmap/josm/plugins/licensechange/util/Bag.java	(revision 25955)
+++ applications/editors/josm/plugins/licensechange/src/org/openstreetmap/josm/plugins/licensechange/util/Bag.java	(revision 25955)
@@ -0,0 +1,94 @@
+// License: GPL. See LICENSE file for details.
+package org.openstreetmap.josm.plugins.licensechange.util;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ *
+ * A very simple bag to store multiple occurences of a same key.
+ * <p>
+ * The bag will keep, for each key, a list of values.
+ *
+ * @author frsantos
+ *
+ * @param <K> The key class
+ * @param <V> The value class
+ */
+public class Bag<K,V> extends HashMap<K, List<V>>
+{
+    /** Serializable ID */
+    private static final long serialVersionUID = 5374049172859211610L;
+
+    /**
+     * Returns the list of elements with the same key
+     * @param key The key to obtain the elements
+     * @return the list of elements with the same key
+     */
+    public List<V> get(K key)
+    {
+        return super.get(key);
+    }
+
+    /**
+     * Adds an element to the bag
+     * @param key The key of the element
+     * @param value The element to add
+     */
+    public void add(K key, V value)
+    {
+        List<V> values = get(key);
+        if( values == null )
+        {
+            values = new ArrayList<V>();
+            put(key, values);
+        }
+        values.add(value);
+    }
+
+    /**
+     * Adds an element to the bag
+     * @param key The key of the element
+     * @param value The element to add
+     */
+    public void add(K key)
+    {
+        List<V> values = get(key);
+        if( values == null )
+        {
+            values = new ArrayList<V>();
+            put(key, values);
+        }
+    }
+
+    /**
+     * Constructor
+     */
+    public Bag()
+    {
+        super();
+    }
+
+    /**
+     * Constructor
+     *
+     * @param initialCapacity The initial capacity
+     */
+    public Bag(int initialCapacity)
+    {
+        super(initialCapacity);
+    }
+
+    /**
+     * Returns true if the bag contains a value for a key
+     * @param key The key
+     * @param value The value
+     * @return true if the key contains the value
+     */
+    public boolean contains(K key, V value)
+    {
+        List<V> values = get(key);
+        return (values == null) ? false : values.contains(value);
+    }
+}
