/**
* Copyright 2010 Wealthfront Inc. Licensed under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law
* or agreed to in writing, software distributed under the License is
* distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
package com.kaching.platform.converters;
import static com.google.common.collect.Lists.newArrayList;
import static com.kaching.platform.converters.InstantiatorErrors.cannotAnnotateOptionWithOptional;
import static com.kaching.platform.converters.InstantiatorErrors.cannotSpecifyDefaultValueAndConstant;
import static com.kaching.platform.converters.InstantiatorErrors.constantHasIncompatibleType;
import static com.kaching.platform.converters.InstantiatorErrors.duplicateConverterBindingForType;
import static com.kaching.platform.converters.InstantiatorErrors.enumHasAmbiguousNames;
import static com.kaching.platform.converters.InstantiatorErrors.illegalConstructor;
import static com.kaching.platform.converters.InstantiatorErrors.incorrectBoundForConverter;
import static com.kaching.platform.converters.InstantiatorErrors.incorrectDefaultValue;
import static com.kaching.platform.converters.InstantiatorErrors.moreThanOneConstructor;
import static com.kaching.platform.converters.InstantiatorErrors.moreThanOneConstructorWithInstantiate;
import static com.kaching.platform.converters.InstantiatorErrors.moreThanOneMatchingFunction;
import static com.kaching.platform.converters.InstantiatorErrors.noConstructorFound;
import static com.kaching.platform.converters.InstantiatorErrors.noConverterForType;
import static com.kaching.platform.converters.InstantiatorErrors.noSuchField;
import static com.kaching.platform.converters.InstantiatorErrors.optionalLiteralParameterMustHaveDefault;
import static com.kaching.platform.converters.InstantiatorErrors.unableToResolveConstant;
import static com.kaching.platform.converters.InstantiatorErrors.unableToResolveFullyQualifiedConstant;
import static com.kaching.platform.converters.InstantiatorImplFactory.createFactory;
import static com.kaching.platform.converters.NativeConverters.C_BOOLEAN;
import static com.kaching.platform.converters.NativeConverters.C_BYTE;
import static com.kaching.platform.converters.NativeConverters.C_CHAR;
import static com.kaching.platform.converters.NativeConverters.C_DOUBLE;
import static com.kaching.platform.converters.NativeConverters.C_FLOAT;
import static com.kaching.platform.converters.NativeConverters.C_INT;
import static com.kaching.platform.converters.NativeConverters.C_LONG;
import static com.kaching.platform.converters.NativeConverters.C_SHORT;
import static com.kaching.platform.converters.NativeConverters.C_STRING;
import static java.lang.String.format;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
import org.junit.Before;
import org.junit.Test;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableMap;
import com.google.inject.BindingAnnotation;
import com.google.inject.TypeLiteral;
import com.google.inject.name.Named;
import com.kaching.platform.common.Errors;
import com.kaching.platform.common.Option;
import com.kaching.platform.converters.ConstructorAnalysis.FormalParameter;
public class InstantiatorImplFactoryTest {
private Errors actualErrors;
@Before
public void createErrors() {
actualErrors = new Errors();
}
public void buildShouldReportIfErrorsExist() {
assertFalse(InstantiatorImplFactory
.createFactory(actualErrors, SomethingUsingHasConvertedByWrongBound.class)
.build()
.isDefined());
assertTrue(actualErrors.hasErrors());
}
static class SomethingUsingHasConvertedByWrongBound {
SomethingUsingHasConvertedByWrongBound(HasConvertedByWrongBound p0) {
}
}
@Test
public void createConverterConvertedBy() throws Exception {
Converter<?> converter = createFactory(actualErrors, null).createConverter(HasConvertedBy.class).getOrThrow();
assertNotNull(converter);
assertEquals(HasConvertedByConverter.class, converter.getClass());
}
@ConvertedBy(HasConvertedByConverter.class)
static class HasConvertedBy {
}
static class HasConvertedByConverter implements Converter<HasConvertedBy> {
@Override
public String toString(HasConvertedBy value) { return null; }
@Override
public HasConvertedBy fromString(String representation) { return null; }
}
@Test
public void createConverterConvertedByWrongBound() throws Exception {
InstantiatorImplFactory<Object> factory = createFactory(actualErrors, null);
factory.createConverter(HasConvertedByWrongBound.class);
assertEquals(
incorrectBoundForConverter(
new Errors(),
HasConvertedByWrongBound.class,
HasConvertedByConverterWrongBound.class,
String.class),
factory.getErrors());
}
@ConvertedBy(HasConvertedByConverterWrongBound.class)
static class HasConvertedByWrongBound {
}
/* This converter does not produce objects of type HasConvertedByWrongBound
* and therefore cannot be used as a converter for HasConvertedByWrongBound.
*/
static class HasConvertedByConverterWrongBound implements Converter<String> {
@Override
public String toString(String value) { return null; }
@Override
public String fromString(String representation) { return null; }
}
@Test
public void createConverterDefaultIfHasStringConstructor() throws Exception {
Converter<?> converter =
createFactory(actualErrors, null).createConverter(HasStringConstructor.class).getOrThrow();
assertNotNull(converter);
assertEquals(StringConstructorConverter.class, converter.getClass());
}
static class HasStringConstructor {
final String representation;
HasStringConstructor(String representation) {
this.representation = representation;
}
}
@Test
public void createConverterDefaultIfHasStringConstructorParameterized() throws Exception {
Converter<?> converter = createFactory(actualErrors, null)
.createConverter(new TypeLiteral<HasStringConstructorParam<Double>>(){}.getType())
.getOrThrow();
assertNotNull(converter);
assertEquals(StringConstructorConverter.class, converter.getClass());
}
static class HasStringConstructorParam<T> {
final String representation;
public HasStringConstructorParam(String representation) {
this.representation = representation;
}
}
@Test
public void createConverterForEnum() throws Exception {
Converter<?> converter = createFactory(actualErrors, null).createConverter(AnEnum.class).getOrThrow();
assertNotNull(converter);
assertEquals(EnumConverter.class, converter.getClass());
}
static enum AnEnum {
FOO, BAR
}
@Test
public void createConverterForAmbiguousEnum() throws Exception {
InstantiatorImplFactory<Object> factory = createFactory(actualErrors, null);
factory.createConverter(AmbiguousEnum.class);
assertEquals(
enumHasAmbiguousNames(new Errors(), AmbiguousEnum.class),
factory.getErrors());
}
static enum AmbiguousEnum {
FOO, Foo
}
@Test
public void createConverterNatives() throws Exception {
Object[] fixtures = new Object[] {
C_STRING, String.class,
C_BOOLEAN, Boolean.TYPE,
C_BYTE, Byte.TYPE,
C_CHAR, Character.TYPE,
C_DOUBLE, Double.TYPE,
C_FLOAT, Float.TYPE,
C_INT, Integer.TYPE,
C_LONG, Long.TYPE,
C_SHORT, Short.TYPE
};
for (int i = 0; i < fixtures.length; i += 2) {
String message = format("type %s", fixtures[i + 1]);
Option<? extends Converter<?>> converter =
createFactory(actualErrors, null).createConverter((Type) fixtures[i + 1]);
assertTrue(message, converter.isDefined());
assertEquals(message, fixtures[i], converter.getOrThrow());
}
}
@Test
public void createInstantiatorWithIncorrectDefaultValue() throws Exception {
checkErrorCase(
WrongDefaultValue.class,
incorrectDefaultValue(
new Errors(),
"foobar",
new IllegalArgumentException()));
}
static class WrongDefaultValue {
WrongDefaultValue(@Optional("foobar") char c) {
}
}
@Test
public void convertedAnnotatedClass() throws Exception {
InstantiatorImplFactory<Object> factory = createFactory(actualErrors, null);
Converter<?> converter = factory.createConverter(AnnotatedClass.class).getOrThrow();
assertEquals(StringConstructorConverter.class, converter.getClass());
}
@Test
public void convertedAnnotatedClassWithFunction() throws Exception {
InstantiatorImplFactory<Object> factory = createFactory(actualErrors, null);
InstantiatorModule module = new AbstractInstantiatorModule() {
@Override
protected void configure() {
register(new Function<Type, Option<? extends Converter<?>>>() {
@SuppressWarnings({ "unchecked", "rawtypes" })
@Override
public Option<? extends Converter<?>> apply(Type type) {
if (type instanceof Class &&
((Class) type).getAnnotation(AnAnnotation.class) != null) {
return Option.some(new ConverterForAnnotatedClass());
} else {
return Option.none();
}
}
});
}
};
module.configure(factory.binder());
Converter<?> converter = factory.createConverter(AnnotatedClass.class).getOrThrow();
assertEquals(ConverterForAnnotatedClass.class, converter.getClass());
Converter<?> converter2 = factory.createConverter(NonAnnotatedClass.class).getOrThrow();
assertEquals(StringConstructorConverter.class, converter2.getClass());
}
@Test
public void convertedAnnotatedClassWithTwoFunctions() throws Exception {
InstantiatorImplFactory<Object> factory = createFactory(actualErrors, null);
InstantiatorModule module = new AbstractInstantiatorModule() {
@Override
protected void configure() {
Function<Type, Option<? extends Converter<?>>> function =
new Function<Type, Option<? extends Converter<?>>>() {
@SuppressWarnings({ "unchecked", "rawtypes" })
@Override
public Option<? extends Converter<?>> apply(Type type) {
if (type instanceof Class &&
((Class) type).getAnnotation(AnAnnotation.class) != null) {
return Option.some(new ConverterForAnnotatedClass());
} else {
return Option.none();
}
}
};
register(function);
register(function);
}
};
module.configure(factory.binder());
factory.createConverter(AnnotatedClass.class);
assertEquals(
moreThanOneMatchingFunction(new Errors(), AnnotatedClass.class),
factory.getErrors());
}
@AnAnnotation
static class AnnotatedClass {
String value;
AnnotatedClass(String value) {
this.value = value;
}
}
static class NonAnnotatedClass {
String value;
NonAnnotatedClass(String value) {
this.value = value;
}
}
@Retention(RUNTIME)
@interface AnAnnotation {
}
static class ConverterForAnnotatedClass implements Converter<AnnotatedClass> {
@Override
public String toString(AnnotatedClass value) {
return null;
}
@Override
public AnnotatedClass fromString(String representation) {
return null;
}
}
@Test
public void createInstantiatorWithLiteralTypeNotHavingDefault() throws Exception {
checkErrorCase(
OptionalLiteralParameterWithoutDefault.class,
optionalLiteralParameterMustHaveDefault(new Errors(), 0));
}
@Test
public void createInstantiatorWithIllegalConstructor() throws Exception {
checkErrorCase(
HasIllegalConstructor.class,
illegalConstructor(new Errors(), HasIllegalConstructor.class, null));
}
static class HasIllegalConstructor {
HasIllegalConstructor() {
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
}
}
@Test
public void doesNotKnowHowToConvert() throws Exception {
checkErrorCase(
DoesNotKnowHowToConvert.class,
noConverterForType(new Errors(), new TypeLiteral<Map<String, String>>() {}.getType()));
}
static class DoesNotKnowHowToConvert {
DoesNotKnowHowToConvert(Map<String, String> names) {
}
}
@Test
public void duplicateConverterBinding1() throws Exception {
Errors expected = new Errors();
duplicateConverterBindingForType(expected, new TypeLiteral<String>() {}.getType());
moreThanOneConstructor(expected, String.class);
checkErrorCase(
String.class,
expected,
new AbstractInstantiatorModule() {
@Override
protected void configure() {
registerFor(String.class).converter(C_STRING);
registerFor(String.class).converter(C_STRING);
}
});
}
@Test
public void duplicateConverterBinding2() throws Exception {
Errors expected = new Errors();
duplicateConverterBindingForType(expected, new TypeLiteral<String>() {}.getType());
moreThanOneConstructor(expected, String.class);
checkErrorCase(
String.class,
expected,
new AbstractInstantiatorModule() {
@Override
protected void configure() {
registerFor(String.class).converter(C_STRING.getClass());
registerFor(String.class).converter(C_STRING.getClass());
}
});
}
@Test
public void duplicateConverterBinding3() throws Exception {
Errors expected = new Errors();
duplicateConverterBindingForType(expected, new TypeLiteral<String>() {}.getType());
moreThanOneConstructor(expected, String.class);
checkErrorCase(
String.class,
expected,
new AbstractInstantiatorModule() {
@Override
protected void configure() {
registerFor(String.class).converter(C_STRING);
registerFor(String.class).converter(C_STRING.getClass());
}
});
}
static class DefaultValueAndDefaultConstant {
DefaultValueAndDefaultConstant(@Optional(value = "4", constant = "F") int a) {
}
}
@Test
public void defaultValueAndDefaultConstant() throws Exception {
checkErrorCase(
DefaultValueAndDefaultConstant.class,
cannotSpecifyDefaultValueAndConstant(
new Errors(),
(Optional) DefaultValueAndDefaultConstant.class.getDeclaredConstructor(int.class).getParameterAnnotations()[0][0]));
}
static class UnresolvableLocalConstant {
UnresolvableLocalConstant(@Optional(constant = "F") int a) {
}
}
@Test
public void unresolvableLocalConstant() throws Exception {
checkErrorCase(
UnresolvableLocalConstant.class,
unableToResolveConstant(new Errors(), UnresolvableLocalConstant.class, "F"));
}
static class UnresolvableFullyQualifiedConstant {
UnresolvableFullyQualifiedConstant(@Optional(constant = "A#F") int a) {
}
}
@Test
public void unresolvableFullyQualifiedConstant() throws Exception {
checkErrorCase(
UnresolvableFullyQualifiedConstant.class,
unableToResolveFullyQualifiedConstant(new Errors(), "A#F"));
}
static class ConstantIsNotStaticFinal {
int NOT_STATIC_FINAL;
ConstantIsNotStaticFinal(@Optional(constant = "NOT_STATIC_FINAL") int a) {
}
}
@Test
public void constantIsNotStaticFinal() throws Exception {
checkErrorCase(
ConstantIsNotStaticFinal.class,
InstantiatorErrors.constantIsNotStaticFinal(
new Errors(), ConstantIsNotStaticFinal.class, "NOT_STATIC_FINAL"));
}
static class ConstantIsOfAnIncompatibleType {
static final String WRONG_TYPE = "noononono";
ConstantIsOfAnIncompatibleType(@Optional(constant = "WRONG_TYPE") int a) {
}
}
@Test
public void constantIsOfAnIncompatibleType() throws Exception {
checkErrorCase(
ConstantIsOfAnIncompatibleType.class,
constantHasIncompatibleType(new Errors(), ConstantIsOfAnIncompatibleType.class, "WRONG_TYPE"));
}
static class AtOptionalOnOption {
AtOptionalOnOption(@Optional Option<Integer> foo) {
}
}
@Test
public void atOptionalOnOption() throws Exception {
checkErrorCase(
AtOptionalOnOption.class,
cannotAnnotateOptionWithOptional(new Errors(), new TypeLiteral<Option<Integer>>() {}.getType()));
}
private <T> void checkErrorCase(Class<T> klass, Errors expected, InstantiatorModule... modules) {
InstantiatorImplFactory<T> f = InstantiatorImplFactory.createFactory(actualErrors, klass);
for (InstantiatorModule m : modules) {
m.configure(f.binder());
}
assertTrue(f.build().isEmpty());
assertEquals(expected, actualErrors);
}
@Test
public void retrieveFieldsFromAssignment1() {
Field[] fields = createFactory(actualErrors, HasStringConstructor.class)
.retrieveFieldsFromAssignment(
1,
ImmutableMap.of(
"representation", new FormalParameter(0, null)));
assertEquals(1, fields.length);
assertEquals("representation", fields[0].getName());
assertTrue(fields[0].isAccessible());
}
@Test
public void retrieveFieldsFromAssignment2() {
InstantiatorImplFactory<HasStringConstructor> f = createFactory(actualErrors, HasStringConstructor.class);
f.retrieveFieldsFromAssignment(
1,
ImmutableMap.of(
"thisfielddoesnotexist", new FormalParameter(0, null)));
assertEquals(
noSuchField(new Errors(), "thisfielddoesnotexist"),
f.getErrors());
}
@Test(expected = IllegalStateException.class)
public void retrieveFieldsFromAssignment3() {
createFactory(actualErrors, HasStringConstructor.class).retrieveFieldsFromAssignment(
1,
ImmutableMap.of(
"representation", new FormalParameter(1, null))); // wrong index
}
@Test
public void retrieveFieldsFromAssignment4FieldIsInSuperclass() {
Field[] fields = createFactory(actualErrors, FieldIsInSuperSuperclass.class)
.retrieveFieldsFromAssignment(
1,
ImmutableMap.of(
"theFieldYouAreLookingFor", new FormalParameter(0, null)));
assertEquals(1, fields.length);
assertEquals("theFieldYouAreLookingFor", fields[0].getName());
assertTrue(fields[0].isAccessible());
}
static class FieldIsHere { String theFieldYouAreLookingFor; }
static class FieldIsInSuperclass extends FieldIsHere {}
static class FieldIsInSuperSuperclass extends FieldIsInSuperclass {}
static class OptionalLiteralParameterWithoutDefault {
OptionalLiteralParameterWithoutDefault(@Optional short s) {}
}
@Test
public void getPublicConstructor() throws Exception {
createFactory(actualErrors, A.class).getConstructor();
}
@Test
public void getProtectedConstructor() throws Exception {
createFactory(actualErrors, B.class).getConstructor();
}
@Test
public void getDefaultVisibleConstructor() throws Exception {
createFactory(actualErrors, C.class).getConstructor();
}
@Test
public void getPrivateConstructor() throws Exception {
createFactory(actualErrors, D.class).getConstructor();
}
@Test
public void getNonExistingConstructor() throws Exception {
assertTrue(createFactory(actualErrors, E.class).getConstructor().isEmpty());
assertEquals(
noConstructorFound(new Errors(), E.class),
actualErrors);
}
@Test
public void getNonUniqueConstructor() throws Exception {
assertTrue(createFactory(actualErrors, F.class).getConstructor().isEmpty());
assertEquals(
moreThanOneConstructor(new Errors(), F.class),
actualErrors);
}
@Test
public void getConstructorFromSuperclass() throws Exception {
createFactory(actualErrors, G.class).getConstructor();
}
@Test
public void getConstructorFromSuperclassWithMultipleConstructors() {
createFactory(actualErrors, H.class).getConstructor();
}
@Test
public void getNonUniqueConstructorWithAnnotation1() throws Exception {
assertNotNull(createFactory(actualErrors, P.class).getConstructor());
}
@Test
public void getNonUniqueConstructorWithAnnotation2() throws Exception {
InstantiatorImplFactory<Q> factory = createFactory(actualErrors, Q.class);
factory.getConstructor();
assertEquals(
moreThanOneConstructorWithInstantiate(new Errors(), Q.class),
factory.getErrors());
}
@Test
public void createConverter_int() {
testConverter(int.class, "3", 3, "3");
}
@Test
public void createConverter_double() {
testConverter(double.class, "3", 3.0d, "3.0");
testConverter(double.class, "3.0", 3.0d, "3.0");
testConverter(double.class, "3.110", 3.11d, "3.11");
}
@Test
public void createConverter_listOfIntegers() {
testConverter(new TypeLiteral<List<Integer>>() {}.getType(), "3", newArrayList(3), "3");
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private void testConverter(Type type, String actual,
Object expectedFromString, String expectedToString) {
Option<? extends Converter<?>> maybeConverter = createFactory(actualErrors, null).createConverter(type);
assertTrue(format("no converter for type %s", type), maybeConverter.isDefined());
Converter converter = maybeConverter.getOrThrow();
assertEquals(expectedFromString, converter.fromString(actual));
assertEquals(expectedToString, converter.toString(expectedFromString));
}
static class A {
public A() {}
}
static class B {
protected B() {}
}
static class C {
C() {}
}
static class D {
private D() {}
}
static interface E {
}
static class F {
F() {}
F(String s) {}
}
static class G extends A {}
static class H extends F {}
static class I {
I(String s) {}
}
static class J {
J(List<String> l) {}
}
static class K {
K(@Named("k") String l) {}
}
static class L {
L(@Named("l") @LocalBindingAnnotation String l) {}
}
static class M {
M(@Optional String s) {}
}
static class N {
N(String s, @Optional String t) {}
}
static class O {
O(String s, @Optional("foo") String t, String u) {}
}
static class P {
P() {}
@Instantiate P(String s) {}
}
static class Q {
@Instantiate Q() {}
@Instantiate Q(String s) {}
}
@Retention(RUNTIME)
@Target({ ElementType.FIELD, ElementType.PARAMETER })
@BindingAnnotation
static @interface LocalBindingAnnotation { }
}