001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.data.preferences; 003 004import org.openstreetmap.josm.Main; 005import org.openstreetmap.josm.data.Preferences; 006import org.openstreetmap.josm.data.Preferences.PreferenceChangeEvent; 007import org.openstreetmap.josm.data.Preferences.PreferenceChangedListener; 008 009/** 010 * Captures the common functionality of preference properties 011 * @param <T> The type of object accessed by this property 012 */ 013public abstract class AbstractProperty<T> { 014 015 private final class PreferenceChangedListenerAdapter implements PreferenceChangedListener { 016 private final ValueChangeListener<? super T> listener; 017 018 PreferenceChangedListenerAdapter(ValueChangeListener<? super T> listener) { 019 this.listener = listener; 020 } 021 022 @Override 023 public void preferenceChanged(PreferenceChangeEvent e) { 024 listener.valueChanged(new ValueChangeEvent<>(e, AbstractProperty.this)); 025 } 026 027 @Override 028 public int hashCode() { 029 final int prime = 31; 030 int result = 1; 031 result = prime * result + getOuterType().hashCode(); 032 result = prime * result + ((listener == null) ? 0 : listener.hashCode()); 033 return result; 034 } 035 036 @Override 037 public boolean equals(Object obj) { 038 if (this == obj) 039 return true; 040 if (obj == null) 041 return false; 042 if (getClass() != obj.getClass()) 043 return false; 044 @SuppressWarnings("unchecked") 045 PreferenceChangedListenerAdapter other = (PreferenceChangedListenerAdapter) obj; 046 if (!getOuterType().equals(other.getOuterType())) 047 return false; 048 if (listener == null) { 049 if (other.listener != null) 050 return false; 051 } else if (!listener.equals(other.listener)) 052 return false; 053 return true; 054 } 055 056 private AbstractProperty<T> getOuterType() { 057 return AbstractProperty.this; 058 } 059 060 @Override 061 public String toString() { 062 return "PreferenceChangedListenerAdapter [listener=" + listener + ']'; 063 } 064 } 065 066 /** 067 * A listener that listens to changes in the properties value. 068 * @author michael 069 * @param <T> property type 070 * @since 10824 071 */ 072 @FunctionalInterface 073 public interface ValueChangeListener<T> { 074 /** 075 * Method called when a property value has changed. 076 * @param e property change event 077 */ 078 void valueChanged(ValueChangeEvent<? extends T> e); 079 } 080 081 /** 082 * An event that is triggered if the value of a property changes. 083 * @author Michael Zangl 084 * @param <T> property type 085 * @since 10824 086 */ 087 public static class ValueChangeEvent<T> { 088 private final PreferenceChangeEvent base; 089 090 private final AbstractProperty<T> source; 091 092 ValueChangeEvent(PreferenceChangeEvent base, AbstractProperty<T> source) { 093 this.base = base; 094 this.source = source; 095 } 096 097 /** 098 * Get the property that was changed 099 * @return The property. 100 */ 101 public AbstractProperty<T> getProperty() { 102 return source; 103 } 104 } 105 106 /** 107 * An exception that is thrown if a preference value is invalid. 108 * @author Michael Zangl 109 * @since 10824 110 */ 111 public static class InvalidPreferenceValueException extends RuntimeException { 112 113 /** 114 * Constructs a new {@code InvalidPreferenceValueException} with the specified detail message and cause. 115 * @param message the detail message (which is saved for later retrieval by the {@link #getMessage()} method). 116 * @param cause the cause (which is saved for later retrieval by the {@link #getCause()} method). 117 */ 118 public InvalidPreferenceValueException(String message, Throwable cause) { 119 super(message, cause); 120 } 121 122 /** 123 * Constructs a new {@code InvalidPreferenceValueException} with the specified detail message. 124 * The cause is not initialized, and may subsequently be initialized by a call to {@link #initCause}. 125 * 126 * @param message the detail message. The detail message is saved for later retrieval by the {@link #getMessage()} method. 127 */ 128 public InvalidPreferenceValueException(String message) { 129 super(message); 130 } 131 132 /** 133 * Constructs a new {@code InvalidPreferenceValueException} with the specified cause and a detail message of 134 * <tt>(cause==null ? null : cause.toString())</tt> (which typically contains the class and detail message of <tt>cause</tt>). 135 * 136 * @param cause the cause (which is saved for later retrieval by the {@link #getCause()} method). 137 */ 138 public InvalidPreferenceValueException(Throwable cause) { 139 super(cause); 140 } 141 } 142 143 /** 144 * The preferences object this property is for. 145 */ 146 protected final Preferences preferences; 147 protected final String key; 148 protected final T defaultValue; 149 150 /** 151 * Constructs a new {@code AbstractProperty}. 152 * @param key The property key 153 * @param defaultValue The default value 154 * @since 5464 155 */ 156 public AbstractProperty(String key, T defaultValue) { 157 // Main.pref should not change in production but may change during tests. 158 preferences = Main.pref; 159 this.key = key; 160 this.defaultValue = defaultValue; 161 } 162 163 /** 164 * Store the default value to {@link Preferences}. 165 */ 166 protected void storeDefaultValue() { 167 if (getPreferences() != null) { 168 get(); 169 } 170 } 171 172 /** 173 * Replies the property key. 174 * @return The property key 175 */ 176 public String getKey() { 177 return key; 178 } 179 180 /** 181 * Determines if this property is currently set in JOSM preferences. 182 * @return true if {@code Main.pref} contains this property. 183 */ 184 public boolean isSet() { 185 return !getPreferences().get(key).isEmpty(); 186 } 187 188 /** 189 * Replies the default value of this property. 190 * @return The default value of this property 191 */ 192 public T getDefaultValue() { 193 return defaultValue; 194 } 195 196 /** 197 * Removes this property from JOSM preferences (i.e replace it by its default value). 198 */ 199 public void remove() { 200 put(getDefaultValue()); 201 } 202 203 /** 204 * Replies the value of this property. 205 * @return the value of this property 206 * @since 5464 207 */ 208 public abstract T get(); 209 210 /** 211 * Sets this property to the specified value. 212 * @param value The new value of this property 213 * @return true if something has changed (i.e. value is different than before) 214 * @since 5464 215 */ 216 public abstract boolean put(T value); 217 218 /** 219 * Gets the preferences used for this property. 220 * @return The preferences for this property. 221 * @since 10824 222 */ 223 protected Preferences getPreferences() { 224 return preferences; 225 } 226 227 /** 228 * Adds a listener that listens only for changes to this preference key. 229 * @param listener The listener to add. 230 * @since 10824 231 */ 232 public void addListener(ValueChangeListener<? super T> listener) { 233 addListenerImpl(new PreferenceChangedListenerAdapter(listener)); 234 } 235 236 protected void addListenerImpl(PreferenceChangedListener adapter) { 237 getPreferences().addKeyPreferenceChangeListener(getKey(), adapter); 238 } 239 240 /** 241 * Adds a weak listener that listens only for changes to this preference key. 242 * @param listener The listener to add. 243 * @since 10824 244 */ 245 public void addWeakListener(ValueChangeListener<? super T> listener) { 246 addWeakListenerImpl(new PreferenceChangedListenerAdapter(listener)); 247 } 248 249 protected void addWeakListenerImpl(PreferenceChangedListener adapter) { 250 getPreferences().addWeakKeyPreferenceChangeListener(getKey(), adapter); 251 } 252 253 /** 254 * Removes a listener that listens only for changes to this preference key. 255 * @param listener The listener to add. 256 * @since 10824 257 */ 258 public void removeListener(ValueChangeListener<? super T> listener) { 259 removeListenerImpl(new PreferenceChangedListenerAdapter(listener)); 260 } 261 262 protected void removeListenerImpl(PreferenceChangedListener adapter) { 263 getPreferences().removeKeyPreferenceChangeListener(getKey(), adapter); 264 } 265 266 @Override 267 public int hashCode() { 268 final int prime = 31; 269 int result = 1; 270 result = prime * result + ((key == null) ? 0 : key.hashCode()); 271 result = prime * result + ((preferences == null) ? 0 : preferences.hashCode()); 272 return result; 273 } 274 275 @Override 276 public boolean equals(Object obj) { 277 if (this == obj) 278 return true; 279 if (obj == null) 280 return false; 281 if (getClass() != obj.getClass()) 282 return false; 283 AbstractProperty<?> other = (AbstractProperty<?>) obj; 284 if (key == null) { 285 if (other.key != null) 286 return false; 287 } else if (!key.equals(other.key)) 288 return false; 289 if (preferences == null) { 290 if (other.preferences != null) 291 return false; 292 } else if (!preferences.equals(other.preferences)) 293 return false; 294 return true; 295 } 296}