mirror of https://github.com/apache/jclouds.git
Merge pull request #678 from aplowe/master
Issue 971: adding TypeAdapterFactory and adjusting configuration to allow use of Inject, Named and ConstructorProperties annotations
This commit is contained in:
commit
805712d463
|
@ -18,6 +18,7 @@
|
||||||
*/
|
*/
|
||||||
package org.jclouds.json.config;
|
package org.jclouds.json.config;
|
||||||
|
|
||||||
|
import java.beans.ConstructorProperties;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.reflect.Type;
|
import java.lang.reflect.Type;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
@ -35,20 +36,29 @@ import org.jclouds.crypto.CryptoStreams;
|
||||||
import org.jclouds.date.DateService;
|
import org.jclouds.date.DateService;
|
||||||
import org.jclouds.domain.JsonBall;
|
import org.jclouds.domain.JsonBall;
|
||||||
import org.jclouds.json.Json;
|
import org.jclouds.json.Json;
|
||||||
|
import org.jclouds.json.internal.DeserializationConstructorAndReflectiveTypeAdapterFactory;
|
||||||
import org.jclouds.json.internal.EnumTypeAdapterThatReturnsFromValue;
|
import org.jclouds.json.internal.EnumTypeAdapterThatReturnsFromValue;
|
||||||
import org.jclouds.json.internal.GsonWrapper;
|
import org.jclouds.json.internal.GsonWrapper;
|
||||||
|
import org.jclouds.json.internal.NamingStrategies.AnnotationConstructorNamingStrategy;
|
||||||
|
import org.jclouds.json.internal.NamingStrategies.AnnotationOrNameFieldNamingStrategy;
|
||||||
|
import org.jclouds.json.internal.NamingStrategies.ExtractNamed;
|
||||||
|
import org.jclouds.json.internal.NamingStrategies.ExtractSerializedName;
|
||||||
import org.jclouds.json.internal.NullHackJsonLiteralAdapter;
|
import org.jclouds.json.internal.NullHackJsonLiteralAdapter;
|
||||||
import org.jclouds.json.internal.OptionalTypeAdapterFactory;
|
import org.jclouds.json.internal.OptionalTypeAdapterFactory;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
import com.google.common.collect.ImmutableMap.Builder;
|
import com.google.common.collect.ImmutableMap.Builder;
|
||||||
|
import com.google.common.collect.ImmutableSet;
|
||||||
import com.google.common.collect.Maps;
|
import com.google.common.collect.Maps;
|
||||||
import com.google.common.collect.Sets;
|
import com.google.common.collect.Sets;
|
||||||
import com.google.common.primitives.Bytes;
|
import com.google.common.primitives.Bytes;
|
||||||
|
import com.google.gson.FieldNamingStrategy;
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.GsonBuilder;
|
import com.google.gson.GsonBuilder;
|
||||||
import com.google.gson.TypeAdapter;
|
import com.google.gson.TypeAdapter;
|
||||||
import com.google.gson.TypeAdapterFactory;
|
import com.google.gson.TypeAdapterFactory;
|
||||||
|
import com.google.gson.internal.ConstructorConstructor;
|
||||||
|
import com.google.gson.internal.Excluder;
|
||||||
import com.google.gson.internal.JsonReaderInternalAccess;
|
import com.google.gson.internal.JsonReaderInternalAccess;
|
||||||
import com.google.gson.reflect.TypeToken;
|
import com.google.gson.reflect.TypeToken;
|
||||||
import com.google.gson.stream.JsonReader;
|
import com.google.gson.stream.JsonReader;
|
||||||
|
@ -69,8 +79,12 @@ public class GsonModule extends AbstractModule {
|
||||||
@Singleton
|
@Singleton
|
||||||
Gson provideGson(TypeAdapter<JsonBall> jsonAdapter, DateAdapter adapter, ByteListAdapter byteListAdapter,
|
Gson provideGson(TypeAdapter<JsonBall> jsonAdapter, DateAdapter adapter, ByteListAdapter byteListAdapter,
|
||||||
ByteArrayAdapter byteArrayAdapter, PropertiesAdapter propertiesAdapter, JsonAdapterBindings bindings)
|
ByteArrayAdapter byteArrayAdapter, PropertiesAdapter propertiesAdapter, JsonAdapterBindings bindings)
|
||||||
throws ClassNotFoundException, Exception {
|
throws Exception {
|
||||||
GsonBuilder builder = new GsonBuilder();
|
|
||||||
|
FieldNamingStrategy serializationPolicy = new AnnotationOrNameFieldNamingStrategy(new ExtractSerializedName(),
|
||||||
|
new ExtractNamed());
|
||||||
|
|
||||||
|
GsonBuilder builder = new GsonBuilder().setFieldNamingStrategy(serializationPolicy);
|
||||||
|
|
||||||
// simple (type adapters)
|
// simple (type adapters)
|
||||||
builder.registerTypeAdapter(Properties.class, propertiesAdapter.nullSafe());
|
builder.registerTypeAdapter(Properties.class, propertiesAdapter.nullSafe());
|
||||||
|
@ -81,6 +95,14 @@ public class GsonModule extends AbstractModule {
|
||||||
builder.registerTypeAdapter(JsonBall.class, jsonAdapter.nullSafe());
|
builder.registerTypeAdapter(JsonBall.class, jsonAdapter.nullSafe());
|
||||||
builder.registerTypeAdapterFactory(new OptionalTypeAdapterFactory());
|
builder.registerTypeAdapterFactory(new OptionalTypeAdapterFactory());
|
||||||
|
|
||||||
|
AnnotationConstructorNamingStrategy deserializationPolicy =
|
||||||
|
new AnnotationConstructorNamingStrategy(
|
||||||
|
ImmutableSet.of(ConstructorProperties.class, Inject.class), ImmutableSet.of(new ExtractNamed()));
|
||||||
|
|
||||||
|
builder.registerTypeAdapterFactory(
|
||||||
|
new DeserializationConstructorAndReflectiveTypeAdapterFactory(new ConstructorConstructor(),
|
||||||
|
serializationPolicy, Excluder.DEFAULT, deserializationPolicy));
|
||||||
|
|
||||||
// complicated (serializers/deserializers as they need context to operate)
|
// complicated (serializers/deserializers as they need context to operate)
|
||||||
builder.registerTypeHierarchyAdapter(Enum.class, new EnumTypeAdapterThatReturnsFromValue());
|
builder.registerTypeHierarchyAdapter(Enum.class, new EnumTypeAdapterThatReturnsFromValue());
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,256 @@
|
||||||
|
/**
|
||||||
|
* Licensed to jclouds, Inc. (jclouds) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. jclouds licenses this file
|
||||||
|
* to you 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 org.jclouds.json.internal;
|
||||||
|
|
||||||
|
import static com.google.common.base.Preconditions.checkArgument;
|
||||||
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.jclouds.json.internal.NamingStrategies.ConstructorFieldNamingStrategy;
|
||||||
|
|
||||||
|
import com.google.gson.FieldNamingStrategy;
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.JsonSyntaxException;
|
||||||
|
import com.google.gson.TypeAdapter;
|
||||||
|
import com.google.gson.TypeAdapterFactory;
|
||||||
|
import com.google.gson.internal.$Gson$Types;
|
||||||
|
import com.google.gson.internal.ConstructorConstructor;
|
||||||
|
import com.google.gson.internal.Excluder;
|
||||||
|
import com.google.gson.internal.bind.ReflectiveTypeAdapterFactory;
|
||||||
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
import com.google.gson.stream.JsonReader;
|
||||||
|
import com.google.gson.stream.JsonToken;
|
||||||
|
import com.google.gson.stream.JsonWriter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates type adapters for types handled in the following ways:
|
||||||
|
* <p/>
|
||||||
|
* <ul>
|
||||||
|
* <li>Deserialization</li>
|
||||||
|
* If there's an annotation designating a parameterized constructor, invoke that for fields
|
||||||
|
* correlating to named parameter annotations. Otherwise, use {@link ConstructorConstructor}, and
|
||||||
|
* set fields via reflection.
|
||||||
|
* <li>Serialization</li>
|
||||||
|
* Serialize based on reflective access to fields, delegating to ReflectiveTypeAdaptor.
|
||||||
|
* </ul>
|
||||||
|
* <h3>Example: Using javax inject to select a constructor and corresponding named parameters</h3>
|
||||||
|
* <p/>
|
||||||
|
* <pre>
|
||||||
|
*
|
||||||
|
* import NamingStrategies.*;
|
||||||
|
*
|
||||||
|
* serializationStrategy = new AnnotationOrNameFieldNamingStrategy(
|
||||||
|
* new ExtractSerializedName(), new ExtractNamed());
|
||||||
|
*
|
||||||
|
* deserializationStrategy = new AnnotationConstructorNamingStrategy(
|
||||||
|
* Collections.singleton(javax.inject.Inject.class),
|
||||||
|
* Collections.singleton(new ExtractNamed()));
|
||||||
|
*
|
||||||
|
* factory = new DeserializationConstructorAndReflectiveTypeAdapterFactory(new ConstructorConstructor(),
|
||||||
|
* serializationStrategy, Excluder.DEFAULT, deserializationStrategy);
|
||||||
|
*
|
||||||
|
* gson = new GsonBuilder(serializationStrategy).registerTypeAdapterFactory(factory).create();
|
||||||
|
*
|
||||||
|
* </pre>
|
||||||
|
* <p/>
|
||||||
|
* The above would work fine on the following class, which has no gson-specific annotations:
|
||||||
|
* <p/>
|
||||||
|
* <pre>
|
||||||
|
* private static class ImmutableAndVerifiedInCtor {
|
||||||
|
* final int foo;
|
||||||
|
* @Named("_bar")
|
||||||
|
* final int bar;
|
||||||
|
*
|
||||||
|
* @Inject
|
||||||
|
* ImmutableAndVerifiedInCtor(@Named("foo") int foo, @Named("_bar") int bar) {
|
||||||
|
* if (foo < 0)
|
||||||
|
* throw new IllegalArgumentException("negative!");
|
||||||
|
* this.foo = foo;
|
||||||
|
* this.bar = bar;
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* </pre>
|
||||||
|
* <p/>
|
||||||
|
* <br/>
|
||||||
|
*
|
||||||
|
* @author Adrian Cole
|
||||||
|
* @author Adam Lowe
|
||||||
|
*/
|
||||||
|
public final class DeserializationConstructorAndReflectiveTypeAdapterFactory implements TypeAdapterFactory {
|
||||||
|
private final ConstructorFieldNamingStrategy constructorFieldNamingPolicy;
|
||||||
|
private final ReflectiveTypeAdapterFactory delegateFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param constructorConstructor passed through to delegate ReflectiveTypeAdapterFactory for serialization
|
||||||
|
* @param serializationFieldNamingPolicy passed through to delegate ReflectiveTypeAdapterFactory for serialization
|
||||||
|
* @param excluder passed through to delegate ReflectiveTypeAdapterFactory for serialization
|
||||||
|
* @param deserializationFieldNamingPolicy
|
||||||
|
* determines which constructor to use and how to determine field names for
|
||||||
|
* deserialization
|
||||||
|
* @see ReflectiveTypeAdapterFactory
|
||||||
|
*/
|
||||||
|
public DeserializationConstructorAndReflectiveTypeAdapterFactory(
|
||||||
|
ConstructorConstructor constructorConstructor,
|
||||||
|
FieldNamingStrategy serializationFieldNamingPolicy,
|
||||||
|
Excluder excluder,
|
||||||
|
ConstructorFieldNamingStrategy deserializationFieldNamingPolicy) {
|
||||||
|
this.constructorFieldNamingPolicy = checkNotNull(deserializationFieldNamingPolicy, "deserializationFieldNamingPolicy");
|
||||||
|
this.delegateFactory = new ReflectiveTypeAdapterFactory(constructorConstructor, checkNotNull(serializationFieldNamingPolicy, "fieldNamingPolicy"), checkNotNull(excluder, "excluder"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public <T> TypeAdapter<T> create(Gson gson, final TypeToken<T> type) {
|
||||||
|
Class<? super T> raw = type.getRawType();
|
||||||
|
Constructor<? super T> deserializationCtor = constructorFieldNamingPolicy.getDeserializationConstructor(raw);
|
||||||
|
|
||||||
|
if (deserializationCtor == null) {
|
||||||
|
return null; // allow GSON to choose the correct Adapter (can't simply return delegateFactory.create())
|
||||||
|
} else {
|
||||||
|
deserializationCtor.setAccessible(true);
|
||||||
|
return new DeserializeWithParameterizedConstructorSerializeWithDelegate<T>(delegateFactory.create(gson, type), deserializationCtor,
|
||||||
|
getParameterReaders(gson, type, deserializationCtor));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class DeserializeWithParameterizedConstructorSerializeWithDelegate<T> extends TypeAdapter<T> {
|
||||||
|
private final Constructor<? super T> parameterizedCtor;
|
||||||
|
private final Map<String, ParameterReader> parameterReaders;
|
||||||
|
private final TypeAdapter<T> delegate;
|
||||||
|
|
||||||
|
private DeserializeWithParameterizedConstructorSerializeWithDelegate(TypeAdapter<T> delegate,
|
||||||
|
Constructor<? super T> parameterizedCtor, Map<String, ParameterReader> parameterReaders) {
|
||||||
|
this.delegate = delegate;
|
||||||
|
this.parameterizedCtor = parameterizedCtor;
|
||||||
|
this.parameterReaders = parameterReaders;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T read(JsonReader in) throws IOException {
|
||||||
|
if (in.peek() == JsonToken.NULL) {
|
||||||
|
in.nextNull();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Class<?>[] paramTypes = parameterizedCtor.getParameterTypes();
|
||||||
|
Object[] ctorParams = new Object[paramTypes.length];
|
||||||
|
|
||||||
|
// TODO determine if we can drop this
|
||||||
|
for (int i = 0; i < paramTypes.length; i++) {
|
||||||
|
if (paramTypes[i] == boolean.class) {
|
||||||
|
ctorParams[i] = Boolean.FALSE;
|
||||||
|
} else if (paramTypes[i].isPrimitive()) {
|
||||||
|
ctorParams[i] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
in.beginObject();
|
||||||
|
while (in.hasNext()) {
|
||||||
|
String name = in.nextName();
|
||||||
|
ParameterReader parameter = parameterReaders.get(name);
|
||||||
|
if (parameter == null) {
|
||||||
|
in.skipValue();
|
||||||
|
} else {
|
||||||
|
Object value = parameter.read(in);
|
||||||
|
if (value != null) ctorParams[parameter.index] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IllegalStateException e) {
|
||||||
|
throw new JsonSyntaxException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < paramTypes.length; i++) {
|
||||||
|
if (paramTypes[i].isPrimitive()) {
|
||||||
|
checkArgument(ctorParams[i] != null, "Primative param[" + i + "] in constructor " + parameterizedCtor
|
||||||
|
+ " cannot be absent!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
in.endObject();
|
||||||
|
return newInstance(ctorParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* pass to delegate
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void write(JsonWriter out, T value) throws IOException {
|
||||||
|
delegate.write(out, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private T newInstance(Object[] ctorParams) throws AssertionError {
|
||||||
|
try {
|
||||||
|
return (T) parameterizedCtor.newInstance(ctorParams);
|
||||||
|
} catch (InstantiationException e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
} catch (InvocationTargetException e) {
|
||||||
|
if (e.getCause() instanceof RuntimeException)
|
||||||
|
throw RuntimeException.class.cast(e.getCause());
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// logic borrowed from ReflectiveTypeAdapterFactory
|
||||||
|
static class ParameterReader<T> {
|
||||||
|
final String name;
|
||||||
|
final int index;
|
||||||
|
final TypeAdapter<T> typeAdapter;
|
||||||
|
|
||||||
|
ParameterReader(String name, int index, TypeAdapter<T> typeAdapter) {
|
||||||
|
this.name = name;
|
||||||
|
this.index = index;
|
||||||
|
this.typeAdapter = typeAdapter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object read(JsonReader reader) throws IOException {
|
||||||
|
return typeAdapter.read(reader);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private Map<String, ParameterReader> getParameterReaders(Gson context, TypeToken<?> declaring, Constructor<?> constructor) {
|
||||||
|
Map<String, ParameterReader> result = new LinkedHashMap<String, ParameterReader>();
|
||||||
|
|
||||||
|
for (int index = 0; index < constructor.getGenericParameterTypes().length; index++) {
|
||||||
|
Type parameterType = getTypeOfConstructorParameter(declaring, constructor, index);
|
||||||
|
TypeAdapter<?> adapter = context.getAdapter(TypeToken.get(parameterType));
|
||||||
|
String parameterName = constructorFieldNamingPolicy.translateName(constructor, index);
|
||||||
|
checkArgument(parameterName != null, constructor + " parameter " + 0 + " failed to be named by " + constructorFieldNamingPolicy);
|
||||||
|
ParameterReader parameterReader = new ParameterReader(parameterName, index, adapter);
|
||||||
|
ParameterReader previous = result.put(parameterReader.name, parameterReader);
|
||||||
|
checkArgument(previous == null, constructor + " declares multiple JSON parameters named " + parameterReader.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Type getTypeOfConstructorParameter(TypeToken<?> declaring, Constructor<?> constructor, int index) {
|
||||||
|
Type genericParameter = constructor.getGenericParameterTypes()[index];
|
||||||
|
return $Gson$Types.resolve(declaring.getType(), declaring.getRawType(), genericParameter);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,226 @@
|
||||||
|
/**
|
||||||
|
* Licensed to jclouds, Inc. (jclouds) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. jclouds licenses this file
|
||||||
|
* to you 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 org.jclouds.json.internal;
|
||||||
|
|
||||||
|
import static com.google.common.base.Preconditions.checkArgument;
|
||||||
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
|
||||||
|
import java.beans.ConstructorProperties;
|
||||||
|
import java.lang.annotation.Annotation;
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import javax.inject.Named;
|
||||||
|
|
||||||
|
import com.google.common.base.Function;
|
||||||
|
import com.google.common.base.Joiner;
|
||||||
|
import com.google.common.collect.ImmutableSet;
|
||||||
|
import com.google.common.collect.Iterables;
|
||||||
|
import com.google.common.collect.Maps;
|
||||||
|
import com.google.gson.FieldNamingStrategy;
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NamingStrategies used for JSON deserialization using GSON
|
||||||
|
*
|
||||||
|
* @author Adrian Cole
|
||||||
|
* @author Adam Lowe
|
||||||
|
*/
|
||||||
|
public class NamingStrategies {
|
||||||
|
/**
|
||||||
|
* Specifies how to extract the name from an annotation for use in determining the serialized
|
||||||
|
* name.
|
||||||
|
*
|
||||||
|
* @see com.google.gson.annotations.SerializedName
|
||||||
|
* @see ExtractSerializedName
|
||||||
|
*/
|
||||||
|
public abstract static class NameExtractor<A extends Annotation> {
|
||||||
|
protected final Class<A> annotationType;
|
||||||
|
|
||||||
|
protected NameExtractor(Class<A> annotationType) {
|
||||||
|
this.annotationType = checkNotNull(annotationType, "annotationType");
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract String extractName(A in);
|
||||||
|
|
||||||
|
public Class<A> annotationType() {
|
||||||
|
return annotationType;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "nameExtractor(" + annotationType.getSimpleName() + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return annotationType.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj)
|
||||||
|
return true;
|
||||||
|
if (obj == null || getClass() != obj.getClass())
|
||||||
|
return false;
|
||||||
|
return annotationType.equals(NameExtractor.class.cast(obj).annotationType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ExtractSerializedName extends NameExtractor<SerializedName> {
|
||||||
|
public ExtractSerializedName() {
|
||||||
|
super(SerializedName.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String extractName(SerializedName in) {
|
||||||
|
return checkNotNull(in, "input annotation").value();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ExtractNamed extends NameExtractor<Named> {
|
||||||
|
public ExtractNamed() {
|
||||||
|
super(Named.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String extractName(Named in) {
|
||||||
|
return checkNotNull(in, "input annotation").value();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static abstract class AnnotationBasedNamingStrategy {
|
||||||
|
protected final Map<Class<? extends Annotation>, ? extends NameExtractor> annotationToNameExtractor;
|
||||||
|
private String forToString;
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public AnnotationBasedNamingStrategy(Iterable<? extends NameExtractor> extractors) {
|
||||||
|
checkNotNull(extractors, "means to extract names by annotations");
|
||||||
|
|
||||||
|
this.annotationToNameExtractor = Maps.uniqueIndex(extractors, new Function<NameExtractor, Class<? extends Annotation>>() {
|
||||||
|
@Override
|
||||||
|
public Class<? extends Annotation> apply(NameExtractor input) {
|
||||||
|
return input.annotationType();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.forToString = Joiner.on(",").join(Iterables.transform(extractors, new Function<NameExtractor, String>() {
|
||||||
|
@Override
|
||||||
|
public String apply(NameExtractor input) {
|
||||||
|
return input.annotationType().getName();
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "AnnotationBasedNamingStrategy requiring one of " + forToString;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Definition of field naming policy for annotation-based field
|
||||||
|
*/
|
||||||
|
public static class AnnotationFieldNamingStrategy extends AnnotationBasedNamingStrategy implements FieldNamingStrategy {
|
||||||
|
|
||||||
|
public AnnotationFieldNamingStrategy(Iterable<? extends NameExtractor> extractors) {
|
||||||
|
super(extractors);
|
||||||
|
checkArgument(extractors.iterator().hasNext(), "you must supply at least one name extractor, for example: "
|
||||||
|
+ ExtractSerializedName.class.getSimpleName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@Override
|
||||||
|
public String translateName(Field f) {
|
||||||
|
for (Annotation annotation : f.getAnnotations()) {
|
||||||
|
if (annotationToNameExtractor.containsKey(annotation.annotationType())) {
|
||||||
|
return annotationToNameExtractor.get(annotation.annotationType()).extractName(annotation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class AnnotationOrNameFieldNamingStrategy extends AnnotationFieldNamingStrategy implements FieldNamingStrategy {
|
||||||
|
public AnnotationOrNameFieldNamingStrategy(NameExtractor... extractors) {
|
||||||
|
this(ImmutableSet.copyOf(extractors));
|
||||||
|
}
|
||||||
|
|
||||||
|
public AnnotationOrNameFieldNamingStrategy(Iterable<? extends NameExtractor> extractors) {
|
||||||
|
super(extractors);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String translateName(Field f) {
|
||||||
|
String result = super.translateName(f);
|
||||||
|
return result == null ? f.getName() : result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static interface ConstructorFieldNamingStrategy {
|
||||||
|
public String translateName(Constructor<?> c, int index);
|
||||||
|
|
||||||
|
public <T> Constructor<? super T> getDeserializationConstructor(Class<?> raw);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines field naming from constructor annotations
|
||||||
|
*/
|
||||||
|
public static class AnnotationConstructorNamingStrategy extends AnnotationBasedNamingStrategy implements ConstructorFieldNamingStrategy {
|
||||||
|
private final Set<Class<? extends Annotation>> markers;
|
||||||
|
|
||||||
|
public AnnotationConstructorNamingStrategy(Iterable<? extends Class<? extends Annotation>> markers, Iterable<? extends NameExtractor> extractors) {
|
||||||
|
super(extractors);
|
||||||
|
this.markers = ImmutableSet.copyOf(checkNotNull(markers, "you must supply at least one annotation to mark deserialization constructors"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public <T> Constructor<? super T> getDeserializationConstructor(Class<?> raw) {
|
||||||
|
for (Constructor<?> ctor : raw.getDeclaredConstructors())
|
||||||
|
for (Class<? extends Annotation> deserializationCtorAnnotation : markers)
|
||||||
|
if (ctor.isAnnotationPresent(deserializationCtorAnnotation))
|
||||||
|
return (Constructor<T>) ctor;
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@Override
|
||||||
|
public String translateName(Constructor<?> c, int index) {
|
||||||
|
String name = null;
|
||||||
|
|
||||||
|
if (markers.contains(ConstructorProperties.class) && c.getAnnotation(ConstructorProperties.class) != null) {
|
||||||
|
String[] names = c.getAnnotation(ConstructorProperties.class).value();
|
||||||
|
if (names != null && names.length > index) {
|
||||||
|
name = names[index];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Annotation annotation : c.getParameterAnnotations()[index]) {
|
||||||
|
if (annotationToNameExtractor.containsKey(annotation.annotationType())) {
|
||||||
|
name = annotationToNameExtractor.get(annotation.annotationType()).extractName(annotation);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,245 @@
|
||||||
|
package org.jclouds.json.internal;
|
||||||
|
/**
|
||||||
|
* Licensed to jclouds, Inc. (jclouds) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. jclouds licenses this file
|
||||||
|
* to you 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import static org.testng.Assert.assertEquals;
|
||||||
|
import static org.testng.Assert.assertNotSame;
|
||||||
|
import static org.testng.Assert.assertNull;
|
||||||
|
import static org.testng.Assert.fail;
|
||||||
|
|
||||||
|
import java.beans.ConstructorProperties;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Named;
|
||||||
|
|
||||||
|
import org.jclouds.json.internal.NamingStrategies.AnnotationOrNameFieldNamingStrategy;
|
||||||
|
import org.jclouds.json.internal.NamingStrategies.ExtractNamed;
|
||||||
|
import org.jclouds.json.internal.NamingStrategies.ExtractSerializedName;
|
||||||
|
import org.testng.annotations.Test;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableSet;
|
||||||
|
import com.google.gson.FieldNamingStrategy;
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
|
import com.google.gson.TypeAdapter;
|
||||||
|
import com.google.gson.internal.ConstructorConstructor;
|
||||||
|
import com.google.gson.internal.Excluder;
|
||||||
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Adrian Cole
|
||||||
|
* @author Adam Lowe
|
||||||
|
*/
|
||||||
|
@Test(testName = "DeserializationConstructorTypeAdapterFactoryTest")
|
||||||
|
public final class DeserializationConstructorAndReflectiveTypeAdapterFactoryTest {
|
||||||
|
|
||||||
|
Gson gson = new Gson();
|
||||||
|
|
||||||
|
DeserializationConstructorAndReflectiveTypeAdapterFactory parameterizedCtorFactory = parameterizedCtorFactory();
|
||||||
|
|
||||||
|
static DeserializationConstructorAndReflectiveTypeAdapterFactory parameterizedCtorFactory() {
|
||||||
|
FieldNamingStrategy serializationPolicy = new AnnotationOrNameFieldNamingStrategy(
|
||||||
|
ImmutableSet.of(new ExtractSerializedName(), new ExtractNamed())
|
||||||
|
);
|
||||||
|
NamingStrategies.AnnotationConstructorNamingStrategy deserializationPolicy =
|
||||||
|
new NamingStrategies.AnnotationConstructorNamingStrategy(
|
||||||
|
ImmutableSet.of(ConstructorProperties.class, Inject.class),
|
||||||
|
ImmutableSet.of(new ExtractNamed()));
|
||||||
|
|
||||||
|
return new DeserializationConstructorAndReflectiveTypeAdapterFactory(new ConstructorConstructor(),
|
||||||
|
serializationPolicy, Excluder.DEFAULT, deserializationPolicy);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testNullWhenPrimitive() {
|
||||||
|
assertNull(parameterizedCtorFactory.create(gson, TypeToken.get(int.class)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class DefaultConstructor {
|
||||||
|
int foo;
|
||||||
|
int bar;
|
||||||
|
|
||||||
|
private DefaultConstructor() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
DefaultConstructor other = DefaultConstructor.class.cast(obj);
|
||||||
|
if (bar != other.bar)
|
||||||
|
return false;
|
||||||
|
if (foo != other.foo)
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testRejectsIfNoConstuctorMarked() throws IOException {
|
||||||
|
TypeAdapter<DefaultConstructor> adapter = parameterizedCtorFactory.create(gson, TypeToken.get(DefaultConstructor.class));
|
||||||
|
assertNull(adapter);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class WithDeserializationConstructorButWithoutSerializedName {
|
||||||
|
final int foo;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
WithDeserializationConstructorButWithoutSerializedName(int foo) {
|
||||||
|
this.foo = foo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testSerializedNameRequiredOnAllParameters() {
|
||||||
|
try {
|
||||||
|
parameterizedCtorFactory.create(gson, TypeToken
|
||||||
|
.get(WithDeserializationConstructorButWithoutSerializedName.class));
|
||||||
|
fail();
|
||||||
|
} catch (IllegalArgumentException actual) {
|
||||||
|
assertEquals(actual.getMessage(),
|
||||||
|
"org.jclouds.json.internal.DeserializationConstructorAndReflectiveTypeAdapterFactoryTest$WithDeserializationConstructorButWithoutSerializedName(int)" +
|
||||||
|
" parameter 0 failed to be named by AnnotationBasedNamingStrategy requiring one of javax.inject.Named");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class DuplicateSerializedNames {
|
||||||
|
final int foo;
|
||||||
|
final int bar;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
DuplicateSerializedNames(@Named("foo") int foo, @Named("foo") int bar) {
|
||||||
|
this.foo = foo;
|
||||||
|
this.bar = bar;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testNoDuplicateSerializedNamesRequiredOnAllParameters() {
|
||||||
|
try {
|
||||||
|
parameterizedCtorFactory.create(gson, TypeToken.get(DuplicateSerializedNames.class));
|
||||||
|
fail();
|
||||||
|
} catch (IllegalArgumentException actual) {
|
||||||
|
assertEquals(actual.getMessage(),
|
||||||
|
"org.jclouds.json.internal.DeserializationConstructorAndReflectiveTypeAdapterFactoryTest$DuplicateSerializedNames(int,int)" +
|
||||||
|
" declares multiple JSON parameters named foo");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class ValidatedConstructor {
|
||||||
|
final int foo;
|
||||||
|
final int bar;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
ValidatedConstructor(@Named("foo") int foo, @Named("bar") int bar) {
|
||||||
|
if (foo < 0)
|
||||||
|
throw new IllegalArgumentException("negative!");
|
||||||
|
this.foo = foo;
|
||||||
|
this.bar = bar;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
ValidatedConstructor other = ValidatedConstructor.class.cast(obj);
|
||||||
|
if (bar != other.bar)
|
||||||
|
return false;
|
||||||
|
if (foo != other.foo)
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testValidatedConstructor() throws IOException {
|
||||||
|
TypeAdapter<ValidatedConstructor> adapter = parameterizedCtorFactory.create(gson, TypeToken
|
||||||
|
.get(ValidatedConstructor.class));
|
||||||
|
assertEquals(new ValidatedConstructor(0, 1), adapter.fromJson("{\"foo\":0,\"bar\":1}"));
|
||||||
|
try {
|
||||||
|
adapter.fromJson("{\"foo\":-1,\"bar\":1}");
|
||||||
|
fail();
|
||||||
|
} catch (IllegalArgumentException expected) {
|
||||||
|
assertEquals("negative!", expected.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class GenericParamsCopiedIn {
|
||||||
|
final List<String> foo;
|
||||||
|
final Map<String, String> bar;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
GenericParamsCopiedIn(@Named("foo") List<String> foo, @Named("bar") Map<String, String> bar) {
|
||||||
|
this.foo = new ArrayList<String>(foo);
|
||||||
|
this.bar = new HashMap<String, String>(bar);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testGenericParamsCopiedIn() throws IOException {
|
||||||
|
TypeAdapter<GenericParamsCopiedIn> adapter = parameterizedCtorFactory.create(gson, TypeToken
|
||||||
|
.get(GenericParamsCopiedIn.class));
|
||||||
|
List<String> inputFoo = new ArrayList<String>();
|
||||||
|
inputFoo.add("one");
|
||||||
|
HashMap<String, String> inputBar = new HashMap<String, String>();
|
||||||
|
inputBar.put("2", "two");
|
||||||
|
|
||||||
|
GenericParamsCopiedIn toTest = adapter.fromJson("{ \"foo\":[\"one\"], \"bar\":{ \"2\":\"two\"}}");
|
||||||
|
assertEquals(inputFoo, toTest.foo);
|
||||||
|
assertNotSame(inputFoo, toTest.foo);
|
||||||
|
assertEquals(inputBar, toTest.bar);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class RenamedFields {
|
||||||
|
final int foo;
|
||||||
|
@Named("_bar")
|
||||||
|
final int bar;
|
||||||
|
|
||||||
|
@ConstructorProperties({"foo", "_bar"})
|
||||||
|
RenamedFields(int foo, int bar) {
|
||||||
|
if (foo < 0)
|
||||||
|
throw new IllegalArgumentException("negative!");
|
||||||
|
this.foo = foo;
|
||||||
|
this.bar = bar;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
RenamedFields other = RenamedFields.class.cast(obj);
|
||||||
|
if (bar != other.bar)
|
||||||
|
return false;
|
||||||
|
if (foo != other.foo)
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testRenamedFields() throws IOException {
|
||||||
|
TypeAdapter<RenamedFields> adapter = parameterizedCtorFactory.create(gson, TypeToken.get(RenamedFields.class));
|
||||||
|
assertEquals(new RenamedFields(0, 1), adapter.fromJson("{\"foo\":0,\"_bar\":1}"));
|
||||||
|
assertEquals(adapter.toJson(new RenamedFields(0, 1)), "{\"foo\":0,\"_bar\":1}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testCanOverrideDefault() throws IOException {
|
||||||
|
Gson gson = new GsonBuilder().registerTypeAdapterFactory(parameterizedCtorFactory).create();
|
||||||
|
|
||||||
|
assertEquals(new RenamedFields(0, 1), gson.fromJson("{\"foo\":0,\"_bar\":1}", RenamedFields.class));
|
||||||
|
assertEquals(gson.toJson(new RenamedFields(0, 1)), "{\"foo\":0,\"_bar\":1}");
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,203 @@
|
||||||
|
package org.jclouds.json.internal;
|
||||||
|
/**
|
||||||
|
* Licensed to jclouds, Inc. (jclouds) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. jclouds licenses this file
|
||||||
|
* to you 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import static org.testng.Assert.assertEquals;
|
||||||
|
import static org.testng.Assert.assertNotNull;
|
||||||
|
import static org.testng.Assert.assertNull;
|
||||||
|
import static org.testng.Assert.fail;
|
||||||
|
|
||||||
|
import java.beans.ConstructorProperties;
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Named;
|
||||||
|
|
||||||
|
import org.jclouds.json.internal.NamingStrategies.AnnotationConstructorNamingStrategy;
|
||||||
|
import org.jclouds.json.internal.NamingStrategies.AnnotationFieldNamingStrategy;
|
||||||
|
import org.jclouds.json.internal.NamingStrategies.AnnotationOrNameFieldNamingStrategy;
|
||||||
|
import org.jclouds.json.internal.NamingStrategies.ConstructorFieldNamingStrategy;
|
||||||
|
import org.jclouds.json.internal.NamingStrategies.ExtractNamed;
|
||||||
|
import org.jclouds.json.internal.NamingStrategies.ExtractSerializedName;
|
||||||
|
import org.jclouds.json.internal.NamingStrategies.NameExtractor;
|
||||||
|
import org.testng.annotations.Test;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableSet;
|
||||||
|
import com.google.gson.FieldNamingStrategy;
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Adam Lowe
|
||||||
|
*/
|
||||||
|
@Test(testName = "NamingStrategiesTest")
|
||||||
|
public final class NamingStrategiesTest {
|
||||||
|
|
||||||
|
private static class SimpleTest {
|
||||||
|
@SerializedName("aardvark")
|
||||||
|
private String a;
|
||||||
|
private String b;
|
||||||
|
@Named("cat")
|
||||||
|
private String c;
|
||||||
|
@Named("dog")
|
||||||
|
private String d;
|
||||||
|
|
||||||
|
@ConstructorProperties({"aardvark", "bat", "coyote", "dog"})
|
||||||
|
private SimpleTest(String aa, String bb, String cc, @Named("dingo") String dd) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private SimpleTest(@Named("aa") String aa, @Named("bb") String bb, @Named("cc") String cc, @Named("dd") String dd, boolean nothing) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class MixedConstructorTest {
|
||||||
|
@Inject
|
||||||
|
@ConstructorProperties("thiscanbeoverriddenbyNamed")
|
||||||
|
private MixedConstructorTest(@Named("aardvark") String aa, @Named("bat") String bb, @Named("cat") String cc, @Named("dog") String dd) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void testExtractSerializedName() throws Exception {
|
||||||
|
NameExtractor extractor = new ExtractSerializedName();
|
||||||
|
assertEquals(extractor.extractName(SimpleTest.class.getDeclaredField("a").getAnnotation(SerializedName.class)),
|
||||||
|
"aardvark");
|
||||||
|
try {
|
||||||
|
extractor.extractName(SimpleTest.class.getDeclaredField("b").getAnnotation(SerializedName.class));
|
||||||
|
fail();
|
||||||
|
} catch (NullPointerException e) {
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
extractor.extractName(SimpleTest.class.getDeclaredField("c").getAnnotation(SerializedName.class));
|
||||||
|
fail();
|
||||||
|
} catch (NullPointerException e) {
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
extractor.extractName(SimpleTest.class.getDeclaredField("d").getAnnotation(SerializedName.class));
|
||||||
|
fail();
|
||||||
|
} catch (NullPointerException e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testExtractNamed() throws Exception {
|
||||||
|
NameExtractor extractor = new ExtractNamed();
|
||||||
|
try {
|
||||||
|
extractor.extractName(SimpleTest.class.getDeclaredField("a").getAnnotation(Named.class));
|
||||||
|
} catch (NullPointerException e) {
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
extractor.extractName(SimpleTest.class.getDeclaredField("b").getAnnotation(Named.class));
|
||||||
|
fail();
|
||||||
|
} catch (NullPointerException e) {
|
||||||
|
}
|
||||||
|
assertEquals(extractor.extractName(SimpleTest.class.getDeclaredField("c").getAnnotation(Named.class)),
|
||||||
|
"cat");
|
||||||
|
assertEquals(extractor.extractName(SimpleTest.class.getDeclaredField("d").getAnnotation(Named.class)),
|
||||||
|
"dog");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testAnnotationFieldNamingStrategy() throws Exception {
|
||||||
|
FieldNamingStrategy strategy = new AnnotationFieldNamingStrategy(ImmutableSet.of(new ExtractNamed()));
|
||||||
|
|
||||||
|
assertNull(strategy.translateName(SimpleTest.class.getDeclaredField("a")));
|
||||||
|
assertNull(strategy.translateName(SimpleTest.class.getDeclaredField("b")));
|
||||||
|
assertEquals(strategy.translateName(SimpleTest.class.getDeclaredField("c")), "cat");
|
||||||
|
assertEquals(strategy.translateName(SimpleTest.class.getDeclaredField("d")), "dog");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testAnnotationOrNameFieldNamingStrategy() throws Exception {
|
||||||
|
FieldNamingStrategy strategy = new AnnotationOrNameFieldNamingStrategy(ImmutableSet.of(new ExtractNamed()));
|
||||||
|
|
||||||
|
assertEquals(strategy.translateName(SimpleTest.class.getDeclaredField("a")), "a");
|
||||||
|
assertEquals(strategy.translateName(SimpleTest.class.getDeclaredField("b")), "b");
|
||||||
|
assertEquals(strategy.translateName(SimpleTest.class.getDeclaredField("c")), "cat");
|
||||||
|
assertEquals(strategy.translateName(SimpleTest.class.getDeclaredField("d")), "dog");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testAnnotationConstructorFieldNamingStrategyCPAndNamed() throws Exception {
|
||||||
|
ConstructorFieldNamingStrategy strategy = new AnnotationConstructorNamingStrategy(
|
||||||
|
ImmutableSet.of(ConstructorProperties.class), ImmutableSet.of(new ExtractNamed()));
|
||||||
|
|
||||||
|
Constructor<? super SimpleTest> constructor = strategy.getDeserializationConstructor(SimpleTest.class);
|
||||||
|
assertNotNull(constructor);
|
||||||
|
assertEquals(constructor.getParameterTypes().length, 4);
|
||||||
|
|
||||||
|
assertEquals(strategy.translateName(constructor, 0), "aardvark");
|
||||||
|
assertEquals(strategy.translateName(constructor, 1), "bat");
|
||||||
|
assertEquals(strategy.translateName(constructor, 2), "coyote");
|
||||||
|
// Note: @Named overrides the ConstructorProperties setting
|
||||||
|
assertEquals(strategy.translateName(constructor, 3), "dingo");
|
||||||
|
|
||||||
|
Constructor<? super MixedConstructorTest> mixedCtor = strategy.getDeserializationConstructor(MixedConstructorTest.class);
|
||||||
|
assertNotNull(mixedCtor);
|
||||||
|
assertEquals(mixedCtor.getParameterTypes().length, 4);
|
||||||
|
|
||||||
|
assertEquals(strategy.translateName(mixedCtor, 0), "aardvark");
|
||||||
|
assertEquals(strategy.translateName(mixedCtor, 1), "bat");
|
||||||
|
assertEquals(strategy.translateName(mixedCtor, 2), "cat");
|
||||||
|
assertEquals(strategy.translateName(mixedCtor, 3), "dog");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testAnnotationConstructorFieldNamingStrategyCP() throws Exception {
|
||||||
|
ConstructorFieldNamingStrategy strategy = new AnnotationConstructorNamingStrategy(
|
||||||
|
ImmutableSet.of(ConstructorProperties.class), ImmutableSet.<NameExtractor>of());
|
||||||
|
|
||||||
|
Constructor<? super SimpleTest> constructor = strategy.getDeserializationConstructor(SimpleTest.class);
|
||||||
|
assertNotNull(constructor);
|
||||||
|
assertEquals(constructor.getParameterTypes().length, 4);
|
||||||
|
|
||||||
|
assertEquals(strategy.translateName(constructor, 0), "aardvark");
|
||||||
|
assertEquals(strategy.translateName(constructor, 1), "bat");
|
||||||
|
assertEquals(strategy.translateName(constructor, 2), "coyote");
|
||||||
|
assertEquals(strategy.translateName(constructor, 3), "dog");
|
||||||
|
|
||||||
|
Constructor<? super MixedConstructorTest> mixedCtor = strategy.getDeserializationConstructor(MixedConstructorTest.class);
|
||||||
|
assertNotNull(mixedCtor);
|
||||||
|
assertEquals(mixedCtor.getParameterTypes().length, 4);
|
||||||
|
|
||||||
|
assertEquals(strategy.translateName(mixedCtor, 0), "thiscanbeoverriddenbyNamed");
|
||||||
|
assertNull(strategy.translateName(mixedCtor, 1));
|
||||||
|
assertNull(strategy.translateName(mixedCtor, 2));
|
||||||
|
assertNull(strategy.translateName(mixedCtor, 3));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testAnnotationConstructorFieldNamingStrategyInject() throws Exception {
|
||||||
|
ConstructorFieldNamingStrategy strategy = new AnnotationConstructorNamingStrategy(
|
||||||
|
ImmutableSet.of(Inject.class), ImmutableSet.of(new ExtractNamed()));
|
||||||
|
|
||||||
|
Constructor<? super SimpleTest> constructor = strategy.getDeserializationConstructor(SimpleTest.class);
|
||||||
|
assertNotNull(constructor);
|
||||||
|
assertEquals(constructor.getParameterTypes().length, 5);
|
||||||
|
|
||||||
|
assertEquals(strategy.translateName(constructor, 0), "aa");
|
||||||
|
assertEquals(strategy.translateName(constructor, 1), "bb");
|
||||||
|
assertEquals(strategy.translateName(constructor, 2), "cc");
|
||||||
|
assertEquals(strategy.translateName(constructor, 3), "dd");
|
||||||
|
|
||||||
|
Constructor<? super MixedConstructorTest> mixedCtor = strategy.getDeserializationConstructor(MixedConstructorTest.class);
|
||||||
|
assertNotNull(mixedCtor);
|
||||||
|
assertEquals(mixedCtor.getParameterTypes().length, 4);
|
||||||
|
|
||||||
|
assertEquals(strategy.translateName(mixedCtor, 0), "aardvark");
|
||||||
|
assertEquals(strategy.translateName(mixedCtor, 1), "bat");
|
||||||
|
assertEquals(strategy.translateName(mixedCtor, 2), "cat");
|
||||||
|
assertEquals(strategy.translateName(mixedCtor, 3), "dog");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue