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

Last change on this file since 12825 was 12207, checked in by michael2402, 7 years ago

Add documentation on WeakPreferenceAdapter

  • Property svn:eol-style set to native
File size: 10.9 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.data.preferences;
3
4import org.openstreetmap.josm.Main;
5import org.openstreetmap.josm.data.Preferences;
6import org.openstreetmap.josm.data.Preferences.PreferenceChangeEvent;
7import org.openstreetmap.josm.data.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 * <tt>(cause==null ? null : cause.toString())</tt> (which typically contains the class and detail message of <tt>cause</tt>).
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 Preferences 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 = Main.pref;
167 this.key = key;
168 this.defaultValue = defaultValue;
169 }
170
171 /**
172 * Store the default value to {@link 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().get(key).isEmpty();
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 put(getDefaultValue());
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 10824
230 */
231 protected Preferences getPreferences() {
232 return preferences;
233 }
234
235 /**
236 * Adds a listener that listens only for changes to this preference key.
237 * @param listener The listener to add.
238 * @since 10824
239 */
240 public void addListener(ValueChangeListener<? super T> listener) {
241 try {
242 addListenerImpl(new PreferenceChangedListenerAdapter(listener));
243 } catch (RuntimeException e) {
244 throw BugReport.intercept(e).put("listener", listener).put("preference", key);
245 }
246 }
247
248 protected void addListenerImpl(PreferenceChangedListener adapter) {
249 getPreferences().addKeyPreferenceChangeListener(getKey(), adapter);
250 }
251
252 /**
253 * Adds a weak listener that listens only for changes to this preference key.
254 * @param listener The listener to add.
255 * @since 10824
256 */
257 public void addWeakListener(ValueChangeListener<? super T> listener) {
258 try {
259 ValueChangeListener<T> weakListener = new WeakPreferenceAdapter(listener);
260 PreferenceChangedListenerAdapter adapter = new PreferenceChangedListenerAdapter(weakListener);
261 addListenerImpl(adapter);
262 } catch (RuntimeException e) {
263 throw BugReport.intercept(e).put("listener", listener).put("preference", key);
264 }
265 }
266
267 /**
268 * This class wraps the ValueChangeListener in a ListenableWeakReference that automatically removes itself
269 * if the listener is garbage collected.
270 * @author Michael Zangl
271 */
272 private class WeakPreferenceAdapter extends ListenableWeakReference<ValueChangeListener<? super T>>
273 implements ValueChangeListener<T> {
274 WeakPreferenceAdapter(ValueChangeListener<? super T> referent) {
275 super(referent);
276 }
277
278 @Override
279 public void valueChanged(ValueChangeEvent<? extends T> e) {
280 ValueChangeListener<? super T> r = super.get();
281 if (r != null) {
282 r.valueChanged(e);
283 }
284 }
285
286 @Override
287 protected void onDereference() {
288 removeListenerImpl(new PreferenceChangedListenerAdapter(this));
289 }
290 }
291
292 /**
293 * Removes a listener that listens only for changes to this preference key.
294 * @param listener The listener to add.
295 * @since 10824
296 */
297 public void removeListener(ValueChangeListener<? super T> listener) {
298 try {
299 removeListenerImpl(new PreferenceChangedListenerAdapter(listener));
300 } catch (RuntimeException e) {
301 throw BugReport.intercept(e).put("listener", listener).put("preference", key);
302 }
303 }
304
305 protected void removeListenerImpl(PreferenceChangedListener adapter) {
306 getPreferences().removeKeyPreferenceChangeListener(getKey(), adapter);
307 }
308
309 @Override
310 public int hashCode() {
311 final int prime = 31;
312 int result = 1;
313 result = prime * result + ((key == null) ? 0 : key.hashCode());
314 result = prime * result + ((preferences == null) ? 0 : preferences.hashCode());
315 return result;
316 }
317
318 @Override
319 public boolean equals(Object obj) {
320 if (this == obj)
321 return true;
322 if (obj == null || getClass() != obj.getClass())
323 return false;
324 AbstractProperty<?> other = (AbstractProperty<?>) obj;
325 if (key == null) {
326 if (other.key != null)
327 return false;
328 } else if (!key.equals(other.key))
329 return false;
330 if (preferences == null) {
331 if (other.preferences != null)
332 return false;
333 } else if (!preferences.equals(other.preferences))
334 return false;
335 return true;
336 }
337}
Note: See TracBrowser for help on using the repository browser.