Index: /applications/editors/josm/plugins/merge-overlap/.classpath
===================================================================
--- /applications/editors/josm/plugins/merge-overlap/.classpath	(revision 26575)
+++ /applications/editors/josm/plugins/merge-overlap/.classpath	(revision 26575)
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="src" path="src"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/java-6-openjdk"/>
+	<classpathentry kind="lib" path="/usr/share/josm/josm.jar" sourcepath="/home2/brunner/workspace/josm-0.0.svn3094/src"/>
+	<classpathentry combineaccessrules="false" kind="src" path="/JOSM"/>
+	<classpathentry kind="output" path="bin"/>
+</classpath>
Index: /applications/editors/josm/plugins/merge-overlap/.project
===================================================================
--- /applications/editors/josm/plugins/merge-overlap/.project	(revision 26575)
+++ /applications/editors/josm/plugins/merge-overlap/.project	(revision 26575)
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>merge-overlap</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+	</natures>
+</projectDescription>
Index: /applications/editors/josm/plugins/merge-overlap/.settings/org.eclipse.jdt.core.prefs
===================================================================
--- /applications/editors/josm/plugins/merge-overlap/.settings/org.eclipse.jdt.core.prefs	(revision 26575)
+++ /applications/editors/josm/plugins/merge-overlap/.settings/org.eclipse.jdt.core.prefs	(revision 26575)
@@ -0,0 +1,12 @@
+#Thu Nov 05 21:57:09 CET 2009
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
+org.eclipse.jdt.core.compiler.compliance=1.5
+org.eclipse.jdt.core.compiler.debug.lineNumber=generate
+org.eclipse.jdt.core.compiler.debug.localVariable=generate
+org.eclipse.jdt.core.compiler.debug.sourceFile=generate
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.source=1.5
Index: /applications/editors/josm/plugins/merge-overlap/LICENSE
===================================================================
--- /applications/editors/josm/plugins/merge-overlap/LICENSE	(revision 26575)
+++ /applications/editors/josm/plugins/merge-overlap/LICENSE	(revision 26575)
@@ -0,0 +1,345 @@
+		    GNU GENERAL PUBLIC LICENSE
+		       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+	51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+			    Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+
+		    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+			    NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+		     END OF TERMS AND CONDITIONS
+
+
+	    How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year  name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Library General
+Public License instead of this License.
Index: /applications/editors/josm/plugins/merge-overlap/README
===================================================================
--- /applications/editors/josm/plugins/merge-overlap/README	(revision 26575)
+++ /applications/editors/josm/plugins/merge-overlap/README	(revision 26575)
@@ -0,0 +1,1 @@
+Merge overlapping part of ways.
Index: /applications/editors/josm/plugins/merge-overlap/build.xml
===================================================================
--- /applications/editors/josm/plugins/merge-overlap/build.xml	(revision 26575)
+++ /applications/editors/josm/plugins/merge-overlap/build.xml	(revision 26575)
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+** This is the build file for the merge-overlap plugin.
+**
+** Maintaining versions
+** ====================
+** see README.template
+**
+** Usage
+** =====
+** To build it run
+**
+**    > ant  dist
+**
+** To install the generated plugin locally (in you default plugin directory) run
+**
+**    > ant  install
+**
+** The generated plugin jar is not automatically available in JOSMs plugin configuration
+** dialog. You have to check it in first.
+**
+** Use the ant target 'publish' to check in the plugin and make it available to other
+** JOSM users:
+**    set the properties commit.message and plugin.main.version
+** and run
+**    > ant  publish
+**
+**
+-->
+<project name="merge-overlap" default="dist" basedir=".">
+    <!-- enter the SVN commit message -->
+    <property name="commit.message" value="preset maintenance (${ant.project.name})"/>
+    <!-- enter the *lowest* JOSM version this plugin is currently compatible with -->
+    <property name="plugin.main.version" value="4279"/>
+
+    <import file="../build-common.xml"/>
+
+    <target name="dist" depends="compile,revision">
+        <echo message="creating ${ant.project.name}.jar ... "/>
+        <copy todir="${plugin.build.dir}/images">
+            <fileset dir="images"/>
+        </copy>
+        <copy todir="${plugin.build.dir}/data">
+            <fileset dir="data"/>
+        </copy>
+	    <copy todir="${plugin.build.dir}">
+	        <fileset dir=".">
+	            <include name="README"/>
+	            <include name="LICENSE"/>
+	        </fileset>
+	    </copy>
+        <jar destfile="${plugin.jar}" basedir="${plugin.build.dir}">
+            <manifest>
+                <attribute name="Author" value="Stéphane Brunner"/>
+                <attribute name="Plugin-Class" value="mergeoverlap.MergeOverlapPlugin"/>
+                <attribute name="Plugin-Description" value="Merge overlapping part of ways."/>
+                <attribute name="Plugin-Icon" value="images/merge_overlap.png"/>
+                <attribute name="Plugin-Version" value="${version.entry.commit.revision}"/>
+                <attribute name="Plugin-Date" value="${version.entry.commit.date}"/>
+            </manifest>
+        </jar>
+    </target>
+</project>
Index: /applications/editors/josm/plugins/merge-overlap/src/mergeoverlap/MergeOverlapAction.java
===================================================================
--- /applications/editors/josm/plugins/merge-overlap/src/mergeoverlap/MergeOverlapAction.java	(revision 26575)
+++ /applications/editors/josm/plugins/merge-overlap/src/mergeoverlap/MergeOverlapAction.java	(revision 26575)
@@ -0,0 +1,749 @@
+package mergeoverlap;
+
+import static org.openstreetmap.josm.gui.conflict.tags.TagConflictResolutionUtil.combineTigerTags;
+import static org.openstreetmap.josm.gui.conflict.tags.TagConflictResolutionUtil.completeTagCollectionForEditing;
+import static org.openstreetmap.josm.gui.conflict.tags.TagConflictResolutionUtil.normalizeTagCollectionBeforeEditing;
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+import java.rmi.server.RMIClassLoader;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.swing.JOptionPane;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.JosmAction;
+import org.openstreetmap.josm.actions.SplitWayAction;
+import org.openstreetmap.josm.actions.CombineWayAction.NodeGraph;
+import org.openstreetmap.josm.actions.SplitWayAction.SplitWayResult;
+import org.openstreetmap.josm.command.AddCommand;
+import org.openstreetmap.josm.command.ChangeCommand;
+import org.openstreetmap.josm.command.Command;
+import org.openstreetmap.josm.command.DeleteCommand;
+import org.openstreetmap.josm.command.SequenceCommand;
+import org.openstreetmap.josm.corrector.ReverseWayTagCorrector;
+import org.openstreetmap.josm.corrector.UserCancelException;
+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.RelationMember;
+import org.openstreetmap.josm.data.osm.TagCollection;
+import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.gui.layer.OsmDataLayer;
+import org.openstreetmap.josm.tools.Pair;
+import org.openstreetmap.josm.tools.Shortcut;
+
+/**
+ * Merge overlapping part of ways.
+ */
+@SuppressWarnings("serial")
+public class MergeOverlapAction extends JosmAction {
+
+    public MergeOverlapAction() {
+        super(tr("Merge overlap"), "merge_overlap",
+                tr("Merge overlap of ways."), Shortcut.registerShortcut(
+                        "tools:mergeoverlap", tr("Tool: {0}",
+                                tr("Merge overlap")), KeyEvent.VK_O,
+                        Shortcut.GROUP_EDIT, Shortcut.SHIFT_DEFAULT), true);
+    }
+
+    Map<Way, List<Relation>> relations = new HashMap<Way, List<Relation>>();
+    Map<Way, Way> oldWays = new HashMap<Way, Way>();
+    Map<Relation, Relation> newRelations = new HashMap<Relation, Relation>();
+    Set<Way> deletes = new HashSet<Way>();
+
+    /**
+     * The action button has been clicked
+     * 
+     * @param e Action Event
+     */
+    public void actionPerformed(ActionEvent e) {
+
+        // List of selected ways
+        List<Way> ways = new ArrayList<Way>();
+        relations.clear();
+        newRelations.clear();
+        
+        // For every selected way
+        for (OsmPrimitive osm : Main.main.getCurrentDataSet().getSelected()) {
+            if (osm instanceof Way && !osm.isDeleted()) {
+            	Way way = (Way)osm;
+                ways.add(way);
+            	List<Relation> rels = new ArrayList<Relation>();
+            	for (Relation r: OsmPrimitive.getFilteredList(way.getReferrers(), Relation.class)) {
+                	rels.add(r);
+            	}
+            	relations.put(way, rels);
+            }
+        }
+        
+        List<Way> sel = new ArrayList<Way>(ways);
+        Collection<Command> cmds = new LinkedList<Command>();
+
+        // *****
+        // split
+        // *****
+        for (Way way : ways) {
+            Set<Node> nodes = new HashSet<Node>();
+            for (Way opositWay : ways) {
+	            if (way != opositWay) {
+	                List<NodePos> nodesPos = new LinkedList<NodePos>();
+	
+                	int pos = 0;
+                    for (Node node : way.getNodes()) {
+                    	int opositPos = 0;
+                        for (Node opositNode : opositWay.getNodes()) {
+                            if (node == opositNode) {
+                            	if (opositWay.isClosed()) {
+                            		opositPos %= opositWay.getNodesCount() - 1;
+                            	}
+                            	nodesPos.add(new NodePos(node, pos, opositPos));
+                            	break;
+                            }
+                            opositPos++;
+                        }
+                        pos++;
+                    }
+
+	            	NodePos start = null;
+	            	NodePos end = null;
+	            	int increment = 0;
+
+	                boolean hasFirst = false;
+	                for (NodePos node : nodesPos) {
+	                	if (start == null) {
+	                		start = node;
+	                	}
+	                	else {
+	                		if (end == null) {
+                				if (follows(way, opositWay, start, node, 1)) {
+                					end = node;
+                					increment = +1;
+                				}
+                				else if (follows(way, opositWay, start, node, -1)) {
+                					end = node;
+                					increment = -1;
+                				}
+                				else {
+                					start = node;
+                					end = null;
+                				}
+	                		}
+	                		else {
+	                			if (follows(way, opositWay, end, node, increment)) {
+	                				end = node;
+	                			}
+	                			else {
+	                				hasFirst = addNodes(start, end, way, nodes, hasFirst);
+	                                start = node;
+	                                end = null;
+	                			}
+	                		}
+	                    }
+	                }
+	                
+	                if (start != null && end != null) {
+	                	hasFirst = addNodes(start, end, way, nodes, hasFirst);
+	                    start = null;
+	                    end = null;
+	                }
+	            }
+            }
+            if (!nodes.isEmpty() && !way.isClosed() || nodes.size() >= 2) {
+                List<List<Node>> wayChunks = SplitWayAction.buildSplitChunks(way, new ArrayList<Node>(nodes));
+                SplitWayResult result = splitWay(getEditLayer(), way, wayChunks);
+                
+                cmds.add(result.getCommand());
+                sel.remove(way);
+                sel.add(result.getOriginalWay());
+                sel.addAll(result.getNewWays());
+                List<Relation> rels = relations.remove(way);
+                relations.put(result.getOriginalWay(), rels);
+                for (Way w: result.getNewWays()) {
+                	relations.put(w, rels);
+                }
+            }
+        }
+
+        // *****
+        // merge
+        // *****
+        ways = new ArrayList<Way>(sel);
+        while (!ways.isEmpty()) {
+            Way way = ways.get(0);
+            List<Way> combine = new ArrayList<Way>();
+            combine.add(way);
+            for (Way opositWay : ways) {
+                if (way != opositWay && way.getNodesCount() == opositWay.getNodesCount()) {
+                    boolean equals1 = true;
+                    for (int i = 0 ; i < way.getNodesCount() ; i++) {
+                        if (way.getNode(i) != opositWay.getNode(i)) {
+                            equals1 = false;
+                            break;
+                        }
+                    }
+                    boolean equals2 = true;
+                    for (int i = 0 ; i < way.getNodesCount() ; i++) {
+                        if (way.getNode(i) != opositWay.getNode(way.getNodesCount() - i -1)) {
+                            equals2 = false;
+                            break;
+                        }
+                    }
+                    if (equals1 || equals2) {
+                        combine.add(opositWay);
+                    }
+                }
+            }
+            ways.removeAll(combine);
+            if (combine.size() > 1) {
+                sel.removeAll(combine);
+                // combine
+                Pair<Way, List<Command>> combineResult;
+                try {
+                    combineResult = combineWaysWorker(combine);
+                } catch (UserCancelException e1) {
+                    return;
+                }
+                sel.add(combineResult.a);
+                cmds.addAll(combineResult.b);
+            }
+        }
+
+        for (Relation old: newRelations.keySet()) {
+        	System.out.println("+++++++++++++");
+        	System.out.println("old");
+        	System.out.println(old.getDataSet());
+        	System.out.println("new");
+        	System.out.println(newRelations.get(old).getDataSet());
+
+//        	for (int i=0; i < old.getMembersCount(); i++) {
+        	for (int i=0; i < newRelations.get(old).getMembersCount(); i++) {
+                RelationMember rm = newRelations.get(old).getMember(i);
+//                RelationMember rm = old.getMember(i);
+            	System.out.println("***********");
+            	System.out.println("old");
+ //           	System.out.println(rm.getMember());
+            	System.out.println(rm.getWay().getId());
+            	System.out.println(rm.getWay().getDataSet());
+/*            	System.out.println("new");
+            	Way w = newWays.get(rm.getWay());
+//            	System.out.println(w);
+            	if (w == null) {
+            		System.out.println(w);
+            	}
+            	else {
+            		System.out.println(w.getDataSet());            		
+            	}*/
+        	}
+        	cmds.add(new ChangeCommand(old, newRelations.get(old)));
+        }
+        
+        List<Way> del = new LinkedList<Way>();
+    	for (Way w: deletes) {
+    		if (!w.isDeleted()) {
+    			del.add(w);
+    		}
+    	}
+        if (!del.isEmpty()) {
+        	cmds.add(new DeleteCommand(del));
+        }
+
+        // Commit
+        Main.main.undoRedo.add(new SequenceCommand(tr("Merge Overlap (combine)"), cmds));
+        getCurrentDataSet().setSelected(sel);
+        Main.map.repaint();
+        
+        relations.clear();
+        newRelations.clear();
+        oldWays.clear();
+    }
+
+	private class NodePos {
+		Node node;
+		int pos;
+		int opositPos;
+		NodePos (Node n, int p, int op) {
+			node = n;
+			pos = p;
+			opositPos = op;
+		}
+		public String toString() {
+			return "NodePos: " + pos + ", " + opositPos + ", " + node;
+		}
+	}
+
+	private boolean addNodes(NodePos start, NodePos end, Way way, Set<Node> nodes,
+			boolean hasFirst) {
+		if (way.isClosed() || (start.node != way.getNode(0)
+		        && start.node != way.getNode(way.getNodesCount() - 1))) {
+			hasFirst = hasFirst || start.node == way.getNode(0);
+		    nodes.add(start.node);
+		}
+		if (way.isClosed() || (end.node != way.getNode(0)
+		        && end.node != way.getNode(way.getNodesCount() - 1))) {
+		    if (hasFirst && (end.node == way.getNode(way.getNodesCount() - 1))) {
+		    	nodes.remove(way.getNode(0));
+		    }
+		    else {
+		    	nodes.add(end.node);
+		    }
+		}
+		return hasFirst;
+	}
+
+    private boolean follows(Way way1, Way way2, NodePos np1, NodePos np2, int incr) {
+    	if (way2.isClosed() && incr == 1 && np1.opositPos == way2.getNodesCount() - 2) {
+    		return np2.pos == np1.pos + 1 && np2.opositPos == 0;        	
+    	}
+    	else if (way2.isClosed() && incr == 1 && np1.opositPos == 0) {
+    		return np2.pos == np1.pos && np2.opositPos == 0 
+    				|| np2.pos == np1.pos + 1 && np2.opositPos == 1;        	
+    	}	
+    	else if (way2.isClosed() && incr == -1 && np1.opositPos == 0) {
+    		return np2.pos == np1.pos && np2.opositPos == 0 
+    				|| np2.pos == np1.pos + 1 && np2.opositPos == way2.getNodesCount() - 2;        	
+    	}
+    	else {
+    		return np2.pos == np1.pos + 1 && np2.opositPos == np1.opositPos + incr;
+    	}
+    }
+
+    /**
+     * Splits a way
+     * 
+     * @param layer
+     * @param way
+     * @param wayChunks
+     * @return
+     */
+    private SplitWayResult splitWay(OsmDataLayer layer, Way way, List<List<Node>> wayChunks) {
+        // build a list of commands, and also a new selection list
+        Collection<Command> commandList = new ArrayList<Command>(wayChunks.size());
+
+        Iterator<List<Node>> chunkIt = wayChunks.iterator();
+        Collection<String> nowarnroles = Main.pref.getCollection(
+                "way.split.roles.nowarn", Arrays.asList(new String[] { 
+                		"outer", "inner", "forward", "backward" }));
+
+        // First, change the original way
+        Way changedWay = new Way(way);
+        oldWays.put(changedWay, way);
+        changedWay.setNodes(chunkIt.next());
+        commandList.add(new ChangeCommand(way, changedWay));
+
+        List<Way> newWays = new ArrayList<Way>();
+        // Second, create new ways
+        while (chunkIt.hasNext()) {
+            Way wayToAdd = new Way();
+            wayToAdd.setKeys(way.getKeys());
+            newWays.add(wayToAdd);
+            wayToAdd.setNodes(chunkIt.next());
+            commandList.add(new AddCommand(layer, wayToAdd));
+        }
+        boolean warnmerole = false;
+        boolean warnme = false;
+        // now copy all relations to new way also
+
+        for (Relation r : getParentRelations(way)) {
+            if (!r.isUsable()) {
+                continue;
+            }
+            Relation c = null;
+            String type = r.get("type");
+            if (type == null) {
+                type = "";
+            }
+
+            int i_c = 0, i_r = 0;
+            List<RelationMember> relationMembers = r.getMembers();
+            for (RelationMember rm : relationMembers) {
+                if (rm.isWay() && rm.getMember() == way) {
+                    boolean insert = true;
+                    if ("restriction".equals(type)) {
+                        /*
+                         * this code assumes the restriction is correct. No real
+                         * error checking done
+                         */
+                        String role = rm.getRole();
+                        if ("from".equals(role) || "to".equals(role)) {
+                            OsmPrimitive via = null;
+                            for (RelationMember rmv : r.getMembers()) {
+                                if ("via".equals(rmv.getRole())) {
+                                    via = rmv.getMember();
+                                }
+                            }
+                            List<Node> nodes = new ArrayList<Node>();
+                            if (via != null) {
+                                if (via instanceof Node) {
+                                    nodes.add((Node) via);
+                                } else if (via instanceof Way) {
+                                    nodes.add(((Way) via).lastNode());
+                                    nodes.add(((Way) via).firstNode());
+                                }
+                            }
+                            Way res = null;
+                            for (Node n : nodes) {
+                                if (changedWay.isFirstLastNode(n)) {
+                                    res = way;
+                                }
+                            }
+                            if (res == null) {
+                                for (Way wayToAdd : newWays) {
+                                    for (Node n : nodes) {
+                                        if (wayToAdd.isFirstLastNode(n)) {
+                                            res = wayToAdd;
+                                        }
+                                    }
+                                }
+                                if (res != null) {
+                                    if (c == null) {
+                                        c = getNew(r);
+                                    }
+                                    c.addMember(new RelationMember(role, res));
+                                    c.removeMembersFor(way);
+                                    insert = false;
+                                }
+                            } else {
+                                insert = false;
+                            }
+                        } else if (!"via".equals(role)) {
+                            warnme = true;
+                        }
+                    } else if (!("route".equals(type))
+                            && !("multipolygon".equals(type))) {
+                        warnme = true;
+                    }
+                    if (c == null) {
+                        c = getNew(r);
+                    }
+
+                    if (insert) {
+                        if (rm.hasRole() && !nowarnroles.contains(rm.getRole())) {
+                            warnmerole = true;
+                        }
+
+                        Boolean backwards = null;
+                        int k = 1;
+                        while (i_r - k >= 0 || i_r + k < relationMembers.size()) {
+                            if ((i_r - k >= 0)
+                                    && relationMembers.get(i_r - k).isWay()) {
+                                Way w = relationMembers.get(i_r - k).getWay();
+                                if ((w.lastNode() == way.firstNode())
+                                        || w.firstNode() == way.firstNode()) {
+                                    backwards = false;
+                                } else if ((w.firstNode() == way.lastNode())
+                                        || w.lastNode() == way.lastNode()) {
+                                    backwards = true;
+                                }
+                                break;
+                            }
+                            if ((i_r + k < relationMembers.size())
+                                    && relationMembers.get(i_r + k).isWay()) {
+                                Way w = relationMembers.get(i_r + k).getWay();
+                                if ((w.lastNode() == way.firstNode())
+                                        || w.firstNode() == way.firstNode()) {
+                                    backwards = true;
+                                } else if ((w.firstNode() == way.lastNode())
+                                        || w.lastNode() == way.lastNode()) {
+                                    backwards = false;
+                                }
+                                break;
+                            }
+                            k++;
+                        }
+
+                        int j = i_c;
+                        for (Way wayToAdd : newWays) {
+                            RelationMember em = new RelationMember(
+                                    rm.getRole(), wayToAdd);
+                            j++;
+                            if ((backwards != null) && backwards) {
+                                c.addMember(i_c, em);
+                            } else {
+                                c.addMember(j, em);
+                            }
+                        }
+                        i_c = j;
+                    }
+                }
+                i_c++;
+                i_r++;
+            }
+
+            if (c != null) {
+                //commandList.add(new ChangeCommand(layer, r, c));
+            	newRelations.put(r, c);
+            }
+        }
+        if (warnmerole) {
+            JOptionPane
+                    .showMessageDialog(
+                            Main.parent,
+                            tr("<html>A role based relation membership was copied to all new ways.<br>You should verify this and correct it when necessary.</html>"),
+                            tr("Warning"), JOptionPane.WARNING_MESSAGE);
+        } else if (warnme) {
+            JOptionPane
+                    .showMessageDialog(
+                            Main.parent,
+                            tr("<html>A relation membership was copied to all new ways.<br>You should verify this and correct it when necessary.</html>"),
+                            tr("Warning"), JOptionPane.WARNING_MESSAGE);
+        }
+
+        return new SplitWayResult(new SequenceCommand("Split way", commandList), null, changedWay, newWays);
+    }
+
+    /**
+     * @param ways
+     * @return null if ways cannot be combined. Otherwise returns the combined
+     *              ways and the commands to combine
+     * @throws UserCancelException
+     */
+    private Pair<Way, List<Command>> combineWaysWorker(Collection<Way> ways) throws UserCancelException {        		
+        
+        // prepare and clean the list of ways to combine
+        if (ways == null || ways.isEmpty())
+            return null;
+        ways.remove(null); // just in case -  remove all null ways from the collection
+
+        // remove duplicates, preserving order
+        ways = new LinkedHashSet<Way>(ways);
+
+        // try to build a new way which includes all the combined ways
+        NodeGraph graph = NodeGraph.createUndirectedGraphFromNodeWays(ways);
+        List<Node> path = graph.buildSpanningPath();
+
+        // check whether any ways have been reversed in the process
+        // and build the collection of tags used by the ways to combine
+        TagCollection wayTags = TagCollection.unionOfAllPrimitives(ways);
+
+        List<Way> reversedWays = new LinkedList<Way>();
+        List<Way> unreversedWays = new LinkedList<Way>();
+        for (Way w: ways) {
+            if ((path.indexOf(w.getNode(0)) + 1) == path.lastIndexOf(w.getNode(1))) {
+                unreversedWays.add(w);
+            } else {
+                reversedWays.add(w);
+            }
+        }
+        // reverse path if all ways have been reversed
+        if (unreversedWays.isEmpty()) {
+            Collections.reverse(path);
+            unreversedWays = reversedWays;
+            reversedWays = null;
+        }
+        if ((reversedWays != null) && !reversedWays.isEmpty()) {
+            // filter out ways that have no direction-dependent tags
+            unreversedWays = ReverseWayTagCorrector.irreversibleWays(unreversedWays);
+            reversedWays = ReverseWayTagCorrector.irreversibleWays(reversedWays);
+            // reverse path if there are more reversed than unreversed ways with direction-dependent tags
+            if (reversedWays.size() > unreversedWays.size()) {
+                Collections.reverse(path);
+                List<Way> tempWays = unreversedWays;
+                unreversedWays = reversedWays;
+                reversedWays = tempWays;
+            }
+            // if there are still reversed ways with direction-dependent tags, reverse their tags
+            if (!reversedWays.isEmpty()) {
+                List<Way> unreversedTagWays = new ArrayList<Way>(ways);
+                unreversedTagWays.removeAll(reversedWays);
+                ReverseWayTagCorrector reverseWayTagCorrector = new ReverseWayTagCorrector();
+                List<Way> reversedTagWays = new ArrayList<Way>();
+                Collection<Command> changePropertyCommands =  null;
+                for (Way w : reversedWays) {
+                    Way wnew = new Way(w);
+                    reversedTagWays.add(wnew);
+                    changePropertyCommands = reverseWayTagCorrector.execute(w, wnew);
+                }
+                if ((changePropertyCommands != null) && !changePropertyCommands.isEmpty()) {
+                    for (Command c : changePropertyCommands) {
+                        c.executeCommand();
+                    }
+                }
+                wayTags = TagCollection.unionOfAllPrimitives(reversedTagWays);
+                wayTags.add(TagCollection.unionOfAllPrimitives(unreversedTagWays));
+            }
+        }
+
+        // create the new way and apply the new node list
+        Way targetWay = getTargetWay(ways);
+        Way modifiedTargetWay = new Way(targetWay);
+        modifiedTargetWay.setNodes(path);
+
+        TagCollection completeWayTags = new TagCollection(wayTags);
+        combineTigerTags(completeWayTags);
+        normalizeTagCollectionBeforeEditing(completeWayTags, ways);
+        TagCollection tagsToEdit = new TagCollection(completeWayTags);
+        completeTagCollectionForEditing(tagsToEdit);
+
+        MyCombinePrimitiveResolverDialog dialog = MyCombinePrimitiveResolverDialog.getInstance();
+        dialog.getTagConflictResolverModel().populate(tagsToEdit, completeWayTags.getKeysWithMultipleValues());
+        dialog.setTargetPrimitive(targetWay);
+        Set<Relation> parentRelations = getParentRelations(ways);
+        dialog.getRelationMemberConflictResolverModel().populate(parentRelations, ways, oldWays);
+        dialog.prepareDefaultDecisions();
+
+        // resolve tag conflicts if necessary
+        if (askForMergeTag(ways) || duplicateParentRelations(ways)) {
+            dialog.setVisible(true);
+            if (dialog.isCancelled())
+                throw new UserCancelException();
+        }
+
+        LinkedList<Command> cmds = new LinkedList<Command>();
+        deletes.addAll(ways);
+        deletes.remove(targetWay);
+
+        cmds.add(new ChangeCommand(targetWay, modifiedTargetWay));
+        cmds.addAll(dialog.buildWayResolutionCommands());
+        dialog.buildRelationCorrespondance(newRelations, oldWays);
+
+        return new Pair<Way, List<Command>>(targetWay, cmds);
+    }
+
+    private static Way getTargetWay(Collection<Way> combinedWays) {
+        // init with an arbitrary way
+        Way targetWay = combinedWays.iterator().next();
+
+        // look for the first way already existing on
+        // the server
+        for (Way w : combinedWays) {
+            targetWay = w;
+            if (!w.isNew()) {
+                break;
+            }
+        }
+        return targetWay;
+    }
+
+    /**
+     * @return has tag to be merged (=> ask)
+     */
+    private static boolean askForMergeTag(Collection<Way> ways) {
+        for (Way way: ways) {
+            for (Way oposite: ways) {
+            	for (String key: way.getKeys().keySet()) {
+            		if (!"source".equals(key) && oposite.hasKey(key)
+            				&& !way.get(key).equals(oposite.get(key))) {
+            			return true;
+            		}
+            	}
+            }
+        }
+        return false;
+    }
+    
+    /**
+     * @return has duplicate parent relation
+     */
+    private boolean duplicateParentRelations(Collection<Way> ways) {
+        Set<Relation> relations = new HashSet<Relation>();
+        for (Way w: ways) {
+            List<Relation> rs = getParentRelations(w);
+            for (Relation r: rs) {
+                if (relations.contains(r)) {
+                    return true;
+                }
+            }
+            relations.addAll(rs);
+        }
+        return false;
+    }
+
+    /**
+     * Replies the set of referring relations
+     *
+     * @return the set of referring relations
+     */
+    private List<Relation> getParentRelations(Way way) {
+    	List<Relation> rels = new ArrayList<Relation>();
+    	for (Relation r: relations.get(way)) {
+    		if (newRelations.containsKey(r)) {
+    			rels.add(newRelations.get(r));
+    		}
+    		else {
+    			rels.add(r);
+    		}
+    	}
+    	return rels;
+    }
+    
+    private Relation getNew(Relation r) {
+    	return getNew(r, newRelations);
+    }
+
+    public static Relation getNew(Relation r, Map<Relation, Relation> newRelations) {
+    	if (newRelations.containsValue(r)) {
+    		return r;
+    	}
+    	else {
+    		Relation c = new Relation(r);
+    		newRelations.put(r, c);
+    		return c;
+    	}
+    }
+    
+    private Way getOld(Way r) {
+    	return getOld(r, oldWays);
+    }
+
+    public static Way getOld(Way w, Map<Way, Way> oldWays) {
+    	if (oldWays.containsKey(w)) {
+    		return oldWays.get(w);
+    	}
+    	else {
+    		return w;
+    	}
+    }
+    
+    /**
+     * Replies the set of referring relations
+     *
+     * @return the set of referring relations
+     */
+    private Set<Relation> getParentRelations(Collection<Way> ways) {
+        HashSet<Relation> ret = new HashSet<Relation>();
+        for (Way w: ways) {
+            ret.addAll(getParentRelations(w));
+        }
+        return ret;
+    }
+
+    /** Enable this action only if something is selected */
+    @Override
+    protected void updateEnabledState() {
+        if (getCurrentDataSet() == null) {
+            setEnabled(false);
+        } else {
+            updateEnabledState(getCurrentDataSet().getSelected());
+        }
+    }
+
+    /** Enable this action only if something is selected */
+    @Override
+    protected void updateEnabledState(
+            Collection<? extends OsmPrimitive> selection) {
+        if (selection == null) {
+            setEnabled(false);
+            return;
+        }
+        for (OsmPrimitive primitive: selection) {
+            if (!(primitive instanceof Way) || primitive.isDeleted()) {
+                setEnabled(false);
+                return;
+            }
+        }
+        setEnabled(selection.size() >= 2);
+    }
+}
Index: /applications/editors/josm/plugins/merge-overlap/src/mergeoverlap/MergeOverlapPlugin.java
===================================================================
--- /applications/editors/josm/plugins/merge-overlap/src/mergeoverlap/MergeOverlapPlugin.java	(revision 26575)
+++ /applications/editors/josm/plugins/merge-overlap/src/mergeoverlap/MergeOverlapPlugin.java	(revision 26575)
@@ -0,0 +1,40 @@
+package mergeoverlap;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import javax.swing.JMenu;
+import javax.swing.JMenuItem;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.plugins.Plugin;
+import org.openstreetmap.josm.plugins.PluginInformation;
+
+/**
+ * A plugin merge overlapping part of selected ways to fix warns
+ */
+public class MergeOverlapPlugin extends Plugin {
+
+    protected String name;
+
+        public MergeOverlapPlugin(PluginInformation info) {
+        super(info);
+        name = tr("Merge overlap");
+        JMenu toolsMenu = null;
+        for (int i = 0; i < Main.main.menu.getMenuCount() && toolsMenu == null; i++) {
+            JMenu menu = Main.main.menu.getMenu(i);
+            String name = menu.getText();
+            if (name != null && name.equals(tr("Tools"))) {
+                toolsMenu = menu;
+            }
+        }
+
+        if (toolsMenu == null) {
+            toolsMenu = new JMenu(name);
+            toolsMenu.add(new JMenuItem(new MergeOverlapAction()));
+            Main.main.menu.add(toolsMenu, 2);
+        } else {
+            toolsMenu.addSeparator();
+            toolsMenu.add(new JMenuItem(new MergeOverlapAction()));
+        }
+    }
+}
Index: /applications/editors/josm/plugins/merge-overlap/src/mergeoverlap/MyCombinePrimitiveResolverDialog.java
===================================================================
--- /applications/editors/josm/plugins/merge-overlap/src/mergeoverlap/MyCombinePrimitiveResolverDialog.java	(revision 26575)
+++ /applications/editors/josm/plugins/merge-overlap/src/mergeoverlap/MyCombinePrimitiveResolverDialog.java	(revision 26575)
@@ -0,0 +1,1436 @@
+// License: GPL. For details, see LICENSE file.
+package mergeoverlap;
+
+import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
+import static org.openstreetmap.josm.tools.I18n.tr;
+import static org.openstreetmap.josm.tools.I18n.trc;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.event.ActionEvent;
+import java.awt.event.FocusAdapter;
+import java.awt.event.FocusEvent;
+import java.awt.event.HierarchyBoundsListener;
+import java.awt.event.HierarchyEvent;
+import java.awt.event.KeyEvent;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.swing.AbstractAction;
+import javax.swing.AbstractButton;
+import javax.swing.Action;
+import javax.swing.BorderFactory;
+import javax.swing.BoxLayout;
+import javax.swing.ButtonModel;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JComboBox;
+import javax.swing.JComponent;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JSplitPane;
+import javax.swing.JTable;
+import javax.swing.KeyStroke;
+import javax.swing.ListSelectionModel;
+import javax.swing.UIManager;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import javax.swing.table.DefaultTableModel;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.command.ChangePropertyCommand;
+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.RelationMember;
+import org.openstreetmap.josm.data.osm.RelationToChildReference;
+import org.openstreetmap.josm.data.osm.TagCollection;
+import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.gui.DefaultNameFormatter;
+import org.openstreetmap.josm.gui.JMultilineLabel;
+import org.openstreetmap.josm.gui.SideButton;
+import org.openstreetmap.josm.gui.conflict.tags.MultiValueCellEditor;
+import org.openstreetmap.josm.gui.conflict.tags.MultiValueDecisionType;
+import org.openstreetmap.josm.gui.conflict.tags.MultiValueResolutionDecision;
+import org.openstreetmap.josm.gui.conflict.tags.RelationMemberConflictDecision;
+import org.openstreetmap.josm.gui.conflict.tags.RelationMemberConflictDecisionType;
+import org.openstreetmap.josm.gui.conflict.tags.RelationMemberConflictResolverColumnModel;
+import org.openstreetmap.josm.gui.conflict.tags.TagConflictResolverColumnModel;
+import org.openstreetmap.josm.gui.help.ContextSensitiveHelpAction;
+import org.openstreetmap.josm.gui.help.HelpUtil;
+import org.openstreetmap.josm.gui.tagging.ac.AutoCompletingTextField;
+import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionList;
+import org.openstreetmap.josm.tools.CheckParameterUtil;
+import org.openstreetmap.josm.tools.ImageProvider;
+import org.openstreetmap.josm.tools.WindowGeometry;
+
+/**
+ * This dialog helps to resolve conflicts occurring when ways are combined or
+ * nodes are merged.
+ *
+ * There is a singleton instance of this dialog which can be retrieved using
+ * {@see #getInstance()}.
+ *
+ * The dialog uses two models: one  for resolving tag conflicts, the other
+ * for resolving conflicts in relation memberships. For both models there are accessors,
+ * i.e {@see #getTagConflictResolverModel()} and {@see #getRelationMemberConflictResolverModel()}.
+ *
+ * Models have to be <strong>populated</strong> before the dialog is launched. Example:
+ * <pre>
+ *    CombinePrimitiveResolverDialog dialog = CombinePrimitiveResolverDialog.getInstance();
+ *    dialog.getTagConflictResolverModel().populate(aTagCollection);
+ *    dialog.getRelationMemberConflictResolverModel().populate(aRelationLinkCollection);
+ *    dialog.prepareDefaultDecisions();
+ * </pre>
+ *
+ * You should also set the target primitive which other primitives (ways or nodes) are
+ * merged to, see {@see #setTargetPrimitive(OsmPrimitive)}.
+ *
+ * After the dialog is closed use {@see #isCancelled()} to check whether the user canceled
+ * the dialog. If it wasn't canceled you may build a collection of {@see Command} objects
+ * which reflect the conflict resolution decisions the user made in the dialog:
+ * see {@see #buildResolutionCommands()}
+ *
+ *
+ */
+public class MyCombinePrimitiveResolverDialog extends JDialog {
+
+    /** the unique instance of the dialog */
+    static private MyCombinePrimitiveResolverDialog instance;
+
+    /**
+     * Replies the unique instance of the dialog
+     *
+     * @return the unique instance of the dialog
+     */
+    public static MyCombinePrimitiveResolverDialog getInstance() {
+        if (instance == null) {
+            instance = new MyCombinePrimitiveResolverDialog(Main.parent);
+        }
+        return instance;
+    }
+
+    private AutoAdjustingSplitPane spTagConflictTypes;
+    private MyTagConflictResolver pnlTagConflictResolver;
+    private MyRelationMemberConflictResolver pnlRelationMemberConflictResolver;
+    private boolean cancelled;
+    private JPanel pnlButtons;
+    private OsmPrimitive targetPrimitive;
+
+    /** the private help action */
+    private ContextSensitiveHelpAction helpAction;
+    /** the apply button */
+    private SideButton btnApply;
+
+    /**
+     * Replies the target primitive the collection of primitives is merged
+     * or combined to.
+     *
+     * @return the target primitive
+     */
+    public OsmPrimitive getTargetPrimitmive() {
+        return targetPrimitive;
+    }
+
+    /**
+     * Sets the primitive the collection of primitives is merged or combined
+     * to.
+     *
+     * @param primitive the target primitive
+     */
+    public void setTargetPrimitive(OsmPrimitive primitive) {
+        this.targetPrimitive = primitive;
+        updateTitle();
+        if (primitive instanceof Way) {
+            pnlRelationMemberConflictResolver.initForWayCombining();
+        } else if (primitive instanceof Node) {
+            pnlRelationMemberConflictResolver.initForNodeMerging();
+        }
+    }
+
+    protected void updateTitle() {
+        if (targetPrimitive == null) {
+            setTitle(tr("Conflicts when combining primitives"));
+            return;
+        }
+        if (targetPrimitive instanceof Way) {
+            setTitle(tr("Conflicts when combining ways - combined way is ''{0}''", targetPrimitive
+                    .getDisplayName(DefaultNameFormatter.getInstance())));
+            helpAction.setHelpTopic(ht("/Action/CombineWay#ResolvingConflicts"));
+            getRootPane().putClientProperty("help", ht("/Action/CombineWay#ResolvingConflicts"));
+        } else if (targetPrimitive instanceof Node) {
+            setTitle(tr("Conflicts when merging nodes - target node is ''{0}''", targetPrimitive
+                    .getDisplayName(DefaultNameFormatter.getInstance())));
+            helpAction.setHelpTopic(ht("/Action/MergeNodes#ResolvingConflicts"));
+            getRootPane().putClientProperty("help", ht("/Action/MergeNodes#ResolvingConflicts"));
+        }
+    }
+
+    protected void build() {
+        getContentPane().setLayout(new BorderLayout());
+        updateTitle();
+        spTagConflictTypes = new AutoAdjustingSplitPane(JSplitPane.VERTICAL_SPLIT);
+        spTagConflictTypes.setTopComponent(buildTagConflictResolverPanel());
+        spTagConflictTypes.setBottomComponent(buildRelationMemberConflictResolverPanel());
+        getContentPane().add(pnlButtons = buildButtonPanel(), BorderLayout.SOUTH);
+        addWindowListener(new AdjustDividerLocationAction());
+        HelpUtil.setHelpContext(getRootPane(), ht("/"));
+    }
+
+    protected JPanel buildTagConflictResolverPanel() {
+        pnlTagConflictResolver = new MyTagConflictResolver();
+        return pnlTagConflictResolver;
+    }
+
+    protected JPanel buildRelationMemberConflictResolverPanel() {
+        pnlRelationMemberConflictResolver = new MyRelationMemberConflictResolver();
+        return pnlRelationMemberConflictResolver;
+    }
+
+    protected JPanel buildButtonPanel() {
+        JPanel pnl = new JPanel();
+        pnl.setLayout(new FlowLayout(FlowLayout.CENTER));
+
+        // -- apply button
+        ApplyAction applyAction = new ApplyAction();
+        pnlTagConflictResolver.getModel().addPropertyChangeListener(applyAction);
+        pnlRelationMemberConflictResolver.getModel().addPropertyChangeListener(applyAction);
+        btnApply = new SideButton(applyAction);
+        btnApply.setFocusable(true);
+        pnl.add(btnApply);
+
+        // -- cancel button
+        CancelAction cancelAction = new CancelAction();
+        pnl.add(new SideButton(cancelAction));
+
+        // -- help button
+        helpAction = new ContextSensitiveHelpAction();
+        pnl.add(new SideButton(helpAction));
+
+        return pnl;
+    }
+
+    public MyCombinePrimitiveResolverDialog(Component owner) {
+        super(JOptionPane.getFrameForComponent(owner), ModalityType.DOCUMENT_MODAL);
+        build();
+    }
+
+    public MyTagConflictResolverModel getTagConflictResolverModel() {
+        return pnlTagConflictResolver.getModel();
+    }
+
+    public MyRelationMemberConflictResolverModel getRelationMemberConflictResolverModel() {
+        return pnlRelationMemberConflictResolver.getModel();
+    }
+
+    protected List<Command> buildTagChangeCommand(OsmPrimitive primitive, TagCollection tc) {
+        LinkedList<Command> cmds = new LinkedList<Command>();
+        for (String key : tc.getKeys()) {
+            if (tc.hasUniqueEmptyValue(key)) {
+                if (primitive.get(key) != null) {
+                    cmds.add(new ChangePropertyCommand(primitive, key, null));
+                }
+            } else {
+                String value = tc.getJoinedValues(key);
+                if (!value.equals(primitive.get(key))) {
+                    cmds.add(new ChangePropertyCommand(primitive, key, value));
+                }
+            }
+        }
+        return cmds;
+    }
+
+    public List<Command> buildWayResolutionCommands() {
+        List<Command> cmds = new LinkedList<Command>();
+
+        TagCollection allResolutions = getTagConflictResolverModel().getAllResolutions();
+        if (allResolutions.size() > 0) {
+            cmds.addAll(buildTagChangeCommand(targetPrimitive, allResolutions));
+        }
+        if (targetPrimitive.get("created_by") != null) {
+            cmds.add(new ChangePropertyCommand(targetPrimitive, "created_by", null));
+        }
+
+        Command cmd = pnlRelationMemberConflictResolver.buildTagApplyCommands(getRelationMemberConflictResolverModel()
+                .getModifiedRelations(targetPrimitive));
+        if (cmd != null) {
+            cmds.add(cmd);
+        }
+        return cmds;
+    }
+
+    public void buildRelationCorrespondance(Map<Relation, Relation> newRelations, Map<Way, Way> oldWays) {
+    	getRelationMemberConflictResolverModel().buildRelationCorrespondance(targetPrimitive, newRelations, oldWays);
+    }
+    
+    protected void prepareDefaultTagDecisions() {
+        MyTagConflictResolverModel model = getTagConflictResolverModel();
+        for (int i = 0; i < model.getRowCount(); i++) {
+            MultiValueResolutionDecision decision = model.getDecision(i);
+            List<String> values = decision.getValues();
+            values.remove("");
+            if (values.size() == 1) {
+                decision.keepOne(values.get(0));
+            } else {
+                decision.keepAll();
+            }
+        }
+        model.rebuild();
+    }
+
+    protected void prepareDefaultRelationDecisions() {
+        MyRelationMemberConflictResolverModel model = getRelationMemberConflictResolverModel();
+        Set<Relation> relations = new HashSet<Relation>();
+        for (int i = 0; i < model.getNumDecisions(); i++) {
+            RelationMemberConflictDecision decision = model.getDecision(i);
+            if (!relations.contains(decision.getRelation())) {
+                decision.decide(RelationMemberConflictDecisionType.KEEP);
+                relations.add(decision.getRelation());
+            } else {
+                decision.decide(RelationMemberConflictDecisionType.REMOVE);
+            }
+        }
+        model.refresh();
+    }
+
+    public void prepareDefaultDecisions() {
+        prepareDefaultTagDecisions();
+        prepareDefaultRelationDecisions();
+    }
+
+    protected JPanel buildEmptyConflictsPanel() {
+        JPanel pnl = new JPanel();
+        pnl.setLayout(new BorderLayout());
+        pnl.add(new JLabel(tr("No conflicts to resolve")));
+        return pnl;
+    }
+
+    protected void prepareGUIBeforeConflictResolutionStarts() {
+        MyRelationMemberConflictResolverModel relModel = getRelationMemberConflictResolverModel();
+        MyTagConflictResolverModel tagModel = getTagConflictResolverModel();
+        getContentPane().removeAll();
+
+        if (relModel.getNumDecisions() > 0 && tagModel.getNumDecisions() > 0) {
+            // display both, the dialog for resolving relation conflicts and for resolving
+            // tag conflicts
+            spTagConflictTypes.setTopComponent(pnlTagConflictResolver);
+            spTagConflictTypes.setBottomComponent(pnlRelationMemberConflictResolver);
+            getContentPane().add(spTagConflictTypes, BorderLayout.CENTER);
+        } else if (relModel.getNumDecisions() > 0) {
+            // relation conflicts only
+            //
+            getContentPane().add(pnlRelationMemberConflictResolver, BorderLayout.CENTER);
+        } else if (tagModel.getNumDecisions() > 0) {
+            // tag conflicts only
+            //
+            getContentPane().add(pnlTagConflictResolver, BorderLayout.CENTER);
+        } else {
+            getContentPane().add(buildEmptyConflictsPanel(), BorderLayout.CENTER);
+        }
+
+        getContentPane().add(pnlButtons, BorderLayout.SOUTH);
+        validate();
+        int numTagDecisions = getTagConflictResolverModel().getNumDecisions();
+        int numRelationDecisions = getRelationMemberConflictResolverModel().getNumDecisions();
+        if (numTagDecisions > 0 && numRelationDecisions > 0) {
+            spTagConflictTypes.setDividerLocation(0.5);
+        }
+        pnlRelationMemberConflictResolver.prepareForEditing();
+    }
+
+    protected void setCancelled(boolean cancelled) {
+        this.cancelled = cancelled;
+    }
+
+    public boolean isCancelled() {
+        return cancelled;
+    }
+
+    @Override
+    public void setVisible(boolean visible) {
+        if (visible) {
+            prepareGUIBeforeConflictResolutionStarts();
+            new WindowGeometry(getClass().getName() + ".geometry", WindowGeometry.centerInWindow(Main.parent,
+                    new Dimension(600, 400))).applySafe(this);
+            setCancelled(false);
+            btnApply.requestFocusInWindow();
+        } else {
+            new WindowGeometry(this).remember(getClass().getName() + ".geometry");
+        }
+        super.setVisible(visible);
+    }
+
+    class CancelAction extends AbstractAction {
+
+        public CancelAction() {
+            putValue(Action.SHORT_DESCRIPTION, tr("Cancel conflict resolution"));
+            putValue(Action.NAME, tr("Cancel"));
+            putValue(Action.SMALL_ICON, ImageProvider.get("", "cancel"));
+            setEnabled(true);
+        }
+
+        public void actionPerformed(ActionEvent arg0) {
+            setCancelled(true);
+            setVisible(false);
+        }
+    }
+
+    class ApplyAction extends AbstractAction implements PropertyChangeListener {
+
+        public ApplyAction() {
+            putValue(Action.SHORT_DESCRIPTION, tr("Apply resolved conflicts"));
+            putValue(Action.NAME, tr("Apply"));
+            putValue(Action.SMALL_ICON, ImageProvider.get("ok"));
+            updateEnabledState();
+        }
+
+        public void actionPerformed(ActionEvent arg0) {
+            setVisible(false);
+            pnlTagConflictResolver.rememberPreferences();
+        }
+
+        protected void updateEnabledState() {
+            setEnabled(pnlTagConflictResolver.getModel().getNumConflicts() == 0
+                    && pnlRelationMemberConflictResolver.getModel().getNumConflicts() == 0);
+        }
+
+        public void propertyChange(PropertyChangeEvent evt) {
+            if (evt.getPropertyName().equals(MyTagConflictResolverModel.NUM_CONFLICTS_PROP)) {
+                updateEnabledState();
+            }
+            if (evt.getPropertyName().equals(MyRelationMemberConflictResolverModel.NUM_CONFLICTS_PROP)) {
+                updateEnabledState();
+            }
+        }
+    }
+
+    class AdjustDividerLocationAction extends WindowAdapter {
+        @Override
+        public void windowOpened(WindowEvent e) {
+            int numTagDecisions = getTagConflictResolverModel().getNumDecisions();
+            int numRelationDecisions = getRelationMemberConflictResolverModel().getNumDecisions();
+            if (numTagDecisions > 0 && numRelationDecisions > 0) {
+                spTagConflictTypes.setDividerLocation(0.5);
+            }
+        }
+    }
+
+    static class AutoAdjustingSplitPane extends JSplitPane implements PropertyChangeListener, HierarchyBoundsListener {
+        private double dividerLocation;
+
+        public AutoAdjustingSplitPane(int newOrientation) {
+            super(newOrientation);
+            addPropertyChangeListener(JSplitPane.DIVIDER_LOCATION_PROPERTY, this);
+            addHierarchyBoundsListener(this);
+        }
+
+        public void ancestorResized(HierarchyEvent e) {
+            setDividerLocation((int) (dividerLocation * getHeight()));
+        }
+
+        public void ancestorMoved(HierarchyEvent e) {
+            // do nothing
+        }
+
+        public void propertyChange(PropertyChangeEvent evt) {
+            if (evt.getPropertyName().equals(JSplitPane.DIVIDER_LOCATION_PROPERTY)) {
+                int newVal = (Integer) evt.getNewValue();
+                if (getHeight() != 0) {
+                    dividerLocation = (double) newVal / (double) getHeight();
+                }
+            }
+        }
+    }
+
+    /**
+     * This model manages a list of conflicting relation members.
+     *
+     * It can be used as {@see TableModel}.
+     *
+     *
+     */
+    public static class MyRelationMemberConflictResolverModel extends DefaultTableModel {
+        /** the property name for the number conflicts managed by this model */
+        static public final String NUM_CONFLICTS_PROP = MyRelationMemberConflictResolverModel.class.getName() + ".numConflicts";
+
+        /** the list of conflict decisions */
+        private List<RelationMemberConflictDecision> decisions;
+        /** the collection of relations for which we manage conflicts */
+        private Collection<Relation> relations;
+        /** the number of conflicts */
+        private int numConflicts;
+        private PropertyChangeSupport support;
+
+        /**
+         * Replies the current number of conflicts
+         *
+         * @return the current number of conflicts
+         */
+        public int getNumConflicts() {
+            return numConflicts;
+        }
+
+        /**
+         * Updates the current number of conflicts from list of decisions and emits
+         * a property change event if necessary.
+         *
+         */
+        protected void updateNumConflicts() {
+            int count = 0;
+            for (RelationMemberConflictDecision decision: decisions) {
+                if (!decision.isDecided()) {
+                    count++;
+                }
+            }
+            int oldValue = numConflicts;
+            numConflicts = count;
+            if (numConflicts != oldValue) {
+                support.firePropertyChange(NUM_CONFLICTS_PROP, oldValue, numConflicts);
+            }
+        }
+
+        public void addPropertyChangeListener(PropertyChangeListener l) {
+            support.addPropertyChangeListener(l);
+        }
+
+        public void removePropertyChangeListener(PropertyChangeListener l) {
+            support.removePropertyChangeListener(l);
+        }
+
+        public MyRelationMemberConflictResolverModel() {
+            decisions = new ArrayList<RelationMemberConflictDecision>();
+            support = new PropertyChangeSupport(this);
+        }
+
+        @Override
+        public int getRowCount() {
+            if (decisions == null) return 0;
+            return decisions.size();
+        }
+
+        @Override
+        public Object getValueAt(int row, int column) {
+            if (decisions == null) return null;
+
+            RelationMemberConflictDecision d = decisions.get(row);
+            switch(column) {
+            case 0: /* relation */ return d.getRelation();
+            case 1: /* pos */ return Integer.toString(d.getPos() + 1); // position in "user space" starting at 1
+            case 2: /* role */ return d.getRole();
+            case 3: /* original */ return d.getOriginalPrimitive();
+            case 4: /* decision */ return d.getDecision();
+            }
+            return null;
+        }
+
+        @Override
+        public void setValueAt(Object value, int row, int column) {
+            RelationMemberConflictDecision d = decisions.get(row);
+            switch(column) {
+            case 2: /* role */
+                d.setRole((String)value);
+                break;
+            case 4: /* decision */
+                d.decide((RelationMemberConflictDecisionType)value);
+                refresh();
+                break;
+            }
+            fireTableDataChanged();
+        }
+
+        /**
+         * Populates the model with the members of the relation <code>relation</code>
+         * referring to <code>primitive</code>.
+         *
+         * @param relation the parent relation
+         * @param primitive the child primitive
+         */
+        protected void populate(Relation relation, OsmPrimitive primitive, Map<Way, Way> oldWays) {
+            for (int i = 0; i<relation.getMembersCount(); i++) {
+                if (MergeOverlapAction.getOld(relation.getMember(i).getWay(), oldWays) == MergeOverlapAction.getOld((Way)primitive, oldWays)) {
+                    decisions.add(new RelationMemberConflictDecision(relation, i));
+                }
+            }
+        }
+
+        /**
+         * Populates the model with the relation members belonging to one of the relations in <code>relations</code>
+         * and referring to one of the primitives in <code>memberPrimitives</code>.
+         *
+         * @param relations  the parent relations. Empty list assumed if null.
+         * @param memberPrimitives the child primitives. Empty list assumed if null.
+         */
+        public void populate(Collection<Relation> relations, Collection<? extends OsmPrimitive> memberPrimitives, Map<Way, Way> oldWays) {
+            decisions.clear();
+        	
+            relations = relations == null ? new LinkedList<Relation>() : relations;
+            memberPrimitives = memberPrimitives == null ? new LinkedList<OsmPrimitive>() : memberPrimitives;
+            for (Relation r : relations) {
+                for (OsmPrimitive p: memberPrimitives) {
+                    populate(r, p, oldWays);
+                }
+            }
+            this.relations = relations;
+            refresh();
+        }
+
+        /**
+         * Populates the model with the relation members represented as a collection of
+         * {@see RelationToChildReference}s.
+         *
+         * @param references the references. Empty list assumed if null.
+         */
+        public void populate(Collection<RelationToChildReference> references) {
+            references = references == null ? new LinkedList<RelationToChildReference>() : references;
+            decisions.clear();
+            this.relations = new HashSet<Relation>(references.size());
+            for (RelationToChildReference reference: references) {
+                decisions.add(new RelationMemberConflictDecision(reference.getParent(), reference.getPosition()));
+                relations.add(reference.getParent());
+            }
+            refresh();
+        }
+
+        /**
+         * Replies the decision at position <code>row</code>
+         *
+         * @param row
+         * @return the decision at position <code>row</code>
+         */
+        public RelationMemberConflictDecision getDecision(int row) {
+            return decisions.get(row);
+        }
+
+        /**
+         * Replies the number of decisions managed by this model
+         *
+         * @return the number of decisions managed by this model
+         */
+        public int getNumDecisions() {
+            return  getRowCount();
+        }
+
+        /**
+         * Refreshes the model state. Invoke this method to trigger necessary change
+         * events after an update of the model data.
+         *
+         */
+        public void refresh() {
+            updateNumConflicts();
+            fireTableDataChanged();
+        }
+
+        /**
+         * Apply a role to all member managed by this model.
+         *
+         * @param role the role. Empty string assumed if null.
+         */
+        public void applyRole(String role) {
+            role = role == null ? "" : role;
+            for (RelationMemberConflictDecision decision : decisions) {
+                decision.setRole(role);
+            }
+            refresh();
+        }
+
+        protected RelationMemberConflictDecision getDecision(Relation relation, int pos) {
+            for(RelationMemberConflictDecision decision: decisions) {
+                if (decision.matches(relation, pos)) return decision;
+            }
+            return null;
+        }
+
+        protected void buildResolveCorrespondance(Relation relation, OsmPrimitive newPrimitive, Map<Relation, Relation> newRelations, Map<Way, Way> oldWays) {
+
+        	List<RelationMember> relationsMembers = relation.getMembers();
+        	Relation modifiedRelation = MergeOverlapAction.getNew(relation, newRelations);
+            modifiedRelation.setMembers(null);
+//            boolean isChanged = false;
+            for (int i=0; i < relationsMembers.size(); i++) {
+            	RelationMember rm = relationsMembers.get(i);
+//                RelationMember rm = relation.getMember(i);
+//                RelationMember rmNew;
+                RelationMemberConflictDecision decision = getDecision(relation, i);
+                if (decision == null) {
+                    modifiedRelation.addMember(rm);
+                } else {
+                	System.out.println(modifiedRelation);
+                	System.out.println(111);
+                    switch(decision.getDecision()) {
+                    case KEEP:
+//                    	modifiedRelation.removeMembersFor(newPrimitive);
+                    	System.out.println(222);
+                    	if (newPrimitive instanceof Way) {
+	                        modifiedRelation.addMember(new RelationMember(decision.getRole(), MergeOverlapAction.getOld((Way)newPrimitive, oldWays)));
+                    	}
+                    	else {
+                    		modifiedRelation.addMember(new RelationMember(decision.getRole(), newPrimitive));
+                    	}
+//                    	modifiedRelation.addMember(new RelationMember(decision.getRole(), newPrimitive));
+                        break;
+                    case REMOVE:
+                    	System.out.println(333);
+//                    	modifiedRelation.removeMembersFor(rm.getMember());
+//                        isChanged = true;
+                        // do nothing
+                        break;
+                    case UNDECIDED:
+                        // FIXME: this is an error
+                        break;
+                    }
+                }
+            }
+        }
+
+        /**
+         * Builds a collection of commands executing the decisions made in this model.
+         *
+         * @param newPrimitive the primitive which members shall refer to if the
+         * decision is {@see RelationMemberConflictDecisionType#REPLACE}
+         * @return a list of commands
+         */
+        public void buildRelationCorrespondance(OsmPrimitive newPrimitive, Map<Relation, Relation> newRelations, Map<Way, Way> oldWays) {
+            for (Relation relation : relations) {
+            	buildResolveCorrespondance(relation, newPrimitive, newRelations, oldWays);
+            }
+        }
+
+        protected boolean isChanged(Relation relation, OsmPrimitive newPrimitive) {
+            for (int i=0; i < relation.getMembersCount(); i++) {
+                RelationMemberConflictDecision decision = getDecision(relation, i);
+                if (decision == null) {
+                    continue;
+                }
+                switch(decision.getDecision()) {
+                case REMOVE: return true;
+                case KEEP:
+                    if (!relation.getMember(i).getRole().equals(decision.getRole()))
+                        return true;
+                    if (relation.getMember(i).getMember() != newPrimitive)
+                        return true;
+                case UNDECIDED:
+                    // FIXME: handle error
+                }
+            }
+            return false;
+        }
+
+        /**
+         * Replies the set of relations which have to be modified according
+         * to the decisions managed by this model.
+         *
+         * @param newPrimitive the primitive which members shall refer to if the
+         * decision is {@see RelationMemberConflictDecisionType#REPLACE}
+         *
+         * @return the set of relations which have to be modified according
+         * to the decisions managed by this model
+         */
+        public Set<Relation> getModifiedRelations(OsmPrimitive newPrimitive) {
+            HashSet<Relation> ret = new HashSet<Relation>();
+            for (Relation relation: relations) {
+                if (isChanged(relation, newPrimitive)) {
+                    ret.add(relation);
+                }
+            }
+            return ret;
+        }
+    }
+    
+    public class MyRelationMemberConflictResolver extends JPanel {
+
+        private AutoCompletingTextField tfRole;
+        private AutoCompletingTextField tfKey;
+        private AutoCompletingTextField tfValue;
+        private JCheckBox cbTagRelations;
+        private MyRelationMemberConflictResolverModel model;
+        private MyRelationMemberConflictResolverTable tblResolver;
+        private JMultilineLabel lblHeader;
+
+        protected void build() {
+            setLayout(new GridBagLayout());
+            JPanel pnl = new JPanel();
+            pnl.setLayout(new BorderLayout());
+            pnl.add(lblHeader = new JMultilineLabel(""));
+            GridBagConstraints gc = new GridBagConstraints();
+            gc.fill = GridBagConstraints.HORIZONTAL;
+            gc.weighty = 0.0;
+            gc.weightx = 1.0;
+            gc.insets = new Insets(5,5,5,5);
+            add(pnl, gc);
+            model = new MyRelationMemberConflictResolverModel();
+
+            gc.gridy = 1;
+            gc.weighty = 1.0;
+            gc.fill = GridBagConstraints.BOTH;
+            gc.insets = new Insets(0,0,0,0);
+            add(new JScrollPane(tblResolver = new MyRelationMemberConflictResolverTable(model)), gc);
+            pnl = new JPanel();
+            pnl.setLayout(new BoxLayout(pnl, BoxLayout.Y_AXIS));
+            pnl.add(buildRoleEditingPanel());
+            pnl.add(buildTagRelationsPanel());
+            gc.gridy = 2;
+            gc.weighty = 0.0;
+            gc.fill = GridBagConstraints.HORIZONTAL;
+            add(pnl,gc);
+        }
+
+        protected JPanel buildRoleEditingPanel() {
+            JPanel pnl = new JPanel();
+            pnl.setLayout(new FlowLayout(FlowLayout.LEFT));
+            pnl.add(new JLabel(tr("Role:")));
+            pnl.add(tfRole = new AutoCompletingTextField(10));
+            tfRole.setToolTipText(tr("Enter a role for all relation memberships"));
+            pnl.add(new JButton(new ApplyRoleAction()));
+            tfRole.addActionListener(new ApplyRoleAction());
+            tfRole.addFocusListener(
+                    new FocusAdapter() {
+                        @Override
+                        public void focusGained(FocusEvent e) {
+                            tfRole.selectAll();
+                        }
+                    }
+            );
+            return pnl;
+        }
+
+        protected JPanel buildTagRelationsPanel() {
+            JPanel pnl = new JPanel();
+            pnl.setLayout(new FlowLayout(FlowLayout.LEFT));
+            cbTagRelations = new JCheckBox(tr("Tag modified relations with "));
+            cbTagRelations.addChangeListener(new ToggleTagRelationsAction());
+            cbTagRelations.setToolTipText(
+                    tr("<html>Select to enable entering a tag which will be applied<br>"
+                            + "to all modified relations.</html>"));
+            pnl.add(cbTagRelations);
+            pnl.add(new JLabel(trc("tag", "Key:")));
+            pnl.add(tfKey = new AutoCompletingTextField(10));
+            tfKey.setToolTipText(tr("<html>Enter a tag key, i.e. <strong><tt>fixme</tt></strong></html>"));
+            pnl.add(new JLabel(tr("Value:")));
+            pnl.add(tfValue = new AutoCompletingTextField(10));
+            tfValue.setToolTipText(tr("<html>Enter a tag value, i.e. <strong><tt>check members</tt></strong></html>"));
+            cbTagRelations.setSelected(false);
+            tfKey.setEnabled(false);
+            tfValue.setEnabled(false);
+            return pnl;
+        }
+
+        public MyRelationMemberConflictResolver() {
+            build();
+        }
+
+        public void initForWayCombining() {
+            lblHeader.setText(tr("<html>The combined ways are members in one ore more relations. "
+                    + "Please decide whether you want to <strong>keep</strong> these memberships "
+                    + "for the combined way or whether you want to <strong>remove</strong> them.<br>"
+                    + "The default is to <strong>keep</strong> the first way and <strong>remove</strong> "
+                    + "the other ways that are members of the same relation: the combined way will "
+                    + "take the place of the original way in the relation."
+                    + "</html>"));
+            invalidate();
+        }
+
+        public void initForNodeMerging() {
+            lblHeader.setText(tr("<html>The merged nodes are members in one ore more relations. "
+                    + "Please decide whether you want to <strong>keep</strong> these memberships "
+                    + "for the target node or whether you want to <strong>remove</strong> them.<br>"
+                    + "The default is to <strong>keep</strong> the first node and <strong>remove</strong> "
+                    + "the other nodes that are members of the same relation: the target node will "
+                    + "take the place of the original node in the relation."
+                    + "</html>"));
+            invalidate();
+        }
+
+        class ApplyRoleAction extends AbstractAction {
+            public ApplyRoleAction() {
+                putValue(NAME, tr("Apply"));
+                putValue(SMALL_ICON, ImageProvider.get("ok"));
+                putValue(SHORT_DESCRIPTION, tr("Apply this role to all members"));
+            }
+
+            public void actionPerformed(ActionEvent e) {
+                model.applyRole(tfRole.getText());
+            }
+        }
+
+        class ToggleTagRelationsAction implements ChangeListener {
+            public void stateChanged(ChangeEvent e) {
+                ButtonModel buttonModel = ((AbstractButton) e.getSource()).getModel();
+                tfKey.setEnabled(buttonModel.isSelected());
+                tfValue.setEnabled(buttonModel.isSelected());
+                tfKey.setBackground(buttonModel.isSelected() ? UIManager.getColor("TextField.background") : UIManager
+                        .getColor("Panel.background"));
+                tfValue.setBackground(buttonModel.isSelected() ? UIManager.getColor("TextField.background") : UIManager
+                        .getColor("Panel.background"));
+            }
+        }
+
+        public MyRelationMemberConflictResolverModel getModel() {
+            return model;
+        }
+
+        public Command buildTagApplyCommands(Collection<? extends OsmPrimitive> primitives) {
+            if (!cbTagRelations.isSelected())
+                return null;
+            if (tfKey.getText().trim().equals(""))
+                return null;
+            if (tfValue.getText().trim().equals(""))
+                return null;
+            if (primitives == null || primitives.isEmpty())
+                return null;
+            return new ChangePropertyCommand(primitives, tfKey.getText(), tfValue.getText());
+        }
+
+        public void prepareForEditing() {
+            AutoCompletionList acList = new AutoCompletionList();
+            Main.main.getEditLayer().data.getAutoCompletionManager().populateWithMemberRoles(acList);
+            tfRole.setAutoCompletionList(acList);
+            AutoCompletingTextField editor = (AutoCompletingTextField) tblResolver.getColumnModel().getColumn(2).getCellEditor();
+            if (editor != null) {
+                editor.setAutoCompletionList(acList);
+            }
+            AutoCompletionList acList2 = new AutoCompletionList();
+            Main.main.getEditLayer().data.getAutoCompletionManager().populateWithKeys(acList2);
+            tfKey.setAutoCompletionList(acList2);
+        }
+    }
+
+
+    public class MyRelationMemberConflictResolverTable extends JTable implements MultiValueCellEditor.NavigationListener {
+
+        private SelectNextColumnCellAction selectNextColumnCellAction;
+        private SelectPreviousColumnCellAction selectPreviousColumnCellAction;
+
+        public MyRelationMemberConflictResolverTable(MyRelationMemberConflictResolverModel model) {
+            super(model, new RelationMemberConflictResolverColumnModel());
+            build();
+        }
+
+        protected void build() {
+            setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
+            setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+            putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);
+
+            // make ENTER behave like TAB
+            //
+            getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(
+                    KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0, false), "selectNextColumnCell");
+
+            // install custom navigation actions
+            //
+            selectNextColumnCellAction = new SelectNextColumnCellAction();
+            selectPreviousColumnCellAction = new SelectPreviousColumnCellAction();
+            getActionMap().put("selectNextColumnCell", selectNextColumnCellAction);
+            getActionMap().put("selectPreviousColumnCell", selectPreviousColumnCellAction);
+
+            setRowHeight((int)new JComboBox().getPreferredSize().getHeight());
+        }
+
+        /**
+         * Action to be run when the user navigates to the next cell in the table, for instance by
+         * pressing TAB or ENTER. The action alters the standard navigation path from cell to cell: <ul>
+         * <li>it jumps over cells in the first column</li> <li>it automatically add a new empty row
+         * when the user leaves the last cell in the table</li> <ul>
+         *
+         *
+         */
+        class SelectNextColumnCellAction extends AbstractAction {
+            public void actionPerformed(ActionEvent e) {
+                run();
+            }
+
+            public void run() {
+                int col = getSelectedColumn();
+                int row = getSelectedRow();
+                if (getCellEditor() != null) {
+                    getCellEditor().stopCellEditing();
+                }
+
+                if (col == 2 && row < getRowCount() - 1) {
+                    row++;
+                } else if (row < getRowCount() - 1) {
+                    col = 2;
+                    row++;
+                }
+                changeSelection(row, col, false, false);
+                editCellAt(getSelectedRow(), getSelectedColumn());
+                getEditorComponent().requestFocusInWindow();
+            }
+        }
+
+        /**
+         * Action to be run when the user navigates to the previous cell in the table, for instance by
+         * pressing Shift-TAB
+         *
+         */
+        class SelectPreviousColumnCellAction extends AbstractAction {
+
+            public void actionPerformed(ActionEvent e) {
+                run();
+            }
+
+            public void run() {
+                int col = getSelectedColumn();
+                int row = getSelectedRow();
+                if (getCellEditor() != null) {
+                    getCellEditor().stopCellEditing();
+                }
+
+                if (col <= 0 && row <= 0) {
+                    // change nothing
+                } else if (row > 0) {
+                    col = 2;
+                    row--;
+                }
+                changeSelection(row, col, false, false);
+                editCellAt(getSelectedRow(), getSelectedColumn());
+                getEditorComponent().requestFocusInWindow();
+            }
+        }
+
+        public void gotoNextDecision() {
+            selectNextColumnCellAction.run();
+        }
+
+        public void gotoPreviousDecision() {
+            selectPreviousColumnCellAction.run();
+        }
+    }
+
+
+    public static class MyTagConflictResolverModel extends DefaultTableModel {
+        static public final String NUM_CONFLICTS_PROP = MyTagConflictResolverModel.class.getName() + ".numConflicts";
+
+        private TagCollection tags;
+        private List<String> displayedKeys;
+        private Set<String> keysWithConflicts;
+        private HashMap<String, MultiValueResolutionDecision> decisions;
+        private int numConflicts;
+        private PropertyChangeSupport support;
+        private boolean showTagsWithConflictsOnly = false;
+        private boolean showTagsWithMultiValuesOnly = false;
+
+        public MyTagConflictResolverModel() {
+            numConflicts = 0;
+            support = new PropertyChangeSupport(this);
+        }
+
+        public void addPropertyChangeListener(PropertyChangeListener listener) {
+            support.addPropertyChangeListener(listener);
+        }
+
+        public void removePropertyChangeListener(PropertyChangeListener listener) {
+            support.removePropertyChangeListener(listener);
+        }
+
+        protected void setNumConflicts(int numConflicts) {
+            int oldValue = this.numConflicts;
+            this.numConflicts = numConflicts;
+            if (oldValue != this.numConflicts) {
+                support.firePropertyChange(NUM_CONFLICTS_PROP, oldValue, this.numConflicts);
+            }
+        }
+
+        protected void refreshNumConflicts() {
+            int count = 0;
+            for (MultiValueResolutionDecision d : decisions.values()) {
+                if (!d.isDecided()) {
+                    count++;
+                }
+            }
+            setNumConflicts(count);
+        }
+
+        protected void sort() {
+            Collections.sort(
+                    displayedKeys,
+                    new Comparator<String>() {
+                        public int compare(String key1, String key2) {
+                            if (decisions.get(key1).isDecided() && ! decisions.get(key2).isDecided())
+                                return 1;
+                            else if (!decisions.get(key1).isDecided() && decisions.get(key2).isDecided())
+                                return -1;
+                            return key1.compareTo(key2);
+                        }
+                    }
+            );
+        }
+
+        /**
+         * initializes the model from the current tags
+         *
+         */
+        protected void rebuild() {
+            if (tags == null) return;
+            for(String key: tags.getKeys()) {
+                MultiValueResolutionDecision decision = new MultiValueResolutionDecision(tags.getTagsFor(key));
+                if (decisions.get(key) == null) {
+                    decisions.put(key,decision);
+                }
+            }
+            displayedKeys.clear();
+            Set<String> keys = tags.getKeys();
+            if (showTagsWithConflictsOnly) {
+                keys.retainAll(keysWithConflicts);
+                if (showTagsWithMultiValuesOnly) {
+                    Set<String> keysWithMultiValues = new HashSet<String>();
+                    for (String key: keys) {
+                        if (decisions.get(key).canKeepAll()) {
+                            keysWithMultiValues.add(key);
+                        }
+                    }
+                    keys.retainAll(keysWithMultiValues);
+                }
+                for (String key: tags.getKeys()) {
+                    if (!decisions.get(key).isDecided() && !keys.contains(key)) {
+                        keys.add(key);
+                    }
+                }
+            }
+            displayedKeys.addAll(keys);
+            refreshNumConflicts();
+            sort();
+            fireTableDataChanged();
+        }
+
+        /**
+         * Populates the model with the tags for which conflicts are to be resolved.
+         *
+         * @param tags  the tag collection with the tags. Must not be null.
+         * @param keysWithConflicts the set of tag keys with conflicts
+         * @throws IllegalArgumentException thrown if tags is null
+         */
+        public void populate(TagCollection tags, Set<String> keysWithConflicts) {
+            CheckParameterUtil.ensureParameterNotNull(tags, "tags");
+            this.tags = tags;
+            displayedKeys = new ArrayList<String>();
+            this.keysWithConflicts = keysWithConflicts == null ? new HashSet<String>() : keysWithConflicts;
+            decisions = new HashMap<String, MultiValueResolutionDecision>();
+            rebuild();
+        }
+
+        @Override
+        public int getRowCount() {
+            if (displayedKeys == null) return 0;
+            return displayedKeys.size();
+        }
+
+        @Override
+        public Object getValueAt(int row, int column) {
+            return decisions.get(displayedKeys.get(row));
+        }
+
+        @Override
+        public boolean isCellEditable(int row, int column) {
+            return column == 2;
+        }
+
+        @Override
+        public void setValueAt(Object value, int row, int column) {
+            MultiValueResolutionDecision decision = decisions.get(displayedKeys.get(row));
+            if (value instanceof String) {
+                decision.keepOne((String)value);
+            } else if (value instanceof MultiValueDecisionType) {
+                MultiValueDecisionType type = (MultiValueDecisionType)value;
+                switch(type) {
+                case KEEP_NONE:
+                    decision.keepNone();
+                    break;
+                case KEEP_ALL:
+                    decision.keepAll();
+                    break;
+                }
+            }
+            fireTableDataChanged();
+            refreshNumConflicts();
+        }
+
+        /**
+         * Replies true if each {@see MultiValueResolutionDecision} is decided.
+         *
+         * @return true if each {@see MultiValueResolutionDecision} is decided; false
+         * otherwise
+         */
+        public boolean isResolvedCompletely() {
+            return numConflicts == 0;
+        }
+
+        public int getNumConflicts() {
+            return numConflicts;
+        }
+
+        public int getNumDecisions() {
+            return getRowCount();
+        }
+
+        //TODO Should this method work with all decisions or only with displayed decisions? For MergeNodes it should be
+        //all decisions, but this method is also used on other places, so I've made new method just for MergeNodes
+        public TagCollection getResolution() {
+            TagCollection tc = new TagCollection();
+            for (String key: displayedKeys) {
+                tc.add(decisions.get(key).getResolution());
+            }
+            return tc;
+        }
+
+        public TagCollection getAllResolutions() {
+            TagCollection tc = new TagCollection();
+            for (MultiValueResolutionDecision value: decisions.values()) {
+                tc.add(value.getResolution());
+            }
+            return tc;
+        }
+
+        public MultiValueResolutionDecision getDecision(int row) {
+            return decisions.get(displayedKeys.get(row));
+        }
+
+        /**
+         * Sets whether all tags or only tags with conflicts are displayed
+         *
+         * @param showTagsWithConflictsOnly if true, only tags with conflicts are displayed
+         */
+        public void setShowTagsWithConflictsOnly(boolean showTagsWithConflictsOnly) {
+            this.showTagsWithConflictsOnly = showTagsWithConflictsOnly;
+            rebuild();
+        }
+
+        /**
+         * Sets whether all conflicts or only conflicts with multiple values are displayed
+         *
+         * @param showTagsWithMultiValuesOnly if true, only tags with multiple values are displayed
+         */
+        public void setShowTagsWithMultiValuesOnly(boolean showTagsWithMultiValuesOnly) {
+            this.showTagsWithMultiValuesOnly = showTagsWithMultiValuesOnly;
+            rebuild();
+        }
+
+        /**
+         * Prepare the default decisions for the current model
+         *
+         */
+        public void prepareDefaultTagDecisions() {
+            for (MultiValueResolutionDecision decision: decisions.values()) {
+                List<String> values = decision.getValues();
+                values.remove("");
+                if (values.size() == 1) {
+                    decision.keepOne(values.get(0));
+                } else {
+                    decision.keepAll();
+                }
+            }
+            rebuild();
+        }
+
+    }
+
+
+    /**
+     * This is a UI widget for resolving tag conflicts, i.e. differences of the tag values
+     * of multiple {@see OsmPrimitive}s.
+     *
+     *
+     */
+    public class MyTagConflictResolver extends JPanel {
+
+        /** the model for the tag conflict resolver */
+        private MyTagConflictResolverModel model;
+        /** selects wheter only tags with conflicts are displayed */
+        private JCheckBox cbShowTagsWithConflictsOnly;
+        private JCheckBox cbShowTagsWithMultiValuesOnly;
+
+        protected JPanel buildInfoPanel() {
+            JPanel pnl = new JPanel();
+            pnl.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
+            pnl.setLayout(new GridBagLayout());
+            GridBagConstraints gc = new GridBagConstraints();
+            gc.fill = GridBagConstraints.BOTH;
+            gc.weighty = 1.0;
+            gc.weightx = 1.0;
+            gc.anchor = GridBagConstraints.LINE_START;
+            pnl.add(new JLabel(tr("<html>Please select the values to keep for the following tags.</html>")), gc);
+
+            gc.gridy = 1;
+            gc.fill = GridBagConstraints.HORIZONTAL;
+            gc.weighty = 0.0;
+            pnl.add(cbShowTagsWithConflictsOnly = new JCheckBox(tr("Show tags with conflicts only")), gc);
+            pnl.add(cbShowTagsWithMultiValuesOnly = new JCheckBox(tr("Show tags with multiple values only")), gc);
+            cbShowTagsWithConflictsOnly.addChangeListener(
+                    new ChangeListener() {
+                        public void stateChanged(ChangeEvent e) {
+                            model.setShowTagsWithConflictsOnly(cbShowTagsWithConflictsOnly.isSelected());
+                            cbShowTagsWithMultiValuesOnly.setEnabled(cbShowTagsWithConflictsOnly.isSelected());
+                        }
+                    }
+            );
+            cbShowTagsWithConflictsOnly.setSelected(
+                    Main.pref.getBoolean(getClass().getName() + ".showTagsWithConflictsOnly", false)
+            );
+            cbShowTagsWithMultiValuesOnly.addChangeListener(
+                    new ChangeListener() {
+                        public void stateChanged(ChangeEvent e) {
+                            model.setShowTagsWithMultiValuesOnly(cbShowTagsWithMultiValuesOnly.isSelected());
+                        }
+                    }
+            );
+            cbShowTagsWithMultiValuesOnly.setSelected(
+                    Main.pref.getBoolean(getClass().getName() + ".showTagsWithMultiValuesOnly", false)
+            );
+            cbShowTagsWithMultiValuesOnly.setEnabled(cbShowTagsWithConflictsOnly.isSelected());
+            return pnl;
+        }
+
+        /**
+         * Remembers the current settings in the global preferences
+         *
+         */
+        public void rememberPreferences() {
+            Main.pref.put(getClass().getName() + ".showTagsWithConflictsOnly", cbShowTagsWithConflictsOnly.isSelected());
+            Main.pref.put(getClass().getName() + ".showTagsWithMultiValuesOnly", cbShowTagsWithMultiValuesOnly.isSelected());
+        }
+
+        protected void build() {
+            setLayout(new BorderLayout());
+            add(buildInfoPanel(), BorderLayout.NORTH);
+            add(new JScrollPane(new MyTagConflictResolverTable(model)), BorderLayout.CENTER);
+        }
+
+        public MyTagConflictResolver() {
+            this.model = new MyTagConflictResolverModel();
+            build();
+        }
+
+        /**
+         * Replies the model used by this dialog
+         *
+         * @return the model
+         */
+        public MyTagConflictResolverModel getModel() {
+            return model;
+        }
+    }
+
+    public class MyTagConflictResolverTable extends JTable implements MultiValueCellEditor.NavigationListener {
+
+        private SelectNextColumnCellAction selectNextColumnCellAction;
+        private SelectPreviousColumnCellAction selectPreviousColumnCellAction;
+
+        public MyTagConflictResolverTable(MyTagConflictResolverModel model) {
+            super(model, new TagConflictResolverColumnModel());
+            build();
+        }
+
+        protected void build() {
+            setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
+            setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+            putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);
+
+            // make ENTER behave like TAB
+            //
+            getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(
+                    KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0, false), "selectNextColumnCell");
+
+            // install custom navigation actions
+            //
+            selectNextColumnCellAction = new SelectNextColumnCellAction();
+            selectPreviousColumnCellAction = new SelectPreviousColumnCellAction();
+            getActionMap().put("selectNextColumnCell", selectNextColumnCellAction);
+            getActionMap().put("selectPreviousColumnCell", selectPreviousColumnCellAction);
+
+            ((MultiValueCellEditor)getColumnModel().getColumn(2).getCellEditor()).addNavigationListeners(this);
+
+            setRowHeight((int)new JComboBox().getPreferredSize().getHeight());
+        }
+
+        /**
+         * Action to be run when the user navigates to the next cell in the table, for instance by
+         * pressing TAB or ENTER. The action alters the standard navigation path from cell to cell: <ul>
+         * <li>it jumps over cells in the first column</li> <li>it automatically add a new empty row
+         * when the user leaves the last cell in the table</li> <ul>
+         *
+         *
+         */
+        class SelectNextColumnCellAction extends AbstractAction {
+            public void actionPerformed(ActionEvent e) {
+                run();
+            }
+
+            public void run() {
+                int col = getSelectedColumn();
+                int row = getSelectedRow();
+                if (getCellEditor() != null) {
+                    getCellEditor().stopCellEditing();
+                }
+
+                if (col == 2 && row < getRowCount() - 1) {
+                    row++;
+                } else if (row < getRowCount() - 1) {
+                    col = 2;
+                    row++;
+                }
+                changeSelection(row, col, false, false);
+                editCellAt(getSelectedRow(), getSelectedColumn());
+                getEditorComponent().requestFocusInWindow();
+            }
+        }
+
+        /**
+         * Action to be run when the user navigates to the previous cell in the table, for instance by
+         * pressing Shift-TAB
+         *
+         */
+        class SelectPreviousColumnCellAction extends AbstractAction {
+
+            public void actionPerformed(ActionEvent e) {
+                run();
+            }
+
+            public void run() {
+                int col = getSelectedColumn();
+                int row = getSelectedRow();
+                if (getCellEditor() != null) {
+                    getCellEditor().stopCellEditing();
+                }
+
+                if (col <= 0 && row <= 0) {
+                    // change nothing
+                } else if (row > 0) {
+                    col = 2;
+                    row--;
+                }
+                changeSelection(row, col, false, false);
+                editCellAt(getSelectedRow(), getSelectedColumn());
+                getEditorComponent().requestFocusInWindow();
+            }
+        }
+
+        public void gotoNextDecision() {
+            selectNextColumnCellAction.run();
+        }
+
+        public void gotoPreviousDecision() {
+            selectPreviousColumnCellAction.run();
+        }
+    }
+}
