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

Last change on this file was 16553, checked in by Don-vip, 4 years ago

see #19334 - javadoc fixes + protected constructors for abstract classes

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