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 /** This specifies the property key that will be modified by the item. */ 032 public String key; // NOSONAR 033 /** The text to display */ 034 public String text; // NOSONAR 035 /** The context used for translating {@link #text} */ 036 public String text_context; // NOSONAR 037 /** 038 * Allows to change the matching process, i.e., determining whether the tags of an OSM object fit into this preset. 039 * If a preset fits then it is linked in the Tags/Membership dialog.<ul> 040 * <li>none: neutral, i.e., do not consider this item for matching</li> 041 * <li>key: positive if key matches, neutral otherwise</li> 042 * <li>key!: positive if key matches, negative otherwise</li> 043 * <li>keyvalue: positive if key and value matches, neutral otherwise</li> 044 * <li>keyvalue!: positive if key and value matches, negative otherwise</li></ul> 045 * Note that for a match, at least one positive and no negative is required. 046 * Default is "keyvalue!" for {@link Key} and "none" for {@link Text}, {@link Combo}, {@link MultiSelect} and {@link Check}. 047 */ 048 public String match = getDefaultMatch().getValue(); // NOSONAR 049 050 /** 051 * Enum denoting how a match (see {@link TaggingPresetItem#matches}) is performed. 052 */ 053 protected enum MatchType { 054 055 /** Neutral, i.e., do not consider this item for matching. */ 056 NONE("none"), 057 /** Positive if key matches, neutral otherwise. */ 058 KEY("key"), 059 /** Positive if key matches, negative otherwise. */ 060 KEY_REQUIRED("key!"), 061 /** Positive if key and value matches, neutral otherwise. */ 062 KEY_VALUE("keyvalue"), 063 /** Positive if key and value matches, negative otherwise. */ 064 KEY_VALUE_REQUIRED("keyvalue!"); 065 066 private final String value; 067 068 MatchType(String value) { 069 this.value = value; 070 } 071 072 /** 073 * Replies the associated textual value. 074 * @return the associated textual value 075 */ 076 public String getValue() { 077 return value; 078 } 079 080 /** 081 * Determines the {@code MatchType} for the given textual value. 082 * @param type the textual value 083 * @return the {@code MatchType} for the given textual value 084 */ 085 public static MatchType ofString(String type) { 086 for (MatchType i : EnumSet.allOf(MatchType.class)) { 087 if (i.getValue().equals(type)) 088 return i; 089 } 090 throw new IllegalArgumentException(type + " is not allowed"); 091 } 092 } 093 094 protected static class Usage { 095 public SortedSet<String> values; // NOSONAR 096 private boolean hadKeys; 097 private boolean hadEmpty; 098 099 public boolean hasUniqueValue() { 100 return values.size() == 1 && !hadEmpty; 101 } 102 103 public boolean unused() { 104 return values.isEmpty(); 105 } 106 107 public String getFirst() { 108 return values.first(); 109 } 110 111 public boolean hadKeys() { 112 return hadKeys; 113 } 114 } 115 116 protected static Usage determineTextUsage(Collection<OsmPrimitive> sel, String key) { 117 Usage returnValue = new Usage(); 118 returnValue.values = new TreeSet<>(); 119 for (OsmPrimitive s : sel) { 120 String v = s.get(key); 121 if (v != null) { 122 returnValue.values.add(v); 123 } else { 124 returnValue.hadEmpty = true; 125 } 126 if (s.hasKeys()) { 127 returnValue.hadKeys = true; 128 } 129 } 130 return returnValue; 131 } 132 133 protected static Usage determineBooleanUsage(Collection<OsmPrimitive> sel, String key) { 134 135 Usage returnValue = new Usage(); 136 returnValue.values = new TreeSet<>(); 137 for (OsmPrimitive s : sel) { 138 String booleanValue = OsmUtils.getNamedOsmBoolean(s.get(key)); 139 if (booleanValue != null) { 140 returnValue.values.add(booleanValue); 141 } 142 } 143 return returnValue; 144 } 145 146 /** 147 * Returns the default match. 148 * @return the default match 149 */ 150 public abstract MatchType getDefaultMatch(); 151 152 /** 153 * Returns the list of values. 154 * @return the list of values 155 */ 156 public abstract Collection<String> getValues(); 157 158 protected String getKeyTooltipText() { 159 return tr("This corresponds to the key ''{0}''", key); 160 } 161 162 @Override 163 protected Boolean matches(Map<String, String> tags) { 164 switch (MatchType.ofString(match)) { 165 case NONE: 166 return null; 167 case KEY: 168 return tags.containsKey(key) ? Boolean.TRUE : null; 169 case KEY_REQUIRED: 170 return tags.containsKey(key); 171 case KEY_VALUE: 172 return tags.containsKey(key) && getValues().contains(tags.get(key)) ? Boolean.TRUE : null; 173 case KEY_VALUE_REQUIRED: 174 return tags.containsKey(key) && getValues().contains(tags.get(key)); 175 default: 176 throw new IllegalStateException(); 177 } 178 } 179 180 @Override 181 public String toString() { 182 return "KeyedItem [key=" + key + ", text=" + text 183 + ", text_context=" + text_context + ", match=" + match 184 + ']'; 185 } 186}