Serialization-pocalypse: change serialization format

The previous use of Java's default serialization dumped
all implementation-detail class names and fields into the serialization,
making it basically impossible to improve the implementation.

Two strategies here:
 - prohibit serialization of unresolved configs, which are
   the location of a lot of implementation detail
 - delegate all serialization to an Externalizable
   SerializedConfigValue class, which serializes
   using fields that have lengths. Unknown fields
   can thus be skipped and we can write code to
   support obsolete fields, and so on.

As a side effect, this makes the serialization far more compact
because we don't need the Java per-class header noise, and we
jump through some hoops to avoid writing out duplicate ConfigOrigin
information. It still isn't super-compact compared to something
like protobuf but it's a lot less insane.
This commit is contained in:
Havoc Pennington 2012-04-12 13:04:14 -04:00
parent d14d8cae78
commit 388d85fb5d
28 changed files with 992 additions and 499 deletions

16
NEWS.md
View File

@ -1,5 +1,15 @@
# 0.NEXT.0: Sometime
- the serialization format has changed to one that's extensible
and lets the library evolve without breaking serialization all
the time. The new format is also much more compact. However,
this change is incompatible with old serializations, if you
rely on that. The hope is to avoid serialization breakage in
the future now that the format is not the default Java one
(which was a direct dump of all the implementation details).
- serializing an unresolved Config (one that hasn't had
resolve() called on it) is no longer supported, you will get
NotSerializableException if you try.
- supports self-referential substitutions, such as
`path=${path}":/bin"`, by "looking backward" to the previous
value of `path`
@ -32,12 +42,6 @@
ConfigIncludeContext.parseOptions() if appropriate.
- cycles in include statements (self-includes) are now detected
and result in a nicer error instead of stack overflow
- the serialization format has changed for a Config that has not
had resolve() called on it. The library can still deserialize
the old format, but old versions of the library will not be
able to deserialize the new format. Serializing unresolved
Config is probably a bad idea anyway and maybe shouldn't even
be supported, but keeping it for back compat.
- since 0.3.0, there is an obscure incompatible semantic change
in that self-referential substitutions where the cycle could
be broken by partially resolving the object now "look backward"

View File

@ -18,8 +18,6 @@ libraryDependencies += "net.liftweb" %% "lift-json" % "2.4" % "test"
libraryDependencies += "com.novocode" % "junit-interface" % "0.7" % "test"
libraryDependencies += "commons-codec" % "commons-codec" % "1.4" % "test"
externalResolvers += "Scala Tools Snapshots" at "http://scala-tools.org/repo-snapshots/"
seq(findbugsSettings : _*)

View File

@ -18,8 +18,6 @@ import com.typesafe.config.ConfigValueType;
abstract class AbstractConfigObject extends AbstractConfigValue implements ConfigObject {
private static final long serialVersionUID = 1L;
final private SimpleConfig config;
protected AbstractConfigObject(ConfigOrigin origin) {

View File

@ -3,7 +3,6 @@
*/
package com.typesafe.config.impl;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@ -21,9 +20,7 @@ import com.typesafe.config.ConfigValue;
* improperly-factored and non-modular code. Please don't add parent().
*
*/
abstract class AbstractConfigValue implements ConfigValue, MergeableValue, Serializable {
private static final long serialVersionUID = 1L;
abstract class AbstractConfigValue implements ConfigValue, MergeableValue {
final private SimpleConfigOrigin origin;

View File

@ -3,12 +3,15 @@
*/
package com.typesafe.config.impl;
import java.io.ObjectStreamException;
import java.io.Serializable;
import com.typesafe.config.ConfigOrigin;
import com.typesafe.config.ConfigValueType;
final class ConfigBoolean extends AbstractConfigValue {
final class ConfigBoolean extends AbstractConfigValue implements Serializable {
private static final long serialVersionUID = 1L;
private static final long serialVersionUID = 2L;
final private boolean value;
@ -36,4 +39,9 @@ final class ConfigBoolean extends AbstractConfigValue {
protected ConfigBoolean newCopy(ConfigOrigin origin) {
return new ConfigBoolean(origin, value);
}
// serialization all goes through SerializedConfigValue
private Object writeReplace() throws ObjectStreamException {
return new SerializedConfigValue(this);
}
}

View File

@ -1,6 +1,5 @@
package com.typesafe.config.impl;
import java.io.ObjectStreamException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@ -23,7 +22,6 @@ import com.typesafe.config.ConfigValueType;
* since a concat of objects really will merge, not concatenate.
*/
final class ConfigConcatenation extends AbstractConfigValue implements Unmergeable {
private static final long serialVersionUID = 1L;
final private List<AbstractConfigValue> pieces;
@ -234,16 +232,6 @@ final class ConfigConcatenation extends AbstractConfigValue implements Unmergeab
}
}
// This ridiculous hack is because some JDK versions apparently can't
// serialize an array, which is used to implement ArrayList and EmptyList.
// maybe
// http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6446627
private Object writeReplace() throws ObjectStreamException {
// switch to LinkedList
return new ConfigConcatenation(origin(), new java.util.LinkedList<AbstractConfigValue>(
pieces));
}
static List<AbstractConfigValue> valuesFromPieces(ConfigOrigin origin, List<Object> pieces) {
List<AbstractConfigValue> values = new ArrayList<AbstractConfigValue>(pieces.size());
for (Object p : pieces) {

View File

@ -3,7 +3,6 @@
*/
package com.typesafe.config.impl;
import java.io.ObjectStreamException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@ -24,15 +23,8 @@ import com.typesafe.config.ConfigValueType;
final class ConfigDelayedMerge extends AbstractConfigValue implements Unmergeable,
ReplaceableMergeStack {
private static final long serialVersionUID = 1L;
// earlier items in the stack win
final private List<AbstractConfigValue> stack;
// this is just here for serialization compat; whether we ignore is purely
// a function of the bottom of the merge stack
@SuppressWarnings("unused")
@Deprecated
final private boolean ignoresFallbacks = false;
ConfigDelayedMerge(ConfigOrigin origin, List<AbstractConfigValue> stack) {
super(origin);
@ -283,14 +275,4 @@ final class ConfigDelayedMerge extends AbstractConfigValue implements Unmergeabl
sb.append("# ) end of unresolved merge\n");
}
}
// This ridiculous hack is because some JDK versions apparently can't
// serialize an array, which is used to implement ArrayList and EmptyList.
// maybe
// http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6446627
private Object writeReplace() throws ObjectStreamException {
// switch to LinkedList
return new ConfigDelayedMerge(origin(),
new java.util.LinkedList<AbstractConfigValue>(stack));
}
}

View File

@ -3,7 +3,6 @@
*/
package com.typesafe.config.impl;
import java.io.ObjectStreamException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@ -21,16 +20,8 @@ import com.typesafe.config.ConfigValue;
final class ConfigDelayedMergeObject extends AbstractConfigObject implements Unmergeable,
ReplaceableMergeStack {
private static final long serialVersionUID = 1L;
final private List<AbstractConfigValue> stack;
// this is just here for serialization compat; whether we ignore is purely
// a function of the bottom of the merge stack
@SuppressWarnings("unused")
@Deprecated
final private boolean ignoresFallbacks = false;
ConfigDelayedMergeObject(ConfigOrigin origin, List<AbstractConfigValue> stack) {
super(origin);
this.stack = stack;
@ -317,14 +308,4 @@ final class ConfigDelayedMergeObject extends AbstractConfigObject implements Unm
"Delayed merge stack does not contain any unmergeable values");
}
// This ridiculous hack is because some JDK versions apparently can't
// serialize an array, which is used to implement ArrayList and EmptyList.
// maybe
// http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6446627
private Object writeReplace() throws ObjectStreamException {
// switch to LinkedList
return new ConfigDelayedMergeObject(origin(),
new java.util.LinkedList<AbstractConfigValue>(stack));
}
}

View File

@ -3,12 +3,15 @@
*/
package com.typesafe.config.impl;
import java.io.ObjectStreamException;
import java.io.Serializable;
import com.typesafe.config.ConfigOrigin;
import com.typesafe.config.ConfigValueType;
final class ConfigDouble extends ConfigNumber {
final class ConfigDouble extends ConfigNumber implements Serializable {
private static final long serialVersionUID = 1L;
private static final long serialVersionUID = 2L;
final private double value;
@ -50,4 +53,9 @@ final class ConfigDouble extends ConfigNumber {
protected ConfigDouble newCopy(ConfigOrigin origin) {
return new ConfigDouble(origin, value, originalText);
}
// serialization all goes through SerializedConfigValue
private Object writeReplace() throws ObjectStreamException {
return new SerializedConfigValue(this);
}
}

View File

@ -3,12 +3,15 @@
*/
package com.typesafe.config.impl;
import java.io.ObjectStreamException;
import java.io.Serializable;
import com.typesafe.config.ConfigOrigin;
import com.typesafe.config.ConfigValueType;
final class ConfigInt extends ConfigNumber {
final class ConfigInt extends ConfigNumber implements Serializable {
private static final long serialVersionUID = 1L;
private static final long serialVersionUID = 2L;
final private int value;
@ -50,4 +53,9 @@ final class ConfigInt extends ConfigNumber {
protected ConfigInt newCopy(ConfigOrigin origin) {
return new ConfigInt(origin, value, originalText);
}
// serialization all goes through SerializedConfigValue
private Object writeReplace() throws ObjectStreamException {
return new SerializedConfigValue(this);
}
}

View File

@ -3,12 +3,15 @@
*/
package com.typesafe.config.impl;
import java.io.ObjectStreamException;
import java.io.Serializable;
import com.typesafe.config.ConfigOrigin;
import com.typesafe.config.ConfigValueType;
final class ConfigLong extends ConfigNumber {
final class ConfigLong extends ConfigNumber implements Serializable {
private static final long serialVersionUID = 1L;
private static final long serialVersionUID = 2L;
final private long value;
@ -50,4 +53,9 @@ final class ConfigLong extends ConfigNumber {
protected ConfigLong newCopy(ConfigOrigin origin) {
return new ConfigLong(origin, value, originalText);
}
// serialization all goes through SerializedConfigValue
private Object writeReplace() throws ObjectStreamException {
return new SerializedConfigValue(this);
}
}

View File

@ -3,6 +3,9 @@
*/
package com.typesafe.config.impl;
import java.io.ObjectStreamException;
import java.io.Serializable;
import com.typesafe.config.ConfigOrigin;
import com.typesafe.config.ConfigValueType;
@ -14,9 +17,9 @@ import com.typesafe.config.ConfigValueType;
* not.
*
*/
final class ConfigNull extends AbstractConfigValue {
final class ConfigNull extends AbstractConfigValue implements Serializable {
private static final long serialVersionUID = 1L;
private static final long serialVersionUID = 2L;
ConfigNull(ConfigOrigin origin) {
super(origin);
@ -46,4 +49,9 @@ final class ConfigNull extends AbstractConfigValue {
protected ConfigNull newCopy(ConfigOrigin origin) {
return new ConfigNull(origin);
}
// serialization all goes through SerializedConfigValue
private Object writeReplace() throws ObjectStreamException {
return new SerializedConfigValue(this);
}
}

View File

@ -3,12 +3,15 @@
*/
package com.typesafe.config.impl;
import java.io.ObjectStreamException;
import java.io.Serializable;
import com.typesafe.config.ConfigException;
import com.typesafe.config.ConfigOrigin;
abstract class ConfigNumber extends AbstractConfigValue {
abstract class ConfigNumber extends AbstractConfigValue implements Serializable {
private static final long serialVersionUID = 1L;
private static final long serialVersionUID = 2L;
// This is so when we concatenate a number into a string (say it appears in
// a sentence) we always have it exactly as the person typed it into the
@ -99,4 +102,9 @@ abstract class ConfigNumber extends AbstractConfigValue {
return new ConfigDouble(origin, number, originalText);
}
}
// serialization all goes through SerializedConfigValue
private Object writeReplace() throws ObjectStreamException {
return new SerializedConfigValue(this);
}
}

View File

@ -13,7 +13,6 @@ import com.typesafe.config.ConfigValueType;
* kind of value.
*/
final class ConfigReference extends AbstractConfigValue implements Unmergeable {
private static final long serialVersionUID = 1L;
final private SubstitutionExpression expr;
// the length of any prefixes added with relativized()

View File

@ -3,12 +3,15 @@
*/
package com.typesafe.config.impl;
import java.io.ObjectStreamException;
import java.io.Serializable;
import com.typesafe.config.ConfigOrigin;
import com.typesafe.config.ConfigValueType;
final class ConfigString extends AbstractConfigValue {
final class ConfigString extends AbstractConfigValue implements Serializable {
private static final long serialVersionUID = 1L;
private static final long serialVersionUID = 2L;
final private String value;
@ -41,4 +44,9 @@ final class ConfigString extends AbstractConfigValue {
protected ConfigString newCopy(ConfigOrigin origin) {
return new ConfigString(origin, value);
}
// serialization all goes through SerializedConfigValue
private Object writeReplace() throws ObjectStreamException {
return new SerializedConfigValue(this);
}
}

View File

@ -5,6 +5,7 @@ package com.typesafe.config.impl;
import java.io.IOException;
import java.io.ObjectStreamException;
import java.io.Serializable;
import java.util.Collection;
import java.util.List;
@ -18,8 +19,7 @@ import com.typesafe.config.ConfigValueType;
* and ConfigConcatenation.
*/
@Deprecated
final class ConfigSubstitution extends AbstractConfigValue implements
Unmergeable {
final class ConfigSubstitution extends AbstractConfigValue implements Unmergeable, Serializable {
private static final long serialVersionUID = 1L;

View File

@ -1,5 +1,6 @@
package com.typesafe.config.impl;
// caution: ordinals used in serialization
enum OriginType {
GENERIC,
FILE,

View File

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

View File

@ -0,0 +1,478 @@
/**
* Copyright (C) 2011-2012 Typesafe Inc. <http://typesafe.com>
*/
package com.typesafe.config.impl;
import java.io.ByteArrayOutputStream;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.DataOutputStream;
import java.io.Externalizable;
import java.io.IOException;
import java.io.NotSerializableException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.io.ObjectStreamException;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigException;
import com.typesafe.config.ConfigList;
import com.typesafe.config.ConfigObject;
import com.typesafe.config.ConfigOrigin;
import com.typesafe.config.ConfigValue;
import com.typesafe.config.ConfigValueType;
/**
* Deliberately shoving all the serialization code into this class instead of
* doing it OO-style with each subclass. Seems better to have it all in one
* place. This class implements a lame serialization format that supports
* skipping unknown fields, so it's moderately more extensible than the default
* Java serialization format.
*/
class SerializedConfigValue extends AbstractConfigValue implements Externalizable {
// this is the version used by Java serialization, if it increments it's
// essentially an ABI break and bad
private static final long serialVersionUID = 1L;
// this is how we try to be extensible
static enum SerializedField {
// represents a field code we didn't recognize
UNKNOWN,
// end of a list of fields
END_MARKER,
// Fields at the root
ROOT_VALUE,
ROOT_WAS_CONFIG,
// Fields that make up a value
VALUE_DATA,
VALUE_ORIGIN,
// Fields that make up an origin
ORIGIN_DESCRIPTION,
ORIGIN_LINE_NUMBER,
ORIGIN_END_LINE_NUMBER,
ORIGIN_TYPE,
ORIGIN_URL,
ORIGIN_COMMENTS,
ORIGIN_NULL_DESCRIPTION,
ORIGIN_NULL_URL,
ORIGIN_NULL_COMMENTS;
static SerializedField forInt(int b) {
if (b < values().length)
return values()[b];
else
return UNKNOWN;
}
};
private static enum SerializedValueType {
// the ordinals here are in the wire format, caution
NULL(ConfigValueType.NULL),
BOOLEAN(ConfigValueType.BOOLEAN),
INT(ConfigValueType.NUMBER),
LONG(ConfigValueType.NUMBER),
DOUBLE(ConfigValueType.NUMBER),
STRING(ConfigValueType.STRING),
LIST(ConfigValueType.LIST),
OBJECT(ConfigValueType.OBJECT);
ConfigValueType configType;
SerializedValueType(ConfigValueType configType) {
this.configType = configType;
}
static SerializedValueType forInt(int b) {
if (b < values().length)
return values()[b];
else
return null;
}
static SerializedValueType forValue(ConfigValue value) {
ConfigValueType t = value.valueType();
if (t == ConfigValueType.NUMBER) {
if (value instanceof ConfigInt)
return INT;
else if (value instanceof ConfigLong)
return LONG;
else if (value instanceof ConfigDouble)
return DOUBLE;
} else {
for (SerializedValueType st : values()) {
if (st.configType == t)
return st;
}
}
throw new ConfigException.BugOrBroken("don't know how to serialize " + value);
}
};
private ConfigValue value;
private boolean wasConfig;
// this has to be public for the Java deserializer
public SerializedConfigValue() {
super(null);
}
SerializedConfigValue(ConfigValue value) {
this();
this.value = value;
this.wasConfig = false;
}
SerializedConfigValue(Config conf) {
this(conf.root());
this.wasConfig = true;
}
// when Java deserializer reads this object, return the contained
// object instead.
private Object readResolve() throws ObjectStreamException {
if (wasConfig)
return ((ConfigObject) value).toConfig();
else
return value;
}
private static class FieldOut {
final SerializedField code;
final ByteArrayOutputStream bytes;
final DataOutput data;
FieldOut(SerializedField code) {
this.code = code;
this.bytes = new ByteArrayOutputStream();
this.data = new DataOutputStream(bytes);
}
}
// this is a separate function to prevent bugs writing to the
// outer stream instead of field.data
private static void writeOriginField(DataOutput out, SerializedField code, Object v)
throws IOException {
switch (code) {
case ORIGIN_DESCRIPTION:
out.writeUTF((String) v);
break;
case ORIGIN_LINE_NUMBER:
out.writeInt((Integer) v);
break;
case ORIGIN_END_LINE_NUMBER:
out.writeInt((Integer) v);
break;
case ORIGIN_TYPE:
out.writeByte((Integer) v);
break;
case ORIGIN_URL:
out.writeUTF((String) v);
break;
case ORIGIN_COMMENTS:
@SuppressWarnings("unchecked")
List<String> list = (List<String>) v;
int size = list.size();
out.writeInt(size);
for (String s : list) {
out.writeUTF(s);
}
break;
case ORIGIN_NULL_DESCRIPTION: // FALL THRU
case ORIGIN_NULL_URL: // FALL THRU
case ORIGIN_NULL_COMMENTS:
// nothing to write out besides code and length
break;
default:
throw new IOException("Unhandled field from origin: " + code);
}
}
private static void writeOrigin(DataOutput out, SimpleConfigOrigin origin,
SimpleConfigOrigin baseOrigin) throws IOException {
Map<SerializedField, Object> m = origin.toFieldsDelta(baseOrigin);
for (Map.Entry<SerializedField, Object> e : m.entrySet()) {
FieldOut field = new FieldOut(e.getKey());
Object v = e.getValue();
writeOriginField(field.data, field.code, v);
writeField(out, field);
}
writeEndMarker(out);
}
private static SimpleConfigOrigin readOrigin(DataInput in, SimpleConfigOrigin baseOrigin)
throws IOException {
Map<SerializedField, Object> m = new EnumMap<SerializedField, Object>(SerializedField.class);
while (true) {
Object v = null;
SerializedField field = readCode(in);
switch (field) {
case END_MARKER:
return SimpleConfigOrigin.fromBase(baseOrigin, m);
case ORIGIN_DESCRIPTION:
in.readInt(); // discard length
v = in.readUTF();
break;
case ORIGIN_LINE_NUMBER:
in.readInt(); // discard length
v = in.readInt();
break;
case ORIGIN_END_LINE_NUMBER:
in.readInt(); // discard length
v = in.readInt();
break;
case ORIGIN_TYPE:
in.readInt(); // discard length
v = in.readUnsignedByte();
break;
case ORIGIN_URL:
in.readInt(); // discard length
v = in.readUTF();
break;
case ORIGIN_COMMENTS:
in.readInt(); // discard length
int size = in.readInt();
List<String> list = new ArrayList<String>(size);
for (int i = 0; i < size; ++i) {
list.add(in.readUTF());
}
v = list;
break;
case ORIGIN_NULL_DESCRIPTION: // FALL THRU
case ORIGIN_NULL_URL: // FALL THRU
case ORIGIN_NULL_COMMENTS:
// nothing to read besides code and length
in.readInt(); // discard length
v = ""; // just something non-null to put in the map
break;
case ROOT_VALUE:
case ROOT_WAS_CONFIG:
case VALUE_DATA:
case VALUE_ORIGIN:
throw new IOException("Not expecting this field here: " + field);
case UNKNOWN:
// skip unknown field
skipField(in);
break;
}
if (v != null)
m.put(field, v);
}
}
private static void writeValueData(DataOutput out, ConfigValue value) throws IOException {
SerializedValueType st = SerializedValueType.forValue(value);
out.writeByte(st.ordinal());
switch (st) {
case BOOLEAN:
out.writeBoolean(((ConfigBoolean) value).unwrapped());
break;
case NULL:
break;
case INT:
// saving numbers as both string and binary is redundant but easy
out.writeInt(((ConfigInt) value).unwrapped());
out.writeUTF(((ConfigNumber) value).transformToString());
break;
case LONG:
out.writeLong(((ConfigLong) value).unwrapped());
out.writeUTF(((ConfigNumber) value).transformToString());
break;
case DOUBLE:
out.writeDouble(((ConfigDouble) value).unwrapped());
out.writeUTF(((ConfigNumber) value).transformToString());
break;
case STRING:
out.writeUTF(((ConfigString) value).unwrapped());
break;
case LIST:
ConfigList list = (ConfigList) value;
out.writeInt(list.size());
for (ConfigValue v : list) {
writeValue(out, v, (SimpleConfigOrigin) list.origin());
}
break;
case OBJECT:
ConfigObject obj = (ConfigObject) value;
out.writeInt(obj.size());
for (Map.Entry<String, ConfigValue> e : obj.entrySet()) {
out.writeUTF(e.getKey());
writeValue(out, e.getValue(), (SimpleConfigOrigin) obj.origin());
}
break;
}
}
private static AbstractConfigValue readValueData(DataInput in, SimpleConfigOrigin origin)
throws IOException {
int stb = in.readUnsignedByte();
SerializedValueType st = SerializedValueType.forInt(stb);
if (st == null)
throw new IOException("Unknown serialized value type: " + stb);
switch (st) {
case BOOLEAN:
return new ConfigBoolean(origin, in.readBoolean());
case NULL:
return new ConfigNull(origin);
case INT:
int vi = in.readInt();
String si = in.readUTF();
return new ConfigInt(origin, vi, si);
case LONG:
long vl = in.readLong();
String sl = in.readUTF();
return new ConfigLong(origin, vl, sl);
case DOUBLE:
double vd = in.readDouble();
String sd = in.readUTF();
return new ConfigDouble(origin, vd, sd);
case STRING:
return new ConfigString(origin, in.readUTF());
case LIST:
int listSize = in.readInt();
List<AbstractConfigValue> list = new ArrayList<AbstractConfigValue>(listSize);
for (int i = 0; i < listSize; ++i) {
AbstractConfigValue v = readValue(in, origin);
list.add(v);
}
return new SimpleConfigList(origin, list);
case OBJECT:
int mapSize = in.readInt();
Map<String, AbstractConfigValue> map = new HashMap<String, AbstractConfigValue>(mapSize);
for (int i = 0; i < mapSize; ++i) {
String key = in.readUTF();
AbstractConfigValue v = readValue(in, origin);
map.put(key, v);
}
return new SimpleConfigObject(origin, map);
}
throw new IOException("Unhandled serialized value type: " + st);
}
private static void writeValue(DataOutput out, ConfigValue value, SimpleConfigOrigin baseOrigin)
throws IOException {
FieldOut origin = new FieldOut(SerializedField.VALUE_ORIGIN);
writeOrigin(origin.data, (SimpleConfigOrigin) value.origin(),
baseOrigin);
writeField(out, origin);
FieldOut data = new FieldOut(SerializedField.VALUE_DATA);
writeValueData(data.data, value);
writeField(out, data);
writeEndMarker(out);
}
private static AbstractConfigValue readValue(DataInput in, SimpleConfigOrigin baseOrigin)
throws IOException {
AbstractConfigValue value = null;
SimpleConfigOrigin origin = null;
while (true) {
SerializedField code = readCode(in);
if (code == SerializedField.END_MARKER) {
if (value == null)
throw new IOException("No value data found in serialization of value");
return value;
} else if (code == SerializedField.VALUE_DATA) {
if (origin == null)
throw new IOException("Origin must be stored before value data");
in.readInt(); // discard length
value = readValueData(in, origin);
} else if (code == SerializedField.VALUE_ORIGIN) {
in.readInt(); // discard length
origin = readOrigin(in, baseOrigin);
} else {
// ignore unknown field
skipField(in);
}
}
}
private static void writeField(DataOutput out, FieldOut field) throws IOException {
byte[] bytes = field.bytes.toByteArray();
out.writeByte(field.code.ordinal());
out.writeInt(bytes.length);
out.write(bytes);
}
private static void writeEndMarker(DataOutput out) throws IOException {
out.writeByte(SerializedField.END_MARKER.ordinal());
}
private static SerializedField readCode(DataInput in) throws IOException {
int c = in.readUnsignedByte();
if (c == SerializedField.UNKNOWN.ordinal())
throw new IOException("field code " + c + " is not supposed to be on the wire");
return SerializedField.forInt(c);
}
private static void skipField(DataInput in) throws IOException {
int len = in.readInt();
in.skipBytes(len);
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
if (((AbstractConfigValue) value).resolveStatus() != ResolveStatus.RESOLVED)
throw new NotSerializableException(
"tried to serialize a value with unresolved substitutions, need to Config#resolve() first, see API docs");
FieldOut field = new FieldOut(SerializedField.ROOT_VALUE);
writeValue(field.data, value, null /* baseOrigin */);
writeField(out, field);
field = new FieldOut(SerializedField.ROOT_WAS_CONFIG);
field.data.writeBoolean(wasConfig);
writeField(out, field);
writeEndMarker(out);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
while (true) {
SerializedField code = readCode(in);
if (code == SerializedField.END_MARKER) {
return;
} else if (code == SerializedField.ROOT_VALUE) {
in.readInt(); // discard length
this.value = readValue(in, null /* baseOrigin */);
} else if (code == SerializedField.ROOT_WAS_CONFIG) {
in.readInt(); // discard length
this.wasConfig = in.readBoolean();
} else {
// ignore unknown field
skipField(in);
}
}
}
private static ConfigException shouldNotBeUsed() {
return new ConfigException.BugOrBroken(SerializedConfigValue.class.getName()
+ " should not exist outside of serialization");
}
@Override
public ConfigValueType valueType() {
throw shouldNotBeUsed();
}
@Override
public Object unwrapped() {
throw shouldNotBeUsed();
}
@Override
protected SerializedConfigValue newCopy(ConfigOrigin origin) {
throw shouldNotBeUsed();
}
}

View File

@ -3,6 +3,7 @@
*/
package com.typesafe.config.impl;
import java.io.ObjectStreamException;
import java.io.Serializable;
import java.util.AbstractMap;
import java.util.ArrayList;
@ -844,4 +845,9 @@ final class SimpleConfig implements Config, MergeableValue, Serializable {
Path path = Path.newPath(pathExpression);
return new SimpleConfig(root().withoutPath(path));
}
// serialization all goes through SerializedConfigValue
private Object writeReplace() throws ObjectStreamException {
return new SerializedConfigValue(this);
}
}

View File

@ -4,6 +4,7 @@
package com.typesafe.config.impl;
import java.io.ObjectStreamException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
@ -16,9 +17,9 @@ import com.typesafe.config.ConfigOrigin;
import com.typesafe.config.ConfigValue;
import com.typesafe.config.ConfigValueType;
final class SimpleConfigList extends AbstractConfigValue implements ConfigList {
final class SimpleConfigList extends AbstractConfigValue implements ConfigList, Serializable {
private static final long serialVersionUID = 1L;
private static final long serialVersionUID = 2L;
final private List<AbstractConfigValue> value;
final private boolean resolved;
@ -409,13 +410,8 @@ final class SimpleConfigList extends AbstractConfigValue implements ConfigList {
return new SimpleConfigList(combinedOrigin, combined);
}
// This ridiculous hack is because some JDK versions apparently can't
// serialize an array, which is used to implement ArrayList and EmptyList.
// maybe
// http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6446627
// serialization all goes through SerializedConfigValue
private Object writeReplace() throws ObjectStreamException {
// switch to LinkedList
return new SimpleConfigList(origin(), new java.util.LinkedList<AbstractConfigValue>(value),
resolveStatus());
return new SerializedConfigValue(this);
}
}

View File

@ -3,6 +3,8 @@
*/
package com.typesafe.config.impl;
import java.io.ObjectStreamException;
import java.io.Serializable;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Collection;
@ -18,9 +20,9 @@ import com.typesafe.config.ConfigObject;
import com.typesafe.config.ConfigOrigin;
import com.typesafe.config.ConfigValue;
final class SimpleConfigObject extends AbstractConfigObject {
final class SimpleConfigObject extends AbstractConfigObject implements Serializable {
private static final long serialVersionUID = 1L;
private static final long serialVersionUID = 2L;
// this map should never be modified - assume immutable
final private Map<String, AbstractConfigValue> value;
@ -484,4 +486,9 @@ final class SimpleConfigObject extends AbstractConfigObject {
baseOrigin.description() + " (not found)"),
Collections.<String, AbstractConfigValue> emptyMap());
}
// serialization all goes through SerializedConfigValue
private Object writeReplace() throws ObjectStreamException {
return new SerializedConfigValue(this);
}
}

View File

@ -4,23 +4,24 @@
package com.typesafe.config.impl;
import java.io.File;
import java.io.Serializable;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import com.typesafe.config.ConfigException;
import com.typesafe.config.ConfigOrigin;
import com.typesafe.config.impl.SerializedConfigValue.SerializedField;
// 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.
final class SimpleConfigOrigin implements ConfigOrigin, Serializable {
private static final long serialVersionUID = 1L;
final class SimpleConfigOrigin implements ConfigOrigin {
final private String description;
final private int lineNumber;
@ -349,4 +350,171 @@ final class SimpleConfigOrigin implements ConfigOrigin, Serializable {
return mergeOrigins(remaining);
}
}
Map<SerializedField, Object> toFields() {
Map<SerializedField, Object> m = new EnumMap<SerializedField, Object>(SerializedField.class);
if (description != null)
m.put(SerializedField.ORIGIN_DESCRIPTION, description);
if (lineNumber >= 0)
m.put(SerializedField.ORIGIN_LINE_NUMBER, lineNumber);
if (endLineNumber >= 0)
m.put(SerializedField.ORIGIN_END_LINE_NUMBER, endLineNumber);
m.put(SerializedField.ORIGIN_TYPE, originType.ordinal());
if (urlOrNull != null)
m.put(SerializedField.ORIGIN_URL, urlOrNull);
if (commentsOrNull != null)
m.put(SerializedField.ORIGIN_COMMENTS, commentsOrNull);
return m;
}
Map<SerializedField, Object> toFieldsDelta(SimpleConfigOrigin baseOrigin) {
Map<SerializedField, Object> baseFields;
if (baseOrigin != null)
baseFields = baseOrigin.toFields();
else
baseFields = Collections.<SerializedField, Object> emptyMap();
return fieldsDelta(baseFields, toFields());
}
// Here we're trying to avoid serializing the same info over and over
// in the common case that child objects have the same origin fields
// as their parent objects. e.g. we don't need to store the source
// filename with every single value.
static Map<SerializedField, Object> fieldsDelta(Map<SerializedField, Object> base,
Map<SerializedField, Object> child) {
Map<SerializedField, Object> m = new EnumMap<SerializedField, Object>(child);
for (Map.Entry<SerializedField, Object> baseEntry : base.entrySet()) {
SerializedField f = baseEntry.getKey();
if (m.containsKey(f)
&& ConfigImplUtil.equalsHandlingNull(baseEntry.getValue(), m.get(f))) {
// if field is unchanged, just remove it so we inherit
m.remove(f);
} else if (!m.containsKey(f)) {
// if field has been removed, we have to add a deletion entry
switch (f) {
case ORIGIN_DESCRIPTION:
m.put(SerializedField.ORIGIN_NULL_DESCRIPTION, "");
break;
case ORIGIN_LINE_NUMBER:
m.put(SerializedField.ORIGIN_LINE_NUMBER, -1);
break;
case ORIGIN_END_LINE_NUMBER:
m.put(SerializedField.ORIGIN_END_LINE_NUMBER, -1);
break;
case ORIGIN_TYPE:
throw new ConfigException.BugOrBroken("should always be an ORIGIN_TYPE field");
case ORIGIN_URL:
m.put(SerializedField.ORIGIN_NULL_URL, "");
break;
case ORIGIN_COMMENTS:
m.put(SerializedField.ORIGIN_NULL_COMMENTS, "");
break;
case ORIGIN_NULL_DESCRIPTION: // FALL THRU
case ORIGIN_NULL_URL: // FALL THRU
case ORIGIN_NULL_COMMENTS:
// inherit the deletion, nothing to do
break;
case END_MARKER:
case ROOT_VALUE:
case ROOT_WAS_CONFIG:
case UNKNOWN:
case VALUE_DATA:
case VALUE_ORIGIN:
throw new ConfigException.BugOrBroken("should not appear here: " + f);
}
}
}
return m;
}
static SimpleConfigOrigin fromFields(Map<SerializedField, Object> m) throws IOException {
String description = (String) m.get(SerializedField.ORIGIN_DESCRIPTION);
Integer lineNumber = (Integer) m.get(SerializedField.ORIGIN_LINE_NUMBER);
Integer endLineNumber = (Integer) m.get(SerializedField.ORIGIN_END_LINE_NUMBER);
Number originTypeOrdinal = (Number) m.get(SerializedField.ORIGIN_TYPE);
if (originTypeOrdinal == null)
throw new IOException("Missing ORIGIN_TYPE field");
OriginType originType = OriginType.values()[originTypeOrdinal.byteValue()];
String urlOrNull = (String) m.get(SerializedField.ORIGIN_URL);
@SuppressWarnings("unchecked")
List<String> commentsOrNull = (List<String>) m.get(SerializedField.ORIGIN_COMMENTS);
return new SimpleConfigOrigin(description, lineNumber != null ? lineNumber : -1,
endLineNumber != null ? endLineNumber : -1, originType, urlOrNull, commentsOrNull);
}
static Map<SerializedField, Object> applyFieldsDelta(Map<SerializedField, Object> base,
Map<SerializedField, Object> delta) throws IOException {
Map<SerializedField, Object> m = new EnumMap<SerializedField, Object>(delta);
for (Map.Entry<SerializedField, Object> baseEntry : base.entrySet()) {
SerializedField f = baseEntry.getKey();
if (delta.containsKey(f)) {
// delta overrides when keys are in both
// "m" should already contain the right thing
} else if (!delta.containsKey(f)) {
// base has the key and delta does not.
// we inherit from base unless a "NULL" key blocks.
switch (f) {
case ORIGIN_DESCRIPTION:
// add to assembled unless delta nulls
if (!delta.containsKey(SerializedField.ORIGIN_NULL_DESCRIPTION))
m.put(f, base.get(f));
break;
case ORIGIN_URL:
if (!delta.containsKey(SerializedField.ORIGIN_NULL_URL))
m.put(f, base.get(f));
break;
case ORIGIN_COMMENTS:
if (!delta.containsKey(SerializedField.ORIGIN_NULL_COMMENTS))
m.put(f, base.get(f));
break;
case ORIGIN_NULL_DESCRIPTION:
if (!delta.containsKey(SerializedField.ORIGIN_DESCRIPTION))
m.put(f, base.get(f));
break;
case ORIGIN_NULL_URL:
if (!delta.containsKey(SerializedField.ORIGIN_URL))
m.put(f, base.get(f));
break;
case ORIGIN_NULL_COMMENTS:
if (!delta.containsKey(SerializedField.ORIGIN_COMMENTS))
m.put(f, base.get(f));
break;
case ORIGIN_END_LINE_NUMBER: // FALL THRU
case ORIGIN_LINE_NUMBER: // FALL THRU
case ORIGIN_TYPE:
m.put(f, base.get(f));
break;
case END_MARKER:
case ROOT_VALUE:
case ROOT_WAS_CONFIG:
case UNKNOWN:
case VALUE_DATA:
case VALUE_ORIGIN:
throw new ConfigException.BugOrBroken("should not appear here: " + f);
}
}
}
return m;
}
static SimpleConfigOrigin fromBase(SimpleConfigOrigin baseOrigin,
Map<SerializedField, Object> delta) throws IOException {
Map<SerializedField, Object> baseFields;
if (baseOrigin != null)
baseFields = baseOrigin.toFields();
else
baseFields = Collections.<SerializedField, Object> emptyMap();
Map<SerializedField, Object> fields = applyFieldsDelta(baseFields, delta);
return fromFields(fields);
}
}

View File

@ -1,10 +1,6 @@
package com.typesafe.config.impl;
import java.io.Serializable;
final class SubstitutionExpression implements Serializable {
private static final long serialVersionUID = 1L;
final class SubstitutionExpression {
final private Path path;
final private boolean optional;

View File

@ -708,163 +708,8 @@ class ConfigSubstitutionTest extends TestUtils {
}
@Test
def deserializeOldUnresolvedObject() {
val expectedSerialization = "" +
"aced00057372002b636f6d2e74797065736166652e636f6e6669672e696d706c2e53696d706c6543" +
"6f6e6669674f626a65637400000000000000010200035a001069676e6f72657346616c6c6261636b" +
"735a00087265736f6c7665644c000576616c756574000f4c6a6176612f7574696c2f4d61703b7872" +
"002d636f6d2e74797065736166652e636f6e6669672e696d706c2e4162737472616374436f6e6669" +
"674f626a65637400000000000000010200014c0006636f6e6669677400274c636f6d2f7479706573" +
"6166652f636f6e6669672f696d706c2f53696d706c65436f6e6669673b7872002c636f6d2e747970" +
"65736166652e636f6e6669672e696d706c2e4162737472616374436f6e66696756616c7565000000" +
"00000000010200014c00066f726967696e74002d4c636f6d2f74797065736166652f636f6e666967" +
"2f696d706c2f53696d706c65436f6e6669674f726967696e3b78707372002b636f6d2e7479706573" +
"6166652e636f6e6669672e696d706c2e53696d706c65436f6e6669674f726967696e000000000000" +
"000102000649000d656e644c696e654e756d62657249000a6c696e654e756d6265724c000e636f6d" +
"6d656e74734f724e756c6c7400104c6a6176612f7574696c2f4c6973743b4c000b64657363726970" +
"74696f6e7400124c6a6176612f6c616e672f537472696e673b4c000a6f726967696e547970657400" +
"254c636f6d2f74797065736166652f636f6e6669672f696d706c2f4f726967696e547970653b4c00" +
"0975726c4f724e756c6c71007e0009787000000002000000027074000b7465737420737472696e67" +
"7e720023636f6d2e74797065736166652e636f6e6669672e696d706c2e4f726967696e5479706500" +
"000000000000001200007872000e6a6176612e6c616e672e456e756d000000000000000012000078" +
"7074000747454e455249437073720025636f6d2e74797065736166652e636f6e6669672e696d706c" +
"2e53696d706c65436f6e66696700000000000000010200014c00066f626a65637474002f4c636f6d" +
"2f74797065736166652f636f6e6669672f696d706c2f4162737472616374436f6e6669674f626a65" +
"63743b787071007e00060000737200116a6176612e7574696c2e486173684d61700507dac1c31660" +
"d103000246000a6c6f6164466163746f724900097468726573686f6c6478703f4000000000000c77" +
"08000000100000000a7400046f626a457372002b636f6d2e74797065736166652e636f6e6669672e" +
"696d706c2e436f6e666967537562737469747574696f6e00000000000000010200035a001069676e" +
"6f72657346616c6c6261636b7349000c7072656669784c656e6774684c000670696563657371007e" +
"00087871007e00047371007e000700000008000000087071007e000c71007e000f70000000000073" +
"7200146a6176612e7574696c2e4c696e6b65644c6973740c29535d4a608822030000787077040000" +
"00017372002f636f6d2e74797065736166652e636f6e6669672e696d706c2e537562737469747574" +
"696f6e45787072657373696f6e00000000000000010200025a00086f7074696f6e616c4c00047061" +
"746874001f4c636f6d2f74797065736166652f636f6e6669672f696d706c2f506174683b78700073" +
"72001d636f6d2e74797065736166652e636f6e6669672e696d706c2e506174680000000000000001" +
"0200024c0005666972737471007e00094c000972656d61696e64657271007e001d78707400016173" +
"71007e001f740001627371007e001f740001657078740007666f6f2e62617273720022636f6d2e74" +
"797065736166652e636f6e6669672e696d706c2e436f6e666967496e740000000000000001020001" +
"49000576616c756578720025636f6d2e74797065736166652e636f6e6669672e696d706c2e436f6e" +
"6669674e756d62657200000000000000010200014c000c6f726967696e616c5465787471007e0009" +
"7871007e00047371007e000700000009000000097071007e000c71007e000f707400023337000000" +
"257400046f626a427371007e00177371007e000700000007000000077071007e000c71007e000f70" +
"00000000007371007e001a7704000000017371007e001c007371007e001f740001617371007e001f" +
"74000162707874000361727273720029636f6d2e74797065736166652e636f6e6669672e696d706c" +
"2e53696d706c65436f6e6669674c69737400000000000000010200025a00087265736f6c7665644c" +
"000576616c756571007e00087871007e00047371007e00070000000a0000000a7071007e000c7100" +
"7e000f70007371007e001a7704000000067371007e00177371007e00070000000a0000000a707100" +
"7e000c71007e000f7000000000007371007e001a7704000000017371007e001c007371007e001f74" +
"0003666f6f70787371007e001771007e003b00000000007371007e001a7704000000017371007e00" +
"1c007371007e001f740001617371007e001f740001627371007e001f7400016370787371007e0017" +
"71007e003b00000000007371007e001a7704000000017371007e001c007371007e001f740007666f" +
"6f2e62617270787371007e001771007e003b00000000007371007e001a7704000000017371007e00" +
"1c007371007e001f7400046f626a427371007e001f7400016470787371007e001771007e003b0000" +
"0000007371007e001a7704000000017371007e001c007371007e001f7400046f626a417371007e00" +
"1f740001627371007e001f740001657371007e001f7400016670787371007e001771007e003b0000" +
"0000007371007e001a7704000000017371007e001c007371007e001f7400046f626a457371007e00" +
"1f740001667078787400046f626a417371007e00177371007e000700000006000000067071007e00" +
"0c71007e000f7000000000007371007e001a7704000000017371007e001c007371007e001f740001" +
"617078740001617371007e00007371007e000700000005000000057071007e000c71007e000f7073" +
"71007e001171007e006f00007371007e00143f4000000000000c7708000000100000000174000162" +
"7371007e00007371007e000700000005000000057071007e000c71007e000f707371007e00117100" +
"7e007400007371007e00143f4000000000000c77080000001000000003740001647371007e001773" +
"71007e000700000005000000057071007e000c71007e000f7000000000007371007e001a77040000" +
"00017371007e001c007371007e001f740003666f6f7078740001657371007e00007371007e000700" +
"000005000000057071007e000c71007e000f707371007e001171007e008000007371007e00143f40" +
"00000000000c77080000001000000001740001667371007e001771007e007a00000000007371007e" +
"001a7704000000017371007e001c007371007e001f740003666f6f707878740001637371007e0027" +
"71007e007a7400023537000000397878740003666f6f7371007e00177371007e0007000000030000" +
"00037071007e000c71007e000f7000000000007371007e001a7704000000017371007e001c007371" +
"007e001f7400036261727078740008707472546f4172727371007e00177371007e00070000000b00" +
"00000b7071007e000c71007e000f7000000000007371007e001a7704000000017371007e001c0073" +
"71007e001f74000361727270787400036261727371007e00177371007e0007000000040000000470" +
"71007e000c71007e000f7000000000007371007e001a7704000000017371007e001c007371007e00" +
"1f740001617371007e001f740001627371007e001f740001637078740001787371007e0000737100" +
"7e00070000000c0000000c7071007e000c71007e000f707371007e001171007e00a700007371007e" +
"00143f4000000000000c77080000001000000001740001797371007e00007371007e00070000000c" +
"0000000c7071007e000c71007e000f707371007e001171007e00ac00007371007e00143f40000000" +
"00000c7708000000100000000174000d707472546f507472546f4172727371007e00177371007e00" +
"070000000c0000000c7071007e000c71007e000f7000000000007371007e001a7704000000017371" +
"007e001c007371007e001f740008707472546f4172727078787878"
checkSerializableOldFormat(expectedSerialization, substComplexObject)
}
@Test
def serializeUnresolvedObject() {
val expectedSerialization = "" +
"aced00057372002b636f6d2e74797065736166652e636f6e6669672e696d706c2e53696d706c6543" +
"6f6e6669674f626a65637400000000000000010200035a001069676e6f72657346616c6c6261636b" +
"735a00087265736f6c7665644c000576616c756574000f4c6a6176612f7574696c2f4d61703b7872" +
"002d636f6d2e74797065736166652e636f6e6669672e696d706c2e4162737472616374436f6e6669" +
"674f626a65637400000000000000010200014c0006636f6e6669677400274c636f6d2f7479706573" +
"6166652f636f6e6669672f696d706c2f53696d706c65436f6e6669673b7872002c636f6d2e747970" +
"65736166652e636f6e6669672e696d706c2e4162737472616374436f6e66696756616c7565000000" +
"00000000010200014c00066f726967696e74002d4c636f6d2f74797065736166652f636f6e666967" +
"2f696d706c2f53696d706c65436f6e6669674f726967696e3b78707372002b636f6d2e7479706573" +
"6166652e636f6e6669672e696d706c2e53696d706c65436f6e6669674f726967696e000000000000" +
"000102000649000d656e644c696e654e756d62657249000a6c696e654e756d6265724c000e636f6d" +
"6d656e74734f724e756c6c7400104c6a6176612f7574696c2f4c6973743b4c000b64657363726970" +
"74696f6e7400124c6a6176612f6c616e672f537472696e673b4c000a6f726967696e547970657400" +
"254c636f6d2f74797065736166652f636f6e6669672f696d706c2f4f726967696e547970653b4c00" +
"0975726c4f724e756c6c71007e0009787000000002000000027074000b7465737420737472696e67" +
"7e720023636f6d2e74797065736166652e636f6e6669672e696d706c2e4f726967696e5479706500" +
"000000000000001200007872000e6a6176612e6c616e672e456e756d000000000000000012000078" +
"7074000747454e455249437073720025636f6d2e74797065736166652e636f6e6669672e696d706c" +
"2e53696d706c65436f6e66696700000000000000010200014c00066f626a65637474002f4c636f6d" +
"2f74797065736166652f636f6e6669672f696d706c2f4162737472616374436f6e6669674f626a65" +
"63743b787071007e00060000737200116a6176612e7574696c2e486173684d61700507dac1c31660" +
"d103000246000a6c6f6164466163746f724900097468726573686f6c6478703f4000000000000c77" +
"08000000100000000a7400046f626a4573720028636f6d2e74797065736166652e636f6e6669672e" +
"696d706c2e436f6e6669675265666572656e6365000000000000000102000249000c707265666978" +
"4c656e6774684c0004657870727400314c636f6d2f74797065736166652f636f6e6669672f696d70" +
"6c2f537562737469747574696f6e45787072657373696f6e3b7871007e00047371007e0007000000" +
"08000000087071007e000c71007e000f70000000007372002f636f6d2e74797065736166652e636f" +
"6e6669672e696d706c2e537562737469747574696f6e45787072657373696f6e0000000000000001" +
"0200025a00086f7074696f6e616c4c00047061746874001f4c636f6d2f74797065736166652f636f" +
"6e6669672f696d706c2f506174683b7870007372001d636f6d2e74797065736166652e636f6e6669" +
"672e696d706c2e5061746800000000000000010200024c0005666972737471007e00094c00097265" +
"6d61696e64657271007e001c7870740001617371007e001e740001627371007e001e740001657074" +
"0007666f6f2e62617273720022636f6d2e74797065736166652e636f6e6669672e696d706c2e436f" +
"6e666967496e74000000000000000102000149000576616c756578720025636f6d2e747970657361" +
"66652e636f6e6669672e696d706c2e436f6e6669674e756d62657200000000000000010200014c00" +
"0c6f726967696e616c5465787471007e00097871007e00047371007e000700000009000000097071" +
"007e000c71007e000f707400023337000000257400046f626a427371007e00177371007e00070000" +
"0007000000077071007e000c71007e000f70000000007371007e001b007371007e001e7400016173" +
"71007e001e740001627074000361727273720029636f6d2e74797065736166652e636f6e6669672e" +
"696d706c2e53696d706c65436f6e6669674c69737400000000000000010200025a00087265736f6c" +
"7665644c000576616c756571007e00087871007e00047371007e00070000000a0000000a7071007e" +
"000c71007e000f7000737200146a6176612e7574696c2e4c696e6b65644c6973740c29535d4a6088" +
"2203000078707704000000067371007e00177371007e00070000000a0000000a7071007e000c7100" +
"7e000f70000000007371007e001b007371007e001e740003666f6f707371007e001771007e003a00" +
"0000007371007e001b007371007e001e740001617371007e001e740001627371007e001e74000163" +
"707371007e001771007e003a000000007371007e001b007371007e001e740007666f6f2e62617270" +
"7371007e001771007e003a000000007371007e001b007371007e001e7400046f626a427371007e00" +
"1e74000164707371007e001771007e003a000000007371007e001b007371007e001e7400046f626a" +
"417371007e001e740001627371007e001e740001657371007e001e74000166707371007e00177100" +
"7e003a000000007371007e001b007371007e001e7400046f626a457371007e001e74000166707874" +
"00046f626a417371007e00177371007e000700000006000000067071007e000c71007e000f700000" +
"00007371007e001b007371007e001e7400016170740001617371007e00007371007e000700000005" +
"000000057071007e000c71007e000f707371007e001171007e006700007371007e00143f40000000" +
"00000c77080000001000000001740001627371007e00007371007e00070000000500000005707100" +
"7e000c71007e000f707371007e001171007e006c00007371007e00143f4000000000000c77080000" +
"001000000003740001647371007e00177371007e000700000005000000057071007e000c71007e00" +
"0f70000000007371007e001b007371007e001e740003666f6f70740001657371007e00007371007e" +
"000700000005000000057071007e000c71007e000f707371007e001171007e007700007371007e00" +
"143f4000000000000c77080000001000000001740001667371007e001771007e0072000000007371" +
"007e001b007371007e001e740003666f6f7078740001637371007e002671007e0072740002353700" +
"0000397878740003666f6f7371007e00177371007e000700000003000000037071007e000c71007e" +
"000f70000000007371007e001b007371007e001e74000362617270740008707472546f4172727371" +
"007e00177371007e00070000000b0000000b7071007e000c71007e000f70000000007371007e001b" +
"007371007e001e740003617272707400036261727371007e00177371007e00070000000400000004" +
"7071007e000c71007e000f70000000007371007e001b007371007e001e740001617371007e001e74" +
"0001627371007e001e7400016370740001787371007e00007371007e00070000000c0000000c7071" +
"007e000c71007e000f707371007e001171007e009a00007371007e00143f4000000000000c770800" +
"00001000000001740001797371007e00007371007e00070000000c0000000c7071007e000c71007e" +
"000f707371007e001171007e009f00007371007e00143f4000000000000c77080000001000000001" +
"74000d707472546f507472546f4172727371007e00177371007e00070000000c0000000c7071007e" +
"000c71007e000f70000000007371007e001b007371007e001e740008707472546f41727270787878"
checkSerializable(expectedSerialization, substComplexObject)
def doNotSerializeUnresolvedObject() {
checkNotSerializable(substComplexObject)
}
// this is a weird test, it used to test fallback to system props which made more sense.

View File

@ -811,6 +811,9 @@ class ConfigTest extends TestUtils {
@Test
def test01Serializable() {
// we can't ever test an expected serialization here because it
// will have system props in it that vary by test system,
// and the ConfigOrigin in there will also vary by test system
val conf = ConfigFactory.load("test01")
val confCopy = checkSerializable(conf)
}
@ -1005,4 +1008,16 @@ class ConfigTest extends TestUtils {
}
}
}
@Test
def serializeRoundTrip() {
for (i <- 1 to 10) {
val numString = i.toString
val name = "/test" + { if (numString.size == 1) "0" else "" } + numString
val conf = ConfigFactory.parseResourcesAnySyntax(classOf[ConfigTest], name,
ConfigParseOptions.defaults().setAllowMissing(false))
val resolved = conf.resolve()
checkSerializable(resolved)
}
}
}

View File

@ -29,20 +29,9 @@ class ConfigValueTest extends TestUtils {
}
@Test
def configOriginSerializable() {
val expectedSerialization = "" +
"aced00057372002b636f6d2e74797065736166652e636f6e6669672e696d706c2e53696d706c6543" +
"6f6e6669674f726967696e000000000000000102000649000d656e644c696e654e756d6265724900" +
"0a6c696e654e756d6265724c000e636f6d6d656e74734f724e756c6c7400104c6a6176612f757469" +
"6c2f4c6973743b4c000b6465736372697074696f6e7400124c6a6176612f6c616e672f537472696e" +
"673b4c000a6f726967696e547970657400254c636f6d2f74797065736166652f636f6e6669672f69" +
"6d706c2f4f726967696e547970653b4c000975726c4f724e756c6c71007e00027870ffffffffffff" +
"ffff70740003666f6f7e720023636f6d2e74797065736166652e636f6e6669672e696d706c2e4f72" +
"6967696e5479706500000000000000001200007872000e6a6176612e6c616e672e456e756d000000" +
"0000000000120000787074000747454e4552494370"
def configOriginNotSerializable() {
val a = SimpleConfigOrigin.newSimple("foo")
checkSerializable(expectedSerialization, a)
checkNotSerializable(a)
}
@Test
@ -59,23 +48,10 @@ class ConfigValueTest extends TestUtils {
@Test
def configIntSerializable() {
val expectedSerialization = "" +
"aced000573720022636f6d2e74797065736166652e636f6e6669672e696d706c2e436f6e66696749" +
"6e74000000000000000102000149000576616c756578720025636f6d2e74797065736166652e636f" +
"6e6669672e696d706c2e436f6e6669674e756d62657200000000000000010200014c000c6f726967" +
"696e616c546578747400124c6a6176612f6c616e672f537472696e673b7872002c636f6d2e747970" +
"65736166652e636f6e6669672e696d706c2e4162737472616374436f6e66696756616c7565000000" +
"00000000010200014c00066f726967696e74002d4c636f6d2f74797065736166652f636f6e666967" +
"2f696d706c2f53696d706c65436f6e6669674f726967696e3b78707372002b636f6d2e7479706573" +
"6166652e636f6e6669672e696d706c2e53696d706c65436f6e6669674f726967696e000000000000" +
"000102000649000d656e644c696e654e756d62657249000a6c696e654e756d6265724c000e636f6d" +
"6d656e74734f724e756c6c7400104c6a6176612f7574696c2f4c6973743b4c000b64657363726970" +
"74696f6e71007e00024c000a6f726967696e547970657400254c636f6d2f74797065736166652f63" +
"6f6e6669672f696d706c2f4f726967696e547970653b4c000975726c4f724e756c6c71007e000278" +
"70ffffffffffffffff7074000b66616b65206f726967696e7e720023636f6d2e7479706573616665" +
"2e636f6e6669672e696d706c2e4f726967696e5479706500000000000000001200007872000e6a61" +
"76612e6c616e672e456e756d0000000000000000120000787074000747454e455249437070000000" +
"2a"
"ACED0005_s_r00_._c_o_m_._t_y_p_e_s_a_f_e_._c_o_n_f_i_g_._i_m_p_l_._S_e_r_i_a_l_i" +
"_z_e_d_C_o_n_f_i_g_V_a_l_u_e00000000000000010C0000_x_p_w_902000000_-050000001906" +
"0000000D000B_f_a_k_e_ _o_r_i_g_i_n090000000100010400000009020000002A0002_4_20103" +
"000000010001_x"
val a = intValue(42)
val b = checkSerializable(expectedSerialization, a)
assertEquals(42, b.unwrapped)
@ -92,6 +68,19 @@ class ConfigValueTest extends TestUtils {
checkNotEqualObjects(a, b)
}
@Test
def configLongSerializable() {
val expectedSerialization = "" +
"ACED0005_s_r00_._c_o_m_._t_y_p_e_s_a_f_e_._c_o_n_f_i_g_._i_m_p_l_._S_e_r_i_a_l_i" +
"_z_e_d_C_o_n_f_i_g_V_a_l_u_e00000000000000010C0000_x_p_w_E02000000_9050000001906" +
"0000000D000B_f_a_k_e_ _o_r_i_g_i_n090000000100010400000015030000000080000029000A" +
"_2_1_4_7_4_8_3_6_8_90103000000010001_x"
val a = longValue(Integer.MAX_VALUE + 42L)
val b = checkSerializable(expectedSerialization, a)
assertEquals(Integer.MAX_VALUE + 42L, b.unwrapped)
}
@Test
def configIntAndLongEquality() {
val longVal = longValue(42L)
@ -105,6 +94,80 @@ class ConfigValueTest extends TestUtils {
checkNotEqualObjects(intValueB, longVal)
}
@Test
def configDoubleEquality() {
val a = doubleValue(3.14)
val sameAsA = doubleValue(3.14)
val b = doubleValue(4.14)
checkEqualObjects(a, a)
checkEqualObjects(a, sameAsA)
checkNotEqualObjects(a, b)
}
@Test
def configDoubleSerializable() {
val expectedSerialization = "" +
"ACED0005_s_r00_._c_o_m_._t_y_p_e_s_a_f_e_._c_o_n_f_i_g_._i_m_p_l_._S_e_r_i_a_l_i" +
"_z_e_d_C_o_n_f_i_g_V_a_l_u_e00000000000000010C0000_x_p_w3F02000000_3050000001906" +
"0000000D000B_f_a_k_e_ _o_r_i_g_i_n09000000010001040000000F0440091EB8_QEB851F0004" +
"_3_._1_40103000000010001_x"
val a = doubleValue(3.14)
val b = checkSerializable(expectedSerialization, a)
assertEquals(3.14, b.unwrapped)
}
@Test
def configIntAndDoubleEquality() {
val doubleVal = doubleValue(3.0)
val intValue = longValue(3)
val doubleValueB = doubleValue(4.0)
val intValueB = doubleValue(4)
checkEqualObjects(intValue, doubleVal)
checkEqualObjects(intValueB, doubleValueB)
checkNotEqualObjects(intValue, doubleValueB)
checkNotEqualObjects(intValueB, doubleVal)
}
@Test
def configNullSerializable() {
val expectedSerialization = "" +
"ACED0005_s_r00_._c_o_m_._t_y_p_e_s_a_f_e_._c_o_n_f_i_g_._i_m_p_l_._S_e_r_i_a_l_i" +
"_z_e_d_C_o_n_f_i_g_V_a_l_u_e00000000000000010C0000_x_p_w_10200000025050000001906" +
"0000000D000B_f_a_k_e_ _o_r_i_g_i_n090000000100010400000001000103000000010001_x"
val a = nullValue()
val b = checkSerializable(expectedSerialization, a)
assertNull("b is null", b.unwrapped)
}
@Test
def configBooleanSerializable() {
val expectedSerialization = "" +
"ACED0005_s_r00_._c_o_m_._t_y_p_e_s_a_f_e_._c_o_n_f_i_g_._i_m_p_l_._S_e_r_i_a_l_i" +
"_z_e_d_C_o_n_f_i_g_V_a_l_u_e00000000000000010C0000_x_p_w_20200000026050000001906" +
"0000000D000B_f_a_k_e_ _o_r_i_g_i_n09000000010001040000000201010103000000010001_x"
val a = boolValue(true)
val b = checkSerializable(expectedSerialization, a)
assertEquals(true, b.unwrapped)
}
@Test
def configStringSerializable() {
val expectedSerialization = "" +
"ACED0005_s_r00_._c_o_m_._t_y_p_e_s_a_f_e_._c_o_n_f_i_g_._i_m_p_l_._S_e_r_i_a_l_i" +
"_z_e_d_C_o_n_f_i_g_V_a_l_u_e00000000000000010C0000_x_p_w_F02000000_:050000001906" +
"0000000D000B_f_a_k_e_ _o_r_i_g_i_n090000000100010400000016050013_T_h_e_ _q_u_i_c" +
"_k_ _b_r_o_w_n_ _f_o_x0103000000010001_x"
val a = stringValue("The quick brown fox")
val b = checkSerializable(expectedSerialization, a)
assertEquals("The quick brown fox", b.unwrapped)
}
private def configMap(pairs: (String, Int)*): java.util.Map[String, AbstractConfigValue] = {
val m = new java.util.HashMap[String, AbstractConfigValue]()
for (p <- pairs) {
@ -149,35 +212,11 @@ class ConfigValueTest extends TestUtils {
@Test
def configObjectSerializable() {
val expectedSerialization = "" +
"aced00057372002b636f6d2e74797065736166652e636f6e6669672e696d706c2e53696d706c6543" +
"6f6e6669674f626a65637400000000000000010200035a001069676e6f72657346616c6c6261636b" +
"735a00087265736f6c7665644c000576616c756574000f4c6a6176612f7574696c2f4d61703b7872" +
"002d636f6d2e74797065736166652e636f6e6669672e696d706c2e4162737472616374436f6e6669" +
"674f626a65637400000000000000010200014c0006636f6e6669677400274c636f6d2f7479706573" +
"6166652f636f6e6669672f696d706c2f53696d706c65436f6e6669673b7872002c636f6d2e747970" +
"65736166652e636f6e6669672e696d706c2e4162737472616374436f6e66696756616c7565000000" +
"00000000010200014c00066f726967696e74002d4c636f6d2f74797065736166652f636f6e666967" +
"2f696d706c2f53696d706c65436f6e6669674f726967696e3b78707372002b636f6d2e7479706573" +
"6166652e636f6e6669672e696d706c2e53696d706c65436f6e6669674f726967696e000000000000" +
"000102000649000d656e644c696e654e756d62657249000a6c696e654e756d6265724c000e636f6d" +
"6d656e74734f724e756c6c7400104c6a6176612f7574696c2f4c6973743b4c000b64657363726970" +
"74696f6e7400124c6a6176612f6c616e672f537472696e673b4c000a6f726967696e547970657400" +
"254c636f6d2f74797065736166652f636f6e6669672f696d706c2f4f726967696e547970653b4c00" +
"0975726c4f724e756c6c71007e00097870ffffffffffffffff7074000b66616b65206f726967696e" +
"7e720023636f6d2e74797065736166652e636f6e6669672e696d706c2e4f726967696e5479706500" +
"000000000000001200007872000e6a6176612e6c616e672e456e756d000000000000000012000078" +
"7074000747454e455249437073720025636f6d2e74797065736166652e636f6e6669672e696d706c" +
"2e53696d706c65436f6e66696700000000000000010200014c00066f626a65637474002f4c636f6d" +
"2f74797065736166652f636f6e6669672f696d706c2f4162737472616374436f6e6669674f626a65" +
"63743b787071007e00060001737200116a6176612e7574696c2e486173684d61700507dac1c31660" +
"d103000246000a6c6f6164466163746f724900097468726573686f6c6478703f4000000000000c77" +
"0800000010000000037400016273720022636f6d2e74797065736166652e636f6e6669672e696d70" +
"6c2e436f6e666967496e74000000000000000102000149000576616c756578720025636f6d2e7479" +
"7065736166652e636f6e6669672e696d706c2e436f6e6669674e756d626572000000000000000102" +
"00014c000c6f726967696e616c5465787471007e00097871007e00047371007e0007ffffffffffff" +
"ffff7071007e000c71007e000f707000000002740001637371007e00177371007e0007ffffffffff" +
"ffffff7071007e000c71007e000f707000000003740001617371007e00177371007e0007ffffffff" +
"ffffffff7071007e000c71007e000f70700000000178"
"ACED0005_s_r00_._c_o_m_._t_y_p_e_s_a_f_e_._c_o_n_f_i_g_._i_m_p_l_._S_e_r_i_a_l_i" +
"_z_e_d_C_o_n_f_i_g_V_a_l_u_e00000000000000010C0000_x_p_w_z02000000_n050000001906" +
"0000000D000B_f_a_k_e_ _o_r_i_g_i_n0900000001000104000000_J07000000030001_a050000" +
"000101040000000802000000010001_1010001_c050000000101040000000802000000030001_301" +
"0001_b050000000101040000000802000000020001_2010103000000010001_x"
val aMap = configMap("a" -> 1, "b" -> 2, "c" -> 3)
val a = new SimpleConfigObject(fakeOrigin(), aMap)
@ -187,6 +226,23 @@ class ConfigValueTest extends TestUtils {
assertTrue(b.toConfig.root eq b)
}
@Test
def configConfigSerializable() {
val expectedSerialization = "" +
"ACED0005_s_r00_._c_o_m_._t_y_p_e_s_a_f_e_._c_o_n_f_i_g_._i_m_p_l_._S_e_r_i_a_l_i" +
"_z_e_d_C_o_n_f_i_g_V_a_l_u_e00000000000000010C0000_x_p_w_z02000000_n050000001906" +
"0000000D000B_f_a_k_e_ _o_r_i_g_i_n0900000001000104000000_J07000000030001_a050000" +
"000101040000000802000000010001_1010001_c050000000101040000000802000000030001_301" +
"0001_b050000000101040000000802000000020001_2010103000000010101_x"
val aMap = configMap("a" -> 1, "b" -> 2, "c" -> 3)
val a = new SimpleConfigObject(fakeOrigin(), aMap)
val b = checkSerializable(expectedSerialization, a.toConfig())
assertEquals(1, b.getInt("a"))
// check that deserialized Config and ConfigObject refer to each other
assertTrue(b.root.toConfig eq b)
}
@Test
def configListEquality() {
val aScalaSeq = Seq(1, 2, 3) map { intValue(_): AbstractConfigValue }
@ -203,28 +259,11 @@ class ConfigValueTest extends TestUtils {
@Test
def configListSerializable() {
val expectedSerialization = "" +
"aced000573720029636f6d2e74797065736166652e636f6e6669672e696d706c2e53696d706c6543" +
"6f6e6669674c69737400000000000000010200025a00087265736f6c7665644c000576616c756574" +
"00104c6a6176612f7574696c2f4c6973743b7872002c636f6d2e74797065736166652e636f6e6669" +
"672e696d706c2e4162737472616374436f6e66696756616c756500000000000000010200014c0006" +
"6f726967696e74002d4c636f6d2f74797065736166652f636f6e6669672f696d706c2f53696d706c" +
"65436f6e6669674f726967696e3b78707372002b636f6d2e74797065736166652e636f6e6669672e" +
"696d706c2e53696d706c65436f6e6669674f726967696e000000000000000102000649000d656e64" +
"4c696e654e756d62657249000a6c696e654e756d6265724c000e636f6d6d656e74734f724e756c6c" +
"71007e00014c000b6465736372697074696f6e7400124c6a6176612f6c616e672f537472696e673b" +
"4c000a6f726967696e547970657400254c636f6d2f74797065736166652f636f6e6669672f696d70" +
"6c2f4f726967696e547970653b4c000975726c4f724e756c6c71007e00067870ffffffffffffffff" +
"7074000b66616b65206f726967696e7e720023636f6d2e74797065736166652e636f6e6669672e69" +
"6d706c2e4f726967696e5479706500000000000000001200007872000e6a6176612e6c616e672e45" +
"6e756d0000000000000000120000787074000747454e455249437001737200146a6176612e757469" +
"6c2e4c696e6b65644c6973740c29535d4a608822030000787077040000000373720022636f6d2e74" +
"797065736166652e636f6e6669672e696d706c2e436f6e666967496e740000000000000001020001" +
"49000576616c756578720025636f6d2e74797065736166652e636f6e6669672e696d706c2e436f6e" +
"6669674e756d62657200000000000000010200014c000c6f726967696e616c5465787471007e0006" +
"7871007e00027371007e0005ffffffffffffffff7071007e000971007e000c707000000001737100" +
"7e00107371007e0005ffffffffffffffff7071007e000971007e000c7070000000027371007e0010" +
"7371007e0005ffffffffffffffff7071007e000971007e000c70700000000378"
"ACED0005_s_r00_._c_o_m_._t_y_p_e_s_a_f_e_._c_o_n_f_i_g_._i_m_p_l_._S_e_r_i_a_l_i" +
"_z_e_d_C_o_n_f_i_g_V_a_l_u_e00000000000000010C0000_x_p_w_q02000000_e050000001906" +
"0000000D000B_f_a_k_e_ _o_r_i_g_i_n0900000001000104000000_A0600000003050000000101" +
"040000000802000000010001_101050000000101040000000802000000020001_201050000000101" +
"040000000802000000030001_3010103000000010001_x"
val aScalaSeq = Seq(1, 2, 3) map { intValue(_): AbstractConfigValue }
val aList = new SimpleConfigList(fakeOrigin(), aScalaSeq.asJava)
val bList = checkSerializable(expectedSerialization, aList)
@ -246,32 +285,9 @@ class ConfigValueTest extends TestUtils {
}
@Test
def configSubstitutionSerializable() {
val expectedSerialization = "" +
"aced00057372002b636f6d2e74797065736166652e636f6e6669672e696d706c2e436f6e66696753" +
"7562737469747574696f6e00000000000000010200035a001069676e6f72657346616c6c6261636b" +
"7349000c7072656669784c656e6774684c00067069656365737400104c6a6176612f7574696c2f4c" +
"6973743b7872002c636f6d2e74797065736166652e636f6e6669672e696d706c2e41627374726163" +
"74436f6e66696756616c756500000000000000010200014c00066f726967696e74002d4c636f6d2f" +
"74797065736166652f636f6e6669672f696d706c2f53696d706c65436f6e6669674f726967696e3b" +
"78707372002b636f6d2e74797065736166652e636f6e6669672e696d706c2e53696d706c65436f6e" +
"6669674f726967696e000000000000000102000649000d656e644c696e654e756d62657249000a6c" +
"696e654e756d6265724c000e636f6d6d656e74734f724e756c6c71007e00014c000b646573637269" +
"7074696f6e7400124c6a6176612f6c616e672f537472696e673b4c000a6f726967696e5479706574" +
"00254c636f6d2f74797065736166652f636f6e6669672f696d706c2f4f726967696e547970653b4c" +
"000975726c4f724e756c6c71007e00067870ffffffffffffffff7074000b66616b65206f72696769" +
"6e7e720023636f6d2e74797065736166652e636f6e6669672e696d706c2e4f726967696e54797065" +
"00000000000000001200007872000e6a6176612e6c616e672e456e756d0000000000000000120000" +
"787074000747454e45524943700000000000737200146a6176612e7574696c2e4c696e6b65644c69" +
"73740c29535d4a60882203000078707704000000017372002f636f6d2e74797065736166652e636f" +
"6e6669672e696d706c2e537562737469747574696f6e45787072657373696f6e0000000000000001" +
"0200025a00086f7074696f6e616c4c00047061746874001f4c636f6d2f74797065736166652f636f" +
"6e6669672f696d706c2f506174683b7870007372001d636f6d2e74797065736166652e636f6e6669" +
"672e696d706c2e5061746800000000000000010200024c0005666972737471007e00064c00097265" +
"6d61696e64657271007e00117870740003666f6f7078"
def configSubstitutionNotSerializable() {
val a = subst("foo")
val b = checkSerializable(expectedSerialization, a)
checkNotSerializable(a)
}
@Test
@ -293,33 +309,10 @@ class ConfigValueTest extends TestUtils {
}
@Test
def configReferenceSerializable() {
val expectedSerialization = "" +
"aced000573720028636f6d2e74797065736166652e636f6e6669672e696d706c2e436f6e66696752" +
"65666572656e6365000000000000000102000249000c7072656669784c656e6774684c0004657870" +
"727400314c636f6d2f74797065736166652f636f6e6669672f696d706c2f53756273746974757469" +
"6f6e45787072657373696f6e3b7872002c636f6d2e74797065736166652e636f6e6669672e696d70" +
"6c2e4162737472616374436f6e66696756616c756500000000000000010200014c00066f72696769" +
"6e74002d4c636f6d2f74797065736166652f636f6e6669672f696d706c2f53696d706c65436f6e66" +
"69674f726967696e3b78707372002b636f6d2e74797065736166652e636f6e6669672e696d706c2e" +
"53696d706c65436f6e6669674f726967696e000000000000000102000649000d656e644c696e654e" +
"756d62657249000a6c696e654e756d6265724c000e636f6d6d656e74734f724e756c6c7400104c6a" +
"6176612f7574696c2f4c6973743b4c000b6465736372697074696f6e7400124c6a6176612f6c616e" +
"672f537472696e673b4c000a6f726967696e547970657400254c636f6d2f74797065736166652f63" +
"6f6e6669672f696d706c2f4f726967696e547970653b4c000975726c4f724e756c6c71007e000778" +
"70ffffffffffffffff7074000b66616b65206f726967696e7e720023636f6d2e7479706573616665" +
"2e636f6e6669672e696d706c2e4f726967696e5479706500000000000000001200007872000e6a61" +
"76612e6c616e672e456e756d0000000000000000120000787074000747454e455249437000000000" +
"7372002f636f6d2e74797065736166652e636f6e6669672e696d706c2e537562737469747574696f" +
"6e45787072657373696f6e00000000000000010200025a00086f7074696f6e616c4c000470617468" +
"74001f4c636f6d2f74797065736166652f636f6e6669672f696d706c2f506174683b787000737200" +
"1d636f6d2e74797065736166652e636f6e6669672e696d706c2e5061746800000000000000010200" +
"024c0005666972737471007e00074c000972656d61696e64657271007e00107870740003666f6f70"
def configReferenceNotSerializable() {
val a = subst("foo").delegate()
assertTrue("wrong type " + a, a.isInstanceOf[ConfigReference])
val b = checkSerializable(expectedSerialization, a)
assertTrue("wrong type " + b, b.isInstanceOf[ConfigReference])
checkNotSerializable(a)
}
@Test
@ -340,39 +333,10 @@ class ConfigValueTest extends TestUtils {
}
@Test
def configConcatenationSerializable() {
val expectedSerialization = "" +
"aced00057372002c636f6d2e74797065736166652e636f6e6669672e696d706c2e436f6e66696743" +
"6f6e636174656e6174696f6e00000000000000010200014c00067069656365737400104c6a617661" +
"2f7574696c2f4c6973743b7872002c636f6d2e74797065736166652e636f6e6669672e696d706c2e" +
"4162737472616374436f6e66696756616c756500000000000000010200014c00066f726967696e74" +
"002d4c636f6d2f74797065736166652f636f6e6669672f696d706c2f53696d706c65436f6e666967" +
"4f726967696e3b78707372002b636f6d2e74797065736166652e636f6e6669672e696d706c2e5369" +
"6d706c65436f6e6669674f726967696e000000000000000102000649000d656e644c696e654e756d" +
"62657249000a6c696e654e756d6265724c000e636f6d6d656e74734f724e756c6c71007e00014c00" +
"0b6465736372697074696f6e7400124c6a6176612f6c616e672f537472696e673b4c000a6f726967" +
"696e547970657400254c636f6d2f74797065736166652f636f6e6669672f696d706c2f4f72696769" +
"6e547970653b4c000975726c4f724e756c6c71007e00067870ffffffffffffffff7074000b66616b" +
"65206f726967696e7e720023636f6d2e74797065736166652e636f6e6669672e696d706c2e4f7269" +
"67696e5479706500000000000000001200007872000e6a6176612e6c616e672e456e756d00000000" +
"00000000120000787074000747454e4552494370737200146a6176612e7574696c2e4c696e6b6564" +
"4c6973740c29535d4a608822030000787077040000000373720025636f6d2e74797065736166652e" +
"636f6e6669672e696d706c2e436f6e666967537472696e6700000000000000010200014c00057661" +
"6c756571007e00067871007e000271007e000874000673746172743c73720028636f6d2e74797065" +
"736166652e636f6e6669672e696d706c2e436f6e6669675265666572656e63650000000000000001" +
"02000249000c7072656669784c656e6774684c0004657870727400314c636f6d2f74797065736166" +
"652f636f6e6669672f696d706c2f537562737469747574696f6e45787072657373696f6e3b787100" +
"7e000271007e0008000000007372002f636f6d2e74797065736166652e636f6e6669672e696d706c" +
"2e537562737469747574696f6e45787072657373696f6e00000000000000010200025a00086f7074" +
"696f6e616c4c00047061746874001f4c636f6d2f74797065736166652f636f6e6669672f696d706c" +
"2f506174683b7870007372001d636f6d2e74797065736166652e636f6e6669672e696d706c2e5061" +
"746800000000000000010200024c0005666972737471007e00064c000972656d61696e6465727100" +
"7e00177870740003666f6f707371007e001071007e00087400043e656e6478"
def configConcatenationNotSerializable() {
val a = substInString("foo").delegate()
assertTrue("wrong type " + a, a.isInstanceOf[ConfigConcatenation])
val b = checkSerializable(expectedSerialization, a)
assertTrue("wrong type " + b, b.isInstanceOf[ConfigConcatenation])
checkNotSerializable(a)
}
@Test
@ -404,39 +368,11 @@ class ConfigValueTest extends TestUtils {
}
@Test
def configDelayedMergeSerializable() {
val expectedSerialization = "" +
"aced00057372002b636f6d2e74797065736166652e636f6e6669672e696d706c2e436f6e66696744" +
"656c617965644d6572676500000000000000010200025a001069676e6f72657346616c6c6261636b" +
"734c0005737461636b7400104c6a6176612f7574696c2f4c6973743b7872002c636f6d2e74797065" +
"736166652e636f6e6669672e696d706c2e4162737472616374436f6e66696756616c756500000000" +
"000000010200014c00066f726967696e74002d4c636f6d2f74797065736166652f636f6e6669672f" +
"696d706c2f53696d706c65436f6e6669674f726967696e3b78707372002b636f6d2e747970657361" +
"66652e636f6e6669672e696d706c2e53696d706c65436f6e6669674f726967696e00000000000000" +
"0102000649000d656e644c696e654e756d62657249000a6c696e654e756d6265724c000e636f6d6d" +
"656e74734f724e756c6c71007e00014c000b6465736372697074696f6e7400124c6a6176612f6c61" +
"6e672f537472696e673b4c000a6f726967696e547970657400254c636f6d2f74797065736166652f" +
"636f6e6669672f696d706c2f4f726967696e547970653b4c000975726c4f724e756c6c71007e0006" +
"7870ffffffffffffffff7074000b66616b65206f726967696e7e720023636f6d2e74797065736166" +
"652e636f6e6669672e696d706c2e4f726967696e5479706500000000000000001200007872000e6a" +
"6176612e6c616e672e456e756d0000000000000000120000787074000747454e4552494370007372" +
"00146a6176612e7574696c2e4c696e6b65644c6973740c29535d4a60882203000078707704000000" +
"027372002b636f6d2e74797065736166652e636f6e6669672e696d706c2e436f6e66696753756273" +
"7469747574696f6e00000000000000010200035a001069676e6f72657346616c6c6261636b734900" +
"0c7072656669784c656e6774684c000670696563657371007e00017871007e00027371007e0005ff" +
"ffffffffffffff7071007e000971007e000c7000000000007371007e000e7704000000017372002f" +
"636f6d2e74797065736166652e636f6e6669672e696d706c2e537562737469747574696f6e457870" +
"72657373696f6e00000000000000010200025a00086f7074696f6e616c4c00047061746874001f4c" +
"636f6d2f74797065736166652f636f6e6669672f696d706c2f506174683b7870007372001d636f6d" +
"2e74797065736166652e636f6e6669672e696d706c2e5061746800000000000000010200024c0005" +
"666972737471007e00064c000972656d61696e64657271007e00157870740003666f6f7078737100" +
"7e00107371007e0005ffffffffffffffff7071007e000971007e000c7000000000007371007e000e" +
"7704000000017371007e0014007371007e0017740003626172707878"
def configDelayedMergeNotSerializable() {
val s1 = subst("foo")
val s2 = subst("bar")
val a = new ConfigDelayedMerge(fakeOrigin(), List[AbstractConfigValue](s1, s2).asJava)
val b = checkSerializable(expectedSerialization, a)
checkNotSerializable(a)
}
@Test
@ -454,51 +390,12 @@ class ConfigValueTest extends TestUtils {
}
@Test
def configDelayedMergeObjectSerializable() {
val expectedSerialization = "" +
"aced000573720031636f6d2e74797065736166652e636f6e6669672e696d706c2e436f6e66696744" +
"656c617965644d657267654f626a65637400000000000000010200025a001069676e6f7265734661" +
"6c6c6261636b734c0005737461636b7400104c6a6176612f7574696c2f4c6973743b7872002d636f" +
"6d2e74797065736166652e636f6e6669672e696d706c2e4162737472616374436f6e6669674f626a" +
"65637400000000000000010200014c0006636f6e6669677400274c636f6d2f74797065736166652f" +
"636f6e6669672f696d706c2f53696d706c65436f6e6669673b7872002c636f6d2e74797065736166" +
"652e636f6e6669672e696d706c2e4162737472616374436f6e66696756616c756500000000000000" +
"010200014c00066f726967696e74002d4c636f6d2f74797065736166652f636f6e6669672f696d70" +
"6c2f53696d706c65436f6e6669674f726967696e3b78707372002b636f6d2e74797065736166652e" +
"636f6e6669672e696d706c2e53696d706c65436f6e6669674f726967696e00000000000000010200" +
"0649000d656e644c696e654e756d62657249000a6c696e654e756d6265724c000e636f6d6d656e74" +
"734f724e756c6c71007e00014c000b6465736372697074696f6e7400124c6a6176612f6c616e672f" +
"537472696e673b4c000a6f726967696e547970657400254c636f6d2f74797065736166652f636f6e" +
"6669672f696d706c2f4f726967696e547970653b4c000975726c4f724e756c6c71007e00087870ff" +
"ffffffffffffff7074000b66616b65206f726967696e7e720023636f6d2e74797065736166652e63" +
"6f6e6669672e696d706c2e4f726967696e5479706500000000000000001200007872000e6a617661" +
"2e6c616e672e456e756d0000000000000000120000787074000747454e455249437073720025636f" +
"6d2e74797065736166652e636f6e6669672e696d706c2e53696d706c65436f6e6669670000000000" +
"0000010200014c00066f626a65637474002f4c636f6d2f74797065736166652f636f6e6669672f69" +
"6d706c2f4162737472616374436f6e6669674f626a6563743b787071007e000600737200146a6176" +
"612e7574696c2e4c696e6b65644c6973740c29535d4a60882203000078707704000000037372002b" +
"636f6d2e74797065736166652e636f6e6669672e696d706c2e53696d706c65436f6e6669674f626a" +
"65637400000000000000010200035a001069676e6f72657346616c6c6261636b735a00087265736f" +
"6c7665644c000576616c756574000f4c6a6176612f7574696c2f4d61703b7871007e00027371007e" +
"0007ffffffffffffffff7074000c656d70747920636f6e66696771007e000e707371007e00107100" +
"7e001700017372001e6a6176612e7574696c2e436f6c6c656374696f6e7324456d7074794d617059" +
"3614855adce7d002000078707372002b636f6d2e74797065736166652e636f6e6669672e696d706c" +
"2e436f6e666967537562737469747574696f6e00000000000000010200035a001069676e6f726573" +
"46616c6c6261636b7349000c7072656669784c656e6774684c000670696563657371007e00017871" +
"007e00047371007e0007ffffffffffffffff7071007e000b71007e000e7000000000007371007e00" +
"137704000000017372002f636f6d2e74797065736166652e636f6e6669672e696d706c2e53756273" +
"7469747574696f6e45787072657373696f6e00000000000000010200025a00086f7074696f6e616c" +
"4c00047061746874001f4c636f6d2f74797065736166652f636f6e6669672f696d706c2f50617468" +
"3b7870007372001d636f6d2e74797065736166652e636f6e6669672e696d706c2e50617468000000" +
"00000000010200024c0005666972737471007e00084c000972656d61696e64657271007e00227870" +
"740003666f6f70787371007e001d7371007e0007ffffffffffffffff7071007e000b71007e000e70" +
"00000000007371007e00137704000000017371007e0021007371007e0024740003626172707878"
def configDelayedMergeObjectNotSerializable() {
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(expectedSerialization, a)
checkNotSerializable(a)
}
@Test

View File

@ -17,11 +17,12 @@ import java.io.ByteArrayOutputStream
import java.io.ObjectOutputStream
import java.io.ByteArrayInputStream
import java.io.ObjectInputStream
import org.apache.commons.codec.binary.Hex
import java.io.NotSerializableException
import scala.annotation.tailrec
import java.net.URL
import java.util.concurrent.Executors
import java.util.concurrent.Callable
import com.typesafe.config._
abstract trait TestUtils {
protected def intercept[E <: Throwable: Manifest](block: => Unit): E = {
@ -91,6 +92,54 @@ abstract trait TestUtils {
checkNotEqualToRandomOtherThing(b)
}
private val hexDigits = {
val a = new Array[Char](16)
var i = 0
for (c <- '0' to '9') {
a(i) = c
i += 1
}
for (c <- 'A' to 'F') {
a(i) = c
i += 1
}
a
}
private def encodeLegibleBinary(bytes: Array[Byte]): String = {
val sb = new java.lang.StringBuilder()
for (b <- bytes) {
if ((b >= 'a' && b <= 'z') ||
(b >= 'A' && b <= 'Z') ||
(b >= '0' && b <= '9') ||
b == '-' || b == ':' || b == '.' || b == '/' || b == ' ') {
sb.append('_')
sb.appendCodePoint(b.asInstanceOf[Char])
} else {
sb.appendCodePoint(hexDigits((b & 0xF0) >> 4))
sb.appendCodePoint(hexDigits(b & 0x0F))
}
}
sb.toString
}
private def decodeLegibleBinary(s: String): Array[Byte] = {
val a = new Array[Byte](s.length() / 2)
var i = 0
var j = 0
while (i < s.length()) {
val sub = s.substring(i, i + 2)
i += 2
if (sub.charAt(0) == '_') {
a(j) = charWrapper(sub.charAt(1)).byteValue
} else {
a(j) = Integer.parseInt(sub, 16).byteValue
}
j += 1
}
a
}
private def copyViaSerialize(o: java.io.Serializable): AnyRef = {
val byteStream = new ByteArrayOutputStream()
val objectStream = new ObjectOutputStream(byteStream)
@ -105,17 +154,20 @@ abstract trait TestUtils {
protected def checkSerializationCompat[T: Manifest](expectedHex: String, o: T, changedOK: Boolean = false): Unit = {
// be sure we can still deserialize the old one
val inStream = new ByteArrayInputStream(Hex.decodeHex(expectedHex.toCharArray()))
val inObjectStream = new ObjectInputStream(inStream)
val inStream = new ByteArrayInputStream(decodeLegibleBinary(expectedHex))
var failure: Option[Exception] = None
var inObjectStream: ObjectInputStream = null
val deserialized = try {
inObjectStream = new ObjectInputStream(inStream) // this can throw too
inObjectStream.readObject()
} catch {
case e: Exception =>
failure = Some(e)
null
} finally {
if (inObjectStream != null)
inObjectStream.close()
}
inObjectStream.close()
val why = failure.map({ e => ": " + e.getClass.getSimpleName + ": " + e.getMessage }).getOrElse("")
@ -123,7 +175,7 @@ abstract trait TestUtils {
val objectStream = new ObjectOutputStream(byteStream)
objectStream.writeObject(o)
objectStream.close()
val hex = Hex.encodeHexString(byteStream.toByteArray())
val hex = encodeLegibleBinary(byteStream.toByteArray())
def showCorrectResult(): Unit = {
if (expectedHex != hex) {
@tailrec
@ -156,6 +208,15 @@ abstract trait TestUtils {
}
}
protected def checkNotSerializable(o: AnyRef): Unit = {
val byteStream = new ByteArrayOutputStream()
val objectStream = new ObjectOutputStream(byteStream)
val e = intercept[NotSerializableException] {
objectStream.writeObject(o)
}
objectStream.close()
}
protected def checkSerializable[T: Manifest](expectedHex: String, o: T): T = {
val t = checkSerializable(o)
checkSerializationCompat(expectedHex, o)
@ -183,6 +244,7 @@ abstract trait TestUtils {
"possibly caused by http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6446627",
nf)
case e: Exception =>
System.err.println(e.getStackTraceString);
throw new AssertionError("failed to make a copy via serialization", e)
}
@ -190,10 +252,32 @@ abstract trait TestUtils {
manifest[T].erasure.isAssignableFrom(b.getClass))
checkEqualObjects(a, b)
checkEqualOrigins(a, b)
b.asInstanceOf[T]
}
// origin() is not part of value equality but is serialized, so
// we check it separately
protected def checkEqualOrigins[T](a: T, b: T): Unit = {
import scala.collection.JavaConverters._
(a, b) match {
case (obj1: ConfigObject, obj2: ConfigObject) =>
assertEquals(obj1.origin(), obj2.origin())
for (e <- obj1.entrySet().asScala) {
checkEqualOrigins(e.getValue(), obj2.get(e.getKey()))
}
case (list1: ConfigList, list2: ConfigList) =>
assertEquals(list1.origin(), list2.origin())
for ((v1, v2) <- list1.asScala zip list2.asScala) {
checkEqualOrigins(v1, v2)
}
case (value1: ConfigValue, value2: ConfigValue) =>
assertEquals(value1.origin(), value2.origin())
case _ =>
}
}
def fakeOrigin() = {
SimpleConfigOrigin.newSimple("fake origin")
}