Skip to content

Commit

Permalink
Merge pull request #11 from petebankhead/prefs
Browse files Browse the repository at this point in the history
Fix bug in preference reset
  • Loading branch information
petebankhead authored Sep 14, 2023
2 parents 2c89057 + 1ead7cb commit 1cc2bde
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 40 deletions.
53 changes: 27 additions & 26 deletions src/main/java/qupath/fx/prefs/PrefUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.util.StringConverter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -40,74 +41,74 @@ class PrefUtils {

private static final Logger logger = LoggerFactory.getLogger(PrefUtils.class);

public static BooleanProperty createPersistentBooleanProperty(Preferences prefs, String key, boolean defaultValue) {
public static BooleanProperty createPersistentBooleanProperty(ObservableValue<Preferences> prefs, String key, boolean defaultValue) {
var prop = createTransientBooleanProperty(key, defaultValue);
prop.set(prefs.getBoolean(key, defaultValue));
prop.addListener((observable, oldValue, newValue) -> updateBooleanValue(prefs, key, newValue));
prop.set(prefs.getValue().getBoolean(key, defaultValue));
prop.addListener((observable, oldValue, newValue) -> updateBooleanValue(prefs.getValue(), key, newValue));
return prop;
}

public static IntegerProperty createPersistentIntegerProperty(Preferences prefs, String key, int defaultValue) {
public static IntegerProperty createPersistentIntegerProperty(ObservableValue<Preferences> prefs, String key, int defaultValue) {
var prop = createTransientIntegerProperty(key, defaultValue);
prop.set(prefs.getInt(key, defaultValue));
prop.addListener((observable, oldValue, newValue) -> updateNumericValue(prefs, key, newValue));
prop.set(prefs.getValue().getInt(key, defaultValue));
prop.addListener((observable, oldValue, newValue) -> updateNumericValue(prefs.getValue(), key, newValue));
return prop;
}

public static LongProperty createPersistentLongProperty(Preferences prefs, String key, long defaultValue) {
public static LongProperty createPersistentLongProperty(ObservableValue<Preferences> prefs, String key, long defaultValue) {
var prop = createTransientLongProperty(key, defaultValue);
prop.set(prefs.getLong(key, defaultValue));
prop.addListener((observable, oldValue, newValue) -> updateNumericValue(prefs, key, newValue));
prop.set(prefs.getValue().getLong(key, defaultValue));
prop.addListener((observable, oldValue, newValue) -> updateNumericValue(prefs.getValue(), key, newValue));
return prop;
}

public static FloatProperty createPersistentFloatProperty(Preferences prefs, String key, float defaultValue) {
public static FloatProperty createPersistentFloatProperty(ObservableValue<Preferences> prefs, String key, float defaultValue) {
var prop = createTransientFloatProperty(key, defaultValue);
prop.set(prefs.getFloat(key, defaultValue));
prop.addListener((observable, oldValue, newValue) -> updateNumericValue(prefs, key, newValue));
prop.set(prefs.getValue().getFloat(key, defaultValue));
prop.addListener((observable, oldValue, newValue) -> updateNumericValue(prefs.getValue(), key, newValue));
return prop;
}

public static DoubleProperty createPersistentDoubleProperty(Preferences prefs, String key, double defaultValue) {
public static DoubleProperty createPersistentDoubleProperty(ObservableValue<Preferences> prefs, String key, double defaultValue) {
var prop = createTransientDoubleProperty(key, defaultValue);
prop.set(prefs.getDouble(key, defaultValue));
prop.addListener((observable, oldValue, newValue) -> updateNumericValue(prefs, key, newValue));
prop.set(prefs.getValue().getDouble(key, defaultValue));
prop.addListener((observable, oldValue, newValue) -> updateNumericValue(prefs.getValue(), key, newValue));
return prop;
}

public static StringProperty createPersistentStringProperty(Preferences prefs, String key, String defaultValue) {
public static StringProperty createPersistentStringProperty(ObservableValue<Preferences> prefs, String key, String defaultValue) {
var prop = createTransientStringProperty(key, defaultValue);
prop.set(prefs.get(key, defaultValue));
prop.addListener((observable, oldValue, newValue) -> updateStringValue(prefs, key, newValue));
prop.set(prefs.getValue().get(key, defaultValue));
prop.addListener((observable, oldValue, newValue) -> updateStringValue(prefs.getValue(), key, newValue));
return prop;
}

public static <T extends Enum> ObjectProperty<T> createPersistentEnumProperty(Preferences prefs, String key, T defaultValue) {
public static <T extends Enum> ObjectProperty<T> createPersistentEnumProperty(ObservableValue<Preferences> prefs, String key, T defaultValue) {
return createPersistentEnumProperty(prefs, key, defaultValue, (Class<? extends T>)defaultValue.getClass());
}

public static <T extends Enum> ObjectProperty<T> createPersistentEnumProperty(Preferences prefs, String key, T defaultValue, Class<? extends T> cls) {
public static <T extends Enum> ObjectProperty<T> createPersistentEnumProperty(ObservableValue<Preferences> prefs, String key, T defaultValue, Class<? extends T> cls) {
var prop = createTransientEnumProperty(key, defaultValue);
T initialValue = tryToGetEnumPreference(prefs, key, cls, defaultValue);
prop.set(initialValue);
prop.addListener((observable, oldValue, newValue) -> updateEnumValue(prefs, key, newValue));
prop.addListener((observable, oldValue, newValue) -> updateEnumValue(prefs.getValue(), key, newValue));
return prop;
}

public static <T> ObjectProperty<T> createPersistentObjectProperty(Preferences prefs, String key, T defaultValue, StringConverter<? super T> converter) {
public static <T> ObjectProperty<T> createPersistentObjectProperty(ObservableValue<Preferences> prefs, String key, T defaultValue, StringConverter<? super T> converter) {
var prop = createTransientObjectProperty(key, defaultValue);
var initialValue = prefs.get(key, null);
var initialValue = prefs.getValue().get(key, null);
if (initialValue == null) {
prop.set(defaultValue);
} else {
prop.set((T)converter.fromString(initialValue));
}
prop.addListener((observable, oldValue, newValue) -> updateObjectValue(prefs, key, newValue, converter));
prop.addListener((observable, oldValue, newValue) -> updateObjectValue(prefs.getValue(), key, newValue, converter));
return prop;
}

static <T extends Enum> T tryToGetEnumPreference(Preferences prefs, String key, Class<? extends T> cls, T defaultValue) {
var defaultName = prefs.get(key, null);
static <T extends Enum> T tryToGetEnumPreference(ObservableValue<Preferences> prefs, String key, Class<? extends T> cls, T defaultValue) {
var defaultName = prefs.getValue().get(key, null);
if (defaultName == null)
return defaultValue;
try {
Expand Down
77 changes: 63 additions & 14 deletions src/main/java/qupath/fx/prefs/PreferenceManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.LongProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.property.SimpleLongProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.StringProperty;
import javafx.util.StringConverter;
import org.slf4j.Logger;
Expand Down Expand Up @@ -59,14 +62,14 @@ public class PreferenceManager {

private static final Logger logger = LoggerFactory.getLogger(PreferenceManager.class);

private final Preferences preferences;
private ReadOnlyObjectWrapper<Preferences> preferences = new ReadOnlyObjectWrapper<>();

private final LongProperty reloadCount = new SimpleLongProperty(0L);
private final LongProperty resetCount = new SimpleLongProperty(0L);

private PreferenceManager(Preferences preferences) {
Objects.requireNonNull(preferences, "Preferences cannot be null");
this.preferences = preferences;
this.preferences.set(preferences);
logger.trace("Preference manager created with name: {}", preferences.name());
}

Expand Down Expand Up @@ -101,10 +104,25 @@ public static PreferenceManager createForSystemPreferences(String pathName) {

/**
* Get the {@link Preferences} object backing this {@link PreferenceManager}.
* @return
* <p>
* Note that the preferences object returned by this method must not be retained and reused,
* because it may be invalidated by a call to {@link #reset()}.
* Rather, as far as possible other methods of this class should be used rather than accessing the
* {@link Preferences} directly.
* </p> * @return
*/
public Preferences getPreferences() {
return preferences;
return preferences.get();
}

/**
* Get a read-only property containing the {@link Preferences} object backing this {@link PreferenceManager}.
* This property can be used to observe changes to the backing preferences object, which occur if the preferences
* are reset.
* @return
*/
public ReadOnlyObjectProperty<Preferences> preferencesProperty() {
return preferences.getReadOnlyProperty();
}

/**
Expand All @@ -113,8 +131,12 @@ public Preferences getPreferences() {
* @throws BackingStoreException
*/
public synchronized void reset() throws BackingStoreException {
preferences.removeNode();
preferences.flush();
var oldPreferences = preferences.get();
oldPreferences.removeNode();
oldPreferences.flush();
var newPreferences = oldPreferences.isUserNode() ? Preferences.userRoot().node(oldPreferences.absolutePath()) :
Preferences.systemRoot().node(oldPreferences.absolutePath());
preferences.set(newPreferences);
resetCount.set(resetCount.get() + 1L);
}

Expand All @@ -131,7 +153,7 @@ public synchronized void reload() {
* @throws BackingStoreException
*/
public synchronized void save() throws BackingStoreException {
preferences.flush();
preferences.get().flush();
}

/**
Expand All @@ -141,7 +163,7 @@ public synchronized void save() throws BackingStoreException {
*/
public String toXml() throws IOException, BackingStoreException {
try (var stream = new ByteArrayOutputStream()) {
preferences.exportSubtree(stream);
preferences.get().exportSubtree(stream);
return stream.toString(StandardCharsets.UTF_8);
}
}
Expand All @@ -156,7 +178,7 @@ public String toXml() throws IOException, BackingStoreException {
*/
public BooleanProperty createPersistentBooleanProperty(String key, boolean defaultValue) {
var prop = PrefUtils.createPersistentBooleanProperty(preferences, key, defaultValue);
reloadCount.addListener((observable, oldValue, newValue) -> prop.set(preferences.getBoolean(key, prop.get())));
reloadCount.addListener((observable, oldValue, newValue) -> prop.set(preferences.getValue().getBoolean(key, prop.get())));
resetCount.addListener((observable, oldValue, newValue) -> prop.set(defaultValue));
return prop;
}
Expand All @@ -183,7 +205,7 @@ public BooleanProperty createTransientBooleanProperty(String key, boolean defaul
*/
public IntegerProperty createPersistentIntegerProperty(String key, int defaultValue) {
var prop = PrefUtils.createPersistentIntegerProperty(preferences, key, defaultValue);
reloadCount.addListener((observable, oldValue, newValue) -> prop.set(preferences.getInt(key, prop.get())));
reloadCount.addListener((observable, oldValue, newValue) -> prop.set(preferences.getValue().getInt(key, prop.get())));
resetCount.addListener((observable, oldValue, newValue) -> prop.set(defaultValue));
return prop;
}
Expand All @@ -210,7 +232,7 @@ public IntegerProperty createTransientIntegerProperty(String key, int defaultVal
*/
public FloatProperty createPersistentFloatProperty(String key, float defaultValue) {
var prop = PrefUtils.createPersistentFloatProperty(preferences, key, defaultValue);
reloadCount.addListener((observable, oldValue, newValue) -> prop.set(preferences.getFloat(key, prop.get())));
reloadCount.addListener((observable, oldValue, newValue) -> prop.set(preferences.getValue().getFloat(key, prop.get())));
resetCount.addListener((observable, oldValue, newValue) -> prop.set(defaultValue));
return prop;
}
Expand All @@ -237,7 +259,7 @@ public FloatProperty createTransientFloatProperty(String key, float defaultValue
*/
public DoubleProperty createPersistentDoubleProperty(String key, double defaultValue) {
var prop = PrefUtils.createPersistentDoubleProperty(preferences, key, defaultValue);
reloadCount.addListener((observable, oldValue, newValue) -> prop.set(preferences.getDouble(key, prop.get())));
reloadCount.addListener((observable, oldValue, newValue) -> prop.set(preferences.getValue().getDouble(key, prop.get())));
resetCount.addListener((observable, oldValue, newValue) -> prop.set(defaultValue));
return prop;
}
Expand All @@ -255,6 +277,33 @@ public DoubleProperty createTransientDoubleProperty(String key, double defaultVa
return prop;
}

/**
* Create a long property that is persisted to the backing store with the specified key.
* @param key key used to store the property value, and used for the property name
* @param defaultValue default property value; used if the property is not found in the backing store,
* or if {@link PreferenceManager#reset()} is called.
* @return the property
*/
public LongProperty createPersistentLongProperty(String key, long defaultValue) {
var prop = PrefUtils.createPersistentLongProperty(preferences, key, defaultValue);
reloadCount.addListener((observable, oldValue, newValue) -> prop.set(preferences.getValue().getLong(key, prop.get())));
resetCount.addListener((observable, oldValue, newValue) -> prop.set(defaultValue));
return prop;
}

/**
* Create a double property that is <b>not</b> persisted to the backing store.
* It can still be reset to its default value upon a call to {@link PreferenceManager#reset()}.
* @param key key used to store the property value, and used for the property name
* @param defaultValue default property value
* @return the property
*/
public LongProperty createTransientLongProperty(String key, long defaultValue) {
var prop = PrefUtils.createTransientLongProperty(key, defaultValue);
resetCount.addListener((observable, oldValue, newValue) -> prop.set(defaultValue));
return prop;
}

/**
* Create a String property that is persisted to the backing store with the specified key.
* @param key key used to store the property value, and used for the property name
Expand All @@ -264,7 +313,7 @@ public DoubleProperty createTransientDoubleProperty(String key, double defaultVa
*/
public StringProperty createPersistentStringProperty(String key, String defaultValue) {
var prop = PrefUtils.createPersistentStringProperty(preferences, key, defaultValue);
reloadCount.addListener((observable, oldValue, newValue) -> prop.set(preferences.get(key, prop.get())));
reloadCount.addListener((observable, oldValue, newValue) -> prop.set(preferences.getValue().get(key, prop.get())));
resetCount.addListener((observable, oldValue, newValue) -> prop.set(defaultValue));
return prop;
}
Expand Down Expand Up @@ -376,7 +425,7 @@ public ObjectProperty<Locale> createPersistentLocaleProperty(String key, Locale
public <T> ObjectProperty<T> createPersistentObjectProperty(String key, T defaultValue, StringConverter<T> converter) {
var prop = PrefUtils.createPersistentObjectProperty(preferences, key, defaultValue, converter);
reloadCount.addListener((observable, oldValue, newValue) ->
prop.set(converter.fromString(preferences.get(key, converter.toString(prop.get())))));
prop.set(converter.fromString(preferences.getValue().get(key, converter.toString(prop.get())))));
resetCount.addListener((observable, oldValue, newValue) -> prop.set(defaultValue));
return prop;
}
Expand Down

0 comments on commit 1cc2bde

Please sign in to comment.