Make concrete Config and ConfigValue serializable

This commit is contained in:
Havoc Pennington 2012-02-01 12:15:05 -05:00
parent 846c8c116a
commit 2d8e42686c
10 changed files with 122 additions and 7 deletions

View File

@ -3,6 +3,8 @@
*/ */
package com.typesafe.config.impl; package com.typesafe.config.impl;
import java.io.Serializable;
import com.typesafe.config.ConfigException; import com.typesafe.config.ConfigException;
import com.typesafe.config.ConfigMergeable; import com.typesafe.config.ConfigMergeable;
import com.typesafe.config.ConfigOrigin; import com.typesafe.config.ConfigOrigin;
@ -16,7 +18,7 @@ import com.typesafe.config.ConfigValue;
* improperly-factored and non-modular code. Please don't add parent(). * improperly-factored and non-modular code. Please don't add parent().
* *
*/ */
abstract class AbstractConfigValue implements ConfigValue, MergeableValue { abstract class AbstractConfigValue implements ConfigValue, MergeableValue, Serializable {
final private SimpleConfigOrigin origin; final private SimpleConfigOrigin origin;

View File

@ -3,12 +3,13 @@
*/ */
package com.typesafe.config.impl; package com.typesafe.config.impl;
import java.io.Serializable;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import com.typesafe.config.ConfigException; import com.typesafe.config.ConfigException;
final class Path { final class Path implements Serializable {
final private String first; final private String first;
final private Path remainder; final private Path remainder;

View File

@ -3,6 +3,7 @@
*/ */
package com.typesafe.config.impl; package com.typesafe.config.impl;
import java.io.Serializable;
import java.util.AbstractMap; import java.util.AbstractMap;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
@ -28,7 +29,7 @@ import com.typesafe.config.ConfigValueType;
* with a one-level java.util.Map from paths to non-null values. Null values are * with a one-level java.util.Map from paths to non-null values. Null values are
* not "in" the map. * not "in" the map.
*/ */
final class SimpleConfig implements Config, MergeableValue { final class SimpleConfig implements Config, MergeableValue, Serializable {
final private AbstractConfigObject object; final private AbstractConfigObject object;

View File

@ -21,7 +21,8 @@ final class SimpleConfigList extends AbstractConfigValue implements ConfigList {
final private boolean resolved; final private boolean resolved;
SimpleConfigList(ConfigOrigin origin, List<AbstractConfigValue> value) { SimpleConfigList(ConfigOrigin origin, List<AbstractConfigValue> value) {
this(origin, value, ResolveStatus.fromValues(value)); this(origin, value, ResolveStatus
.fromValues(value));
} }
SimpleConfigList(ConfigOrigin origin, List<AbstractConfigValue> value, SimpleConfigList(ConfigOrigin origin, List<AbstractConfigValue> value,

View File

@ -4,6 +4,7 @@
package com.typesafe.config.impl; package com.typesafe.config.impl;
import java.io.File; import java.io.File;
import java.io.Serializable;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.util.ArrayList; import java.util.ArrayList;
@ -17,7 +18,7 @@ import com.typesafe.config.ConfigOrigin;
// it would be cleaner to have a class hierarchy for various origin types, // it would be cleaner to have a class hierarchy for various origin types,
// but was hoping this would be enough simpler to be a little messy. eh. // but was hoping this would be enough simpler to be a little messy. eh.
final class SimpleConfigOrigin implements ConfigOrigin { final class SimpleConfigOrigin implements ConfigOrigin, Serializable {
final private String description; final private String description;
final private int lineNumber; final private int lineNumber;
final private int endLineNumber; final private int endLineNumber;

View File

@ -1,6 +1,8 @@
package com.typesafe.config.impl; package com.typesafe.config.impl;
final class SubstitutionExpression { import java.io.Serializable;
final class SubstitutionExpression implements Serializable {
final private Path path; final private Path path;
final private boolean optional; final private boolean optional;

View File

@ -322,6 +322,11 @@ class ConfigSubstitutionTest extends TestUtils {
""") """)
} }
@Test
def serializeUnresolvedObject() {
checkSerializable(substComplexObject)
}
// this is a weird test, it used to test fallback to system props which made more sense. // this is a weird test, it used to test fallback to system props which made more sense.
// Now it just tests that if you override with system props, you can use system props // Now it just tests that if you override with system props, you can use system props
// in substitutions. // in substitutions.

View File

@ -792,6 +792,12 @@ class ConfigTest extends TestUtils {
assertEquals(None, entries.get("nulls.null")) assertEquals(None, entries.get("nulls.null"))
} }
@Test
def test01Serializable() {
val conf = ConfigFactory.load("test01")
val confCopy = checkSerializable(conf)
}
@Test @Test
def test02SubstitutionsWithWeirdPaths() { def test02SubstitutionsWithWeirdPaths() {
val conf = ConfigFactory.load("test02") val conf = ConfigFactory.load("test02")

View File

@ -28,6 +28,12 @@ class ConfigValueTest extends TestUtils {
checkNotEqualObjects(a, b) checkNotEqualObjects(a, b)
} }
@Test
def configOriginSerializable() {
val a = SimpleConfigOrigin.newSimple("foo")
checkSerializable(a)
}
@Test @Test
def configIntEquality() { def configIntEquality() {
val a = intValue(42) val a = intValue(42)
@ -39,6 +45,13 @@ class ConfigValueTest extends TestUtils {
checkNotEqualObjects(a, b) checkNotEqualObjects(a, b)
} }
@Test
def configIntSerializable() {
val a = intValue(42)
val b = checkSerializable(a)
assertEquals(42, b.unwrapped)
}
@Test @Test
def configLongEquality() { def configLongEquality() {
val a = longValue(Integer.MAX_VALUE + 42L) val a = longValue(Integer.MAX_VALUE + 42L)
@ -104,6 +117,16 @@ class ConfigValueTest extends TestUtils {
checkNotEqualObjects(b, b.toConfig()) checkNotEqualObjects(b, b.toConfig())
} }
@Test
def configObjectSerializable() {
val aMap = configMap("a" -> 1, "b" -> 2, "c" -> 3)
val a = new SimpleConfigObject(fakeOrigin(), aMap)
val b = checkSerializable(a)
assertEquals(1, b.toConfig.getInt("a"))
// check that deserialized Config and ConfigObject refer to each other
assertTrue(b.toConfig.root eq b)
}
@Test @Test
def configListEquality() { def configListEquality() {
val aScalaSeq = Seq(1, 2, 3) map { intValue(_): AbstractConfigValue } val aScalaSeq = Seq(1, 2, 3) map { intValue(_): AbstractConfigValue }
@ -117,6 +140,14 @@ class ConfigValueTest extends TestUtils {
checkNotEqualObjects(aList, bList) checkNotEqualObjects(aList, bList)
} }
@Test
def configListSerializable() {
val aScalaSeq = Seq(1, 2, 3) map { intValue(_): AbstractConfigValue }
val aList = new SimpleConfigList(fakeOrigin(), aScalaSeq.asJava)
val bList = checkSerializable(aList)
assertEquals(1, bList.get(0).unwrapped())
}
@Test @Test
def configSubstitutionEquality() { def configSubstitutionEquality() {
val a = subst("foo") val a = subst("foo")
@ -128,6 +159,12 @@ class ConfigValueTest extends TestUtils {
checkNotEqualObjects(a, b) checkNotEqualObjects(a, b)
} }
@Test
def configSubstitutionSerializable() {
val a = subst("foo")
val b = checkSerializable(a)
}
@Test @Test
def configDelayedMergeEquality() { def configDelayedMergeEquality() {
val s1 = subst("foo") val s1 = subst("foo")
@ -141,6 +178,14 @@ class ConfigValueTest extends TestUtils {
checkNotEqualObjects(a, b) checkNotEqualObjects(a, b)
} }
@Test
def configDelayedMergeSerializable() {
val s1 = subst("foo")
val s2 = subst("bar")
val a = new ConfigDelayedMerge(fakeOrigin(), List[AbstractConfigValue](s1, s2).asJava)
val b = checkSerializable(a)
}
@Test @Test
def configDelayedMergeObjectEquality() { def configDelayedMergeObjectEquality() {
val empty = SimpleConfigObject.empty() val empty = SimpleConfigObject.empty()
@ -155,6 +200,15 @@ class ConfigValueTest extends TestUtils {
checkNotEqualObjects(a, b) checkNotEqualObjects(a, b)
} }
@Test
def configDelayedMergeObjectSerializable() {
val empty = SimpleConfigObject.empty()
val s1 = subst("foo")
val s2 = subst("bar")
val a = new ConfigDelayedMergeObject(fakeOrigin(), List[AbstractConfigValue](empty, s1, s2).asJava)
val b = checkSerializable(a)
}
@Test @Test
def valuesToString() { def valuesToString() {
// just check that these don't throw, the exact output // just check that these don't throw, the exact output

View File

@ -13,6 +13,10 @@ import com.typesafe.config.Config
import com.typesafe.config.ConfigSyntax import com.typesafe.config.ConfigSyntax
import com.typesafe.config.ConfigFactory import com.typesafe.config.ConfigFactory
import java.io.File import java.io.File
import java.io.ByteArrayOutputStream
import java.io.ObjectOutputStream
import java.io.ByteArrayInputStream
import java.io.ObjectInputStream
abstract trait TestUtils { abstract trait TestUtils {
protected def intercept[E <: Throwable: Manifest](block: => Unit): E = { protected def intercept[E <: Throwable: Manifest](block: => Unit): E = {
@ -82,6 +86,44 @@ abstract trait TestUtils {
checkNotEqualToRandomOtherThing(b) checkNotEqualToRandomOtherThing(b)
} }
private def copyViaSerialize(o: java.io.Serializable): AnyRef = {
val byteStream = new ByteArrayOutputStream()
val objectStream = new ObjectOutputStream(byteStream)
objectStream.writeObject(o)
objectStream.close()
val inStream = new ByteArrayInputStream(byteStream.toByteArray())
val inObjectStream = new ObjectInputStream(inStream)
val copy = inObjectStream.readObject()
inObjectStream.close()
copy
}
protected def checkSerializable[T: Manifest](o: T): T = {
checkEqualObjects(o, o)
assertTrue(o.getClass.getSimpleName + " not an instance of Serializable", o.isInstanceOf[java.io.Serializable])
val a = o.asInstanceOf[java.io.Serializable]
val b = try {
copyViaSerialize(a)
} catch {
case nf: ClassNotFoundException =>
throw new AssertionError("failed to make a copy via serialization, " +
"possibly caused by http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6446627",
nf)
case e: Exception =>
throw new AssertionError("failed to make a copy via serialization", e)
}
assertTrue("deserialized type " + b.getClass.getSimpleName + " doesn't match serialized type " + a.getClass.getSimpleName,
manifest[T].erasure.isAssignableFrom(b.getClass))
checkEqualObjects(a, b)
b.asInstanceOf[T]
}
def fakeOrigin() = { def fakeOrigin() = {
SimpleConfigOrigin.newSimple("fake origin") SimpleConfigOrigin.newSimple("fake origin")
} }
@ -372,7 +414,7 @@ abstract trait TestUtils {
protected def substInString(ref: String, optional: Boolean): ConfigSubstitution = { protected def substInString(ref: String, optional: Boolean): ConfigSubstitution = {
import scala.collection.JavaConverters._ import scala.collection.JavaConverters._
val path = Path.newPath(ref) val path = Path.newPath(ref)
val pieces = List("start<", new SubstitutionExpression(path, optional), ">end") val pieces = List[AnyRef]("start<", new SubstitutionExpression(path, optional), ">end")
new ConfigSubstitution(fakeOrigin(), pieces.asJava) new ConfigSubstitution(fakeOrigin(), pieces.asJava)
} }