/*
* Copyright (C) 2015 comtel2000
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.cathive.fx.guice.prefs;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Base64;
import java.util.prefs.BackingStoreException;
import java.util.prefs.Preferences;
import javax.annotation.PreDestroy;
import com.cathive.fx.guice.PersistentProperty.NodeType;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.FloatProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.LongProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.Property;
import javafx.beans.property.StringProperty;
/**
* This class allows applications to bind {@link Property} to
* {@link Preferences} and store and retrieve it from the OS-specific registry.
* The selected binding {@code key} is restricted to
* {@link Preferences#MAX_KEY_LENGTH}
*
* @author comtel2000
*
*/
public class PersistentPropertyBinder {
private final Preferences prefs;
public PersistentPropertyBinder(Preferences p) {
prefs = p;
}
/**
* Use the user preference tree with associated
* {@link PersistentPropertyBinder} package name
*
* @see java.util.prefs.Preferences
*/
public PersistentPropertyBinder() {
this(Preferences.userNodeForPackage(PersistentPropertyBinder.class));
}
/**
* Use the user preference tree with associated given class package name
*
* @param c
* package name
*
* @see java.util.prefs.Preferences
*/
public PersistentPropertyBinder(Class<?> c) {
this(Preferences.userNodeForPackage(c));
}
/**
* Use the selected preference tree with associated given class package name
*
* @param c
* package name
* @param pref
* selected preference tree
*
* @see com.cathive.fx.guice.PersistentProperty
*/
public PersistentPropertyBinder(Class<?> c, NodeType pref) {
this(pref == NodeType.USER_NODE ? Preferences.userNodeForPackage(c) : Preferences.systemNodeForPackage(c));
}
@PreDestroy
public void flush() {
try {
prefs.flush();
} catch (BackingStoreException e) {
throw new RuntimeException(e);
}
}
public Preferences getPreferences() {
return prefs;
}
/**
* Generates a bidirectional binding between the {@link Property} and the
* application store value. The store key is used by the property name
* {@link Property#getName()}
*
* @param <T>
* {@link Serializable} small object
* @param property
* {@link Property} to bind
*/
public <T extends Serializable> void bind(ObjectProperty<T> property) {
bind(property, property.getName());
}
/**
* Generates a bidirectional binding between the {@link Property} and the
* application store value identified by the {@code key} {@link String}.
*
* @param <T>
* {@link Serializable} small object
* @param property
* {@link Property} to bind
* @param key
* unique application store key
*/
@SuppressWarnings("unchecked")
public <T extends Serializable> void bind(ObjectProperty<T> property, String key) {
byte[] value = prefs.getByteArray(validateKey(key), null);
if (value != null && value.length > 0) {
try (ObjectInputStream stream = new ObjectInputStream(
new ByteArrayInputStream(Base64.getDecoder().decode(value)))) {
property.set((T) stream.readObject());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
property.addListener(o -> {
T v = property.getValue();
if (v == null) {
prefs.putByteArray(key, new byte[] {});
return;
}
try (ByteArrayOutputStream obj = new ByteArrayOutputStream()) {
try (ObjectOutputStream stream = new ObjectOutputStream(obj)) {
stream.writeObject(v);
stream.flush();
}
prefs.putByteArray(key, Base64.getEncoder().encode(obj.toByteArray()));
} catch (Exception e) {
throw new RuntimeException(e);
}
});
}
/**
* @see #bind(ObjectProperty)
*
* @param property
* {@link Property} to bind
*/
public void bind(BooleanProperty property) {
bind(property, property.getName());
}
/**
* @see #bind(ObjectProperty, String)
*
* @param property
* {@link Property} to bind
* @param key
* unique application store key
*/
public void bind(BooleanProperty property, String key) {
if (prefs.get(validateKey(key), null) != null) {
property.set(prefs.getBoolean(key, false));
}
property.addListener(o -> prefs.putBoolean(key, property.getValue()));
}
/**
* @see #bind(ObjectProperty)
*
* @param property
* {@link Property} to bind
*/
public void bind(IntegerProperty property) {
bind(property, property.getName());
}
/**
* @see #bind(ObjectProperty, String)
*
* @param property
* {@link Property} to bind
* @param key
* unique application store key
*/
public void bind(IntegerProperty property, String key) {
if (prefs.get(validateKey(key), null) != null) {
property.set(prefs.getInt(key, Integer.MIN_VALUE));
}
property.addListener(o -> prefs.putInt(key, property.getValue()));
}
/**
* @see #bind(ObjectProperty)
*
* @param property
* {@link Property} to bind
*/
public void bind(FloatProperty property) {
bind(property, property.getName());
}
/**
* @see #bind(ObjectProperty, String)
*
* @param property
* {@link Property} to bind
* @param key
* unique application store key
*/
public void bind(FloatProperty property, String key) {
if (prefs.get(validateKey(key), null) != null) {
property.set(prefs.getFloat(key, Float.NaN));
}
property.addListener(o -> prefs.putFloat(key, property.getValue()));
}
/**
* @see #bind(ObjectProperty)
*
* @param property
* {@link Property} to bind
*/
public void bind(DoubleProperty property) {
bind(property, property.getName());
}
/**
* @see #bind(ObjectProperty, String)
*
* @param property
* {@link Property} to bind
* @param key
* unique application store key
*/
public void bind(DoubleProperty property, String key) {
if (prefs.get(validateKey(key), null) != null) {
property.set(prefs.getDouble(key, Double.NaN));
}
property.addListener(o -> prefs.putDouble(key, property.getValue()));
}
/**
* @see #bind(ObjectProperty)
*
* @param property
* {@link Property} to bind
*/
public void bind(LongProperty property) {
bind(property, property.getName());
}
/**
* @see #bind(ObjectProperty, String)
*
* @param property
* {@link Property} to bind
* @param key
* unique application store key
*/
public void bind(LongProperty property, String key) {
if (prefs.get(validateKey(key), null) != null) {
property.set(prefs.getLong(key, Long.MIN_VALUE));
}
property.addListener(o -> prefs.putLong(key, property.getValue()));
}
/**
* @see #bind(ObjectProperty)
*
* @param property
* {@link Property} to bind
*/
public void bind(StringProperty property) {
bind(property, property.getName());
}
/**
* @see #bind(ObjectProperty, String)
*
* @param property
* {@link Property} to bind
* @param key
* unique application store key
*/
public void bind(final StringProperty property, String key) {
String value = prefs.get(validateKey(key), null);
if (value != null) {
property.set(value);
}
property.addListener(o -> prefs.put(key, property.getValue()));
}
private final static String validateKey(String key) {
if (key == null || key.length() == 0 || key.length() > Preferences.MAX_KEY_LENGTH) {
throw new IllegalArgumentException("invalid binding key: " + String.valueOf(key));
}
return key;
}
}