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}