// License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.data.preferences; import org.openstreetmap.josm.Main; import org.openstreetmap.josm.data.Preferences; import org.openstreetmap.josm.data.Preferences.PreferenceChangeEvent; import org.openstreetmap.josm.data.Preferences.PreferenceChangedListener; /** * Captures the common functionality of preference properties * @param The type of object accessed by this property */ public abstract class AbstractProperty { private final class PreferenceChangedListenerAdapter implements PreferenceChangedListener { private final ValueChangeListener listener; PreferenceChangedListenerAdapter(ValueChangeListener listener) { this.listener = listener; } @Override public void preferenceChanged(PreferenceChangeEvent e) { listener.valueChanged(new ValueChangeEvent<>(e, AbstractProperty.this)); } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + getOuterType().hashCode(); result = prime * result + ((listener == null) ? 0 : listener.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null || getClass() != obj.getClass()) return false; @SuppressWarnings("unchecked") PreferenceChangedListenerAdapter other = (PreferenceChangedListenerAdapter) obj; if (!getOuterType().equals(other.getOuterType())) return false; if (listener == null) { if (other.listener != null) return false; } else if (!listener.equals(other.listener)) return false; return true; } private AbstractProperty getOuterType() { return AbstractProperty.this; } @Override public String toString() { return "PreferenceChangedListenerAdapter [listener=" + listener + ']'; } } /** * A listener that listens to changes in the properties value. * @author michael * @param property type * @since 10824 */ @FunctionalInterface public interface ValueChangeListener { /** * Method called when a property value has changed. * @param e property change event */ void valueChanged(ValueChangeEvent e); } /** * An event that is triggered if the value of a property changes. * @author Michael Zangl * @param property type * @since 10824 */ public static class ValueChangeEvent { private final PreferenceChangeEvent base; private final AbstractProperty source; ValueChangeEvent(PreferenceChangeEvent base, AbstractProperty source) { this.base = base; this.source = source; } /** * Get the base event. * @return the base event * @since 11496 */ public final PreferenceChangeEvent getBaseEvent() { return base; } /** * Get the property that was changed * @return The property. */ public AbstractProperty getProperty() { return source; } } /** * An exception that is thrown if a preference value is invalid. * @author Michael Zangl * @since 10824 */ public static class InvalidPreferenceValueException extends RuntimeException { /** * Constructs a new {@code InvalidPreferenceValueException} with the specified detail message and cause. * @param message the detail message (which is saved for later retrieval by the {@link #getMessage()} method). * @param cause the cause (which is saved for later retrieval by the {@link #getCause()} method). */ public InvalidPreferenceValueException(String message, Throwable cause) { super(message, cause); } /** * Constructs a new {@code InvalidPreferenceValueException} with the specified detail message. * The cause is not initialized, and may subsequently be initialized by a call to {@link #initCause}. * * @param message the detail message. The detail message is saved for later retrieval by the {@link #getMessage()} method. */ public InvalidPreferenceValueException(String message) { super(message); } /** * Constructs a new {@code InvalidPreferenceValueException} with the specified cause and a detail message of * (cause==null ? null : cause.toString()) (which typically contains the class and detail message of cause). * * @param cause the cause (which is saved for later retrieval by the {@link #getCause()} method). */ public InvalidPreferenceValueException(Throwable cause) { super(cause); } } /** * The preferences object this property is for. */ protected final Preferences preferences; protected final String key; protected final T defaultValue; /** * Constructs a new {@code AbstractProperty}. * @param key The property key * @param defaultValue The default value * @since 5464 */ public AbstractProperty(String key, T defaultValue) { // Main.pref should not change in production but may change during tests. preferences = Main.pref; this.key = key; this.defaultValue = defaultValue; } /** * Store the default value to {@link Preferences}. */ protected void storeDefaultValue() { if (getPreferences() != null) { get(); } } /** * Replies the property key. * @return The property key */ public String getKey() { return key; } /** * Determines if this property is currently set in JOSM preferences. * @return true if {@code Main.pref} contains this property. */ public boolean isSet() { return !getPreferences().get(key).isEmpty(); } /** * Replies the default value of this property. * @return The default value of this property */ public T getDefaultValue() { return defaultValue; } /** * Removes this property from JOSM preferences (i.e replace it by its default value). */ public void remove() { put(getDefaultValue()); } /** * Replies the value of this property. * @return the value of this property * @since 5464 */ public abstract T get(); /** * Sets this property to the specified value. * @param value The new value of this property * @return true if something has changed (i.e. value is different than before) * @since 5464 */ public abstract boolean put(T value); /** * Gets the preferences used for this property. * @return The preferences for this property. * @since 10824 */ protected Preferences getPreferences() { return preferences; } /** * Adds a listener that listens only for changes to this preference key. * @param listener The listener to add. * @since 10824 */ public void addListener(ValueChangeListener listener) { addListenerImpl(new PreferenceChangedListenerAdapter(listener)); } protected void addListenerImpl(PreferenceChangedListener adapter) { getPreferences().addKeyPreferenceChangeListener(getKey(), adapter); } /** * Adds a weak listener that listens only for changes to this preference key. * @param listener The listener to add. * @since 10824 */ public void addWeakListener(ValueChangeListener listener) { addWeakListenerImpl(new PreferenceChangedListenerAdapter(listener)); } protected void addWeakListenerImpl(PreferenceChangedListener adapter) { getPreferences().addWeakKeyPreferenceChangeListener(getKey(), adapter); } /** * Removes a listener that listens only for changes to this preference key. * @param listener The listener to add. * @since 10824 */ public void removeListener(ValueChangeListener listener) { removeListenerImpl(new PreferenceChangedListenerAdapter(listener)); } protected void removeListenerImpl(PreferenceChangedListener adapter) { getPreferences().removeKeyPreferenceChangeListener(getKey(), adapter); } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((key == null) ? 0 : key.hashCode()); result = prime * result + ((preferences == null) ? 0 : preferences.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null || getClass() != obj.getClass()) return false; AbstractProperty other = (AbstractProperty) obj; if (key == null) { if (other.key != null) return false; } else if (!key.equals(other.key)) return false; if (preferences == null) { if (other.preferences != null) return false; } else if (!preferences.equals(other.preferences)) return false; return true; } }