Sinobu

Usage

Sinobu provides a simple mechanism for persisting the state of objects using the Storable interface. This allows objects to save their properties to JSON file and restore them later, making it easy to maintain application state across restarts.

ðŸĶĪ Interface Based

Implement the Storable interface on your class to enable persistence.

class Data implements Storable<Data> {

    public int property;
}

⚒ïļ Saving Data

The Storable#store() method saves the current state of the object's properties to file. (check property)

void save() {
    Data data = new Data();
    data.property = 10;

    data.store(); // save data to file
}

⚒ïļ Restoring Data

The Storable#restore() method restores the object's properties from file (check property). If the file doesn't exist or an error occurs during reading/parsing, the operation typically fails silently, leaving the object in its current state (often default values).

void restore() {
    Data other = new Data();
    other.restore(); // load data from file

    assert other.property == 10;
}

Transient

Normally all properties are eligible for preservation, but there are several ways to make explicit which properties you do not want to preserve.

If the property is defined by field, add the transient modifier to the field declaration.

class TransientField implements Storable<TransientField> {

    public transient int unstorableProperty;
}

If the property is defined by method, add java.beans.Transient annotation. (You only need to add it to either the setter or the getter)

class TransientMethod implements Storable<TransientMethod> {

    private int value;

    @java.beans.Transient
    public int getUnstorableProperty() {
        return value;
    }

    public void setUnstorableProperty(int value) {
        this.value = value;
    }
}

Automatic Saving

Instead of manually calling Storable#store() every time a change occurs, you can be configured to save their state automatically when their properties change. This is achieved using the Storable#auto() method.

Since monitoring the values of arbitrary properties would be prohibitively expensive, value detection is only possible for properties defined by Variable.

Calling Storable#auto() instance monitors its (and nested) properties. When a change is detected, it schedules a save operation. By default, this operation is debounced (typically waiting 1 second after the last change) to avoid excessive writes during rapid changes.

void autoSave() {
    class Data implements Storable<Data> {
        public final Variable<String> name = Variable.empty();
    }

    Data data = new Data();
    data.auto(); // enable auto-save

    data.name.set("Misa"); // save after 1 sec
}

Calling Disposable#dispose() on the returned object will stop the automatic saving process for that instance.

void stopAutoSave() {
    class Data implements Storable<Data> {
        public final Variable<String> name = Variable.empty();
    }

    Data data = new Data();
    Disposable stopper = data.auto();

    stopper.dispose(); // stop auto-save
}

Storage Location

By default, persistence file is stored in a directory named .preferences within the application's working directory. The filename is derived from the fully qualified class name of the storable object, ending with .json. (e.g., .preferences/com.example.MyAppSettings.json).

This location can be customized in two main ways:

Location Method

You can override the Storable#locate() method within your implementing class to return a custom Path for the persistence file.

class Custom implements Storable<Custom> {

    public Path locate() {
        return Path.of("setting.txt");
    }
}

Environment Variable

You can set a global preference directory by defining the environment variable PreferenceDirectory using I#env(String, Object). If this variable is set, the default implementation will use this directory instead of .preferences.

void define() {
    I.env("PreferenceDirectory", "/user/home/setting");
}
LoggingBenchmark