001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.tagging.presets.items;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005
006import java.util.Collection;
007import java.util.EnumSet;
008import java.util.HashMap;
009import java.util.Map;
010import java.util.SortedSet;
011import java.util.TreeSet;
012
013import org.openstreetmap.josm.data.osm.OsmPrimitive;
014import org.openstreetmap.josm.data.osm.OsmUtils;
015import org.openstreetmap.josm.data.preferences.BooleanProperty;
016import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItem;
017
018/**
019 * Preset item associated to an OSM key.
020 */
021public abstract class KeyedItem extends TaggingPresetItem {
022
023    /** Translatation of "<different>". Use in combo boxes to display an entry matching several different values. */
024    protected static final String DIFFERENT = tr("<different>");
025
026    protected static final BooleanProperty PROP_FILL_DEFAULT = new BooleanProperty("taggingpreset.fill-default-for-tagged-primitives", false);
027
028    /** Last value of each key used in presets, used for prefilling corresponding fields */
029    protected static final Map<String, String> LAST_VALUES = new HashMap<>();
030
031    public String key;
032    /** The text to display */
033    public String text;
034    /** The context used for translating {@link #text} */
035    public String text_context;
036    public String match = getDefaultMatch().getValue();
037
038    /**
039     * Enum denoting how a match (see {@link TaggingPresetItem#matches}) is performed.
040     */
041    protected enum MatchType {
042
043        /** Neutral, i.e., do not consider this item for matching. */
044        NONE("none"),
045        /** Positive if key matches, neutral otherwise. */
046        KEY("key"),
047        /** Positive if key matches, negative otherwise. */
048        KEY_REQUIRED("key!"),
049        /** Positive if key and value matches, neutral otherwise. */
050        KEY_VALUE("keyvalue"),
051        /** Positive if key and value matches, negative otherwise. */
052        KEY_VALUE_REQUIRED("keyvalue!");
053
054        private final String value;
055
056        MatchType(String value) {
057            this.value = value;
058        }
059
060        /**
061         * Replies the associated textual value.
062         * @return the associated textual value
063         */
064        public String getValue() {
065            return value;
066        }
067
068        /**
069         * Determines the {@code MatchType} for the given textual value.
070         * @param type the textual value
071         * @return the {@code MatchType} for the given textual value
072         */
073        public static MatchType ofString(String type) {
074            for (MatchType i : EnumSet.allOf(MatchType.class)) {
075                if (i.getValue().equals(type))
076                    return i;
077            }
078            throw new IllegalArgumentException(type + " is not allowed");
079        }
080    }
081
082    protected static class Usage {
083        public SortedSet<String> values;
084        private boolean hadKeys;
085        private boolean hadEmpty;
086
087        public boolean hasUniqueValue() {
088            return values.size() == 1 && !hadEmpty;
089        }
090
091        public boolean unused() {
092            return values.isEmpty();
093        }
094
095        public String getFirst() {
096            return values.first();
097        }
098
099        public boolean hadKeys() {
100            return hadKeys;
101        }
102    }
103
104    protected static Usage determineTextUsage(Collection<OsmPrimitive> sel, String key) {
105        Usage returnValue = new Usage();
106        returnValue.values = new TreeSet<>();
107        for (OsmPrimitive s : sel) {
108            String v = s.get(key);
109            if (v != null) {
110                returnValue.values.add(v);
111            } else {
112                returnValue.hadEmpty = true;
113            }
114            if (s.hasKeys()) {
115                returnValue.hadKeys = true;
116            }
117        }
118        return returnValue;
119    }
120
121    protected static Usage determineBooleanUsage(Collection<OsmPrimitive> sel, String key) {
122
123        Usage returnValue = new Usage();
124        returnValue.values = new TreeSet<>();
125        for (OsmPrimitive s : sel) {
126            String booleanValue = OsmUtils.getNamedOsmBoolean(s.get(key));
127            if (booleanValue != null) {
128                returnValue.values.add(booleanValue);
129            }
130        }
131        return returnValue;
132    }
133
134    public abstract MatchType getDefaultMatch();
135
136    public abstract Collection<String> getValues();
137
138    @Override
139    protected Boolean matches(Map<String, String> tags) {
140        switch (MatchType.ofString(match)) {
141        case NONE:
142            return null;
143        case KEY:
144            return tags.containsKey(key) ? Boolean.TRUE : null;
145        case KEY_REQUIRED:
146            return tags.containsKey(key);
147        case KEY_VALUE:
148            return tags.containsKey(key) && getValues().contains(tags.get(key)) ? Boolean.TRUE : null;
149        case KEY_VALUE_REQUIRED:
150            return tags.containsKey(key) && getValues().contains(tags.get(key));
151        default:
152            throw new IllegalStateException();
153        }
154    }
155
156    @Override
157    public String toString() {
158        return "KeyedItem [key=" + key + ", text=" + text
159                + ", text_context=" + text_context + ", match=" + match
160                + ']';
161    }
162}