source: josm/trunk/src/org/openstreetmap/josm/data/preferences/AbstractProperty.java@ 13818

Last change on this file since 13818 was 13493, checked in by Don-vip, 6 years ago

see #11924, see #15560, see #16048 - tt HTML tag is deprecated in HTML5: use code instead

  • Property svn:eol-style set to native
File size: 11.2 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.data.preferences;
3
4import org.openstreetmap.josm.spi.preferences.Config;
5import org.openstreetmap.josm.spi.preferences.IPreferences;
6import org.openstreetmap.josm.spi.preferences.PreferenceChangeEvent;
7import org.openstreetmap.josm.spi.preferences.PreferenceChangedListener;
8import org.openstreetmap.josm.tools.ListenableWeakReference;
9import org.openstreetmap.josm.tools.bugreport.BugReport;
10
11/**
12 * Captures the common functionality of preference properties
13 * @param <T> The type of object accessed by this property
14 */
15public abstract class AbstractProperty<T> {
16
17 private final class PreferenceChangedListenerAdapter implements PreferenceChangedListener {
18 private final ValueChangeListener<? super T> listener;
19
20 PreferenceChangedListenerAdapter(ValueChangeListener<? super T> listener) {
21 this.listener = listener;
22 }
23
24 @Override
25 public void preferenceChanged(PreferenceChangeEvent e) {
26 listener.valueChanged(new ValueChangeEvent<>(e, AbstractProperty.this));
27 }
28
29 @Override
30 public int hashCode() {
31 final int prime = 31;
32 int result = 1;
33 result = prime * result + getOuterType().hashCode();
34 result = prime * result + ((listener == null) ? 0 : listener.hashCode());
35 return result;
36 }
37
38 @Override
39 public boolean equals(Object obj) {
40 if (this == obj)
41 return true;
42 if (obj == null || getClass() != obj.getClass())
43 return false;
44 @SuppressWarnings("unchecked")
45 PreferenceChangedListenerAdapter other = (PreferenceChangedListenerAdapter) obj;
46 if (!getOuterType().equals(other.getOuterType()))
47 return false;
48 if (listener == null) {
49 if (other.listener != null)
50 return false;
51 } else if (!listener.equals(other.listener))
52 return false;
53 return true;
54 }
55
56 private AbstractProperty<T> getOuterType() {
57 return AbstractProperty.this;
58 }
59
60 @Override
61 public String toString() {
62 return "PreferenceChangedListenerAdapter [listener=" + listener + ']';
63 }
64 }
65
66 /**
67 * A listener that listens to changes in the properties value.
68 * @author michael
69 * @param <T> property type
70 * @since 10824
71 */
72 @FunctionalInterface
73 public interface ValueChangeListener<T> {
74 /**
75 * Method called when a property value has changed.
76 * @param e property change event
77 */
78 void valueChanged(ValueChangeEvent<? extends T> e);
79 }
80
81 /**
82 * An event that is triggered if the value of a property changes.
83 * @author Michael Zangl
84 * @param <T> property type
85 * @since 10824
86 */
87 public static class ValueChangeEvent<T> {
88 private final PreferenceChangeEvent base;
89 private final AbstractProperty<T> source;
90
91 ValueChangeEvent(PreferenceChangeEvent base, AbstractProperty<T> source) {
92 this.base = base;
93 this.source = source;
94 }
95
96 /**
97 * Get the base event.
98 * @return the base event
99 * @since 11496
100 */
101 public final PreferenceChangeEvent getBaseEvent() {
102 return base;
103 }
104
105 /**
106 * Get the property that was changed
107 * @return The property.
108 */
109 public AbstractProperty<T> getProperty() {
110 return source;
111 }
112 }
113
114 /**
115 * An exception that is thrown if a preference value is invalid.
116 * @author Michael Zangl
117 * @since 10824
118 */
119 public static class InvalidPreferenceValueException extends RuntimeException {
120
121 /**
122 * Constructs a new {@code InvalidPreferenceValueException} with the specified detail message and cause.
123 * @param message the detail message (which is saved for later retrieval by the {@link #getMessage()} method).
124 * @param cause the cause (which is saved for later retrieval by the {@link #getCause()} method).
125 */
126 public InvalidPreferenceValueException(String message, Throwable cause) {
127 super(message, cause);
128 }
129
130 /**
131 * Constructs a new {@code InvalidPreferenceValueException} with the specified detail message.
132 * The cause is not initialized, and may subsequently be initialized by a call to {@link #initCause}.
133 *
134 * @param message the detail message. The detail message is saved for later retrieval by the {@link #getMessage()} method.
135 */
136 public InvalidPreferenceValueException(String message) {
137 super(message);
138 }
139
140 /**
141 * Constructs a new {@code InvalidPreferenceValueException} with the specified cause and a detail message of
142 * <code>(cause==null ? null : cause.toString())</code> (which typically contains the class and detail message of <code>cause</code>).
143 *
144 * @param cause the cause (which is saved for later retrieval by the {@link #getCause()} method).
145 */
146 public InvalidPreferenceValueException(Throwable cause) {
147 super(cause);
148 }
149 }
150
151 /**
152 * The preferences object this property is for.
153 */
154 protected final IPreferences preferences;
155 protected final String key;
156 protected final T defaultValue;
157
158 /**
159 * Constructs a new {@code AbstractProperty}.
160 * @param key The property key
161 * @param defaultValue The default value
162 * @since 5464
163 */
164 public AbstractProperty(String key, T defaultValue) {
165 // Main.pref should not change in production but may change during tests.
166 preferences = Config.getPref();
167 this.key = key;
168 this.defaultValue = defaultValue;
169 }
170
171 /**
172 * Store the default value to the preferences.
173 */
174 protected void storeDefaultValue() {
175 if (getPreferences() != null) {
176 get();
177 }
178 }
179
180 /**
181 * Replies the property key.
182 * @return The property key
183 */
184 public String getKey() {
185 return key;
186 }
187
188 /**
189 * Determines if this property is currently set in JOSM preferences.
190 * @return true if {@code Main.pref} contains this property.
191 */
192 public boolean isSet() {
193 return getPreferences().getKeySet().contains(key);
194 }
195
196 /**
197 * Replies the default value of this property.
198 * @return The default value of this property
199 */
200 public T getDefaultValue() {
201 return defaultValue;
202 }
203
204 /**
205 * Removes this property from JOSM preferences (i.e replace it by its default value).
206 */
207 public void remove() {
208 getPreferences().put(key, null);
209 }
210
211 /**
212 * Replies the value of this property.
213 * @return the value of this property
214 * @since 5464
215 */
216 public abstract T get();
217
218 /**
219 * Sets this property to the specified value.
220 * @param value The new value of this property
221 * @return true if something has changed (i.e. value is different than before)
222 * @since 5464
223 */
224 public abstract boolean put(T value);
225
226 /**
227 * Gets the preferences used for this property.
228 * @return The preferences for this property.
229 * @since 12999
230 */
231 protected IPreferences getPreferences() {
232 return preferences;
233 }
234
235 /**
236 * Creates a new {@link CachingProperty} instance for this property.
237 * @return The new caching property instance.
238 * @since 12983
239 */
240 public CachingProperty<T> cached() {
241 return new CachingProperty<>(this);
242 }
243
244 /**
245 * Adds a listener that listens only for changes to this preference key.
246 * @param listener The listener to add.
247 * @since 10824
248 */
249 public void addListener(ValueChangeListener<? super T> listener) {
250 try {
251 addListenerImpl(new PreferenceChangedListenerAdapter(listener));
252 } catch (RuntimeException e) {
253 throw BugReport.intercept(e).put("listener", listener).put("preference", key);
254 }
255 }
256
257 protected void addListenerImpl(PreferenceChangedListener adapter) {
258 getPreferences().addKeyPreferenceChangeListener(getKey(), adapter);
259 }
260
261 /**
262 * Adds a weak listener that listens only for changes to this preference key.
263 * @param listener The listener to add.
264 * @since 10824
265 */
266 public void addWeakListener(ValueChangeListener<? super T> listener) {
267 try {
268 ValueChangeListener<T> weakListener = new WeakPreferenceAdapter(listener);
269 PreferenceChangedListenerAdapter adapter = new PreferenceChangedListenerAdapter(weakListener);
270 addListenerImpl(adapter);
271 } catch (RuntimeException e) {
272 throw BugReport.intercept(e).put("listener", listener).put("preference", key);
273 }
274 }
275
276 /**
277 * This class wraps the ValueChangeListener in a ListenableWeakReference that automatically removes itself
278 * if the listener is garbage collected.
279 * @author Michael Zangl
280 */
281 private class WeakPreferenceAdapter extends ListenableWeakReference<ValueChangeListener<? super T>>
282 implements ValueChangeListener<T> {
283 WeakPreferenceAdapter(ValueChangeListener<? super T> referent) {
284 super(referent);
285 }
286
287 @Override
288 public void valueChanged(ValueChangeEvent<? extends T> e) {
289 ValueChangeListener<? super T> r = super.get();
290 if (r != null) {
291 r.valueChanged(e);
292 }
293 }
294
295 @Override
296 protected void onDereference() {
297 removeListenerImpl(new PreferenceChangedListenerAdapter(this));
298 }
299 }
300
301 /**
302 * Removes a listener that listens only for changes to this preference key.
303 * @param listener The listener to add.
304 * @since 10824
305 */
306 public void removeListener(ValueChangeListener<? super T> listener) {
307 try {
308 removeListenerImpl(new PreferenceChangedListenerAdapter(listener));
309 } catch (RuntimeException e) {
310 throw BugReport.intercept(e).put("listener", listener).put("preference", key);
311 }
312 }
313
314 protected void removeListenerImpl(PreferenceChangedListener adapter) {
315 getPreferences().removeKeyPreferenceChangeListener(getKey(), adapter);
316 }
317
318 @Override
319 public int hashCode() {
320 final int prime = 31;
321 int result = 1;
322 result = prime * result + ((key == null) ? 0 : key.hashCode());
323 result = prime * result + ((preferences == null) ? 0 : preferences.hashCode());
324 return result;
325 }
326
327 @Override
328 public boolean equals(Object obj) {
329 if (this == obj)
330 return true;
331 if (obj == null || getClass() != obj.getClass())
332 return false;
333 AbstractProperty<?> other = (AbstractProperty<?>) obj;
334 if (key == null) {
335 if (other.key != null)
336 return false;
337 } else if (!key.equals(other.key))
338 return false;
339 if (preferences == null) {
340 if (other.preferences != null)
341 return false;
342 } else if (!preferences.equals(other.preferences))
343 return false;
344 return true;
345 }
346}
Note: See TracBrowser for help on using the repository browser.