Index: resources/data/defaultpresets.xml
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/resources/data/defaultpresets.xml b/resources/data/defaultpresets.xml
--- a/resources/data/defaultpresets.xml	(revision 18531)
+++ b/resources/data/defaultpresets.xml	(date 1659984367664)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<presets xmlns="http://josm.openstreetmap.de/tagging-preset-1.0">
+<presets xmlns="http://josm.openstreetmap.de/tagging-preset-1.0" name="defaultpresets">
 <!--
     Format description: https://josm.openstreetmap.de/wiki/TaggingPresets
 -->
Index: resources/data/tagging-preset.xsd
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/resources/data/tagging-preset.xsd b/resources/data/tagging-preset.xsd
--- a/resources/data/tagging-preset.xsd	(revision 18531)
+++ b/resources/data/tagging-preset.xsd	(date 1659984440347)
@@ -75,6 +75,13 @@
                         </documentation>
                     </annotation>
                 </attribute>
+                <attribute name="object_keys" type="string">
+                    <annotation>
+                        <documentation>
+                            The keys to indicate that the preset is a real object. Some keys may be an object by default, such as highway.
+                        </documentation>
+                    </annotation>
+                </attribute>
 
                 <anyAttribute processContents="skip" />
             </extension>
@@ -134,6 +141,7 @@
         </sequence>
         <attributeGroup ref="tns:attributes.name" />
         <attributeGroup ref="tns:attributes.icon" />
+        <attributeGroup ref="tns:attributes.deprecated" />
         <attribute name="type" type="string">
             <annotation>
                 <documentation><![CDATA[
@@ -268,6 +276,13 @@
             </annotation>
         </attribute>
         <attribute name="match" type="tns:match" />
+        <attribute name="object" type="boolean">
+            <annotation>
+                <documentation>
+                    Specify that this preset is an object. Example: highways are objects, contact information is not.
+                </documentation>
+            </annotation>
+        </attribute>
     </complexType>
 
     <complexType name="link">
@@ -300,6 +315,20 @@
         </attribute>
         <attributeGroup ref="tns:attributes.text" />
         <attribute name="name" use="prohibited" />
+        <attribute name="alternative" type="boolean">
+            <annotation>
+                <documentation>
+                    If specified to be true, this indicates that the preset_link points to an alternative tagging of this object
+                </documentation>
+            </annotation>
+        </attribute>
+        <attribute name="parent" type="boolean">
+            <annotation>
+                <documentation>
+                    Indicate that the linked preset should use values from this preset for autofill purposes
+                </documentation>
+            </annotation>
+        </attribute>
         <anyAttribute processContents="skip" />
     </complexType>
 
@@ -328,6 +357,7 @@
         <attributeGroup ref="tns:attributes.key" />
         <attributeGroup ref="tns:attributes.text" />
         <attributeGroup ref="tns:attributes.icon" />
+        <attributeGroup ref="tns:attributes.deprecated" />
         <attribute name="use_last_as_default" type="tns:last_default" />
         <attribute name="auto_increment" type="string">
             <annotation>
@@ -353,6 +383,14 @@
                 ]]></documentation>
             </annotation>
         </attribute>
+
+        <attribute name="i18n" type="boolean">
+            <annotation>
+                <documentation>
+                    If true, this freeform key can have i18n variants. Examples would be name and name:en.
+                </documentation>
+            </annotation>
+        </attribute>
 
         <attribute name="type" use="prohibited" />
         <attribute name="name" use="prohibited" />
@@ -378,6 +416,7 @@
             </annotation>
         </attribute>
         <attributeGroup ref="tns:attributes.icon" />
+        <attributeGroup ref="tns:attributes.deprecated" />
         <anyAttribute processContents="skip" />
     </complexType>
 
@@ -403,6 +442,7 @@
         <attributeGroup ref="tns:attributes.text" />
         <attributeGroup ref="tns:attributes.icon" />
         <attributeGroup ref="tns:attributes.values" />
+        <attributeGroup ref="tns:attributes.deprecated" />
         <attribute name="use_last_as_default" type="tns:last_default" />
         <attribute name="editable" type="boolean">
             <annotation>
@@ -431,6 +471,7 @@
         <attributeGroup ref="tns:attributes.text" />
         <attributeGroup ref="tns:attributes.icon" />
         <attributeGroup ref="tns:attributes.values" />
+        <attributeGroup ref="tns:attributes.deprecated" />
         <attribute name="use_last_as_default" type="tns:last_default" />
         <attribute name="match" type="tns:match" />
 
@@ -439,6 +480,13 @@
         <attribute name="name" use="prohibited" />
         <attribute name="delete-if-empty" use="prohibited" />
         <attribute name="display-values" use="prohibited" />
+        <attribute name="value_count_key" type="string">
+            <annotation>
+                <documentation>
+                    The reference to the key that will hold the number of values that this multiselect should contain. lanes and turn:lanes would use this.
+                </documentation>
+            </annotation>
+        </attribute>
         <anyAttribute processContents="skip" />
     </complexType>
 
@@ -591,6 +639,37 @@
 
     <!-- Types and documentation for attributes -->
 
+    <simpleType name="type.value_type">
+        <restriction base="string">
+            <enumeration value="opening_hours">
+                <annotation>
+                    <documentation>
+                        A standard opening hours tag
+                    </documentation>
+                </annotation>
+            </enumeration>
+            <enumeration value="opening_hours_mixed">
+                <annotation>
+                    <documentation>
+                        A tag with both text values and opening hours
+                    </documentation>
+                </annotation>
+            </enumeration>
+            <enumeration value="conditional">
+                <annotation>
+                    <documentation>
+                        A conditional tag such as maxspeed:conditional
+                    </documentation>
+                </annotation>
+            </enumeration>
+            <enumeration value="integer"/>
+            <enumeration value="website"/>
+            <enumeration value="phone"/>
+            <enumeration value="wikipedia"/>
+            <enumeration value="wikidata"/>
+        </restriction>
+    </simpleType>
+
     <attributeGroup name="attributes.name">
         <attribute name="name" type="string" use="required">
             <annotation>
@@ -607,6 +686,16 @@
             </annotation>
         </attribute>
     </attributeGroup>
+
+    <attributeGroup name="attributes.deprecated">
+        <attribute name="deprecated" type="boolean">
+            <annotation>
+                <documentation>
+                    If indicated to be true, this preset is deprecated and will not normally show up.
+                </documentation>
+            </annotation>
+        </attribute>
+    </attributeGroup>
 
     <attributeGroup name="attributes.key">
         <attribute name="key" type="string" use="required">
@@ -698,6 +787,13 @@
                 <documentation><![CDATA[
                     The character that separates values. In case of <combo /> the default is comma. In case of <multiselect /> the default is semicolon and this will also be used to separate selected values in the tag.
                 ]]></documentation>
+            </annotation>
+        </attribute>
+        <attribute name="value_type" type="tns:type.value_type">
+            <annotation>
+                <documentation>
+                    The type of value to avoid hardcoding the property in JOSM
+                </documentation>
             </annotation>
         </attribute>
     </attributeGroup>
Index: src/org/openstreetmap/josm/data/preferences/sources/ExtendedSourceEntry.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/org/openstreetmap/josm/data/preferences/sources/ExtendedSourceEntry.java b/src/org/openstreetmap/josm/data/preferences/sources/ExtendedSourceEntry.java
--- a/src/org/openstreetmap/josm/data/preferences/sources/ExtendedSourceEntry.java	(revision 18531)
+++ b/src/org/openstreetmap/josm/data/preferences/sources/ExtendedSourceEntry.java	(date 1660071009633)
@@ -28,6 +28,15 @@
     /** minimum JOSM version required to enable this source entry */
     public Integer minJosmVersion;
 
+    /**
+     * A constructor that is only supposed to be used by {@link org.openstreetmap.josm.tools.XmlObjectParser}.
+     * All values will be filled in using field references.
+     * @since xxx
+     */
+    public ExtendedSourceEntry() {
+        this(null, null, null);
+    }
+
     /**
      * Constructs a new {@code ExtendedSourceEntry}.
      * @param type type of source entry
Index: src/org/openstreetmap/josm/data/tagging/ac/AutoCompletionItemRunnable.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/org/openstreetmap/josm/data/tagging/ac/AutoCompletionItemRunnable.java b/src/org/openstreetmap/josm/data/tagging/ac/AutoCompletionItemRunnable.java
new file mode 100644
--- /dev/null	(date 1659707640156)
+++ b/src/org/openstreetmap/josm/data/tagging/ac/AutoCompletionItemRunnable.java	(date 1659707640156)
@@ -0,0 +1,43 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.data.tagging.ac;
+
+import java.util.Objects;
+
+import org.openstreetmap.josm.gui.util.GuiHelper;
+
+/**
+ * An autocompletion item that performs additional actions when it is selected
+ * @since xxx
+ */
+public class AutoCompletionItemRunnable extends AutoCompletionItem implements Runnable {
+    private final Runnable runnable;
+
+    /**
+     * Create a new AutoCompletionItemRunnable
+     * @param runnable The runnable to use. This will be run in the EDT (non-blocking).
+     * @param value The value for the key
+     * @param priority The {@link AutoCompletionPriority}, but it should usually be at least {@link AutoCompletionPriority#IS_IN_STANDARD}
+     */
+    public AutoCompletionItemRunnable(Runnable runnable, String value, AutoCompletionPriority priority) {
+        super(value, priority);
+        Objects.requireNonNull(runnable, "runnable");
+        this.runnable = runnable;
+    }
+
+    @Override
+    public void run() {
+        GuiHelper.runInEDT(this.runnable);
+    }
+
+    @Override
+    public int hashCode() {
+        return super.hashCode() * 31 + this.runnable.hashCode();
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        return super.equals(obj)
+            && this.getClass().equals(obj.getClass())
+            && this.runnable.equals(((AutoCompletionItemRunnable) obj).runnable);
+    }
+}
Index: src/org/openstreetmap/josm/gui/tagging/ac/AutoCompletingTextField.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/org/openstreetmap/josm/gui/tagging/ac/AutoCompletingTextField.java b/src/org/openstreetmap/josm/gui/tagging/ac/AutoCompletingTextField.java
--- a/src/org/openstreetmap/josm/gui/tagging/ac/AutoCompletingTextField.java	(revision 18531)
+++ b/src/org/openstreetmap/josm/gui/tagging/ac/AutoCompletingTextField.java	(date 1659974066047)
@@ -298,4 +298,20 @@
         rememberOriginalValue(getText());
         return this;
     }
+
+    @Override
+    public void focusLost(FocusEvent e) {
+        super.focusLost(e);
+        if (this.getHighlighter().getHighlights().length != 0) {
+            final String currentFilter = this.autoCompletionList.getFilter();
+            try {
+                this.autoCompletionList.applyFilter(this.getText());
+                if (this.autoCompletionList.getFilteredSize() == 1 && this.autoCompletionList.getFilteredItemAt(0) instanceof Runnable) {
+                    ((Runnable) this.autoCompletionList.getFilteredItemAt(0)).run();
+                }
+            } finally {
+                this.autoCompletionList.applyFilter(currentFilter);
+            }
+        }
+    }
 }
Index: src/org/openstreetmap/josm/gui/tagging/ac/AutoCompletionList.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/org/openstreetmap/josm/gui/tagging/ac/AutoCompletionList.java b/src/org/openstreetmap/josm/gui/tagging/ac/AutoCompletionList.java
--- a/src/org/openstreetmap/josm/gui/tagging/ac/AutoCompletionList.java	(revision 18531)
+++ b/src/org/openstreetmap/josm/gui/tagging/ac/AutoCompletionList.java	(date 1660062983714)
@@ -187,7 +187,12 @@
         // apply the pattern to list of possible values. If it matches, add the
         // value to the list of filtered values
         //
-        list.stream().filter(e -> e.getValue().startsWith(filter)).forEach(filtered::add);
+        // Prefer items that are autofill capable
+        list.stream().filter(e -> e instanceof Runnable && e.getValue().startsWith(filter)).forEach(filtered::add);
+        // But fall back to non-autofill items
+        if (list.isEmpty()) {
+            list.stream().filter(e -> e.getValue().startsWith(filter)).forEach(filtered::add);
+        }
         fireTableDataChanged();
     }
 
Index: src/org/openstreetmap/josm/gui/tagging/ac/AutoCompletionManager.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/org/openstreetmap/josm/gui/tagging/ac/AutoCompletionManager.java b/src/org/openstreetmap/josm/gui/tagging/ac/AutoCompletionManager.java
--- a/src/org/openstreetmap/josm/gui/tagging/ac/AutoCompletionManager.java	(revision 18531)
+++ b/src/org/openstreetmap/josm/gui/tagging/ac/AutoCompletionManager.java	(date 1660071174752)
@@ -2,7 +2,6 @@
 package org.openstreetmap.josm.gui.tagging.ac;
 
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
@@ -13,8 +12,9 @@
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Objects;
+import java.util.Optional;
 import java.util.Set;
-import java.util.function.Function;
+import java.util.function.Consumer;
 import java.util.stream.Collectors;
 
 import org.openstreetmap.josm.data.osm.DataSet;
@@ -31,6 +31,7 @@
 import org.openstreetmap.josm.data.osm.event.TagsChangedEvent;
 import org.openstreetmap.josm.data.osm.event.WayNodesChangedEvent;
 import org.openstreetmap.josm.data.tagging.ac.AutoCompletionItem;
+import org.openstreetmap.josm.data.tagging.ac.AutoCompletionItemRunnable;
 import org.openstreetmap.josm.data.tagging.ac.AutoCompletionPriority;
 import org.openstreetmap.josm.data.tagging.ac.AutoCompletionSet;
 import org.openstreetmap.josm.gui.MainApplication;
@@ -41,7 +42,7 @@
 import org.openstreetmap.josm.gui.layer.OsmDataLayer;
 import org.openstreetmap.josm.gui.tagging.presets.TaggingPreset;
 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresets;
-import org.openstreetmap.josm.gui.tagging.presets.items.Roles.Role;
+import org.openstreetmap.josm.gui.tagging.presets.items.Key;
 import org.openstreetmap.josm.tools.CheckParameterUtil;
 import org.openstreetmap.josm.tools.MultiMap;
 import org.openstreetmap.josm.tools.Utils;
@@ -49,7 +50,7 @@
 /**
  * AutoCompletionManager holds a cache of keys with a list of
  * possible auto completion values for each key.
- *
+ * <p>
  * Each DataSet can be assigned one AutoCompletionManager instance such that
  * <ol>
  *   <li>any key used in a tag in the data set is part of the key list in the cache</li>
@@ -60,11 +61,10 @@
  * slow down tabbing from input field to input field. Looping through the complete
  * data set in order to build up the auto completion list for a specific input
  * field is not efficient enough, hence this cache.
- *
+ * <p>
  * TODO: respect the relation type for member role autocompletion
  */
 public class AutoCompletionManager implements DataSetListener {
-
     /**
      * Data class to remember tags that the user has entered.
      */
@@ -187,7 +187,7 @@
 
     /**
      * make sure, the keys and values of all tags held by primitive are
-     * in the auto completion cache
+     * in the autocompletion cache
      *
      * @param primitive an OSM primitive
      */
@@ -239,11 +239,11 @@
     }
 
     /**
-     * replies the auto completion values allowed for a specific key. Replies
+     * replies the autocompletion values allowed for a specific key. Replies
      * an empty list if key is null or if key is not in {@link #getTagKeys()}.
      *
      * @param key OSM key
-     * @return the list of auto completion values
+     * @return the list of autocompletion values
      */
     protected List<String> getDataValues(String key) {
         return new ArrayList<>(getTagCache().getValues(key));
@@ -293,7 +293,7 @@
         if (r != null && !Utils.isEmpty(presets)) {
             for (TaggingPreset tp : presets) {
                 if (tp.roles != null) {
-                    list.add(Utils.transform(tp.roles.roles, (Function<Role, String>) x -> x.key), AutoCompletionPriority.IS_IN_STANDARD);
+                    list.add(Utils.transform(tp.roles.roles, x -> x.key), AutoCompletionPriority.IS_IN_STANDARD);
                 }
             }
             list.add(r.getMemberRoles(), AutoCompletionPriority.IS_IN_DATASET);
@@ -321,7 +321,7 @@
      * @param key the tag key
      */
     public void populateWithTagValues(AutoCompletionList list, String key) {
-        populateWithTagValues(list, Arrays.asList(key));
+        populateWithTagValues(list, Collections.singletonList(key));
     }
 
     /**
@@ -352,7 +352,20 @@
      * @since 18221
      */
     public List<AutoCompletionItem> getAllForKeys(List<String> keys) {
-        Map<String, AutoCompletionPriority> map = new HashMap<>();
+        return getAllForKeys(null, null, keys);
+    }
+
+    /**
+     * Returns all cached {@link AutoCompletionItem}s for given keys.
+     *
+     * @param runnable The runnable to call when the AutoCompletionItem is selected
+     * @param keys retrieve the items for these keys
+     * @param currentPreset the preset to use to find child presets
+     * @return the currently cached items, sorted by priority and alphabet
+     * @since xxx
+     */
+    public List<AutoCompletionItem> getAllForKeys(Consumer<TaggingPreset> runnable, TaggingPreset currentPreset, List<String> keys) {
+        Map<String, AutoCompletionPriority> map = new HashMap<>(keys.size());
 
         for (String key : keys) {
             for (String value : TaggingPresets.getPresetValues(key)) {
@@ -365,7 +378,55 @@
                 map.merge(value, AutoCompletionPriority.UNKNOWN, AutoCompletionPriority::mergeWith);
             }
         }
-        return map.entrySet().stream().map(e -> new AutoCompletionItem(e.getKey(), e.getValue())).sorted().collect(Collectors.toList());
+        final boolean containsPrimaryKey = keys.stream().anyMatch(TaggingPresets::isPrimaryKey);
+        return map.entrySet().stream().map(entry -> createAutoCompletionItem(containsPrimaryKey, entry, runnable, currentPreset)).sorted()
+                .collect(Collectors.toList());
+    }
+
+    /**
+     * Create an autocompletion item from an entry
+     *
+     * @param primaryKey    {@code true} if the key is something that can uniquely identify the object
+     * @param entry         The entry to create the item from
+     * @param runnable      The runnable to use if we end up creating a {@link AutoCompletionItemRunnable}
+     * @param currentPreset The current preset
+     * @return The created autocompletion item
+     */
+    private static AutoCompletionItem createAutoCompletionItem(boolean primaryKey, Entry<String, AutoCompletionPriority> entry,
+                                                               Consumer<TaggingPreset> runnable,
+                                                               TaggingPreset currentPreset) {
+        if (runnable == null || AutoCompletionPriority.UNKNOWN.equals(entry.getValue())
+                || AutoCompletionPriority.IS_IN_SELECTION.equals(entry.getValue())
+                || AutoCompletionPriority.IS_IN_DATASET.equals(entry.getValue())
+        || !primaryKey) {
+            return new AutoCompletionItem(entry.getKey(), entry.getValue());
+        } else if (AutoCompletionPriority.IS_IN_STANDARD.equals(entry.getValue())
+                || AutoCompletionPriority.IS_IN_STANDARD_AND_IN_DATASET.equals(entry.getValue())) {
+            final Map<String, String> tagMap = currentPreset.getMinimalTagMap();
+            Optional<TaggingPreset> best = currentPreset.getChildrenPresets().stream().filter(p ->
+                    // Allow any preset where the preset name matches the value
+                    (p.name != null && p.name.equals(entry.getKey())) ||
+                    // Filter out results that don't have any keys with the value
+                    p.data.stream().filter(Key.class::isInstance).map(Key.class::cast).anyMatch(key -> key.value.equals(entry.getKey())))
+                    .max(Comparator.comparingLong(p -> countCommonKeys(p, tagMap) + (p.name != null && p.name.equals(entry.getKey()) ? 1 : 0)));
+            if (best.isPresent()) {
+                return new AutoCompletionItemRunnable(() -> runnable.accept(best.get()), entry.getKey(), entry.getValue());
+            }
+            return new AutoCompletionItem(entry.getKey(), entry.getValue());
+        }
+        throw new IllegalStateException("Unexpected value: " + entry.getValue());
+    }
+
+    private static int countCommonKeys(TaggingPreset preset, Map<String, String> keys) {
+        return preset.getMinimalTagMap().entrySet().stream().mapToInt(presetEntry -> {
+            if (keys.containsKey(presetEntry.getKey())) {
+                if (Objects.equals(keys.get(presetEntry.getKey()), presetEntry.getValue())) {
+                    return 2;
+                }
+                return 1;
+            }
+            return 0;
+        }).sum();
     }
 
     /**
@@ -396,7 +457,7 @@
      * @since 12859
      */
     public AutoCompletionSet getTagValues(String key) {
-        return getTagValues(Arrays.asList(key));
+        return getTagValues(Collections.singletonList(key));
     }
 
     /**
Index: src/org/openstreetmap/josm/gui/tagging/ac/AutoCompTextField.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/org/openstreetmap/josm/gui/tagging/ac/AutoCompTextField.java b/src/org/openstreetmap/josm/gui/tagging/ac/AutoCompTextField.java
--- a/src/org/openstreetmap/josm/gui/tagging/ac/AutoCompTextField.java	(revision 18531)
+++ b/src/org/openstreetmap/josm/gui/tagging/ac/AutoCompTextField.java	(date 1659974733491)
@@ -5,6 +5,7 @@
 import java.awt.datatransfer.Clipboard;
 import java.awt.datatransfer.StringSelection;
 import java.awt.datatransfer.Transferable;
+import java.awt.event.FocusEvent;
 import java.awt.event.KeyEvent;
 import java.awt.event.KeyListener;
 import java.util.EventObject;
@@ -338,4 +339,15 @@
         rememberOriginalValue(getText());
         return this;
     }
+
+    @Override
+    public void focusLost(FocusEvent e) {
+        super.focusLost(e);
+        if (this.autocompleteEnabled) {
+            E item = getModel().findBestCandidate(getText());
+            if (item instanceof Runnable) {
+                ((Runnable) item).run();
+            }
+        }
+    }
 }
Index: src/org/openstreetmap/josm/gui/tagging/presets/items/Check.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/org/openstreetmap/josm/gui/tagging/presets/items/Check.java b/src/org/openstreetmap/josm/gui/tagging/presets/items/Check.java
--- a/src/org/openstreetmap/josm/gui/tagging/presets/items/Check.java	(revision 18531)
+++ b/src/org/openstreetmap/josm/gui/tagging/presets/items/Check.java	(date 1659973477694)
@@ -92,6 +92,7 @@
             p.add(check, GBC.eol()); // Do not fill, see #15104
         }
         check.addChangeListener(l -> support.fireItemValueModified(this, key, getValue()));
+        support.addField(this.key, this.check);
         return true;
     }
 
Index: src/org/openstreetmap/josm/gui/tagging/presets/items/Combo.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/org/openstreetmap/josm/gui/tagging/presets/items/Combo.java b/src/org/openstreetmap/josm/gui/tagging/presets/items/Combo.java
--- a/src/org/openstreetmap/josm/gui/tagging/presets/items/Combo.java	(revision 18531)
+++ b/src/org/openstreetmap/josm/gui/tagging/presets/items/Combo.java	(date 1660071042712)
@@ -10,8 +10,10 @@
 import java.awt.event.ActionListener;
 import java.awt.event.ComponentAdapter;
 import java.awt.event.ComponentEvent;
-import java.util.Arrays;
+import java.util.Collections;
 import java.util.Comparator;
+import java.util.List;
+import java.util.Objects;
 
 import javax.swing.AbstractAction;
 import javax.swing.JButton;
@@ -27,6 +29,7 @@
 import org.openstreetmap.josm.gui.tagging.ac.AutoCompComboBoxEditor;
 import org.openstreetmap.josm.gui.tagging.ac.AutoCompComboBoxModel;
 import org.openstreetmap.josm.gui.tagging.ac.AutoCompTextField;
+import org.openstreetmap.josm.gui.tagging.presets.TaggingPreset;
 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItem;
 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemGuiSupport;
 import org.openstreetmap.josm.gui.widgets.JosmComboBox;
@@ -85,6 +88,11 @@
 
     @Override
     protected boolean addToPanel(JPanel p, TaggingPresetItemGuiSupport support) {
+        return addToPanel(p, support, null);
+    }
+
+    @Override
+    protected boolean addToPanel(JPanel p, TaggingPresetItemGuiSupport support, TaggingPreset preset) {
         initializeLocaleText(null);
         usage = determineTextUsage(support.getSelected(), key);
         seenValues.clear();
@@ -124,8 +132,11 @@
         combobox.setEditable(editable);
 
         autoCompModel = new AutoCompComboBoxModel<>(Comparator.<AutoCompletionItem>naturalOrder());
-        getAllForKeys(Arrays.asList(key)).forEach(autoCompModel::addElement);
-        getDisplayValues().forEach(s -> autoCompModel.addElement(new AutoCompletionItem(s, AutoCompletionPriority.IS_IN_STANDARD)));
+        List<AutoCompletionItem> allForKeys = getAllForKeys(support::fillFromPreset, preset, Collections.singletonList(key));
+        autoCompModel.addAllElements(allForKeys);
+        getDisplayValues().stream().filter(value -> allForKeys.stream().noneMatch(item -> Objects.equals(item.getValue(), value)))
+                        .map(s -> new AutoCompletionItem(s, AutoCompletionPriority.IS_IN_STANDARD))
+                                .forEach(autoCompModel::addElement);
 
         AutoCompTextField<AutoCompletionItem> tf = editor.getEditorComponent();
         tf.setModel(autoCompModel);
@@ -168,6 +179,7 @@
         combobox.setToolTipText(getKeyTooltipText());
         combobox.applyComponentOrientation(OrientationAction.getValueOrientation(key));
 
+        support.addField(this.key, this.combobox);
         return true;
     }
 
Index: src/org/openstreetmap/josm/gui/tagging/presets/items/Key.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/org/openstreetmap/josm/gui/tagging/presets/items/Key.java b/src/org/openstreetmap/josm/gui/tagging/presets/items/Key.java
--- a/src/org/openstreetmap/josm/gui/tagging/presets/items/Key.java	(revision 18531)
+++ b/src/org/openstreetmap/josm/gui/tagging/presets/items/Key.java	(date 1660051167676)
@@ -4,6 +4,8 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
+import java.util.Objects;
 
 import javax.swing.JPanel;
 
@@ -46,10 +48,40 @@
         return Collections.singleton(value);
     }
 
+    @Override
+    public Boolean matches(Map<String, String> tags) {
+        switch (MatchType.ofString(match)) {
+            case KEY_VALUE:
+                return tags.containsKey(key) && value.equals(tags.get(key)) ? Boolean.TRUE : null;
+            case KEY_VALUE_REQUIRED:
+                return tags.containsKey(key) && value.equals(tags.get(key));
+            default:
+                return super.matches(tags);
+        }
+    }
+
     @Override
     public String toString() {
         return "Key [key=" + key + ", value=" + value + ", text=" + text
                 + ", text_context=" + text_context + ", match=" + match
                 + ']';
     }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(this.key, this.value, this.text, this.text_context, this.match);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj != null && obj.getClass() == this.getClass()) {
+            Key other = (Key) obj;
+            return Objects.equals(this.key, other.key)
+                    && Objects.equals(this.value, other.value)
+                    && Objects.equals(this.text, other.text)
+                    && Objects.equals(this.text_context, other.text_context)
+                    && Objects.equals(this.match, other.match);
+        }
+        return false;
+    }
 }
Index: src/org/openstreetmap/josm/gui/tagging/presets/items/KeyedItem.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/org/openstreetmap/josm/gui/tagging/presets/items/KeyedItem.java b/src/org/openstreetmap/josm/gui/tagging/presets/items/KeyedItem.java
--- a/src/org/openstreetmap/josm/gui/tagging/presets/items/KeyedItem.java	(revision 18531)
+++ b/src/org/openstreetmap/josm/gui/tagging/presets/items/KeyedItem.java	(date 1660071053383)
@@ -4,7 +4,6 @@
 import static org.openstreetmap.josm.tools.I18n.tr;
 
 import java.util.Collection;
-import java.util.EnumSet;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.SortedMap;
@@ -68,6 +67,9 @@
         /** Positive if key and value matches, negative otherwise. */
         KEY_VALUE_REQUIRED("keyvalue!");
 
+        // Avoid many array instantiations
+        private static MatchType[] allValues = values();
+
         private final String value;
 
         MatchType(String value) {
@@ -88,7 +90,7 @@
          * @return the {@code MatchType} for the given textual value
          */
         public static MatchType ofString(String type) {
-            for (MatchType i : EnumSet.allOf(MatchType.class)) {
+            for (MatchType i : allValues) {
                 if (i.getValue().equals(type))
                     return i;
             }
Index: src/org/openstreetmap/josm/gui/tagging/presets/items/MultiSelect.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/org/openstreetmap/josm/gui/tagging/presets/items/MultiSelect.java b/src/org/openstreetmap/josm/gui/tagging/presets/items/MultiSelect.java
--- a/src/org/openstreetmap/josm/gui/tagging/presets/items/MultiSelect.java	(revision 18531)
+++ b/src/org/openstreetmap/josm/gui/tagging/presets/items/MultiSelect.java	(date 1659973576289)
@@ -97,6 +97,7 @@
         list.addListSelectionListener(l -> support.fireItemValueModified(this, key, getSelectedItem().value));
         list.setToolTipText(getKeyTooltipText());
         list.applyComponentOrientation(OrientationAction.getValueOrientation(key));
+        support.addField(this.key, this.list);
 
         return true;
     }
Index: src/org/openstreetmap/josm/gui/tagging/presets/items/PresetLink.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/org/openstreetmap/josm/gui/tagging/presets/items/PresetLink.java b/src/org/openstreetmap/josm/gui/tagging/presets/items/PresetLink.java
--- a/src/org/openstreetmap/josm/gui/tagging/presets/items/PresetLink.java	(revision 18531)
+++ b/src/org/openstreetmap/josm/gui/tagging/presets/items/PresetLink.java	(date 1660071375684)
@@ -5,18 +5,26 @@
 
 import java.awt.event.MouseAdapter;
 import java.awt.event.MouseEvent;
+import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
+import java.util.Objects;
 import java.util.Optional;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Stream;
 
 import javax.swing.JLabel;
 import javax.swing.JPanel;
 
+
 import org.openstreetmap.josm.data.osm.OsmPrimitive;
 import org.openstreetmap.josm.data.osm.Tag;
 import org.openstreetmap.josm.gui.tagging.presets.TaggingPreset;
 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemGuiSupport;
 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetLabel;
+import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetMenu;
 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresets;
 import org.openstreetmap.josm.tools.GBC;
 
@@ -41,8 +49,100 @@
         }
     }
 
-    /** The exact name of the preset to link to. Required. */
+    /**
+     * The pattern for linking to specific presets. josm-preset:[//&lt;preset name&gt;][/]&lt;path&gt;, see #12716.
+     * If just josm-preset:&lt;path&gt; is specified, the preset path is relative (so the linked preset must
+     * exist in the file that created this preset).
+     */
+    private static final Pattern PRESET_LINK_PATTERN = Pattern.compile("^josm-preset:(//(.*?)/)?(.*)$");
+
+    /**
+     * The exact name of the preset to link to. Required.
+     * This may match {@link #PRESET_LINK_PATTERN}.
+     */
     public String preset_name = ""; // NOSONAR
+    /**
+     * If {@code true}, the {@link #preset_name} is a parent of this preset.
+     * Used for autocomplete, but {@link #preset_name} <i>must</i> match the {@link #PRESET_LINK_PATTERN}.
+     * @since xxx
+     */
+    public boolean parent; // NOSONAR
+
+    /**
+     * Get the preset for a given preset name
+     * @param taggingPreset The tagging preset this link is part of. May be {@code null}
+     * @param presetName The preset name to find. May either be a straight name or it may match {@link #PRESET_LINK_PATTERN}.
+     * @return The preset, if any
+     * @since xxx
+     */
+    private static Optional<TaggingPreset> getPreset(TaggingPreset taggingPreset, String presetName) {
+        final Matcher matcher = PRESET_LINK_PATTERN.matcher(presetName);
+        Stream<TaggingPreset> presetStream = TaggingPresets.getTaggingPresets().stream()
+                .filter(preset -> !(preset instanceof TaggingPresetMenu));
+        if (matcher.matches()) {
+            String path = matcher.group(3);
+            if (path != null) {
+                String presetList = matcher.group(2);
+                if (presetList != null) {
+                    presetStream = presetStream.filter(preset -> presetList.equals(preset.preset_list_name));
+                } else if (taggingPreset != null) {
+                    presetStream = presetStream.filter(preset -> Objects.equals(taggingPreset.preset_list_name, preset.preset_list_name));
+                }
+                final String[] expectedPath = path.split("/", -1);
+                final List<String> groupList = new ArrayList<>();
+                presetStream = presetStream.filter(preset -> checkPath(expectedPath, preset, groupList));
+            }
+        } else {
+            presetStream = presetStream.filter(preset -> presetName.equals(preset.name));
+        }
+        return presetStream.findFirst();
+    }
+
+    /**
+     * Ensure that a path matches a preset path
+     * @param expectedPath The expected path
+     * @param preset The preset to build a path from
+     * @return {@code true} if the paths are equal
+     */
+    private static synchronized boolean checkPath(String[] expectedPath, TaggingPreset preset, List<String> presetList) {
+        TaggingPreset current = preset;
+        presetList.clear();
+        while (current != null && current.name != null) {
+            presetList.add(current.name);
+            current = current.group;
+        }
+        Collections.reverse(presetList);
+        if (expectedPath.length == presetList.size()) {
+            for (int i = 0; i < expectedPath.length; i++) {
+                if (!presetList.get(i).equals(expectedPath[i])) {
+                    return false;
+                }
+            }
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * If this preset has a parent (see {@link #parent}), then this preset will be used
+     * for autocomplete values for {@link #preset_name}. Please note that we currently
+     * force {@link #preset_name} to match the {@link #PRESET_LINK_PATTERN}.
+     * @param taggingPreset The tagging preset this link is part of. May be {@code null}.
+     * @since xxx
+     */
+    public void reverseLinkPreset(TaggingPreset taggingPreset) {
+        if (this.parent) {
+            final String presetName = preset_name;
+            final Matcher matcher = PRESET_LINK_PATTERN.matcher(presetName);
+            if (!matcher.matches()) {
+                throw new IllegalArgumentException(
+                        "JOSM only supports matching presets to parents if the parent preset_name is declarative. "
+                                + PRESET_LINK_PATTERN);
+            }
+            Optional<TaggingPreset> preset = getPreset(taggingPreset, presetName);
+            preset.ifPresent(tPreset -> tPreset.addChildPreset(taggingPreset));
+        }
+    }
 
     /**
      * Creates a label to be inserted aboive this link
@@ -55,8 +155,7 @@
 
     @Override
     public boolean addToPanel(JPanel p, TaggingPresetItemGuiSupport support) {
-        final String presetName = preset_name;
-        Optional<TaggingPreset> found = TaggingPresets.getTaggingPresets().stream().filter(preset -> presetName.equals(preset.name)).findFirst();
+        final Optional<TaggingPreset> found = getPreset(null, preset_name);
         if (found.isPresent()) {
             TaggingPreset t = found.get();
             JLabel lbl = new TaggingPresetLabel(t);
Index: src/org/openstreetmap/josm/gui/tagging/presets/items/Text.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/org/openstreetmap/josm/gui/tagging/presets/items/Text.java b/src/org/openstreetmap/josm/gui/tagging/presets/items/Text.java
--- a/src/org/openstreetmap/josm/gui/tagging/presets/items/Text.java	(revision 18531)
+++ b/src/org/openstreetmap/josm/gui/tagging/presets/items/Text.java	(date 1660071117154)
@@ -10,6 +10,7 @@
 import java.text.NumberFormat;
 import java.text.ParseException;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
@@ -29,6 +30,7 @@
 import org.openstreetmap.josm.gui.tagging.ac.AutoCompComboBoxModel;
 import org.openstreetmap.josm.gui.tagging.ac.AutoCompTextField;
 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionManager;
+import org.openstreetmap.josm.gui.tagging.presets.TaggingPreset;
 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItem;
 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemGuiSupport;
 import org.openstreetmap.josm.gui.util.DocumentAdapter;
@@ -72,17 +74,20 @@
     private transient TemplateEntry valueTemplate;
 
     @Override
-    public boolean addToPanel(JPanel p, TaggingPresetItemGuiSupport support) {
+    protected boolean addToPanel(JPanel p, TaggingPresetItemGuiSupport support) {
+        return this.addToPanel(p, support, null);
+    }
+
+    @Override
+    public boolean addToPanel(JPanel p, TaggingPresetItemGuiSupport support, TaggingPreset preset) {
 
         AutoCompComboBoxModel<AutoCompletionItem> model = new AutoCompComboBoxModel<>();
         List<String> keys = new ArrayList<>();
         keys.add(key);
         if (alternative_autocomplete_keys != null) {
-            for (String k : alternative_autocomplete_keys.split(",", -1)) {
-                keys.add(k);
-            }
+            keys.addAll(Arrays.asList(alternative_autocomplete_keys.split(",", -1)));
         }
-        getAllForKeys(keys).forEach(model::addElement);
+        model.addAllElements(getAllForKeys(support::fillFromPreset, preset, keys));
 
         AutoCompTextField<AutoCompletionItem> textField;
         AutoCompComboBoxEditor<AutoCompletionItem> editor = null;
@@ -208,6 +213,7 @@
         label.applyComponentOrientation(support.getDefaultComponentOrientation());
         value.setToolTipText(getKeyTooltipText());
         value.applyComponentOrientation(OrientationAction.getNamelikeOrientation(key));
+        support.addField(this.key, this.value);
         return true;
     }
 
Index: src/org/openstreetmap/josm/gui/tagging/presets/TaggingPreset.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPreset.java b/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPreset.java
--- a/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPreset.java	(revision 18531)
+++ b/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPreset.java	(date 1660071031048)
@@ -14,7 +14,10 @@
 import java.io.File;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
@@ -62,6 +65,7 @@
 import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeListener;
 import org.openstreetmap.josm.gui.preferences.ToolbarPreferences;
 import org.openstreetmap.josm.gui.tagging.presets.items.Key;
+import org.openstreetmap.josm.gui.tagging.presets.items.KeyedItem;
 import org.openstreetmap.josm.gui.tagging.presets.items.Link;
 import org.openstreetmap.josm.gui.tagging.presets.items.Optional;
 import org.openstreetmap.josm.gui.tagging.presets.items.PresetLink;
@@ -136,6 +140,11 @@
      * Show the preset name if true
      */
     public boolean preset_name_label;
+    /**
+     * The name of the preset list (e.g. the name suggestion index)
+     * @since xxx
+     */
+    public String preset_list_name;
 
     /**
      * The types as preparsed collection.
@@ -170,6 +179,7 @@
 
     /** Support functions */
     protected TaggingPresetItemGuiSupport itemGuiSupport;
+    private final Collection<TaggingPreset> children = new HashSet<>();
 
     /**
      * Create an empty tagging preset. This will not have any items and
@@ -349,6 +359,23 @@
         }
     }
 
+    /**
+     * Add a child preset to be used for autocomplete suggestions
+     * @param child The child preset
+     * @since xxx
+     */
+    public void addChildPreset(TaggingPreset child) {
+        this.children.add(child);
+    }
+
+    /**
+     * Get the children presets to be used for autocompletion
+     * @return The child presets
+     */
+    public Collection<TaggingPreset> getChildrenPresets() {
+        return Collections.unmodifiableCollection(this.children);
+    }
+
     private static class PresetPanel extends JPanel {
         private boolean hasElements;
 
@@ -422,7 +449,7 @@
         TaggingPresetItem previous = null;
         for (TaggingPresetItem i : data) {
             if (i instanceof Link) {
-                i.addToPanel(linkPanel, itemGuiSupport);
+                i.addToPanel(linkPanel, itemGuiSupport, this);
                 p.hasElements = true;
             } else {
                 if (i instanceof PresetLink) {
@@ -431,7 +458,7 @@
                         itemPanel.add(link.createLabel(), GBC.eol().insets(0, 8, 0, 0));
                     }
                 }
-                if (i.addToPanel(itemPanel, itemGuiSupport)) {
+                if (i.addToPanel(itemPanel, itemGuiSupport, this)) {
                     p.hasElements = true;
                 }
             }
@@ -735,6 +762,25 @@
         }
     }
 
+    /**
+     * This builds a map of key->value objects, where the value may be {@code null}.
+     * If the value is {@code null}, it means that it is not pre-set.
+     * @return A map of tags
+     * @since xxx
+     */
+    public Map<String, String> getMinimalTagMap() {
+        Map<String, String> tagMap = new HashMap<>();
+        for (TaggingPresetItem item : this.data) {
+            if (item instanceof Key) {
+                Key key = (Key) item;
+                tagMap.put(key.key, key.value);
+            } else if (item instanceof KeyedItem) {
+                tagMap.put(((KeyedItem) item).key, null);
+            }
+        }
+        return tagMap;
+    }
+
     /**
      * Action that adds or removes the button on main toolbar
      */
Index: src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetItem.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetItem.java b/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetItem.java
--- a/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetItem.java	(revision 18531)
+++ b/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetItem.java	(date 1660071390846)
@@ -11,7 +11,9 @@
 import java.util.EnumSet;
 import java.util.List;
 import java.util.Map;
+import java.util.RandomAccess;
 import java.util.Set;
+import java.util.function.Consumer;
 
 import javax.swing.ImageIcon;
 import javax.swing.JPanel;
@@ -66,11 +68,24 @@
      * @since 18221
      */
     protected List<AutoCompletionItem> getAllForKeys(List<String> keys) {
+        return getAllForKeys(null, null, keys);
+    }
+
+    /**
+     * Returns all cached {@link AutoCompletionItem}s for given keys.
+     *
+     * @param consumer a consumer to be called when an AutoCompletionItem is selected. May be {@code null}.
+     * @param currentPreset The preset we are getting keys for
+     * @param keys retrieve the items for these keys
+     * @return the currently cached items, sorted by priority and alphabet
+     * @since xxx
+     */
+    protected List<AutoCompletionItem> getAllForKeys(Consumer<TaggingPreset> consumer, TaggingPreset currentPreset, List<String> keys) {
         DataSet data = OsmDataManager.getInstance().getEditDataSet();
         if (data == null) {
             return Collections.emptyList();
         }
-        return AutoCompletionManager.of(data).getAllForKeys(keys);
+        return AutoCompletionManager.of(data).getAllForKeys(consumer, currentPreset, keys);
     }
 
     /**
@@ -83,6 +98,20 @@
      */
     protected abstract boolean addToPanel(JPanel p, TaggingPresetItemGuiSupport support);
 
+    /**
+     * Called by {@link TaggingPreset#createPanel} during tagging preset panel creation.
+     * All components defining this tagging preset item must be added to given panel.
+     *
+     * @param p The panel where components must be added
+     * @param support supporting class for creating the GUI
+     * @param preset the preset that this data item is being added to the panel for
+     * @return {@code true} if this item adds semantic tagging elements, {@code false} otherwise.
+     * @since xxx
+     */
+    protected boolean addToPanel(JPanel p, TaggingPresetItemGuiSupport support, TaggingPreset preset) {
+        return addToPanel(p, support);
+    }
+
     /**
      * Adds the new tags to apply to selected OSM primitives when the preset holding this item is applied.
      * @param changedTags The list of changed tags to modify if needed
@@ -170,12 +199,24 @@
      */
     public static boolean matches(Iterable<? extends TaggingPresetItem> data, Map<String, String> tags) {
         boolean atLeastOnePositiveMatch = false;
-        for (TaggingPresetItem item : data) {
-            Boolean m = item.matches(tags);
-            if (m != null && !m)
-                return false;
-            else if (m != null) {
-                atLeastOnePositiveMatch = true;
+        if (data instanceof List && data instanceof RandomAccess) {
+            List<? extends TaggingPresetItem> dataList = (List<? extends TaggingPresetItem>) data;
+            for (int i = 0; i < dataList.size(); i++) {
+                Boolean m = dataList.get(i).matches(tags);
+                if (m != null && !m)
+                    return false;
+                else if (m != null) {
+                    atLeastOnePositiveMatch = true;
+                }
+            }
+        } else {
+            for (TaggingPresetItem item : data) {
+                Boolean m = item.matches(tags);
+                if (m != null && !m)
+                    return false;
+                else if (m != null) {
+                    atLeastOnePositiveMatch = true;
+                }
             }
         }
         return atLeastOnePositiveMatch;
Index: src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetItemGuiSupport.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetItemGuiSupport.java b/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetItemGuiSupport.java
--- a/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetItemGuiSupport.java	(revision 18531)
+++ b/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetItemGuiSupport.java	(date 1659983159550)
@@ -5,13 +5,30 @@
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
 import java.util.function.Supplier;
 
+import javax.swing.ComboBoxModel;
+import javax.swing.JComboBox;
+import javax.swing.JComponent;
+import javax.swing.JList;
+import javax.swing.JTextField;
+import javax.swing.ListModel;
+import javax.swing.ListSelectionModel;
+
 import org.openstreetmap.josm.data.osm.OsmPrimitive;
 import org.openstreetmap.josm.data.osm.Tag;
 import org.openstreetmap.josm.data.osm.Tagged;
 import org.openstreetmap.josm.data.osm.search.SearchCompiler;
+import org.openstreetmap.josm.data.tagging.ac.AutoCompletionItem;
+import org.openstreetmap.josm.gui.tagging.presets.items.Key;
+import org.openstreetmap.josm.gui.tagging.presets.items.PresetListEntry;
 import org.openstreetmap.josm.gui.widgets.OrientationAction;
+import org.openstreetmap.josm.gui.widgets.QuadStateCheckBox;
+import org.openstreetmap.josm.tools.JosmRuntimeException;
 import org.openstreetmap.josm.tools.ListenerList;
 import org.openstreetmap.josm.tools.Utils;
 import org.openstreetmap.josm.tools.template_engine.TemplateEngineDataProvider;
@@ -22,6 +39,8 @@
  * @since 17609
  */
 public final class TaggingPresetItemGuiSupport implements TemplateEngineDataProvider {
+    private final HashMap<String, JComponent> keyComponentPrefillMap = new HashMap<>();
+    private final HashMap<JComponent, Object> defaultMap = new HashMap<>();
 
     private final Collection<OsmPrimitive> selected;
     /** True if all selected primitives matched this preset at the moment it was openend. */
@@ -181,4 +200,112 @@
         if (enabled)
             listeners.fireEvent(e -> e.itemValueModified(source, key, newValue));
     }
+
+    /**
+     * Add a field to be used when updating/filling from a preset.
+     * This should be called after the field has been set up with its default value (if any).
+     * @param key The key that will be used to update the component
+     * @param component The component to update
+     */
+    public void addField(String key, JComponent component) {
+        this.keyComponentPrefillMap.put(key, component);
+        if (component instanceof JTextField) {
+            this.defaultMap.put(component, ((JTextField) component).getText());
+        } else if (component instanceof QuadStateCheckBox) {
+            this.defaultMap.put(component, ((QuadStateCheckBox) component).getState());
+        } else if (component instanceof JList) {
+            this.defaultMap.put(component, ((JList<?>) component).getSelectedValuesList());
+        } else if (component instanceof JComboBox) {
+            this.defaultMap.put(component, ((JComboBox<?>) component).getSelectedObjects());
+        } else if (component != null) {
+            throw new JosmRuntimeException(component.getClass() + " is not supported");
+        }
+    }
+
+    /**
+     * Update the UI elements with those from another preset
+     * @param presetItem The item to fill data in from
+     * @since xxx
+     */
+    public void fillFromPreset(TaggingPreset presetItem) {
+        // Clear current fields
+        resetComponents();
+        for (TaggingPresetItem item : presetItem.data) {
+            if (item instanceof Key) {
+                fillComponent((Key) item);
+            }
+        }
+    }
+
+    private void resetComponents() {
+        for (JComponent component : this.keyComponentPrefillMap.values()) {
+            if (component instanceof JTextField) {
+                ((JTextField) component).setText((String) this.defaultMap.get(component));
+            } else if (component instanceof QuadStateCheckBox) {
+                QuadStateCheckBox.State state = (QuadStateCheckBox.State) this.defaultMap.get(component);
+                ((QuadStateCheckBox) component).setState(state != null ? state : QuadStateCheckBox.State.UNSET);
+            } else if (component instanceof JComboBox) {
+                ((JComboBox<?>) component).setSelectedItem(null); // TODO avoid losing original state
+            } else if (component instanceof JList) {
+                ((JList<?>) component).clearSelection(); // TODO avoid losing original state
+            } else if (component != null) {
+                throw new JosmRuntimeException(component.getClass() + " is not supported");
+            }
+        }
+    }
+
+    /**
+     * Update the data in a component
+     * @param item The data to fill in
+     */
+    private void fillComponent(Key item) {
+        JComponent component = this.keyComponentPrefillMap.get(item.key);
+        if (component instanceof JTextField) {
+            ((JTextField) component).setText(item.value);
+        } else if (component instanceof JComboBox) {
+            ComboBoxModel<?> comboBoxModel = ((JComboBox<?>) component).getModel();
+            Object firstElement = comboBoxModel.getElementAt(0);
+            if (firstElement instanceof String) {
+                throw new JosmRuntimeException(comboBoxModel.getElementAt(0).getClass() + " is not supported");
+            } else if (firstElement instanceof AutoCompletionItem) {
+                throw new JosmRuntimeException(comboBoxModel.getElementAt(0).getClass() + " is not supported");
+            } else if (firstElement instanceof PresetListEntry) {
+                Set<String> values = new HashSet<>(splitOsmValue(item.value));
+                for (int i = 0; i < comboBoxModel.getSize(); i++) {
+                    PresetListEntry entry = (PresetListEntry) comboBoxModel.getElementAt(i);
+                    if (values.contains(entry.value)) {
+                        comboBoxModel.setSelectedItem(entry);
+                        break;
+                    }
+                }
+            } else {
+                throw new JosmRuntimeException(comboBoxModel.getElementAt(0).getClass() + " is not supported");
+            }
+        } else if (component instanceof QuadStateCheckBox) {
+            QuadStateCheckBox box = (QuadStateCheckBox) component;
+            final boolean selected = Boolean.parseBoolean(item.value) || "yes".equals(item.value);
+            box.setState(selected ? QuadStateCheckBox.State.SELECTED : QuadStateCheckBox.State.NOT_SELECTED);
+        } else if (component instanceof JList) {
+            ListModel<?> model = ((JList<?>) component).getModel();
+            ListSelectionModel selectionModel = ((JList<?>) component).getSelectionModel();
+            Set<String> values = new HashSet<>(splitOsmValue(item.value));
+            for (int i = 0; i < model.getSize(); i++) {
+                PresetListEntry entry = (PresetListEntry) model.getElementAt(i);
+                if (values.contains(entry.value)) {
+                    selectionModel.addSelectionInterval(i, i);
+                }
+            }
+        } else if (component != null) {
+            throw new JosmRuntimeException(component.getClass() + " is not supported");
+        }
+    }
+
+    /**
+     * Split osm values using `;`
+     * @param value the value to split
+     * @return The list of values
+     */
+    private static List<String> splitOsmValue(String value) {
+        return Arrays.asList(value.split(";", -1));
+    }
 }
Index: src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetReader.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetReader.java b/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetReader.java
--- a/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetReader.java	(revision 18531)
+++ b/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetReader.java	(date 1660071341115)
@@ -23,6 +23,7 @@
 
 import javax.swing.JOptionPane;
 
+import org.openstreetmap.josm.data.preferences.sources.ExtendedSourceEntry;
 import org.openstreetmap.josm.data.preferences.sources.PresetPrefHelper;
 import org.openstreetmap.josm.gui.MainApplication;
 import org.openstreetmap.josm.gui.tagging.presets.items.Check;
@@ -135,6 +136,7 @@
 
     private static XmlObjectParser buildParser() {
         XmlObjectParser parser = new XmlObjectParser();
+        parser.mapOnStart("presets", ExtendedSourceEntry.class);
         parser.mapOnStart("item", TaggingPreset.class);
         parser.mapOnStart("separator", TaggingPresetSeparator.class);
         parser.mapBoth("group", TaggingPresetMenu.class);
@@ -180,6 +182,8 @@
     static Collection<TaggingPreset> readAll(Reader in, boolean validate, HashSetWithLast<TaggingPreset> all) throws SAXException {
         XmlObjectParser parser = buildParser();
 
+        /** to be used to help link presets */
+        ExtendedSourceEntry entry = null;
         /** to detect end of {@code <checkgroup>} */
         CheckGroup lastcheckgroup = null;
         /** to detect end of {@code <group>} */
@@ -247,6 +251,13 @@
                 }
                 continue;
             }
+            if (o instanceof ExtendedSourceEntry) {
+                if (entry != null) {
+                    throw new SAXException(tr("Preset has multiple <preset> elements"));
+                }
+                entry = (ExtendedSourceEntry) o;
+                continue;
+            }
             if (!(o instanceof TaggingPresetItem) && !checks.isEmpty()) {
                 all.getLast().data.addAll(checks);
                 checks.clear();
@@ -338,6 +349,11 @@
             all.getLast().data.addAll(checks);
             checks.clear();
         }
+        if (entry != null) {
+            for (TaggingPreset preset : all) {
+                preset.preset_list_name = entry.name;
+            }
+        }
         return all;
     }
 
@@ -364,7 +380,7 @@
      */
     static Collection<TaggingPreset> readAll(String source, boolean validate, HashSetWithLast<TaggingPreset> all)
             throws SAXException, IOException {
-        Collection<TaggingPreset> tp;
+        Collection<TaggingPreset> tp = all;
         Logging.debug("Reading presets from {0}", source);
         Stopwatch stopwatch = Stopwatch.createStarted();
         try (
@@ -377,7 +393,7 @@
                 I18n.addTexts(zipIcons);
             }
             try (InputStreamReader r = UTFInputStreamReader.create(zip == null ? cf.getInputStream() : zip)) {
-                tp = readAll(new BufferedReader(r), validate, all);
+                tp.addAll(readAll(new BufferedReader(r), validate, new HashSetWithLast<>()));
             }
         }
         Logging.debug(stopwatch.toString("Reading presets"));
Index: src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresets.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresets.java b/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresets.java
--- a/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresets.java	(revision 18531)
+++ b/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresets.java	(date 1660071358005)
@@ -4,16 +4,24 @@
 import static org.openstreetmap.josm.tools.I18n.tr;
 
 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.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
 
 import javax.swing.JMenu;
 import javax.swing.JMenuItem;
+import javax.swing.JOptionPane;
 import javax.swing.JSeparator;
+import javax.swing.SwingWorker;
 
 import org.openstreetmap.josm.actions.PreferencesAction;
 import org.openstreetmap.josm.data.osm.IPrimitive;
@@ -23,12 +31,17 @@
 import org.openstreetmap.josm.gui.MainApplication;
 import org.openstreetmap.josm.gui.MainMenu;
 import org.openstreetmap.josm.gui.MenuScroller;
+import org.openstreetmap.josm.gui.Notification;
 import org.openstreetmap.josm.gui.preferences.ToolbarPreferences;
 import org.openstreetmap.josm.gui.preferences.map.TaggingPresetPreference;
 import org.openstreetmap.josm.gui.tagging.presets.items.CheckGroup;
+import org.openstreetmap.josm.gui.tagging.presets.items.Key;
 import org.openstreetmap.josm.gui.tagging.presets.items.KeyedItem;
+import org.openstreetmap.josm.gui.tagging.presets.items.PresetLink;
 import org.openstreetmap.josm.gui.tagging.presets.items.Roles;
 import org.openstreetmap.josm.gui.tagging.presets.items.Roles.Role;
+import org.openstreetmap.josm.spi.preferences.Config;
+import org.openstreetmap.josm.tools.JosmRuntimeException;
 import org.openstreetmap.josm.tools.Logging;
 import org.openstreetmap.josm.tools.MultiMap;
 import org.openstreetmap.josm.tools.SubclassFilteredCollection;
@@ -39,6 +52,10 @@
  */
 public final class TaggingPresets {
 
+    /** Patterns for primary keys */
+    private static final List<Pattern> PRIMARY_KEY_PATTERNS = Config.getPref().getList("tagging.presets.primary.key.patterns",
+            Arrays.asList("^name(:.*)?$", "^brand$")).stream().map(Pattern::compile).collect(Collectors.toList());
+
     /** The collection of tagging presets */
     private static final Collection<TaggingPreset> taggingPresets = new ArrayList<>();
 
@@ -58,6 +75,51 @@
      */
     public static final ListProperty ICON_SOURCES = new ListProperty("taggingpreset.icon.sources", null);
     private static final IntegerProperty MIN_ELEMENTS_FOR_SCROLLER = new IntegerProperty("taggingpreset.min-elements-for-scroller", 15);
+    /**
+     * A runnable that builds links for autocomplete usage. This can take a few minutes, so run in a background thread.
+     * It doesn't matter if it is done sequentially, so we can use the {@link SwingWorker} executor.
+     */
+    private static PresetLinkBuilder presetLinkBuilder;
+
+    private static class PresetLinkBuilder extends SwingWorker<Void, Void> {
+
+        @Override
+        protected Void doInBackground() {
+            // This defaults to false since it significantly slows down startup.
+            if (Config.getPref().getBoolean("expert.tagging.preset.children.from.heuristics", true)) {
+                // If we want to try and guess what preset something goes to. Only accounts for presets that ask no questions.
+                getPresetChildrenFromTags();
+            }
+            if (isCancelled()) {
+                return null;
+            }
+            getPresetChildrenFromLinks();
+            return null;
+        }
+
+        @Override
+        protected void done() {
+            super.done();
+            resetPresetLinkBuilder(this);
+            if (MainApplication.isDisplayingMapView() && !isCancelled()) {
+                new Notification(tr("Preset linking done. Autofill is now available")).setIcon(JOptionPane.INFORMATION_MESSAGE).show();
+            }
+            try {
+                get();
+            } catch (ExecutionException e) {
+                throw new JosmRuntimeException(e);
+            } catch (InterruptedException e) {
+                Logging.trace(e);
+                Thread.currentThread().interrupt();
+            }
+        }
+
+        private static void resetPresetLinkBuilder(PresetLinkBuilder current) {
+            if (presetLinkBuilder == current) {
+                presetLinkBuilder = null;
+            }
+        }
+    }
 
     private TaggingPresets() {
         // Hide constructor for utility classes
@@ -67,9 +129,64 @@
      * Initializes tagging presets from preferences.
      */
     public static void readFromPreferences() {
+        if (presetLinkBuilder != null) {
+            presetLinkBuilder.cancel(true);
+        }
         taggingPresets.clear();
         taggingPresets.addAll(TaggingPresetReader.readFromPreferences(false, false));
         cachePresets(taggingPresets);
+        presetLinkBuilder = new PresetLinkBuilder();
+        presetLinkBuilder.execute();
+    }
+
+    /**
+     * Generate preset children from the tags. Example: `amenity=fast_food` + `name=McDonald's` will have a parent with `amenity=fast_food`.
+     * This adds a {@link PresetLink} that has {@link PresetLink#parent} set to {@code true}.
+     * This only generates where all of the {@link TaggingPreset#data} objects are {@link Key} objects.
+     */
+    private static void getPresetChildrenFromTags() {
+        // Used to avoid multiple instantiations of an array list
+        List<String> parentList = new ArrayList<>();
+        for (TaggingPreset preset : taggingPresets) {
+            if (preset.data.stream().allMatch(Key.class::isInstance)) {
+                // OK. Let us find the parent.
+                Collection<TaggingPreset> matching = TaggingPresets.getMatchingPresets(null, preset.getMinimalTagMap(), false);
+                List<TaggingPreset> other = matching.stream().filter(p -> !p.equals(preset) && !p.data.equals(preset.data))
+                        .collect(Collectors.toList());
+                if (other.size() == 1) {
+                    TaggingPreset parent = other.iterator().next();
+                    parentList.clear();
+                    String presetListName = parent.preset_list_name;
+                    while (parent != null && parent.name != null) {
+                        parentList.add(parent.name);
+                        parent = parent.group;
+                    }
+                    // Add this last in order to have it in the "right" position when the list is reversed
+                    parentList.add(presetListName);
+                    Collections.reverse(parentList);
+                    PresetLink presetLink = new PresetLink();
+                    presetLink.preset_name = parentList.stream()
+                            .filter(Objects::nonNull).collect(Collectors.joining("/", "josm-preset://", ""));
+                    presetLink.parent = presetLink.preset_name.length() > "josm-preset://".length();
+                    if (presetLink.parent) {
+                        preset.data.add(presetLink);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Tell the parent preset about their children for autocomplete
+     */
+    private static void getPresetChildrenFromLinks() {
+        for (TaggingPreset preset : taggingPresets) {
+            for (TaggingPresetItem item : preset.data) {
+                if (item instanceof PresetLink) {
+                    ((PresetLink) item).reverseLinkPreset(preset);
+                }
+            }
+        }
     }
 
     /**
@@ -228,10 +345,25 @@
         return PRESET_TAG_CACHE.get(key) != null;
     }
 
+    /**
+     * Check if a key is a primary (important) key
+     * @param key The key to check
+     * @return {@code true} if it can be used to prefill other data
+     * @since xxx
+     */
+    public static boolean isPrimaryKey(String key) {
+        for (Pattern pattern : PRIMARY_KEY_PATTERNS) {
+            if (pattern.matcher(key).matches()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     /**
      * Replies a new collection of all presets matching the parameters.
      *
-     * @param t the preset types to include
+     * @param t the preset types to include. {@code null} means any preset type is ok.
      * @param tags the tags to perform matching on, see {@link TaggingPresetItem#matches(Map)}
      * @param onlyShowable whether only {@link TaggingPreset#isShowable() showable} presets should be returned
      * @return a new collection of all presets matching the parameters.
