blob: 8b6ce9c5823948d81fd305dec12bd90e95126a34 [file] [log] [blame]
/*
* 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.internal;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotSame;
import static org.testng.Assert.assertNull;
import java.beans.ConstructorProperties;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
import javax.inject.Inject;
import javax.inject.Named;
import org.jclouds.json.SerializedNames;
import org.jclouds.json.gson.internal.ConstructorConstructor;
import org.jclouds.json.gson.internal.Excluder;
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.testng.annotations.Test;
import com.google.common.base.Objects;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.gson.FieldNamingStrategy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.InstanceCreator;
import com.google.gson.TypeAdapter;
import com.google.gson.reflect.TypeToken;
@Test(testName = "DeserializationConstructorTypeAdapterFactoryTest")
@SuppressWarnings("unused")
public final class DeserializationConstructorAndReflectiveTypeAdapterFactoryTest {
Gson gson = new Gson();
DeserializationConstructorAndReflectiveTypeAdapterFactory parameterizedCtorFactory = parameterizedCtorFactory();
static DeserializationConstructorAndReflectiveTypeAdapterFactory parameterizedCtorFactory() {
FieldNamingStrategy serializationPolicy = new AnnotationOrNameFieldNamingStrategy(ImmutableSet.of(
new ExtractSerializedName(), new ExtractNamed()));
AnnotationConstructorNamingStrategy deserializationPolicy = new AnnotationConstructorNamingStrategy(
ImmutableSet.of(ConstructorProperties.class, SerializedNames.class, Inject.class),
ImmutableSet.of(new ExtractNamed()));
return new DeserializationConstructorAndReflectiveTypeAdapterFactory(new ConstructorConstructor(ImmutableMap.<Type, InstanceCreator<?>>of()),
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() {
}
}
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;
}
}
@Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = ".* parameter 0 failed to be named by AnnotationBasedNamingStrategy requiring one of javax.inject.Named")
public void testSerializedNameRequiredOnAllParameters() {
parameterizedCtorFactory
.create(gson, TypeToken.get(WithDeserializationConstructorButWithoutSerializedName.class));
}
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;
}
}
@Test(expectedExceptions = IllegalArgumentException.class,
expectedExceptionsMessageRegExp = "Multiple entries with same key: foo.*")
public void testNoDuplicateSerializedNamesRequiredOnAllParameters() {
parameterizedCtorFactory.create(gson, TypeToken.get(DuplicateSerializedNames.class));
}
private static class ValidatedConstructor {
final int foo;
final int bar;
@Inject
ValidatedConstructor(@Named("foo") Optional<Integer> foo, @Named("bar") int bar) {
if (!foo.isPresent())
throw new IllegalArgumentException("absent!");
this.foo = foo.get();
this.bar = bar;
}
@Override
public boolean equals(Object obj) {
ValidatedConstructor other = ValidatedConstructor.class.cast(obj);
return other != null && Objects.equal(foo, other.foo) && Objects.equal(bar, other.bar);
}
@Override
public int hashCode() {
return Objects.hashCode(foo, bar);
}
}
@Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = "absent!")
public void testValidatedConstructor() throws IOException {
Gson gson = new GsonBuilder().registerTypeAdapterFactory(parameterizedCtorFactory)
.registerTypeAdapterFactory(new OptionalTypeAdapterFactory()).create();
assertEquals(new ValidatedConstructor(Optional.of(0), 1),
gson.fromJson("{\"foo\":0,\"bar\":1}", ValidatedConstructor.class));
gson.fromJson("{\"bar\":1}", ValidatedConstructor.class);
}
private abstract static class ValueTypeWithFactory {
abstract List<String> foo();
abstract Map<String, String> bar();
@SerializedNames({ "foo", "bar" })
static ValueTypeWithFactory create(List<String> foo, Map<String, String> bar) {
return new GenericParamsCopiedIn(foo, bar);
}
}
private static class GenericParamsCopiedIn extends ValueTypeWithFactory {
final List<String> foo;
final Map<String, String> bar;
@Inject GenericParamsCopiedIn(@Named("foo") List<String> foo, @Named("bar") Map<String, String> bar) {
this.foo = Lists.newArrayList(foo);
this.bar = Maps.newHashMap(bar);
}
@Override List<String> foo() {
return foo;
}
@Override Map<String, String> bar() {
return bar;
}
}
public void testBuilderOnAbstractValueType() throws IOException {
TypeAdapter<ValueTypeWithFactory> adapter = parameterizedCtorFactory.create(gson,
TypeToken.get(ValueTypeWithFactory.class));
List<String> inputFoo = Lists.newArrayList();
inputFoo.add("one");
Map<String, String> inputBar = Maps.newHashMap();
inputBar.put("2", "two");
ValueTypeWithFactory toTest = adapter.fromJson("{ \"foo\":[\"one\"], \"bar\":{ \"2\":\"two\"}}");
assertEquals(inputFoo, toTest.foo());
assertNotSame(inputFoo, toTest.foo());
assertEquals(inputBar, toTest.bar());
}
private abstract static class ValueTypeWithFactoryMissingSerializedNames {
@SerializedNames({ "foo" })
static ValueTypeWithFactoryMissingSerializedNames create(List<String> foo, Map<String, String> bar) {
return null;
}
}
@Test(expectedExceptions = IllegalArgumentException.class,
expectedExceptionsMessageRegExp = "Incorrect count of names on annotation of .*")
public void testSerializedNamesMustHaveCorrectCountOfNames() {
parameterizedCtorFactory.create(gson, TypeToken.get(ValueTypeWithFactoryMissingSerializedNames.class));
}
public void testGenericParamsCopiedIn() throws IOException {
TypeAdapter<GenericParamsCopiedIn> adapter = parameterizedCtorFactory.create(gson,
TypeToken.get(GenericParamsCopiedIn.class));
List<String> inputFoo = Lists.newArrayList();
inputFoo.add("one");
Map<String, String> inputBar = Maps.newHashMap();
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);
return other != null && Objects.equal(foo, other.foo) && Objects.equal(bar, other.bar);
}
@Override
public int hashCode() {
return Objects.hashCode(foo, bar);
}
}
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}");
}
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}");
}
private static class ComposedObjects {
final ValidatedConstructor x;
final ValidatedConstructor y;
@ConstructorProperties({ "x", "y" })
ComposedObjects(ValidatedConstructor x, ValidatedConstructor y) {
this.x = checkNotNull(x);
this.y = checkNotNull(y);
}
@Override
public boolean equals(Object obj) {
ComposedObjects other = ComposedObjects.class.cast(obj);
return other != null && Objects.equal(x, other.x) && Objects.equal(y, other.y);
}
@Override
public int hashCode() {
return Objects.hashCode(x, y);
}
}
public void checkSimpleComposedObject() throws IOException {
ValidatedConstructor x = new ValidatedConstructor(Optional.of(0), 1);
ValidatedConstructor y = new ValidatedConstructor(Optional.of(1), 2);
TypeAdapter<ComposedObjects> adapter = parameterizedCtorFactory
.create(gson, TypeToken.get(ComposedObjects.class));
assertEquals(new ComposedObjects(x, y),
adapter.fromJson("{\"x\":{\"foo\":0,\"bar\":1},\"y\":{\"foo\":1,\"bar\":2}}"));
}
public void testEmptyObjectIsNull() throws IOException {
TypeAdapter<ComposedObjects> adapter = parameterizedCtorFactory
.create(gson, TypeToken.get(ComposedObjects.class));
assertNull(adapter.fromJson("{}"));
}
@Test(expectedExceptions = NullPointerException.class)
public void testPartialObjectStillThrows() throws IOException {
TypeAdapter<ComposedObjects> adapter = parameterizedCtorFactory
.create(gson, TypeToken.get(ComposedObjects.class));
assertNull(adapter.fromJson("{\"x\":{\"foo\":0,\"bar\":1}}"));
}
}