diff --git a/.gitignore b/.gitignore index a8580ca7..c5ea2b1d 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,10 @@ target/ .*.swp *~ .settings -build/ \ No newline at end of file +build/ +out/ +.classpath +.project +*.iml +*.ipr +*.iws diff --git a/build.gradle b/build.gradle index 07fd53a3..5b732fda 100644 --- a/build.gradle +++ b/build.gradle @@ -31,6 +31,7 @@ dependencies { compile group: 'org.slf4j', name: 'slf4j-api', version:'1.7.5' runtime group: 'org.slf4j', name: 'slf4j-simple', version:'1.7.5' testCompile group: 'junit', name: 'junit', version:'4.11' + testCompile group: 'de.ruedigermoeller', name: 'fst', version: '2.55' } task sourceJar(type: Jar) { diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ee6500a5..ddb30844 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-bin.zip +distributionUrl=https://services.gradle.org/distributions/gradle-4.1-bin.zip diff --git a/src/main/java/com/palantir/ptoss/cinch/core/DefaultBindableModel.java b/src/main/java/com/palantir/ptoss/cinch/core/DefaultBindableModel.java index f3ed97e1..89cd5258 100755 --- a/src/main/java/com/palantir/ptoss/cinch/core/DefaultBindableModel.java +++ b/src/main/java/com/palantir/ptoss/cinch/core/DefaultBindableModel.java @@ -13,6 +13,8 @@ // limitations under the License. package com.palantir.ptoss.cinch.core; +import java.io.IOException; +import java.io.ObjectInputStream; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; @@ -21,7 +23,13 @@ */ public class DefaultBindableModel implements BindableModel { - private final List bindings = new CopyOnWriteArrayList(); + // must be transient because Bindings aren't serializable, and + // subclasses might be serialized. + private transient List bindings; + + public DefaultBindableModel() { + bindings = new CopyOnWriteArrayList(); + } /** * {@inheritDoc} @@ -62,4 +70,9 @@ public void update() { this.modelUpdated(ModelUpdates.UNSPECIFIED); } + private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + bindings = new CopyOnWriteArrayList(); + } + } diff --git a/src/test/java/com/palantir/ptoss/cinch/SerializableBindableModel.java b/src/test/java/com/palantir/ptoss/cinch/SerializableBindableModel.java new file mode 100644 index 00000000..274458ab --- /dev/null +++ b/src/test/java/com/palantir/ptoss/cinch/SerializableBindableModel.java @@ -0,0 +1,42 @@ +package com.palantir.ptoss.cinch; + +import com.palantir.ptoss.cinch.core.DefaultBindableModel; + +import java.io.Serializable; + +public class SerializableBindableModel extends DefaultBindableModel implements Serializable { + + private final String data; + + public SerializableBindableModel(String data) { + super(); + this.data = data; + } + + public String getData() { + return data; + } + + @Override + public String toString() { + return "SerializableBindableModel{" + + "data='" + data + '\'' + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + SerializableBindableModel that = (SerializableBindableModel) o; + + return data != null ? data.equals(that.data) : that.data == null; + } + + @Override + public int hashCode() { + return data != null ? data.hashCode() : 0; + } + +} diff --git a/src/test/java/com/palantir/ptoss/cinch/SerializableModelTest.java b/src/test/java/com/palantir/ptoss/cinch/SerializableModelTest.java new file mode 100644 index 00000000..733b5cfe --- /dev/null +++ b/src/test/java/com/palantir/ptoss/cinch/SerializableModelTest.java @@ -0,0 +1,68 @@ +package com.palantir.ptoss.cinch; + +import com.palantir.ptoss.cinch.core.Binding; +import com.palantir.ptoss.cinch.core.ModelUpdate; +import org.apache.commons.lang.SerializationUtils; +import org.junit.Assert; +import org.junit.Test; +import org.nustaq.serialization.FSTConfiguration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class SerializableModelTest { + + private static final Logger log = LoggerFactory.getLogger(SerializableModelTest.class); + private static FSTConfiguration conf = FSTConfiguration.createDefaultConfiguration(); + + private final Binding testBinding = new Binding() { + @Override + public & ModelUpdate> void update(T... changed) { + log.info("Ran update"); + } + }; + + @Test + public void testJavaSerialize() { + SerializableBindableModel model = new SerializableBindableModel("testing"); + javaRoundTrip(model); + } + + @Test + public void testJavaSerializeWithBinding() { + SerializableBindableModel model = new SerializableBindableModel("testing"); + model.bind(testBinding); + model.update(); + SerializableBindableModel rehydrated = javaRoundTrip(model); + rehydrated.bind(testBinding); + rehydrated.update(); + } + + private SerializableBindableModel javaRoundTrip(SerializableBindableModel model) { + SerializableBindableModel rehydrated = (SerializableBindableModel) SerializationUtils.clone(model); + Assert.assertEquals(model, rehydrated); + return rehydrated; + } + + @Test + public void testFstSerialize() { + SerializableBindableModel model = new SerializableBindableModel("testing"); + fstRoundTrip(model); + } + + @Test + public void testFstSerializeWithBinding() { + SerializableBindableModel model = new SerializableBindableModel("testing"); + model.bind(testBinding); + model.update(); + SerializableBindableModel rehydrated = fstRoundTrip(model); + rehydrated.bind(testBinding); + rehydrated.update(); + } + + private SerializableBindableModel fstRoundTrip(SerializableBindableModel model) { + byte[] bytes = conf.asByteArray(model); + SerializableBindableModel rehydrated = (SerializableBindableModel)conf.asObject(bytes); + Assert.assertEquals(model, rehydrated); + return rehydrated; + } +}