I created a simple annotation class:
#Retention(RUNTIME)
public #interface Column {
public String name();
}
I use it in some classes like this:
public class FgnPzt extends Point {
public static final String COLUMN_TYPE = "type";
#Column(name=COLUMN_TYPE)
protected String type;
}
I know that I can iterate over the declared fields and obtain the annotation like this:
for (Field field : current.getDeclaredFields()) {
try {
Column c = field.getAnnotation(Column.class);
[...]
} catch(Exception e) {
[...]
}
}
How can I obtain the field type directly by its annotated name without iterating over declared fields of the class?
If you need to make multiple accesses you can pre-process the annotations.
public class ColumnExtracter<T> {
private final Map<String, Field> fieldsByColumn;
public ColumnExtracter(Class<T> clazz) {
this.fieldsByColumn = Stream.of(clazz.getDeclaredFields())
.filter(field -> field.isAnnotationPresent(Column.class))
.collect(Collectors.toMap(field -> field.getAnnotation(Column.class).name(), Function.identity()));
}
public Field getColumnField(String columnName) {
return fieldsByColumn.get(columnName);
}
public <R> R extract(String columnName, T t, Class<R> clazz) throws IllegalAccessException {
return clazz.cast(extract(columnName, t));
}
public Object extract(String columnName, T t) throws IllegalAccessException {
return getColumnField(columnName).get(t);
}
}
Related
I need to log the values of each object. The type of object may vary every time, i am trying to invoke getters of class using reflection. But i am stuck at a place where i need to reinvoke readData method, if class is a Custom Object. how to get an object to pass in readData(obj) in else block below.
private static void readData(Object resp) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
Method[] allMethods = resp.getClass().getDeclaredMethods();
for (Method m : allMethods) {
if ("get".equalsIgnoreCase(m.getName().substring(0, 3))) {
Class<?> type = m.getReturnType();
if (isWrapperType(type) || type.isPrimitive()) {
System.out.println(m.invoke(resp)) ;
}
else if(Collection.class.isAssignableFrom(type)) {
if(m.getGenericReturnType() instanceof ParameterizedType){
ParameterizedType paramType = (ParameterizedType) m.getGenericReturnType();
System.out.println("List is of type "+(Class<?>) paramType.getActualTypeArguments()[0]);
}
//iterate the object and recall read data with generic type of collection
}
else{
//Problem : need to pass object from type, how do i get this class object, as it should not be any new instance
readData(obj);
}
}
}
}
private static final Set<Class<?>> WRAPPER_TYPES = getWrapperTypes();
public static boolean isWrapperType(Class<?> clazz)
{
return WRAPPER_TYPES.contains(clazz);
}
private static Set<Class<?>> getWrapperTypes()
{
Set<Class<?>> ret = new HashSet<Class<?>>();
ret.add(Boolean.class);
ret.add(Character.class);
ret.add(Byte.class);
ret.add(Short.class);
ret.add(Integer.class);
ret.add(Long.class);
ret.add(Float.class);
ret.add(Double.class);
ret.add(String.class);
ret.add(BigDecimal.class);
ret.add(Number.class);
return ret;
}
This is how BO's look like
Response.java
public class Response {
List<OrderStatusList> orderStatusList;
StatusResponse response;
//getter-setter
}
StatusResponse.java
public class StatusResponse {
protected String type;
protected String message;
// getter-setter
}
OrderStatusList.java
public class OrderStatusList {
Header header;
// getter - setter
}
Header.java
public class Header {
protected String orderNumber;
protected String orderStatus;
protected List<DtOrderStatusResponseList> item;
//getter-setter
}
DtOrderStatusResponseList.java
public class DtOrderStatusResponseList {
protected String orderItemNumber;
protected String orderItemMaterialNumber;
protected String orderItemRequestedQuantity;
protected String orderItemStatus;
//getter-setter
}
Since you only need to log the values and not use them Overwrite the Object#toString method in all the Classes that hold information you want.
With this approach you can effectively have the information of every Object in one line.
For example
public class SOFTest {
privat int age, weight, height;
private Header header;
//Constructor etc.
#Overwrite
public String toString() {
return "SOFTest(" + String.format("%s, %s, %s %s)", age, weight, height, header.toString()));
}
}
I need to invoke the getter to the object of the custom class in readData(), so pass method.invoke(resp). It would be like this :
private static void readData(Object resp) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
Method[] allMethods = resp.getClass().getDeclaredMethods();
for (Method m : allMethods) {
if ("get".equalsIgnoreCase(m.getName().substring(0, 3))) {
Class<?> type = m.getReturnType();
if (isWrapperType(type) || type.isPrimitive()) {
System.out.println(m.invoke(resp)) ;
}
else if(Collection.class.isAssignableFrom(type)) {
if(m.getGenericReturnType() instanceof ParameterizedType){
ParameterizedType paramType = (ParameterizedType) m.getGenericReturnType();
System.out.println("List is of type "+(Class<?>) paramType.getActualTypeArguments()[0]);
}
}
else{
//Solution : need to invoke the getter to get the object and it would work
readData(method.invoke(resp));
}
}
}
}
I am very new to usage of annotation.
can anyone please tell me how can we declare an annotation and also call all the methods / variables that are declared with that annotation
am using java to implement this annotation
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
#Target(ElementType.FIELD)
public #interface isAnnotatedVariable {
String varName();
}
and used the annotation in
public class Example {
#isAnnotatedVariable(varName = "S")
public String var;
#isAnnotatedVariable(varName = "S")
public String var1;
}
and tried to get the variable names using
public class BuildStepClassDetector {
public static void main(String[] args) throws IOException {
BuildStepClassDetector build = new BuildStepClassDetector();
final Logger4J logger = new Logger4J(build.getClass().getName());
final HashMap<String, Class<?>> isAnnotatedVariables = new HashMap<String, Class<?>>();
final TypeReporter reporter = new TypeReporter() {
#SuppressWarnings("unchecked")
#Override
public Class<? extends Annotation>[] annotations() {
return new Class[] { isAnnotatedVariable.class };
}
#SuppressWarnings("unchecked")
#Override
public void reportTypeAnnotation(Class<? extends Annotation> arg0, String arg1) {
Class<? extends isAnnotatedVariable> isAnnotatedVariableClass;
try {
isAnnotatedVariableClass = (Class<? extends isAnnotatedVariable>) Class.forName(arg1);
isAnnotatedVariables.put(
isAnnotatedVariableClass.getAnnotation(isAnnotatedVariable.class).varName(),
isAnnotatedVariableClass);
} catch (ClassNotFoundException e) {
logger.getStackTraceString(e);
}
}
};
final AnnotationDetector cf = new AnnotationDetector(reporter);
cf.detect();
System.out.println(isAnnotatedVariables.keySet());
}
}
Here is a simple example for declaring annotation and retrieving a annotated field using Reflection.
package asif.hossain;
import java.lang.annotation.*;
import java.lang.reflect.Field;
/**
*
* Created by sadasidha on 21-Aug-14.
*/
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
#interface MyAnnotation {
public String value();
}
class TestClass
{
#MyAnnotation("This is a name field")
public String name;
}
public class Main {
public static void main(String ... args) throws IllegalAccessException {
TestClass testObject = new TestClass();
Field[] fields = testObject.getClass().getFields();
for (Field field : fields)
{
Annotation annotation = field.getAnnotation(MyAnnotation.class);
if(annotation instanceof MyAnnotation)
{
System.out.println(field.getName());
// get field value
String value = (String)field.get(testObject);
System.out.println("Field Value = "+ value);
//Set field value
field.set(testObject,"Your Name");
System.out.println(testObject.name);
}
}
}
}
You can follow this tutorial http://tutorials.jenkov.com/java-reflection/index.html to learn more about annotation and reflection.
I've faced with a requirement to deserialize fields that possibly can be transient using XStream 1.4.2. Despite of that, such fields may be annotated with both #XStreamAlias and #XStreamAsAttribute. Yes, I know, it sounds weird, and this is an indicator of bad design, but this is what I currently have. Since XStream offers a way to specify custom converter, I tried to extend com.thoughtworks.xstream.converters.reflection.ReflectionConverter in order to override the default way of omitting all transient fields trying to make XStream allow to deserialize them. However, I've fully stuck having two ideas to implement such a converter, but none of them works. So here is what I tried:
The 1st way doesn't work:
public final class TransientSimpleConverter extends ReflectionConverter {
private final Class<?> type;
private TransientSimpleConverter(Class<?> type, Mapper mapper, ReflectionProvider reflectionProvider) {
super(mapper, reflectionProvider);
this.type = type;
}
public static TransientSimpleConverter transientSimpleConverter(Class<?> type, XStream xStream) {
return new TransientSimpleConverter(type, xStream.getMapper(), xStream.getReflectionProvider());
}
#Override
protected boolean shouldUnmarshalTransientFields() {
return true;
}
#Override
public boolean canConvert(Class type) {
return this.type == type;
}
}
The 2nd way doesn't work either:
public final class TransientComplexConverter extends ReflectionConverter {
private final Class<?> type;
private TransientComplexConverter(Class<?> type, Mapper mapper, ReflectionProvider provider) {
super(mapper, provider);
this.type = type;
}
public static TransientComplexConverter transientComplexConverter(Class<?> type, Mapper mapper, Iterable<String> fieldNames) {
return new TransientComplexConverter(type, mapper, TransientHackReflectionProvider.transientHackReflectionProvider(type, fieldNames));
}
#Override
public boolean canConvert(Class type) {
return this.type == type;
}
private static final class TransientHackReflectionProvider extends PureJavaReflectionProvider {
private final Class<?> type;
private final Collection<Field> allowedFields;
private final Collection<String> allowedAliases;
private TransientHackReflectionProvider(Class<?> type, Collection<Field> allowedFields, Collection<String> allowedAliases) {
this.type = type;
this.allowedFields = allowedFields;
this.allowedAliases = allowedAliases;
}
public static TransientHackReflectionProvider transientHackReflectionProvider(final Class<?> type, Iterable<String> fieldNames) {
final Collection<Field> allowedFields = from(fieldNames).transform(new Function<String, Field>() {
#Override
public Field apply(String name) {
return field(type, name);
}
}).toList();
final Collection<String> allowedAliases = transform(allowedFields, new Function<Field, String>() {
#Override
public String apply(Field f) {
return f.getName();
}
});
return new TransientHackReflectionProvider(type, allowedFields, allowedAliases);
}
#Override
protected boolean fieldModifiersSupported(Field field) {
return allowedFields.contains(field) ? true : super.fieldModifiersSupported(field);
}
#Override
public boolean fieldDefinedInClass(String fieldName, Class type) {
return type == this.type && allowedAliases.contains(fieldName) ? true : super.fieldDefinedInClass(fieldName, type);
}
private static final Field field(Class<?> type, String name) {
try {
final Field field = type.getDeclaredField(name);
checkArgument(isTransient(field.getModifiers()), name + " is not transient");
checkArgument(field.getAnnotation(XStreamAsAttribute.class) != null, name + " must be annotated with XStreamAsAttribute");
checkArgument(field.getAnnotation(XStreamAlias.class) != null, name + " must be annotated with XStreamAlias");
return field;
} catch (final SecurityException ex) {
throw new RuntimeException(ex);
} catch (final NoSuchFieldException ex) {
throw new RuntimeException(ex);
}
}
}
}
Any suggestions or ideas for a workaround? Thanks in advance.
I know this post is old, but maybe someone is still interested. My solution:
XStream xstream = new XStream(new MyPureJavaReflectionProvider());
class MyPureJavaReflectionProvider extends PureJavaReflectionProvider {
public MyPureJavaReflectionProvider() {
this(new FieldDictionary(new ImmutableFieldKeySorter()));
}
public MyPureJavaReflectionProvider(FieldDictionary fieldDictionary) {
super(fieldDictionary);
}
protected boolean fieldModifiersSupported(Field field) {
int modifiers = field.getModifiers();
return !Modifier.isStatic(modifiers);
}
public boolean fieldDefinedInClass(String fieldName, Class type) {
Field field = fieldDictionary.fieldOrNull(type, fieldName, null);
return field != null && fieldModifiersSupported(field);
}
}
I would like to use the generic type safe container pattern, described in Joshua Bloch's Effective Java, but would like to restrict the classes which can be used as keys by using an enum. Below is the code from Joshua's book.
public class Favorites {
private Map<Class<?>, Object> favorites = new HashMap<Class<?>, Object>();
public <T> void putFavorite(Class<T> type, T instance) {
if (type == null)
throw new NullPointerException("Type is null");
favorites.put(type, instance);
}
public <T> T getFavorite(Class<T> type) {
return type.cast(favorites.get(type));
}
}
I would like to write a similar class, but limit the keys to say "Dog.class", and "Cat.class". Ideally, the acceptable keys would be described by an enum, and the "RestrictedFavorites" class would take members of the enum as keys. I'm not sure if I can get the compiler to do all these things for me (type safety, restriction by enum, generality), but if anybody has a suggestion, I'm all ears. Below is attempt V1, which uses runtime checks rather than compile time checks, and is not entirely satisfactory.
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* Attempt V1 At a "RestrictedFavorites" class
*/
public class RestrictedFavorites {
public static enum RestrictedKey {
STRING(String.class),
INTEGER(Integer.class);
private static Set<Class<?>> classes;
static {
classes = new HashSet<>();
for (RestrictedKey key: values()) {
classes.add(key.getKlass());
}
}
private final Class<?> klass;
RestrictedKey(Class<?> klass) {
this.klass = klass;
}
public Class<?> getKlass() {
return klass;
}
public static boolean isValidClassKey(Class<?> klass) {
return classes.contains(klass);
}
}
private Map<Class<?>, Object> favorites = new HashMap<Class<?>, Object>();
//Ideally would use compile time checking
public <T> void putFavorite(RestrictedKey key, T instance) {
if (key == null) throw new NullPointerException("Type is null");
if (!key.getKlass().equals(instance.getClass())) {
throw new IllegalArgumentException(
"The type of the key must match the type of the instance");
}
favorites.put(key.getKlass(), instance);
}
//Ideally would take a RestrictedKey as an argument
public <T> T getFavorite(Class<T> key) {
if (!RestrictedKey.isValidClassKey(key)) {
throw new IllegalArgumentException(
"The key must be a member of RestrictedKeys");
}
return key.cast(favorites.get(key));
}
}
Below are some unit tests to verify that my class is doing roughly what I want it to:
public class RestrictedFavoritesTest extends TestCase {
public void testPutFavorite() {
RestrictedFavorites myFavorites = new RestrictedFavorites();
myFavorites.putFavorite(RestrictedKey.INTEGER, 1);
myFavorites.putFavorite(RestrictedKey.STRING, "hey");
int expectedInt = myFavorites.getFavorite(Integer.class);
assertEquals(1, expectedInt);
String expectedString = myFavorites.getFavorite(String.class);
assertEquals("hey", expectedString);
}
public void testPutFavorite_wrongType() {
RestrictedFavorites myFavorites = new RestrictedFavorites();
try {
myFavorites.putFavorite(RestrictedKey.INTEGER, "hey");
fail();
} catch (IllegalArgumentException expected) {}
}
public void testPutFavorite_wrongClass() {
RestrictedFavorites myFavorites = new RestrictedFavorites();
try {
myFavorites.getFavorite(Boolean.class);
} catch (IllegalArgumentException expected) {}
}
}
Answer (to my own question). Don't use Enums. Because enums can't be generic. Instead, create a class to represent the restricted keys, and restrict access to the constructor. Enumerate the valid keys as fields.
import java.util.HashMap;
import java.util.Map;
public class RestrictedFavorites {
private static final class RestrictedKey<T> {
private final Class<T> type;
private RestrictedKey(Class<T> type) {
this.type = type;
}
private Class<T> getMyType() {
return this.type;
}
}
public static final RestrictedKey<String> STRING_KEY =
new RestrictedKey<>(String.class);
public static final RestrictedKey<Integer> INTEGER_KEY =
new RestrictedKey<>(Integer.class);
private final Map<RestrictedKey<?>, Object> favorites =
new HashMap<RestrictedKey<?>, Object>();
public <T> void putFavorite(RestrictedKey<T> key, T instance) {
favorites.put(key, instance);
}
public <T> T getFavorite(RestrictedKey<T> key) {
return key.getMyType().cast(favorites.get(key));
}
}
And the unit tests:
public class RestrictedFavoritesTest extends TestCase {
public void testPutFavorite() {
RestrictedFavorites myFavorites = new RestrictedFavorites();
myFavorites.putFavorite(RestrictedFavorites.STRING_KEY, "hey");
myFavorites.putFavorite(RestrictedFavorites.INTEGER_KEY, 1);
assertEquals(new Integer(1), myFavorites.getFavorite(RestrictedFavorites.INTEGER_KEY));
assertEquals("hey", myFavorites.getFavorite(RestrictedFavorites.STRING_KEY));
}
}
I've seen that the default TypeAdapter for Enum doesn't fit my need:
private static final class EnumTypeAdapter<T extends Enum<T>> extends TypeAdapter<T> {
private final Map<String, T> nameToConstant = new HashMap<String, T>();
private final Map<T, String> constantToName = new HashMap<T, String>();
public EnumTypeAdapter(Class<T> classOfT) {
try {
for (T constant : classOfT.getEnumConstants()) {
String name = constant.name();
SerializedName annotation = classOfT.getField(name).getAnnotation(SerializedName.class);
if (annotation != null) {
name = annotation.value();
}
nameToConstant.put(name, constant);
constantToName.put(constant, name);
}
} catch (NoSuchFieldException e) {
throw new AssertionError();
}
}
public T read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) {
in.nextNull();
return null;
}
return nameToConstant.get(in.nextString());
}
public void write(JsonWriter out, T value) throws IOException {
out.value(value == null ? null : constantToName.get(value));
}
}
If the Enum has value ONE and TWO, when we try to parse THREE, then this value is unknown and Gson will map null instead of raising a parsing exception. I need something more fail-fast.
But I also need something which permits me to know the name of the field which is currently read and creates a parsing failure.
Is it possible with Gson?
Yes.
Gson is quite modular to allow you to use your own TypeAdapterFactory for the enum case. Your custom adapter will return your own EnumTypeAdapter and manage the wanted case. Let the code speak.
package stackoverflow.questions.q16715117;
import java.io.IOException;
import java.util.*;
import com.google.gson.*;
import com.google.gson.annotations.SerializedName;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.*;
public class Q16715117 {
public static void main(String[] args) {
GsonBuilder gb = new GsonBuilder();
gb.registerTypeAdapterFactory(CUSTOM_ENUM_FACTORY);
Container c1 = new Container();
Gson g = gb.create();
String s1 = "{\"colour\":\"RED\",\"number\":42}";
c1 = g.fromJson(s1, Container.class);
System.out.println("Result: "+ c1.toString());
}
public static final TypeAdapterFactory CUSTOM_ENUM_FACTORY = newEnumTypeHierarchyFactory();
public static TypeAdapterFactory newEnumTypeHierarchyFactory() {
return new TypeAdapterFactory() {
#SuppressWarnings({"rawtypes", "unchecked"})
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
Class<? super T> rawType = typeToken.getRawType();
if (!Enum.class.isAssignableFrom(rawType) || rawType == Enum.class) {
return null;
}
if (!rawType.isEnum()) {
rawType = rawType.getSuperclass(); // handle anonymous subclasses
}
return (TypeAdapter<T>) new CustomEnumTypeAdapter(rawType);
}
};
}
private static final class CustomEnumTypeAdapter<T extends Enum<T>> extends TypeAdapter<T> {
private final Map<String, T> nameToConstant = new HashMap<String, T>();
private final Map<T, String> constantToName = new HashMap<T, String>();
private Class<T> classOfT;
public CustomEnumTypeAdapter(Class<T> classOfT) {
this.classOfT = classOfT;
try {
for (T constant : classOfT.getEnumConstants()) {
String name = constant.name();
SerializedName annotation = classOfT.getField(name).getAnnotation(SerializedName.class);
if (annotation != null) {
name = annotation.value();
}
nameToConstant.put(name, constant);
constantToName.put(constant, name);
}
} catch (NoSuchFieldException e) {
throw new AssertionError();
}
}
public T read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) {
in.nextNull();
return null;
}
String nextString = in.nextString();
T enumValue = nameToConstant.get(nextString);
if (enumValue == null)
throw new GsonEnumParsinException(nextString, classOfT.getName());
return enumValue;
}
public void write(JsonWriter out, T value) throws IOException {
out.value(value == null ? null : constantToName.get(value));
}
}
}
Plus I declared a custom runtime exception:
public class GsonEnumParsinException extends RuntimeException {
String notFoundEnumValue;
String enumName;
String fieldName;
public GsonEnumParsinException(String notFoundEnumValue, String enumName) {
this.notFoundEnumValue = notFoundEnumValue;
this.enumName = enumName;
}
#Override
public String toString() {
return "GsonEnumParsinException [notFoundEnumValue="
+ notFoundEnumValue + ", enumName=" + enumName + "]";
}
public String getNotFoundEnumValue() {
return notFoundEnumValue;
}
#Override
public String getMessage() {
return "Cannot found " + notFoundEnumValue + " for enum " + enumName;
}
}
These are the classes I used in the example:
public enum Colour {
WHITE, YELLOW, BLACK;
}
public class Container {
private Colour colour;
private int number;
public Colour getColour() {
return colour;
}
public void setColour(Colour colour) {
this.colour = colour;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
#Override
public String toString() {
return "Container [colour=" + colour + ", number=" + number + "]";
}
}
This gives this stacktrace:
Exception in thread "main" GsonEnumParsinException [notFoundEnumValue=RED, enumName=stackoverflow.questions.q16715117.Colour]
at stackoverflow.questions.q16715117.Q16715117$CustomEnumTypeAdapter.read(Q16715117.java:77)
at stackoverflow.questions.q16715117.Q16715117$CustomEnumTypeAdapter.read(Q16715117.java:1)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.read(ReflectiveTypeAdapterFactory.java:93)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:172)
at com.google.gson.Gson.fromJson(Gson.java:803)
at com.google.gson.Gson.fromJson(Gson.java:768)
at com.google.gson.Gson.fromJson(Gson.java:717)
at com.google.gson.Gson.fromJson(Gson.java:689)
at stackoverflow.questions.q16715117.Q16715117.main(Q16715117.java:22)
Unfortunately, the EnumTypeAdapter does not know anything about the context it's called, so this solution is not enough to catch the field name.
Edit
So you have to use also another TypeAdapter that I called CustomReflectiveTypeAdapterFactory and is almost a copy of CustomReflectiveTypeAdapterFactory and I changed a bit the exception, so:
public final class CustomReflectiveTypeAdapterFactory implements TypeAdapterFactory {
private final ConstructorConstructor constructorConstructor;
private final FieldNamingStrategy fieldNamingPolicy;
private final Excluder excluder;
public CustomReflectiveTypeAdapterFactory(ConstructorConstructor constructorConstructor,
FieldNamingStrategy fieldNamingPolicy, Excluder excluder) {
this.constructorConstructor = constructorConstructor;
this.fieldNamingPolicy = fieldNamingPolicy;
this.excluder = excluder;
}
public boolean excludeField(Field f, boolean serialize) {
return !excluder.excludeClass(f.getType(), serialize) && !excluder.excludeField(f, serialize);
}
private String getFieldName(Field f) {
SerializedName serializedName = f.getAnnotation(SerializedName.class);
return serializedName == null ? fieldNamingPolicy.translateName(f) : serializedName.value();
}
public <T> TypeAdapter<T> create(Gson gson, final TypeToken<T> type) {
Class<? super T> raw = type.getRawType();
if (!Object.class.isAssignableFrom(raw)) {
return null; // it's a primitive!
}
ObjectConstructor<T> constructor = constructorConstructor.get(type);
return new Adapter<T>(constructor, getBoundFields(gson, type, raw));
}
private CustomReflectiveTypeAdapterFactory.BoundField createBoundField(
final Gson context, final Field field, final String name,
final TypeToken<?> fieldType, boolean serialize, boolean deserialize) {
final boolean isPrimitive = Primitives.isPrimitive(fieldType.getRawType());
// special casing primitives here saves ~5% on Android...
return new CustomReflectiveTypeAdapterFactory.BoundField(name, serialize, deserialize) {
final TypeAdapter<?> typeAdapter = context.getAdapter(fieldType);
#SuppressWarnings({"unchecked", "rawtypes"}) // 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 CustomTypeAdapterRuntimeTypeWrapper(context, this.typeAdapter, fieldType.getType());
t.write(writer, fieldValue);
}
#Override void read(JsonReader reader, Object value)
throws IOException, IllegalAccessException {
Object fieldValue = null;
try {
fieldValue = typeAdapter.read(reader);
} catch (GsonEnumParsinException e){
e.setFieldName(field.getName());
throw e;
}
if (fieldValue != null || !isPrimitive) {
field.set(value, fieldValue);
}
}
};
}
// more copy&paste code follows
The most important part is read method where I catch the exception and add the field name and throw again exception. Note that class CustomTypeAdapterRuntimeTypeWrapper is simply a renamed copy of TypeAdapterRuntimeTypeWrapper in library internals since class is private.
So, main method changes as follows:
Map<Type, InstanceCreator<?>> instanceCreators
= new HashMap<Type, InstanceCreator<?>>();
Excluder excluder = Excluder.DEFAULT;
FieldNamingStrategy fieldNamingPolicy = FieldNamingPolicy.IDENTITY;
GsonBuilder gb = new GsonBuilder();
gb.registerTypeAdapterFactory(new CustomReflectiveTypeAdapterFactory(new ConstructorConstructor(instanceCreators), fieldNamingPolicy, excluder));
gb.registerTypeAdapterFactory(CUSTOM_ENUM_FACTORY);
Gson g = gb.create();
and now you have this stacktrace (changes to exception are so simple that I omitted them):
Exception in thread "main" GsonEnumParsinException [notFoundEnumValue=RED, enumName=stackoverflow.questions.q16715117.Colour, fieldName=colour]
at stackoverflow.questions.q16715117.Q16715117$CustomEnumTypeAdapter.read(Q16715117.java:90)
at stackoverflow.questions.q16715117.Q16715117$CustomEnumTypeAdapter.read(Q16715117.java:1)
at stackoverflow.questions.q16715117.CustomReflectiveTypeAdapterFactory$1.read(CustomReflectiveTypeAdapterFactory.java:79)
at stackoverflow.questions.q16715117.CustomReflectiveTypeAdapterFactory$Adapter.read(CustomReflectiveTypeAdapterFactory.java:162)
at com.google.gson.Gson.fromJson(Gson.java:803)
at com.google.gson.Gson.fromJson(Gson.java:768)
at com.google.gson.Gson.fromJson(Gson.java:717)
at com.google.gson.Gson.fromJson(Gson.java:689)
at stackoverflow.questions.q16715117.Q16715117.main(Q16715117.java:35)
Of course this solution comes at some costs.
First off all, you have to copy some private/final classes and do your changes. If library get updated, you have to check again your code (a fork of source code would be the same, but at least you do not have to copy all that code).
If you customize field exclusion strategy, constructors or field naming policies you have to replicate them into the CustomReflectiveTypeAdapterFactory since I do not find any possibility to pass them from the builder.