MiniGSON prototype!

This commit is contained in:
Jesse Wilson 2011-07-11 16:46:52 +00:00
parent 807aa97ee7
commit c5f1df1017
6 changed files with 622 additions and 0 deletions

View File

@ -0,0 +1,102 @@
/*
* Copyright (C) 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.gson.mini;
import com.google.gson.internal.$Gson$Types;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
/**
* Adapt a homogeneous collection of objects.
*/
final class CollectionTypeAdapter<E> extends TypeAdapter<Collection<E>> {
public static final Factory FACTORY = new Factory() {
public <T> TypeAdapter<T> create(MiniGson context, TypeToken<T> typeToken) {
Type type = typeToken.getType();
if (!(type instanceof ParameterizedType)) {
return null;
}
Class<? super T> rawType = typeToken.getRawType();
if (!Collection.class.isAssignableFrom(rawType)) {
return null;
}
Type elementType = $Gson$Types.getCollectionElementType(type, rawType);
TypeAdapter<?> elementTypeAdapter = context.getAdapter(TypeToken.get(elementType));
Class<?> constructorType;
if (rawType == List.class || rawType == Collection.class) {
constructorType = ArrayList.class;
} else if (rawType == Set.class) {
constructorType = LinkedHashSet.class;
} else {
constructorType = rawType;
}
Constructor<?> constructor;
try {
constructor = constructorType.getConstructor();
} catch (NoSuchMethodException e) {
return null;
}
@SuppressWarnings("unchecked") // we don't define a type parameter for the element type
TypeAdapter<T> result = new CollectionTypeAdapter(elementTypeAdapter, constructor);
return result;
}
};
private final TypeAdapter<E> elementTypeAdapter;
private final Constructor<? extends Collection<E>> constructor;
public CollectionTypeAdapter(TypeAdapter<E> elementTypeAdapter,
Constructor<? extends Collection<E>> constructor) {
this.elementTypeAdapter = elementTypeAdapter;
this.constructor = constructor;
}
public Collection<E> read(JsonReader reader) throws IOException {
Collection<E> collection = MiniGson.newInstance(constructor);
reader.beginArray();
while (reader.hasNext()) {
E instance = elementTypeAdapter.read(reader);
collection.add(instance);
}
reader.endArray();
return collection;
}
public void write(JsonWriter writer, Collection<E> collection) throws IOException {
writer.beginArray();
for (E element : collection) {
elementTypeAdapter.write(writer, element);
}
writer.endArray();
}
}

View File

@ -0,0 +1,117 @@
/*
* Copyright (C) 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.gson.mini;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* A basic binding between JSON and Java objects.
*/
public final class MiniGson {
private final List<TypeAdapter.Factory> factories;
private MiniGson(Builder builder) {
List<TypeAdapter.Factory> factories = new ArrayList<TypeAdapter.Factory>();
factories.addAll(builder.factories);
factories.add(TypeAdapters.BOOLEAN_FACTORY);
factories.add(TypeAdapters.INTEGER_FACTORY);
factories.add(TypeAdapters.DOUBLE_FACTORY);
factories.add(TypeAdapters.LONG_FACTORY);
factories.add(TypeAdapters.STRING_FACTORY);
factories.add(ReflectiveTypeAdapter.FACTORY);
factories.add(CollectionTypeAdapter.FACTORY);
this.factories = Collections.unmodifiableList(factories);
}
// TODO: this should use Joel's unsafe constructor stuff
static <T> T newInstance(Constructor<T> constructor) {
try {
return constructor.newInstance();
} catch (InstantiationException e) {
// TODO: JsonParseException ?
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
// TODO: don't wrap if cause is unchecked!
// TODO: JsonParseException ?
throw new RuntimeException(e.getTargetException());
} catch (IllegalAccessException e) {
throw new AssertionError(e);
}
}
/**
* Returns the type adapter for {@code} type.
*
* @throws IllegalArgumentException if this GSON cannot serialize and
* deserialize {@code type}.
*/
public <T> TypeAdapter<T> getAdapter(TypeToken<T> type) {
for (TypeAdapter.Factory factory : factories) {
TypeAdapter<T> candidate = factory.create(this, type);
if (candidate != null) {
return candidate;
}
}
throw new IllegalArgumentException("This MiniGSON cannot serialize " + type);
}
/**
* Returns the type adapter for {@code} type.
*
* @throws IllegalArgumentException if this GSON cannot serialize and
* deserialize {@code type}.
*/
public <T> TypeAdapter<T> getAdapter(Class<T> type) {
return getAdapter(TypeToken.get(type));
}
/**
* Returns the type adapters of this context in order of precedence.
*/
public List<TypeAdapter.Factory> getFactories() {
return factories;
}
public static final class Builder {
private final List<TypeAdapter.Factory> factories = new ArrayList<TypeAdapter.Factory>();
public void factory(TypeAdapter.Factory factory) {
factories.add(factory);
}
public <T> void typeAdapter(final Class<T> type, final TypeAdapter<T> typeAdapter) {
factories.add(TypeAdapters.newFactory(type, typeAdapter));
}
public <T> void typeAdapter(TypeToken<T> type, TypeAdapter<T> typeAdapter) {
factories.add(TypeAdapters.newFactory(type, typeAdapter));
}
public <T> void typeHierarchyAdapter(TypeToken<T> type, TypeAdapter<T> typeAdapter) {
factories.add(TypeAdapters.newTypeHierarchyFactory(type, typeAdapter));
}
public MiniGson build() {
return new MiniGson(this);
}
}
}

View File

@ -0,0 +1,143 @@
/*
* Copyright (C) 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.gson.mini;
import com.google.gson.internal.$Gson$Types;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* Adapts the fields of an object to the properties of a JSON object.
*/
final class ReflectiveTypeAdapter<T> extends TypeAdapter<T> {
public static final Factory FACTORY = new Factory() {
public <T> TypeAdapter<T> create(MiniGson context, TypeToken<T> type) {
Class<? super T> raw = type.getRawType();
if (!Object.class.isAssignableFrom(raw)) {
// TODO: does this catch primitives?
return null;
}
// TODO: use Joel's constructor calling code (with setAccessible)
Constructor<T> constructor;
try {
constructor = (Constructor<T>) raw.getDeclaredConstructor();
} catch (NoSuchMethodException e) {
return null;
}
return new ReflectiveTypeAdapter<T>(constructor, getBoundFields(context, type, raw));
}
private Map<String, BoundField<?>> getBoundFields(
MiniGson context, TypeToken<?> type, Class<?> raw) {
Map<String, BoundField<?>> result = new LinkedHashMap<String, BoundField<?>>();
while (raw != Object.class) {
for (Field field : raw.getDeclaredFields()) {
Type fieldType = $Gson$Types.resolve(type.getType(), raw, field.getGenericType());
BoundField<?> boundField = BoundField.create(context, field, TypeToken.get(fieldType));
result.put(boundField.name, boundField);
}
type = TypeToken.get($Gson$Types.resolve(type.getType(), raw, raw.getGenericSuperclass()));
raw = type.getRawType();
}
return result;
}
};
private final Constructor<T> constructor;
private final Map<String, BoundField<?>> map;
private final BoundField<?>[] boundFields;
ReflectiveTypeAdapter(Constructor<T> constructor, Map<String, BoundField<?>> map) {
this.constructor = constructor;
this.map = map;
this.boundFields = map.values().toArray(new BoundField<?>[map.size()]);
}
public T read(JsonReader reader) throws IOException {
T instance = MiniGson.newInstance(constructor);
// TODO: null out the other fields?
reader.beginObject();
while (reader.hasNext()) {
String name = reader.nextName();
BoundField<?> field = map.get(name);
if (field == null) {
// TODO: define a better policy
reader.skipValue();
} else {
field.read(reader, instance);
}
}
reader.endObject();
return instance;
}
public void write(JsonWriter writer, T value) throws IOException {
writer.beginObject();
for (BoundField<?> boundField : boundFields) {
writer.name(boundField.name);
boundField.write(writer, value);
}
writer.endObject();
}
static class BoundField<T> {
final String name;
final Field field;
final TypeAdapter<T> typeAdapter;
BoundField(String name, Field field, TypeAdapter<T> typeAdapter) {
this.name = name;
this.field = field;
this.typeAdapter = typeAdapter;
}
static <T> BoundField<T> create(MiniGson context, Field field, TypeToken<T> fieldType) {
return new BoundField<T>(field.getName(), field, context.getAdapter(fieldType));
}
void write(JsonWriter writer, Object value) throws IOException {
try {
@SuppressWarnings("unchecked") // we previously verified that field is of type T
T fieldValue = (T) field.get(value);
typeAdapter.write(writer, fieldValue);
} catch (IllegalAccessException e) {
throw new AssertionError();
}
}
void read(JsonReader reader, Object value) throws IOException {
T fieldValue = typeAdapter.read(reader);
try {
field.set(value, fieldValue);
} catch (IllegalAccessException e) {
throw new AssertionError();
}
}
}
}

View File

@ -0,0 +1,56 @@
/*
* Copyright (C) 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.gson.mini;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
public abstract class TypeAdapter<T> {
public abstract T read(JsonReader reader) throws IOException;
public abstract void write(JsonWriter writer, T value) throws IOException;
public final String toJson(T value) throws IOException {
StringWriter stringWriter = new StringWriter();
write(stringWriter, value);
return stringWriter.toString();
}
public final void write(Writer out, T value) throws IOException {
JsonWriter writer = new JsonWriter(out);
write(writer, value);
}
public final T fromJson(String json) throws IOException {
return read(new StringReader(json));
}
public final T read(Reader in) throws IOException {
JsonReader reader = new JsonReader(in);
reader.setLenient(true); // TODO: why?
return read(reader);
}
interface Factory {
<T> TypeAdapter<T> create(MiniGson context, TypeToken<T> type);
}
}

View File

@ -0,0 +1,129 @@
/*
* Copyright (C) 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.gson.mini;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
/**
* Type adapters for basic types.
*/
final class TypeAdapters {
private TypeAdapters() {}
public static final TypeAdapter<Boolean> BOOLEAN = new TypeAdapter<Boolean>() {
public Boolean read(JsonReader reader) throws IOException {
return reader.nextBoolean();
}
public void write(JsonWriter writer, Boolean value) throws IOException {
writer.value(value);
}
};
public static final TypeAdapter.Factory BOOLEAN_FACTORY
= newFactory(boolean.class, Boolean.class, BOOLEAN);
public static final TypeAdapter<Integer> INTEGER = new TypeAdapter<Integer>() {
public Integer read(JsonReader reader) throws IOException {
return reader.nextInt();
}
public void write(JsonWriter writer, Integer value) throws IOException {
writer.value(value);
}
};
public static final TypeAdapter.Factory INTEGER_FACTORY
= newFactory(int.class, Integer.class, INTEGER);
public static final TypeAdapter<Long> LONG = new TypeAdapter<Long>() {
public Long read(JsonReader reader) throws IOException {
return reader.nextLong();
}
public void write(JsonWriter writer, Long value) throws IOException {
writer.value(value);
}
};
public static final TypeAdapter.Factory LONG_FACTORY
= newFactory(long.class, Long.class, LONG);
public static final TypeAdapter<Double> DOUBLE = new TypeAdapter<Double>() {
public Double read(JsonReader reader) throws IOException {
return reader.nextDouble();
}
public void write(JsonWriter writer, Double value) throws IOException {
writer.value(value);
}
};
public static final TypeAdapter.Factory DOUBLE_FACTORY
= newFactory(double.class, Double.class, DOUBLE);
public static final TypeAdapter<String> STRING = new TypeAdapter<String>() {
public String read(JsonReader reader) throws IOException {
return reader.nextString();
}
public void write(JsonWriter writer, String value) throws IOException {
writer.value(value);
}
};
public static final TypeAdapter.Factory STRING_FACTORY = newFactory(String.class, STRING);
public static <T> TypeAdapter.Factory newFactory(
final TypeToken<T> type, final TypeAdapter<T> typeAdapter) {
return new TypeAdapter.Factory() {
@SuppressWarnings("unchecked") // we use a runtime check to make sure the 'T's equal
public <T> TypeAdapter<T> create(MiniGson context, TypeToken<T> typeToken) {
return typeToken.equals(type) ? (TypeAdapter<T>) typeAdapter : null;
}
};
}
public static <T> TypeAdapter.Factory newFactory(
final Class<T> type, final TypeAdapter<T> typeAdapter) {
return new TypeAdapter.Factory() {
@SuppressWarnings("unchecked") // we use a runtime check to make sure the 'T's equal
public <T> TypeAdapter<T> create(MiniGson context, TypeToken<T> typeToken) {
return typeToken.getRawType() == type ? (TypeAdapter<T>) typeAdapter : null;
}
};
}
private static <T> TypeAdapter.Factory newFactory(
final Class<T> unboxed, final Class<T> boxed, final TypeAdapter<T> typeAdapter) {
return new TypeAdapter.Factory() {
@SuppressWarnings("unchecked") // we use a runtime check to make sure the 'T's equal
public <T> TypeAdapter<T> create(MiniGson context, TypeToken<T> typeToken) {
Class<? super T> rawType = typeToken.getRawType();
return (rawType == unboxed || rawType == boxed) ? (TypeAdapter<T>) typeAdapter : null;
}
};
}
public static <T> TypeAdapter.Factory newTypeHierarchyFactory(
TypeToken<T> type, TypeAdapter<T> typeAdapter) {
return new TypeAdapter.Factory() {
public <T> TypeAdapter<T> create(MiniGson context, TypeToken<T> typeToken) {
// TODO: use Inder's TypeHierarchyAdapter here
throw new UnsupportedOperationException();
}
};
}
}

View File

@ -0,0 +1,75 @@
/*
* Copyright (C) 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.gson.mini;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import junit.framework.TestCase;
public final class MiniGsonTest extends TestCase {
public void testSerialize() throws IOException {
Person jesse = new Person("Jesse", 29);
Person jodie = new Person("Jodie", 29);
Truck truck = new Truck();
truck.passengers = Arrays.asList(jesse, jodie);
truck.horsePower = 300;
MiniGson miniGson = new MiniGson.Builder().build();
TypeAdapter<Truck> truckAdapter = miniGson.getAdapter(Truck.class);
String json = truckAdapter.toJson(truck);
assertEquals("{'horsePower':300.0,"
+ "'passengers':[{'age':29,'name':'Jesse'},{'age':29,'name':'Jodie'}]}",
json.replace('\"', '\''));
}
public void testDeserialize() throws IOException {
String json = "{'horsePower':300.0,"
+ "'passengers':[{'age':29,'name':'Jesse'},{'age':29,'name':'Jodie'}]}";
MiniGson miniGson = new MiniGson.Builder().build();
TypeAdapter<Truck> truckAdapter = miniGson.getAdapter(Truck.class);
Truck truck = truckAdapter.fromJson(json);
assertEquals(300.0, truck.horsePower);
Person jesse = truck.passengers.get(0);
assertEquals("Jesse", jesse.name);
assertEquals(29, jesse.age);
Person jodie = truck.passengers.get(1);
assertEquals("Jodie", jodie.name);
assertEquals(29, jodie.age);
}
static class Truck {
double horsePower;
List<Person> passengers;
}
static class Person {
int age;
String name;
Person(String name, int age) {
this.name = name;
this.age = age;
}
public Person() {} // TODO: use Joel's constructor code so we don't need this
}
}