/*
* 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.smartandroid.sa.json.internal.bind;
import java.io.IOException;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.util.LinkedHashMap;
import java.util.Map;
import com.smartandroid.sa.json.internal.$Gson$Types;
import com.smartandroid.sa.json.internal.UnsafeAllocator;
import com.smartandroid.sa.json.reflect.TypeToken;
import com.smartandroid.sa.json.stream.JsonReader;
import com.smartandroid.sa.json.stream.JsonToken;
import com.smartandroid.sa.json.stream.JsonWriter;
/**
* Adapts the fields of an object to the properties of a JSON object.
*/
public final class ReflectiveTypeAdapter<T> extends TypeAdapter<T> {
public static final Factory FACTORY = new FactoryImpl();
private static final UnsafeAllocator unsafeAllocator = UnsafeAllocator
.create();
private final Class<? super T> rawType;
private final Constructor<? super T> constructor;
private final Map<String, BoundField> map;
private final BoundField[] boundFields;
ReflectiveTypeAdapter(Class<? super T> rawType,
Constructor<? super T> constructor, Map<String, BoundField> map) {
this.rawType = rawType;
this.constructor = constructor;
this.map = map;
this.boundFields = map.values().toArray(new BoundField[map.size()]);
}
@SuppressWarnings("unchecked")
// the '? super T' is a raw T (the only kind we can construct)
public T read(JsonReader reader) throws IOException {
if (reader.peek() == JsonToken.NULL) {
reader.nextNull(); // TODO: does this belong here?
return null;
}
T instance;
if (constructor != null) {
instance = (T) Reflection.newInstance(constructor);
} else {
try {
instance = (T) unsafeAllocator.newInstance(rawType);
} catch (Exception e) {
throw new RuntimeException(
("Unable to invoke no-args constructor for "
+ rawType.getName() + ". Register an InstanceCreator with Gson for this type may fix this problem."),
e);
}
}
// TODO: null out the other fields?
reader.beginObject();
try {
while (reader.hasNext()) {
String name = reader.nextName();
BoundField field = map.get(name);
if (field == null || !field.deserialized) {
// TODO: define a better policy
reader.skipValue();
} else {
field.read(reader, instance);
}
}
} catch (IllegalAccessException e) {
throw new AssertionError();
}
reader.endObject();
return instance;
}
public void write(JsonWriter writer, T value) throws IOException {
if (value == null) {
writer.nullValue(); // TODO: better policy here?
return;
}
writer.beginObject();
try {
for (BoundField boundField : boundFields) {
if (boundField.serialized) {
writer.name(boundField.name);
boundField.write(writer, value);
}
}
} catch (IllegalAccessException e) {
throw new AssertionError();
}
writer.endObject();
}
static BoundField createBoundField(final MiniGson context,
final Field field, final String name, final TypeToken<?> fieldType,
boolean serialize, boolean deserialize) {
// special casing primitives here saves ~5% on Android...
return new BoundField(name, serialize, deserialize) {
final TypeAdapter<?> typeAdapter = context.getAdapter(fieldType);
@SuppressWarnings("unchecked")
// the type adapter and field type always agree
@Override
void write(JsonWriter writer, Object value) throws IOException,
IllegalAccessException {
Object fieldValue = field.get(value);
TypeAdapter t = new TypeAdapterRuntimeTypeWrapper(context,
this.typeAdapter, fieldType.getType());
t.write(writer, fieldValue);
}
@Override
void read(JsonReader reader, Object value) throws IOException,
IllegalAccessException {
Object fieldValue = typeAdapter.read(reader);
field.set(value, fieldValue);
}
};
}
public static class FactoryImpl implements Factory {
public boolean serializeField(Class<?> declaringClazz, Field f,
Type declaredType) {
return true;
}
public boolean deserializeField(Class<?> declaringClazz, Field f,
Type declaredType) {
return true;
}
public String getFieldName(Class<?> declaringClazz, Field f,
Type declaredType) {
return f.getName();
}
public <T> TypeAdapter<T> create(MiniGson context, TypeToken<T> type) {
Class<? super T> raw = type.getRawType();
if (!Object.class.isAssignableFrom(raw)) {
return null; // it's a primitive!
}
Constructor<? super T> constructor = null;
try {
constructor = raw.getDeclaredConstructor();
} catch (NoSuchMethodException ignored) {
}
return new ReflectiveTypeAdapter<T>(raw, constructor,
getBoundFields(context, type, raw));
}
private Map<String, BoundField> getBoundFields(MiniGson context,
TypeToken<?> type, Class<?> raw) {
Map<String, BoundField> result = new LinkedHashMap<String, BoundField>();
if (raw.isInterface()) {
return result;
}
Type declaredType = type.getType();
while (raw != Object.class) {
Field[] fields = raw.getDeclaredFields();
AccessibleObject.setAccessible(fields, true);
for (Field field : fields) {
boolean serialize = serializeField(raw, field, declaredType);
boolean deserialize = deserializeField(raw, field,
declaredType);
if (serialize || deserialize) {
Type fieldType = $Gson$Types.resolve(type.getType(),
raw, field.getGenericType());
BoundField boundField = createBoundField(context,
field, getFieldName(raw, field, declaredType),
TypeToken.get(fieldType), serialize,
deserialize);
BoundField previous = result.put(boundField.name,
boundField);
if (previous != null) {
throw new IllegalArgumentException(declaredType
+ " declares multiple JSON fields named "
+ previous.name);
}
}
}
type = TypeToken.get($Gson$Types.resolve(type.getType(), raw,
raw.getGenericSuperclass()));
raw = type.getRawType();
}
return result;
}
}
static abstract class BoundField {
final String name;
final boolean serialized;
final boolean deserialized;
protected BoundField(String name, boolean serialized,
boolean deserialized) {
this.name = name;
this.serialized = serialized;
this.deserialized = deserialized;
}
abstract void write(JsonWriter writer, Object value)
throws IOException, IllegalAccessException;
abstract void read(JsonReader reader, Object value) throws IOException,
IllegalAccessException;
}
}