mirror of https://github.com/apache/jclouds.git
JCLOUDS-750 Added @SerializedNames which can apply to either a constructor or factory method to capture the corresponding json field names.
Removed wildly confusing mixed naming of constructor params feature.
This commit is contained in:
parent
33ce3eeb12
commit
e7271bc291
|
@ -0,0 +1,33 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright ownership.
|
||||||
|
* The ASF 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;
|
||||||
|
|
||||||
|
import static java.lang.annotation.ElementType.CONSTRUCTOR;
|
||||||
|
import static java.lang.annotation.ElementType.METHOD;
|
||||||
|
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||||
|
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
import com.google.common.annotations.Beta;
|
||||||
|
|
||||||
|
/** An ordered list of json field names that correspond to factory method or constructor parameters. */
|
||||||
|
@Beta @Target({ CONSTRUCTOR, METHOD }) @Retention(RUNTIME)
|
||||||
|
public @interface SerializedNames {
|
||||||
|
|
||||||
|
String[] value();
|
||||||
|
}
|
|
@ -42,6 +42,7 @@ import com.google.inject.Provides;
|
||||||
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.SerializedNames;
|
||||||
import org.jclouds.json.internal.DeserializationConstructorAndReflectiveTypeAdapterFactory;
|
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;
|
||||||
|
@ -119,7 +120,8 @@ public class GsonModule extends AbstractModule {
|
||||||
builder.registerTypeAdapterFactory(immutableMap);
|
builder.registerTypeAdapterFactory(immutableMap);
|
||||||
|
|
||||||
AnnotationConstructorNamingStrategy deserializationPolicy = new AnnotationConstructorNamingStrategy(
|
AnnotationConstructorNamingStrategy deserializationPolicy = new AnnotationConstructorNamingStrategy(
|
||||||
ImmutableSet.of(ConstructorProperties.class, Inject.class), ImmutableSet.of(new ExtractNamed()));
|
ImmutableSet.of(ConstructorProperties.class, SerializedNames.class, Inject.class),
|
||||||
|
ImmutableSet.of(new ExtractNamed()));
|
||||||
|
|
||||||
builder.registerTypeAdapterFactory(new DeserializationConstructorAndReflectiveTypeAdapterFactory(
|
builder.registerTypeAdapterFactory(new DeserializationConstructorAndReflectiveTypeAdapterFactory(
|
||||||
new ConstructorConstructor(ImmutableMap.<Type, InstanceCreator<?>>of()), serializationPolicy, Excluder.DEFAULT, deserializationPolicy));
|
new ConstructorConstructor(ImmutableMap.<Type, InstanceCreator<?>>of()), serializationPolicy, Excluder.DEFAULT, deserializationPolicy));
|
||||||
|
|
|
@ -32,6 +32,8 @@ import java.util.Map;
|
||||||
|
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
|
|
||||||
|
import org.jclouds.json.SerializedNames;
|
||||||
|
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import com.google.common.base.Function;
|
import com.google.common.base.Function;
|
||||||
import com.google.common.base.Joiner;
|
import com.google.common.base.Joiner;
|
||||||
|
@ -229,19 +231,27 @@ public class NamingStrategies {
|
||||||
<T> String translateName(Invokable<T, T> c, int index) {
|
<T> String translateName(Invokable<T, T> c, int index) {
|
||||||
String name = null;
|
String name = null;
|
||||||
|
|
||||||
if (markers.contains(ConstructorProperties.class) && c.getAnnotation(ConstructorProperties.class) != null) {
|
if ((markers.contains(ConstructorProperties.class) && c.getAnnotation(ConstructorProperties.class) != null)
|
||||||
String[] names = c.getAnnotation(ConstructorProperties.class).value();
|
|| (markers.contains(SerializedNames.class) && c.getAnnotation(SerializedNames.class) != null)) {
|
||||||
|
|
||||||
|
String[] names = c.getAnnotation(SerializedNames.class) != null
|
||||||
|
? c.getAnnotation(SerializedNames.class).value()
|
||||||
|
: c.getAnnotation(ConstructorProperties.class).value();
|
||||||
|
|
||||||
|
checkArgument(names.length == c.getParameters().size(), "Incorrect count of names on annotation of %s", c);
|
||||||
|
|
||||||
if (names != null && names.length > index) {
|
if (names != null && names.length > index) {
|
||||||
name = names[index];
|
name = names[index];
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
} else {
|
||||||
for (Annotation annotation : c.getParameters().get(index).getAnnotations()) {
|
for (Annotation annotation : c.getParameters().get(index).getAnnotations()) {
|
||||||
if (annotationToNameExtractor.containsKey(annotation.annotationType())) {
|
if (annotationToNameExtractor.containsKey(annotation.annotationType())) {
|
||||||
name = annotationToNameExtractor.get(annotation.annotationType()).apply(annotation);
|
name = annotationToNameExtractor.get(annotation.annotationType()).apply(annotation);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,7 @@ import java.util.Map;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
|
|
||||||
|
import org.jclouds.json.SerializedNames;
|
||||||
import org.jclouds.json.internal.NamingStrategies.AnnotationConstructorNamingStrategy;
|
import org.jclouds.json.internal.NamingStrategies.AnnotationConstructorNamingStrategy;
|
||||||
import org.jclouds.json.internal.NamingStrategies.AnnotationOrNameFieldNamingStrategy;
|
import org.jclouds.json.internal.NamingStrategies.AnnotationOrNameFieldNamingStrategy;
|
||||||
import org.jclouds.json.internal.NamingStrategies.ExtractNamed;
|
import org.jclouds.json.internal.NamingStrategies.ExtractNamed;
|
||||||
|
@ -63,7 +64,8 @@ public final class DeserializationConstructorAndReflectiveTypeAdapterFactoryTest
|
||||||
FieldNamingStrategy serializationPolicy = new AnnotationOrNameFieldNamingStrategy(ImmutableSet.of(
|
FieldNamingStrategy serializationPolicy = new AnnotationOrNameFieldNamingStrategy(ImmutableSet.of(
|
||||||
new ExtractSerializedName(), new ExtractNamed()));
|
new ExtractSerializedName(), new ExtractNamed()));
|
||||||
AnnotationConstructorNamingStrategy deserializationPolicy = new AnnotationConstructorNamingStrategy(
|
AnnotationConstructorNamingStrategy deserializationPolicy = new AnnotationConstructorNamingStrategy(
|
||||||
ImmutableSet.of(ConstructorProperties.class, Inject.class), ImmutableSet.of(new ExtractNamed()));
|
ImmutableSet.of(ConstructorProperties.class, SerializedNames.class, Inject.class),
|
||||||
|
ImmutableSet.of(new ExtractNamed()));
|
||||||
|
|
||||||
return new DeserializationConstructorAndReflectiveTypeAdapterFactory(new ConstructorConstructor(ImmutableMap.<Type, InstanceCreator<?>>of()),
|
return new DeserializationConstructorAndReflectiveTypeAdapterFactory(new ConstructorConstructor(ImmutableMap.<Type, InstanceCreator<?>>of()),
|
||||||
serializationPolicy, Excluder.DEFAULT, deserializationPolicy);
|
serializationPolicy, Excluder.DEFAULT, deserializationPolicy);
|
||||||
|
@ -157,8 +159,8 @@ public final class DeserializationConstructorAndReflectiveTypeAdapterFactoryTest
|
||||||
abstract List<String> foo();
|
abstract List<String> foo();
|
||||||
abstract Map<String, String> bar();
|
abstract Map<String, String> bar();
|
||||||
|
|
||||||
@Inject
|
@SerializedNames({ "foo", "bar" })
|
||||||
static ValueTypeWithFactory create(@Named("foo") List<String> foo, @Named("bar") Map<String, String> bar) {
|
static ValueTypeWithFactory create(List<String> foo, Map<String, String> bar) {
|
||||||
return new GenericParamsCopiedIn(foo, bar);
|
return new GenericParamsCopiedIn(foo, bar);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -196,14 +198,15 @@ public final class DeserializationConstructorAndReflectiveTypeAdapterFactoryTest
|
||||||
}
|
}
|
||||||
|
|
||||||
private abstract static class ValueTypeWithFactoryMissingSerializedNames {
|
private abstract static class ValueTypeWithFactoryMissingSerializedNames {
|
||||||
@Inject
|
@SerializedNames({ "foo" })
|
||||||
static ValueTypeWithFactoryMissingSerializedNames create(List<String> foo, Map<String, String> bar) {
|
static ValueTypeWithFactoryMissingSerializedNames create(List<String> foo, Map<String, String> bar) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = ".* parameter 0 failed to be named by AnnotationBasedNamingStrategy requiring one of javax.inject.Named")
|
@Test(expectedExceptions = IllegalArgumentException.class,
|
||||||
public void testSerializedNameRequiredOnAllFactoryMethodParameters() {
|
expectedExceptionsMessageRegExp = "Incorrect count of names on annotation of .*")
|
||||||
|
public void testSerializedNamesMustHaveCorrectCountOfNames() {
|
||||||
parameterizedCtorFactory.create(gson, TypeToken.get(ValueTypeWithFactoryMissingSerializedNames.class));
|
parameterizedCtorFactory.create(gson, TypeToken.get(ValueTypeWithFactoryMissingSerializedNames.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -54,7 +54,7 @@ public final class NamingStrategiesTest {
|
||||||
private String d;
|
private String d;
|
||||||
|
|
||||||
@ConstructorProperties({"aardvark", "bat", "coyote", "dog"})
|
@ConstructorProperties({"aardvark", "bat", "coyote", "dog"})
|
||||||
private SimpleTest(String aa, String bb, String cc, @Named("dingo") String dd) {
|
private SimpleTest(String aa, String bb, String cc, String dd) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
|
@ -62,14 +62,6 @@ public final class NamingStrategiesTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
public void testExtractSerializedName() throws Exception {
|
||||||
NameExtractor<SerializedName> extractor = new ExtractSerializedName();
|
NameExtractor<SerializedName> extractor = new ExtractSerializedName();
|
||||||
assertEquals(extractor.extractName(SimpleTest.class.getDeclaredField("a").getAnnotation(SerializedName.class)),
|
assertEquals(extractor.extractName(SimpleTest.class.getDeclaredField("a").getAnnotation(SerializedName.class)),
|
||||||
|
@ -137,17 +129,6 @@ public final class NamingStrategiesTest {
|
||||||
assertEquals(strategy.translateName(constructor, 0), "aardvark");
|
assertEquals(strategy.translateName(constructor, 0), "aardvark");
|
||||||
assertEquals(strategy.translateName(constructor, 1), "bat");
|
assertEquals(strategy.translateName(constructor, 1), "bat");
|
||||||
assertEquals(strategy.translateName(constructor, 2), "coyote");
|
assertEquals(strategy.translateName(constructor, 2), "coyote");
|
||||||
// Note: @Named overrides the ConstructorProperties setting
|
|
||||||
assertEquals(strategy.translateName(constructor, 3), "dingo");
|
|
||||||
|
|
||||||
Invokable<MixedConstructorTest, MixedConstructorTest> mixedCtor = strategy.getDeserializer(typeToken(MixedConstructorTest.class));
|
|
||||||
assertNotNull(mixedCtor);
|
|
||||||
assertEquals(mixedCtor.getParameters().size(), 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 {
|
public void testAnnotationConstructorFieldNamingStrategyCP() throws Exception {
|
||||||
|
@ -162,15 +143,6 @@ public final class NamingStrategiesTest {
|
||||||
assertEquals(strategy.translateName(constructor, 1), "bat");
|
assertEquals(strategy.translateName(constructor, 1), "bat");
|
||||||
assertEquals(strategy.translateName(constructor, 2), "coyote");
|
assertEquals(strategy.translateName(constructor, 2), "coyote");
|
||||||
assertEquals(strategy.translateName(constructor, 3), "dog");
|
assertEquals(strategy.translateName(constructor, 3), "dog");
|
||||||
|
|
||||||
Invokable<MixedConstructorTest, MixedConstructorTest> mixedCtor = strategy.getDeserializer(typeToken(MixedConstructorTest.class));
|
|
||||||
assertNotNull(mixedCtor);
|
|
||||||
assertEquals(mixedCtor.getParameters().size(), 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 {
|
public void testAnnotationConstructorFieldNamingStrategyInject() throws Exception {
|
||||||
|
@ -185,15 +157,5 @@ public final class NamingStrategiesTest {
|
||||||
assertEquals(strategy.translateName(constructor, 1), "bb");
|
assertEquals(strategy.translateName(constructor, 1), "bb");
|
||||||
assertEquals(strategy.translateName(constructor, 2), "cc");
|
assertEquals(strategy.translateName(constructor, 2), "cc");
|
||||||
assertEquals(strategy.translateName(constructor, 3), "dd");
|
assertEquals(strategy.translateName(constructor, 3), "dd");
|
||||||
|
|
||||||
Invokable<MixedConstructorTest, MixedConstructorTest> mixedCtor = strategy.getDeserializer(typeToken(MixedConstructorTest.class));
|
|
||||||
assertNotNull(mixedCtor);
|
|
||||||
assertEquals(mixedCtor.getParameters().size(), 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