Index: /trunk/src/org/openstreetmap/josm/gui/conflict/tags/CombinePrimitiveResolverDialog.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/conflict/tags/CombinePrimitiveResolverDialog.java	(revision 11605)
+++ /trunk/src/org/openstreetmap/josm/gui/conflict/tags/CombinePrimitiveResolverDialog.java	(revision 11606)
@@ -476,5 +476,5 @@
 
         final TagCollection completeWayTags = new TagCollection(tagsOfPrimitives);
-        TagConflictResolutionUtil.combineTigerTags(completeWayTags);
+        TagConflictResolutionUtil.applyAutomaticTagConflictResolution(completeWayTags);
         TagConflictResolutionUtil.normalizeTagCollectionBeforeEditing(completeWayTags, primitives);
         final TagCollection tagsToEdit = new TagCollection(completeWayTags);
Index: /trunk/src/org/openstreetmap/josm/gui/conflict/tags/TagConflictResolutionUtil.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/conflict/tags/TagConflictResolutionUtil.java	(revision 11605)
+++ /trunk/src/org/openstreetmap/josm/gui/conflict/tags/TagConflictResolutionUtil.java	(revision 11606)
@@ -3,10 +3,22 @@
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
-
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+import java.util.stream.Collectors;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.Preferences.pref;
 import org.openstreetmap.josm.data.osm.OsmPrimitive;
 import org.openstreetmap.josm.data.osm.Tag;
 import org.openstreetmap.josm.data.osm.TagCollection;
-import org.openstreetmap.josm.data.osm.TigerUtils;
+import org.openstreetmap.josm.tools.Pair;
 
 /**
@@ -15,4 +27,55 @@
  */
 public final class TagConflictResolutionUtil {
+
+    /** The OSM key 'source' */
+    private static final String KEY_SOURCE = "source";
+
+    /** The group identifier for French Cadastre choices */
+    private static final String GRP_FR_CADASTRE = "FR:cadastre";
+
+    /** The group identifier for Canadian CANVEC choices */
+    private static final String GRP_CA_CANVEC = "CA:canvec";
+
+    /**
+     * Default preferences for the list of AutomaticCombine tag conflict resolvers.
+     */
+    private static final Collection<AutomaticCombine> defaultAutomaticTagConflictCombines = Arrays.asList(
+        new AutomaticCombine("tiger:tlid", "US TIGER tlid", false, ":", "Integer"),
+        new AutomaticCombine("tiger:(?!tlid$).*", "US TIGER not tlid", true, ":", "String")
+    );
+
+    /**
+     * Default preferences for the list of AutomaticChoice tag conflict resolvers.
+     */
+    private static final Collection<AutomaticChoice> defaultAutomaticTagConflictChoices = Arrays.asList(
+        /* "source" "FR:cadastre" - https://wiki.openstreetmap.org/wiki/FR:WikiProject_France/Cadastre
+         * List of choices for the "source" tag of data exported from the French cadastre,
+         * which ends by the exported year generating many conflicts.
+         * The generated score begins with the year number to select the most recent one.
+         */
+        new AutomaticChoice(KEY_SOURCE, GRP_FR_CADASTRE, "FR cadastre source, manual value", true,
+                "cadastre", "0"),
+        new AutomaticChoice(KEY_SOURCE, GRP_FR_CADASTRE, "FR cadastre source, initial format", true,
+                "extraction vectorielle v1 cadastre-dgi-fr source : Direction G[eé]n[eé]rale des Imp[oô]ts"
+                + " - Cadas\\. Mise [aà] jour : (2[0-9]{3})",
+                "$1 1"),
+        new AutomaticChoice(KEY_SOURCE, GRP_FR_CADASTRE, "FR cadastre source, last format", true,
+                "(?:cadastre-dgi-fr source : )?Direction G[eé]n[eé]rale des (?:Imp[oô]ts|Finances Publiques)"
+                + " - Cadas(?:tre)?(?:\\.| ;) [Mm]ise [aà] jour : (2[0-9]{3})",
+                "$1 2"),
+        /* "source" "CA:canvec" - https://wiki.openstreetmap.org/wiki/CanVec
+         * List of choices for the "source" tag of data exported from Natural Resources Canada (NRCan)
+         */
+        new AutomaticChoice(KEY_SOURCE, GRP_CA_CANVEC, "CA canvec source, initial value", true,
+                "CanVec_Import_2009", "00"),
+        new AutomaticChoice(KEY_SOURCE, GRP_CA_CANVEC, "CA canvec source, 4.0/6.0 value", true,
+                "CanVec ([1-9]).0 - NRCan", "0$1"),
+        new AutomaticChoice(KEY_SOURCE, GRP_CA_CANVEC, "CA canvec source, 7.0/8.0 value", true,
+                "NRCan-CanVec-([1-9]).0", "0$1"),
+        new AutomaticChoice(KEY_SOURCE, GRP_CA_CANVEC, "CA canvec source, 10.0/12.0 value", true,
+                "NRCan-CanVec-(1[012]).0", "$1")
+    );
+
+    private static volatile Collection<AutomaticTagConflictResolver> automaticTagConflictResolvers;
 
     private TagConflictResolutionUtil() {
@@ -61,17 +124,4 @@
 
     /**
-     * Combines tags from TIGER data
-     *
-     * @param tc the tag collection
-     */
-    public static void combineTigerTags(TagCollection tc) {
-        for (String key: tc.getKeys()) {
-            if (TigerUtils.isTigerTag(key)) {
-                tc.setUniqueForKey(key, TigerUtils.combineTags(tc.getValues(key)));
-            }
-        }
-    }
-
-    /**
      * Completes tags in the tag collection <code>tc</code> with the empty value
      * for each tag. If the empty value is present the tag conflict resolution dialog
@@ -89,3 +139,353 @@
         }
     }
+
+    /**
+     * Automatically resolve some tag conflicts.
+     * The list of automatic resolution is taken from the preferences.
+     * @param tc the tag collection
+     * @since 11606
+     */
+    public static void applyAutomaticTagConflictResolution(TagCollection tc) {
+        applyAutomaticTagConflictResolution(tc, getAutomaticTagConflictResolvers());
+    }
+
+    /**
+     * Get the AutomaticTagConflictResolvers configured in the Preferences or the default ones.
+     * @return the configured AutomaticTagConflictResolvers.
+     * @since 11606
+     */
+    public static Collection<AutomaticTagConflictResolver> getAutomaticTagConflictResolvers() {
+        if (automaticTagConflictResolvers == null) {
+            Collection<AutomaticCombine> automaticTagConflictCombines =
+                    Main.pref.getListOfStructs(
+                            "automatic-tag-conflict-resolution.combine",
+                            defaultAutomaticTagConflictCombines, AutomaticCombine.class);
+            Collection<AutomaticChoiceGroup> automaticTagConflictChoiceGroups =
+                    AutomaticChoiceGroup.groupChoices(Main.pref.getListOfStructs(
+                            "automatic-tag-conflict-resolution.choice",
+                            defaultAutomaticTagConflictChoices, AutomaticChoice.class));
+            // Use a tmp variable to fully construct the collection before setting
+            // the volatile variable automaticTagConflictResolvers.
+            ArrayList<AutomaticTagConflictResolver> tmp = new ArrayList<>();
+            tmp.addAll(automaticTagConflictCombines);
+            tmp.addAll(automaticTagConflictChoiceGroups);
+            automaticTagConflictResolvers = tmp;
+        }
+        return Collections.unmodifiableCollection(automaticTagConflictResolvers);
+    }
+
+    /**
+     * An automatic tag conflict resolver interface.
+     * @since 11606
+     */
+    interface AutomaticTagConflictResolver {
+        /**
+         * Check if this resolution apply to the given Tag key.
+         * @param key The Tag key to match.
+         * @return true if this automatic resolution apply to the given Tag key.
+         */
+        boolean matchesKey(String key);
+
+        /**
+         * Try to resolve a conflict between a set of values for a Tag
+         * @param values the set of conflicting values for the Tag.
+         * @return the resolved value or null if resolution was not possible.
+         */
+        String resolve(Set<String> values);
+    }
+
+    /**
+     * Automatically resolve some given conflicts using the given resolvers.
+     * @param tc the tag collection.
+     * @param resolvers the list of automatic tag conflict resolvers to apply.
+     * @since 11606
+     */
+    public static void applyAutomaticTagConflictResolution(TagCollection tc,
+            Collection<AutomaticTagConflictResolver> resolvers) {
+        for (String key: tc.getKeysWithMultipleValues()) {
+            for (AutomaticTagConflictResolver resolver : resolvers) {
+                try {
+                    if (resolver.matchesKey(key)) {
+                        String result = resolver.resolve(tc.getValues(key));
+                        if (result != null) {
+                            tc.setUniqueForKey(key, result);
+                            break;
+                        }
+                    }
+                } catch (PatternSyntaxException e) {
+                    // Can happen if a particular resolver has an invalid regular expression pattern
+                    // but it should not stop the other automatic tag conflict resolution.
+                    Main.error(e);
+                }
+            }
+        }
+    }
+
+    /**
+     * Preference for automatic tag-conflict resolver by combining the tag values using a separator.
+     * @since 11606
+     */
+    public static class AutomaticCombine implements AutomaticTagConflictResolver {
+
+        /** The Tag key to match */
+        @pref public String key;
+
+        /** A free description */
+        @pref public String description = "";
+
+        /** If regular expression must be used to match the Tag key or the value. */
+        @pref public boolean isRegex;
+
+        /** The separator to use to combine the values. */
+        @pref public String separator = ";";
+
+        /** If the combined values must be sorted.
+         * Possible values:
+         * <ul>
+         * <li> Integer - Sort using Integer natural order.</li>
+         * <li> String - Sort using String natural order.</li>
+         * <li> * - No ordering.</li>
+         * </ul>
+         */
+        @pref public String sort;
+
+        /** Default constructor. */
+        public AutomaticCombine() {
+            // needed for instantiation from Preferences
+        }
+
+        /** Instantiate an automatic tag-conflict resolver which combining the values using a separator.
+         * @param key The Tag key to match.
+         * @param description A free description.
+         * @param isRegex If regular expression must be used to match the Tag key or the value.
+         * @param separator The separator to use to combine the values.
+         * @param sort If the combined values must be sorted.
+         */
+        public AutomaticCombine(String key, String description, boolean isRegex, String separator, String sort) {
+            this.key = key;
+            this.description = description;
+            this.isRegex = isRegex;
+            this.separator = separator;
+            this.sort = sort;
+        }
+
+        @Override
+        public boolean matchesKey(String k) {
+            if (isRegex) {
+                return Pattern.matches(this.key, k);
+            } else {
+                return this.key.equals(k);
+            }
+        }
+
+        Set<String> instantiateSortedSet() {
+            if ("String".equals(sort)) {
+                return new TreeSet<>();
+            } else if ("Integer".equals(sort)) {
+                return new TreeSet<>((String v1, String v2) -> Long.valueOf(v1).compareTo(Long.valueOf(v2)));
+            } else {
+                return new LinkedHashSet<>();
+            }
+        }
+
+        @Override
+        public String resolve(Set<String> values) {
+            Set<String> results = instantiateSortedSet();
+            for (String value: values) {
+                for (String part: value.split(Pattern.quote(separator))) {
+                    results.add(part);
+                }
+            }
+            return String.join(separator, results);
+        }
+
+        @Override
+        public String toString() {
+            return AutomaticCombine.class.getSimpleName()
+                    + "(key='" + key + "', description='" + description + "', isRegex="
+                    + isRegex + ", separator='" + separator + "', sort='" + sort + "')";
+        }
+    }
+
+    /**
+     * Preference for a particular choice from a group for automatic tag conflict resolution.
+     * {@code AutomaticChoice}s are grouped into {@link AutomaticChoiceGroup}.
+     * @since 11606
+     */
+    public static class AutomaticChoice {
+
+        /** The Tag key to match. */
+        @pref public String key;
+
+        /** The name of the {link AutomaticChoice group} this choice belongs to. */
+        @pref public String group;
+
+        /** A free description. */
+        @pref public String description = "";
+
+        /** If regular expression must be used to match the Tag key or the value. */
+        @pref public boolean isRegex;
+
+        /** The Tag value to match. */
+        @pref public String value;
+
+        /**
+         * The score to give to this choice in order to choose the best value
+         * Natural String ordering is used to identify the best score.
+         */
+        @pref public String score;
+
+        /** Default constructor. */
+        public AutomaticChoice() {
+            // needed for instantiation from Preferences
+        }
+
+        /**
+         * Instantiate a particular choice from a group for automatic tag conflict resolution.
+         * @param key The Tag key to match.
+         * @param group The name of the {link AutomaticChoice group} this choice belongs to.
+         * @param description A free description.
+         * @param isRegex If regular expression must be used to match the Tag key or the value.
+         * @param value The Tag value to match.
+         * @param score The score to give to this choice in order to choose the best value.
+         */
+        public AutomaticChoice(String key, String group, String description, boolean isRegex, String value, String score) {
+            this.key = key;
+            this.group = group;
+            this.description = description;
+            this.isRegex = isRegex;
+            this.value = value;
+            this.score = score;
+        }
+
+        /**
+         * Check if this choice match the given Tag value.
+         * @param v the Tag value to match.
+         * @return true if this choice correspond to the given tag value.
+         */
+        public boolean matchesValue(String v) {
+            if (isRegex) {
+                return Pattern.matches(this.value, v);
+            } else {
+                return this.value.equals(v);
+            }
+        }
+
+        /**
+         * Return the score associated to this choice for the given Tag value.
+         * For the result to be valid the given tag value must {@link #matchesValue(String) match} this choice.
+         * @param v the Tag value of which to get the score.
+         * @return the score associated to the given Tag value.
+         * @throws PatternSyntaxException if the regular expression syntax is invalid
+         */
+        public String computeScoreFromValue(String v) {
+            if (isRegex) {
+                return v.replaceAll("^" + this.value + "$", this.score);
+            } else {
+                return this.score;
+            }
+        }
+
+        @Override
+        public String toString() {
+            return AutomaticChoice.class.getSimpleName()
+                    + "(key='" + key + "', group='" + group + "', description='" + description
+                    + "', isRegex=" + isRegex + ", value='" + value + "', score='" + score + "')";
+        }
+    }
+
+    /**
+     * Preference for an automatic tag conflict resolver which choose from
+     * a group of possible {@link AutomaticChoice choice} values.
+     * @since 11606
+     */
+    public static class AutomaticChoiceGroup implements AutomaticTagConflictResolver {
+
+        /** The Tag key to match. */
+        @pref public String key;
+
+        /** The name of the group. */
+        final String group;
+
+        /** If regular expression must be used to match the Tag key. */
+        @pref public boolean isRegex;
+
+        /** The list of choice to choose from. */
+        final List<AutomaticChoice> choices;
+
+        /** Instantiate an automatic tag conflict resolver which choose from
+         * a given list of {@link AutomaticChoice choice} values.
+         *
+         * @param key The Tag key to match.
+         * @param group The name of the group.
+         * @param isRegex If regular expression must be used to match the Tag key.
+         * @param choices The list of choice to choose from.
+         */
+        public AutomaticChoiceGroup(String key, String group, boolean isRegex, List<AutomaticChoice> choices) {
+            this.key = key;
+            this.group = group;
+            this.isRegex = isRegex;
+            this.choices = choices;
+        }
+
+        /**
+         * Group a given list of {@link AutomaticChoice} by the Tag key and the choice group name.
+         * @param choices the list of {@link AutomaticChoice choices} to group.
+         * @return the resulting list of group.
+         */
+        public static Collection<AutomaticChoiceGroup> groupChoices(Collection<AutomaticChoice> choices) {
+            HashMap<Pair<String, String>, AutomaticChoiceGroup> results = new HashMap<>();
+            for (AutomaticChoice choice: choices) {
+                Pair<String, String> id = new Pair<>(choice.key, choice.group);
+                AutomaticChoiceGroup group = results.get(id);
+                if (group == null) {
+                    boolean isRegex = choice.isRegex && !Pattern.quote(choice.key).equals(choice.key);
+                    group = new AutomaticChoiceGroup(choice.key, choice.group, isRegex, new ArrayList<>());
+                    results.put(id, group);
+                }
+                group.choices.add(choice);
+            }
+            return results.values();
+        }
+
+        @Override
+        public boolean matchesKey(String k) {
+            if (isRegex) {
+                return Pattern.matches(this.key, k);
+            } else {
+                return this.key.equals(k);
+            }
+        }
+
+        @Override
+        public String resolve(Set<String> values) {
+            String bestScore = "";
+            String bestValue = "";
+            for (String value : values) {
+                String score = null;
+                for (AutomaticChoice choice : choices) {
+                    if (choice.matchesValue(value)) {
+                        score = choice.computeScoreFromValue(value);
+                    }
+                }
+                if (score == null) {
+                    // This value is not matched in this group
+                    // so we can not choose from this group for this key.
+                    return null;
+                }
+                if (score.compareTo(bestScore) >= 0) {
+                    bestScore = score;
+                    bestValue = value;
+                }
+            }
+            return bestValue;
+        }
+
+        @Override
+        public String toString() {
+            Collection<String> stringChoices = choices.stream().map(AutomaticChoice::toString).collect(Collectors.toCollection(ArrayList::new));
+            return AutomaticChoiceGroup.class.getSimpleName() + "(key='" + key + "', group='" + group +
+                    "', isRegex=" + isRegex + ", choices=(\n  " + String.join(",\n  ", stringChoices) + "))";
+        }
+    }
 }
Index: /trunk/test/unit/org/openstreetmap/josm/gui/conflict/tags/TagConflictResolutionUtilTest.java
===================================================================
--- /trunk/test/unit/org/openstreetmap/josm/gui/conflict/tags/TagConflictResolutionUtilTest.java	(revision 11606)
+++ /trunk/test/unit/org/openstreetmap/josm/gui/conflict/tags/TagConflictResolutionUtilTest.java	(revision 11606)
@@ -0,0 +1,443 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.conflict.tags;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.openstreetmap.josm.data.osm.Tag;
+import org.openstreetmap.josm.data.osm.TagCollection;
+import org.openstreetmap.josm.gui.conflict.tags.TagConflictResolutionUtil.AutomaticChoice;
+import org.openstreetmap.josm.gui.conflict.tags.TagConflictResolutionUtil.AutomaticChoiceGroup;
+import org.openstreetmap.josm.gui.conflict.tags.TagConflictResolutionUtil.AutomaticCombine;
+import org.openstreetmap.josm.testutils.JOSMTestRules;
+
+import com.google.common.collect.Sets;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+
+/**
+ * Unit tests of {@link TagConflictResolutionUtil} class.
+ */
+public class TagConflictResolutionUtilTest {
+
+    /**
+     * Setup test.
+     */
+    @Rule
+    @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")
+    public JOSMTestRules test = new JOSMTestRules();
+
+    /**
+     * Unit test of {@link TagConflictResolutionUtil#applyAutomaticTagConflictResolution}.
+     * assume predefined rules for US TIGER and French Cadastre.
+     */
+    @Test
+    public void testApplyAutomaticTagConflictResolution() {
+        // Check that general tag conflict are not resolved
+        TagCollection tc = new TagCollection();
+        tc.add(new Tag("building", "school"));
+        tc.add(new Tag("building", "garage"));
+        TagConflictResolutionUtil.applyAutomaticTagConflictResolution(tc);
+        assertEquals(Sets.newHashSet("school", "garage"), new HashSet<>(tc.getValues("building")));
+
+        // Check US Tiger tag conflict resolution
+        tc = new TagCollection();
+        tc.add(new Tag("tiger:test", "A:B"));
+        tc.add(new Tag("tiger:test", "A"));
+        TagConflictResolutionUtil.applyAutomaticTagConflictResolution(tc);
+        assertEquals(Sets.newHashSet("A:B"), new HashSet<>(tc.getValues("tiger:test")));
+
+        // Check FR:cadastre source tag conflict resolution (most common values from taginfo except last one without accentuated characters)
+        tc = new TagCollection();
+        // CHECKSTYLE.OFF: LineLength
+        tc.add(new Tag("source", "cadastre-dgi-fr source : Direction Générale des Impôts - Cadastre ; mise à jour : 2007"));
+        tc.add(new Tag("source", "cadastre-dgi-fr source : Direction Générale des Impôts - Cadastre ; mise à jour : 2008"));
+        tc.add(new Tag("source", "cadastre-dgi-fr source : Direction Générale des Impôts - Cadastre ; mise à jour : 2009"));
+        tc.add(new Tag("source", "cadastre-dgi-fr source : Direction Générale des Impôts - Cadastre ; mise à jour : 2010"));
+        tc.add(new Tag("source", "cadastre-dgi-fr source : Direction Générale des Impôts - Cadastre. Mise à jour : 2008"));
+        tc.add(new Tag("source", "cadastre-dgi-fr source : Direction Générale des Impôts - Cadastre. Mise à jour : 2009"));
+        tc.add(new Tag("source", "cadastre-dgi-fr source : Direction Générale des Impôts - Cadastre. Mise à jour : 2010"));
+        tc.add(new Tag("source", "cadastre-dgi-fr source : Direction Générale des Impôts - Cadastre. Mise à jour : 2011"));
+        tc.add(new Tag("source", "cadastre-dgi-fr source : Direction Générale des Impôts - Cadastre. Mise à jour : 2012"));
+        tc.add(new Tag("source", "cadastre-dgi-fr source : Direction Générale des Impôts - Cadastre. Mise à jour : 2013"));
+        tc.add(new Tag("source", "cadastre-dgi-fr source : Direction Générale des Impôts - Cadastre. Mise à jour : 2014"));
+        tc.add(new Tag("source", "cadastre-dgi-fr source : Direction Générale des Impôts - Cadas. Mise à jour : 2010"));
+        tc.add(new Tag("source", "extraction vectorielle v1 cadastre-dgi-fr source : Direction Générale des Impôts - Cadas. Mise à jour : 2010"));
+        tc.add(new Tag("source", "Direction Générale des Finances Publiques - Cadastre ; mise à jour : 2010"));
+        tc.add(new Tag("source", "cadastre-dgi-fr source : Direction Générale des Finances Publiques - Cadastre. Mise à jour : 2013"));
+        tc.add(new Tag("source", "cadastre-dgi-fr source : Direction Générale des Finances Publiques - Cadastre. Mise à jour : 2014"));
+        tc.add(new Tag("source", "cadastre-dgi-fr source : Direction Generale des Finances Publiques - Cadastre. Mise a jour : 2015"));
+        // CHECKSTYLE.ON: LineLength
+        Tag otherSource = new Tag("source", "other");
+        tc.add(otherSource); // other source should prevent resolution
+        TagConflictResolutionUtil.applyAutomaticTagConflictResolution(tc);
+        assertTrue(tc.getValues("source").size() == 18);
+        tc.remove(otherSource);
+        TagConflictResolutionUtil.applyAutomaticTagConflictResolution(tc);
+        assertEquals(Sets.newHashSet("cadastre-dgi-fr source : Direction Generale des Finances Publiques - Cadastre. Mise a jour : 2015"),
+                new HashSet<>(tc.getValues("source")));
+
+        // Check CA:canvec source tag conflict resolution
+        tc = new TagCollection();
+        tc.add(new Tag("source", "CanVec_Import_2009"));
+        tc.add(new Tag("source", "CanVec 4.0 - NRCan"));
+        tc.add(new Tag("source", "CanVec 6.0 - NRCan"));
+        tc.add(new Tag("source", "NRCan-CanVec-7.0"));
+        tc.add(new Tag("source", "NRCan-CanVec-8.0"));
+        tc.add(new Tag("source", "NRCan-CanVec-10.0"));
+        tc.add(new Tag("source", "NRCan-CanVec-12.0"));
+        TagConflictResolutionUtil.applyAutomaticTagConflictResolution(tc);
+        assertEquals(Sets.newHashSet("NRCan-CanVec-12.0"), new HashSet<>(tc.getValues("source")));
+    }
+
+    /**
+     * Unit tests of {@link AutomaticCombine} class.
+     */
+    public static class AutomaticCombineTest {
+
+        /**
+         * Return AutomaticCombine instantiated with the two possible constructors.
+         * @param ac a model for the constructed object.
+         * @return AutomaticCombine object constructed with the two different constructors.
+         */
+        private static List<AutomaticCombine> differentlyConstructed(AutomaticCombine ac) {
+            AutomaticCombine fullyConstructed = new AutomaticCombine(ac.key, ac.description, ac.isRegex, ac.separator, ac.sort);
+            AutomaticCombine defaultConstructed = new AutomaticCombine();
+            defaultConstructed.key = ac.key;
+            defaultConstructed.description = ac.description;
+            defaultConstructed.isRegex = ac.isRegex;
+            defaultConstructed.separator = ac.separator;
+            defaultConstructed.sort = ac.sort;
+            return Arrays.asList(defaultConstructed, fullyConstructed);
+        }
+
+        /**
+         * Setup test.
+         */
+        @Rule
+        @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")
+        public JOSMTestRules test = new JOSMTestRules();
+
+        /**
+         * Unit test of {@link AutomaticCombine#matchesKey} with empty key.
+         */
+        @Test
+        public void testMatchesKeyEmptyKey() {
+            for (AutomaticCombine resolver: differentlyConstructed(new AutomaticCombine("", "random description", true, ";", null))) {
+                assertFalse(resolver.matchesKey("a"));
+                assertTrue(resolver.matchesKey(""));
+            }
+        }
+
+        /**
+         * Unit test of {@link AutomaticCombine#matchesKey} when regex not used.
+         */
+        @Test
+        public void testMatchesKeyNotRegex() {
+            for (AutomaticCombine resolver: differentlyConstructed(new AutomaticCombine(
+                    "keyname", "random description", false, "|", null))) {
+                assertFalse(resolver.matchesKey("key"));
+                assertFalse(resolver.matchesKey("keyname2"));
+                assertFalse(resolver.matchesKey("name"));
+                assertFalse(resolver.matchesKey("key.*("));
+                assertTrue(resolver.matchesKey("keyname"));
+            }
+        }
+
+        /**
+         * Unit test of {@link AutomaticCombine#matchesKey} when regex used.
+         */
+        @Test
+        public void testMatchesKeyRegex() {
+            for (AutomaticCombine resolver: differentlyConstructed(new AutomaticCombine("test[45].*", "", true, ";", "Integer"))) {
+                assertFalse(resolver.matchesKey("key"));
+                assertFalse(resolver.matchesKey("test[45].*"));
+                assertTrue(resolver.matchesKey("test400 !"));
+            }
+        }
+
+        /**
+         * Unit test of {@link AutomaticCombine} with invalid regex.
+         */
+        @Test
+        public void testInvalidRegex() {
+            for (AutomaticCombine resolver: differentlyConstructed(new AutomaticCombine("invalidregex.(]", "", false, ";", null))) {
+                // Should not raise exception if the resolver.isRexEx == false:
+                assertTrue(resolver.matchesKey("invalidregex.(]"));
+            }
+
+            // Should not raise exception if isRexEx, invalid regex but only constructed:
+            for (AutomaticCombine resolver: differentlyConstructed(new AutomaticCombine("invalidregex.(]", "", true, ";", null))) {
+                assertTrue(resolver.isRegex);
+            }
+        }
+
+        /**
+         * Unit test of {@link AutomaticCombine} with invalid regex.
+         */
+        @Test(expected = java.util.regex.PatternSyntaxException.class)
+        public void testInvalidRegexExceptionDefaultConstructed() {
+            AutomaticCombine resolver = new AutomaticCombine("AB.(]", "", true, ";", null);
+            resolver.matchesKey("AB");
+        }
+
+
+        /**
+         * Unit test of {@link AutomaticCombine} with invalid regex.
+         */
+        @Test(expected = java.util.regex.PatternSyntaxException.class)
+        public void testInvalidRegexExceptionFullyConstructed() {
+            AutomaticCombine resolver = new AutomaticCombine();
+            resolver.key = "AB.(]";
+            resolver.isRegex = true;
+            resolver.matchesKey("AB");
+        }
+
+        /**
+         * Unit test of {@link AutomaticCombine#resolve}.
+         */
+        @Test
+        public void testResolve() {
+            for (AutomaticCombine resolver: differentlyConstructed(new AutomaticCombine("random", "", true, "|", "String"))) {
+                assertEquals(resolver.resolve(Sets.newHashSet("value1", "value2")), "value1|value2");
+                assertEquals(resolver.resolve(Sets.newHashSet("3|1", "4|2|1", "6|05", "3;1")), "05|1|2|3|3;1|4|6");
+            }
+
+            for (AutomaticCombine resolver: differentlyConstructed(new AutomaticCombine("test[45].*", "", true, ";", "Integer"))) {
+                assertEquals(resolver.resolve(Sets.newHashSet("1254545;95;24", "25;24;3")), "3;24;25;95;1254545");
+            }
+
+            for (AutomaticCombine resolver: differentlyConstructed(new AutomaticCombine("AB", "", true, ";", null))) {
+                String resolution = resolver.resolve(Sets.newHashSet("3;x;1", "4;x"));
+                assertTrue(resolution.equals("3;x;1;4") || resolution.equals("4;x;3;1"));
+            }
+        }
+    }
+
+    /**
+     * Unit tests of {@link AutomaticChoice} class.
+     */
+    public static class AutomaticChoiceTest {
+        /**
+         * Setup test.
+         */
+        @Rule
+        @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")
+        public JOSMTestRules test = new JOSMTestRules();
+
+        /**
+
+         * Return AutomaticCombine instantiated with the two possible constructors.
+         * @param ac a model for the constructed object.
+         * @return AutomaticCombine object constructed with the two different constructors.
+         */
+        private static List<AutomaticChoice> differentlyConstructed(AutomaticChoice ac) {
+            AutomaticChoice fullyConstructed = new AutomaticChoice(ac.key, ac.group, ac.description, ac.isRegex, ac.value, ac.score);
+            AutomaticChoice defaultConstructed = new AutomaticChoice();
+            defaultConstructed.key = ac.key;
+            defaultConstructed.group = ac.group;
+            defaultConstructed.description = ac.description;
+            defaultConstructed.isRegex = ac.isRegex;
+            defaultConstructed.value = ac.value;
+            defaultConstructed.score = ac.score;
+            return Arrays.asList(defaultConstructed, fullyConstructed);
+        }
+
+        /**
+         * Unit test of {@link AutomaticChoice#matchesValue}.
+         */
+        @Test
+        public void testMatchesValue() {
+            for (AutomaticChoice resolver: differentlyConstructed(new AutomaticChoice(
+                    "random key", "random group", "random description", false, ".*valueToMatch", "Score$0\\1"))) {
+                assertTrue(resolver.matchesValue(".*valueToMatch"));
+                assertFalse(resolver.matchesValue(".*valueToMatch.*"));
+                assertFalse(resolver.matchesValue("test"));
+                assertFalse(resolver.matchesValue(""));
+            }
+            for (AutomaticChoice resolver: differentlyConstructed(new AutomaticChoice(
+                    "", "", "", true, "test([ab].*)", "ok $1"))) {
+                assertTrue(resolver.matchesValue("testa"));
+                assertTrue(resolver.matchesValue("testb129"));
+                assertFalse(resolver.matchesValue("test[ab].*"));
+                assertFalse(resolver.matchesValue("test"));
+                assertFalse(resolver.matchesValue(""));
+            }
+        }
+
+        /**
+         * Unit test of {@link AutomaticChoice#computeScoreFromValue}.
+         */
+        @Test
+        public void testComputeScoreFromValue() {
+            for (AutomaticChoice resolver: differentlyConstructed(new AutomaticChoice(
+                    "random key", "random group", "random description", false, ".*valueToMatch", "Score$0\\1"))) {
+                assertEquals(resolver.computeScoreFromValue(".*valueToMatch"), "Score$0\\1");
+            }
+            for (AutomaticChoice resolver: differentlyConstructed(new AutomaticChoice(
+                    "", "", "", true, "test([ab].*)", "ok $1"))) {
+                assertEquals(resolver.computeScoreFromValue("testa"), "ok a");
+                assertEquals(resolver.computeScoreFromValue("testb129"), "ok b129");
+            }
+        }
+
+        /**
+         * Unit test of {@link AutomaticChoice} when invalid regex is used.
+         */
+        @Test
+        public void testInvalidRegex() {
+            for (AutomaticChoice resolver: differentlyConstructed(new AutomaticChoice(
+                    "k", "g", "", false, "invalidregex.(]", "InvalidScore$0\\1$-4"))) {
+                // Should not raise exception if the resolver.isRexEx == false:
+                assertTrue(resolver.matchesValue("invalidregex.(]"));
+                assertFalse(resolver.matchesValue("test"));
+                assertEquals(resolver.computeScoreFromValue("invalidregex.(]"), "InvalidScore$0\\1$-4");
+            }
+            // Should not raise exception if isRexEx, invalid regex but only constructed:
+            for (AutomaticChoice resolver: differentlyConstructed(new AutomaticChoice(
+                    "k", "g", "", true, "invalidregex.(]", "InvalidScore$0\\1$-4"))) {
+                assertTrue(resolver.isRegex);
+            }
+        }
+
+        /**
+         * Unit test of {@link AutomaticChoice} when invalid regex is used.
+         */
+        @Test(expected = java.util.regex.PatternSyntaxException.class)
+        public void testMatchesValueInvalidRegex() {
+            AutomaticChoice resolver = new AutomaticChoice("k", "g", "", true, "invalidregex.(]", "InvalidScore$0\\1$-4");
+            resolver.matchesValue("test");
+        }
+
+        /**
+         * Unit test of {@link AutomaticChoice} when invalid regex is used.
+         */
+        @Test(expected = java.util.regex.PatternSyntaxException.class)
+        public void testComputeScoreFromValueInvalidRegex() {
+            AutomaticChoice resolver = new AutomaticChoice("k", "g", "", true, "invalidregex.(]", "valid");
+            resolver.computeScoreFromValue("valid");
+        }
+
+
+        /**
+         * Unit test of {@link AutomaticChoice} when invalid score replacement is used.
+         */
+        @Test
+        public void testComputeScoreFromValueInvalidReplacement() {
+            AutomaticChoice resolver = new AutomaticChoice("k", "g", "", true, "valid", "InvalidScore$0\\1$-4");
+            boolean exceptionThrown = false;
+            try {
+                resolver.computeScoreFromValue("valid");
+            } catch (Exception e) {
+                exceptionThrown = true;
+            }
+            assertTrue(exceptionThrown);
+        }
+    }
+
+    /**
+     * Unit tests of {@link AutomaticChoiceGroup} class.
+     */
+    public static class AutomaticChoiceGroupTest {
+        /**
+         * Setup test.
+         */
+        @Rule
+        @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")
+        public JOSMTestRules test = new JOSMTestRules();
+
+        AutomaticChoice choiceKey1Group1 = new AutomaticChoice("Key1", "Group1", "", false, "value1", "score1");
+        AutomaticChoice choiceKey1Group1bis = new AutomaticChoice("Key1", "Group1", "", false, "value2", "score2");
+        AutomaticChoice choiceKey1Group2 = new AutomaticChoice("Key1", "Group2", "", false, "value1", "score1");
+        AutomaticChoice choiceKey1Group2bis = new AutomaticChoice("Key1", "Group2", "", false, "value2", "score2");
+        AutomaticChoice choiceKey2Group1 = new AutomaticChoice("test[45].*", "Group1", "", true, "value1", "score1");
+        AutomaticChoice choiceKey2Group1bis = new AutomaticChoice("test[45].*", "Group1", "", true, "value2", "score2");
+        AutomaticChoice choiceKey2Group2 = new AutomaticChoice("test[45].*", "Group2", "", true, "value1(.*)", "$1");
+        AutomaticChoice choiceKey2Group2bis = new AutomaticChoice("test[45].*", "Group2", "", true, "value2(.*)", "$1");
+        AutomaticChoice choiceEmpty = new AutomaticChoice();
+
+        /**
+         * Unit test of {@link AutomaticChoiceGroup#groupChoices}.
+         */
+        @Test
+        public void testGroupChoices() {
+            Collection<AutomaticChoiceGroup> groups = AutomaticChoiceGroup.groupChoices(Arrays.asList(choiceKey1Group1, choiceKey1Group2));
+            assertTrue(groups.size() == 2);
+
+            groups = AutomaticChoiceGroup.groupChoices(Arrays.asList(
+                choiceKey1Group1, choiceKey1Group2, choiceKey2Group1, choiceKey2Group2, choiceEmpty));
+            assertTrue(groups.size() == 5);
+
+            groups = AutomaticChoiceGroup.groupChoices(Arrays.asList(choiceKey1Group1, choiceKey1Group1bis));
+            assertTrue(groups.size() == 1);
+            AutomaticChoiceGroup group1 = groups.iterator().next();
+            assertEquals(group1.key, choiceKey1Group1.key);
+            assertEquals(group1.group, choiceKey1Group1.group);
+            assertEquals(new HashSet<>(group1.choices), Sets.newHashSet(choiceKey1Group1, choiceKey1Group1bis));
+
+            groups = AutomaticChoiceGroup.groupChoices(Arrays.asList(
+                choiceKey1Group1, choiceKey1Group1bis, choiceKey1Group2, choiceKey1Group2bis,
+                choiceKey2Group1, choiceKey2Group1bis, choiceKey2Group2, choiceKey2Group2bis));
+            assertTrue(groups.size() == 4);
+            for (AutomaticChoiceGroup group: groups) {
+                for (AutomaticChoice choice: group.choices) {
+                    assertEquals(choice.key, group.key);
+                    assertEquals(choice.group, group.group);
+                    assertEquals(choice.isRegex, group.isRegex);
+                }
+            }
+        }
+
+        /**
+         * Unit test of {@link AutomaticChoiceGroup#matchesKey}.
+         */
+        @Test
+        public void testMatchesKey() {
+            AutomaticChoiceGroup group = new AutomaticChoiceGroup(
+                    choiceKey1Group1.key, choiceKey1Group1.group, choiceKey1Group1.isRegex,
+                    Arrays.asList(choiceKey1Group1, choiceKey1Group1bis));
+            assertFalse(group.matchesKey("key"));
+            assertFalse(group.matchesKey("keyname2"));
+            assertFalse(group.matchesKey("name"));
+            assertFalse(group.matchesKey("key.*("));
+            assertTrue(group.matchesKey(choiceKey1Group1.key));
+
+            group = new AutomaticChoiceGroup(
+                    choiceKey2Group1.key, choiceKey2Group2.group, choiceKey2Group2.isRegex,
+                    Arrays.asList(choiceKey2Group2, choiceKey2Group2bis));
+            assertFalse(group.matchesKey("key"));
+            assertFalse(group.matchesKey("test[45].*"));
+            assertTrue(group.matchesKey("test400 !"));
+        }
+
+        /**
+         * Unit test of {@link AutomaticChoiceGroup#resolve}.
+         */
+        @Test
+        public void testResolve() {
+            AutomaticChoiceGroup group = new AutomaticChoiceGroup(
+                    choiceKey1Group1.key, choiceKey1Group1.group, choiceKey1Group1.isRegex,
+                    Arrays.asList(choiceKey1Group1, choiceKey1Group1bis));
+            assertEquals(group.resolve(Sets.newHashSet(choiceKey1Group1.value)), choiceKey1Group1.value);
+            assertEquals(group.resolve(Sets.newHashSet(choiceKey1Group1.value, choiceKey1Group1bis.value)), choiceKey1Group1bis.value);
+            assertNull(group.resolve(Sets.newHashSet("random", choiceKey1Group1.value, choiceKey1Group1bis.value)));
+
+            group = new AutomaticChoiceGroup(
+                    choiceKey2Group1.key, choiceKey2Group2.group, choiceKey2Group2.isRegex,
+                    Arrays.asList(choiceKey2Group2, choiceKey2Group2bis));
+            assertEquals(group.resolve(Sets.newHashSet("value1")), "value1");
+            assertEquals(group.resolve(Sets.newHashSet("value1Z", "value2A")), "value1Z");
+            assertEquals(group.resolve(Sets.newHashSet("value1A", "value2Z")), "value2Z");
+            assertNull(group.resolve(Sets.newHashSet("value1Z", "value2A", "other not matched value")));
+        }
+    }
+}
