PageRenderTime 60ms CodeModel.GetById 30ms RepoModel.GetById 0ms app.codeStats 0ms

/core/test/com/google/inject/TypeConversionTest.java

https://gitlab.com/metamorphiccode/guice
Java | 495 lines | 410 code | 67 blank | 18 comment | 2 complexity | c741fe89ec928c4fa50aa161789eb996 MD5 | raw file
  1. /**
  2. * Copyright (C) 2006 Google Inc.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package com.google.inject;
  17. import static com.google.inject.Asserts.asModuleChain;
  18. import static com.google.inject.Asserts.assertContains;
  19. import static com.google.inject.Asserts.getDeclaringSourcePart;
  20. import static java.lang.annotation.RetentionPolicy.RUNTIME;
  21. import com.google.common.collect.Iterables;
  22. import com.google.inject.matcher.Matchers;
  23. import com.google.inject.spi.ConvertedConstantBinding;
  24. import com.google.inject.spi.TypeConverter;
  25. import com.google.inject.spi.TypeConverterBinding;
  26. import junit.framework.AssertionFailedError;
  27. import junit.framework.TestCase;
  28. import java.lang.annotation.Retention;
  29. import java.util.Date;
  30. /**
  31. * @author crazybob@google.com (Bob Lee)
  32. */
  33. public class TypeConversionTest extends TestCase {
  34. @Retention(RUNTIME)
  35. @BindingAnnotation @interface NumericValue {}
  36. @Retention(RUNTIME)
  37. @BindingAnnotation @interface BooleanValue {}
  38. @Retention(RUNTIME)
  39. @BindingAnnotation @interface EnumValue {}
  40. @Retention(RUNTIME)
  41. @BindingAnnotation @interface ClassName {}
  42. public static class Foo {
  43. @Inject @BooleanValue Boolean booleanField;
  44. @Inject @BooleanValue boolean primitiveBooleanField;
  45. @Inject @NumericValue Byte byteField;
  46. @Inject @NumericValue byte primitiveByteField;
  47. @Inject @NumericValue Short shortField;
  48. @Inject @NumericValue short primitiveShortField;
  49. @Inject @NumericValue Integer integerField;
  50. @Inject @NumericValue int primitiveIntField;
  51. @Inject @NumericValue Long longField;
  52. @Inject @NumericValue long primitiveLongField;
  53. @Inject @NumericValue Float floatField;
  54. @Inject @NumericValue float primitiveFloatField;
  55. @Inject @NumericValue Double doubleField;
  56. @Inject @NumericValue double primitiveDoubleField;
  57. @Inject @EnumValue Bar enumField;
  58. @Inject @ClassName Class<?> classField;
  59. }
  60. public enum Bar {
  61. TEE, BAZ, BOB
  62. }
  63. public void testOneConstantInjection() throws CreationException {
  64. Injector injector = Guice.createInjector(new AbstractModule() {
  65. @Override protected void configure() {
  66. bindConstant().annotatedWith(NumericValue.class).to("5");
  67. bind(Simple.class);
  68. }
  69. });
  70. Simple simple = injector.getInstance(Simple.class);
  71. assertEquals(5, simple.i);
  72. }
  73. static class Simple {
  74. @Inject @NumericValue int i;
  75. }
  76. public void testConstantInjection() throws CreationException {
  77. Injector injector = Guice.createInjector(new AbstractModule() {
  78. @Override protected void configure() {
  79. bindConstant().annotatedWith(NumericValue.class).to("5");
  80. bindConstant().annotatedWith(BooleanValue.class).to("true");
  81. bindConstant().annotatedWith(EnumValue.class).to("TEE");
  82. bindConstant().annotatedWith(ClassName.class).to(Foo.class.getName());
  83. }
  84. });
  85. Foo foo = injector.getInstance(Foo.class);
  86. checkNumbers(
  87. foo.integerField,
  88. foo.primitiveIntField,
  89. foo.longField,
  90. foo.primitiveLongField,
  91. foo.byteField,
  92. foo.primitiveByteField,
  93. foo.shortField,
  94. foo.primitiveShortField,
  95. foo.floatField,
  96. foo.primitiveFloatField,
  97. foo.doubleField,
  98. foo.primitiveDoubleField
  99. );
  100. assertEquals(Bar.TEE, foo.enumField);
  101. assertEquals(Foo.class, foo.classField);
  102. }
  103. public void testConstantInjectionWithExplicitBindingsRequired() throws CreationException {
  104. Injector injector = Guice.createInjector(new AbstractModule() {
  105. @Override protected void configure() {
  106. binder().requireExplicitBindings();
  107. bind(Foo.class);
  108. bindConstant().annotatedWith(NumericValue.class).to("5");
  109. bindConstant().annotatedWith(BooleanValue.class).to("true");
  110. bindConstant().annotatedWith(EnumValue.class).to("TEE");
  111. bindConstant().annotatedWith(ClassName.class).to(Foo.class.getName());
  112. }
  113. });
  114. Foo foo = injector.getInstance(Foo.class);
  115. checkNumbers(
  116. foo.integerField,
  117. foo.primitiveIntField,
  118. foo.longField,
  119. foo.primitiveLongField,
  120. foo.byteField,
  121. foo.primitiveByteField,
  122. foo.shortField,
  123. foo.primitiveShortField,
  124. foo.floatField,
  125. foo.primitiveFloatField,
  126. foo.doubleField,
  127. foo.primitiveDoubleField
  128. );
  129. assertEquals(Bar.TEE, foo.enumField);
  130. assertEquals(Foo.class, foo.classField);
  131. }
  132. void checkNumbers(Number... ns) {
  133. for (Number n : ns) {
  134. assertEquals(5, n.intValue());
  135. }
  136. }
  137. static class OuterErrorModule extends AbstractModule {
  138. @Override protected void configure() {
  139. install(new InnerErrorModule());
  140. }
  141. }
  142. static class InnerErrorModule extends AbstractModule {
  143. @Override protected void configure() {
  144. bindConstant().annotatedWith(NumericValue.class).to("invalid");
  145. }
  146. }
  147. public void testInvalidInteger() throws CreationException {
  148. Injector injector = Guice.createInjector(new OuterErrorModule());
  149. try {
  150. injector.getInstance(InvalidInteger.class);
  151. fail();
  152. } catch (ConfigurationException expected) {
  153. assertContains(expected.getMessage(),
  154. "Error converting 'invalid' (bound at " + InnerErrorModule.class.getName()
  155. + getDeclaringSourcePart(getClass()),
  156. asModuleChain(OuterErrorModule.class, InnerErrorModule.class),
  157. "using TypeConverter<Integer> which matches identicalTo(class java.lang.Integer)"
  158. + " (bound at [unknown source]).",
  159. "Reason: java.lang.RuntimeException: For input string: \"invalid\"");
  160. }
  161. }
  162. public static class InvalidInteger {
  163. @Inject @NumericValue Integer integerField;
  164. }
  165. public void testInvalidCharacter() throws CreationException {
  166. Injector injector = Guice.createInjector(new AbstractModule() {
  167. @Override protected void configure() {
  168. bindConstant().annotatedWith(NumericValue.class).to("invalid");
  169. }
  170. });
  171. try {
  172. injector.getInstance(InvalidCharacter.class);
  173. fail();
  174. } catch (ConfigurationException expected) {
  175. assertContains(expected.getMessage(), "Error converting 'invalid'");
  176. assertContains(expected.getMessage(), "bound at " + getClass().getName());
  177. assertContains(expected.getMessage(), "to java.lang.Character");
  178. }
  179. }
  180. public static class InvalidCharacter {
  181. @Inject @NumericValue char foo;
  182. }
  183. public void testInvalidEnum() throws CreationException {
  184. Injector injector = Guice.createInjector(new AbstractModule() {
  185. @Override protected void configure() {
  186. bindConstant().annotatedWith(NumericValue.class).to("invalid");
  187. }
  188. });
  189. try {
  190. injector.getInstance(InvalidEnum.class);
  191. fail();
  192. } catch (ConfigurationException expected) {
  193. assertContains(expected.getMessage(), "Error converting 'invalid'");
  194. assertContains(expected.getMessage(), "bound at " + getClass().getName());
  195. assertContains(expected.getMessage(), "to " + Bar.class.getName());
  196. }
  197. }
  198. public static class InvalidEnum {
  199. @Inject @NumericValue Bar foo;
  200. }
  201. public void testToInstanceIsTreatedLikeConstant() throws CreationException {
  202. Injector injector = Guice.createInjector(new AbstractModule() {
  203. @Override protected void configure() {
  204. bind(String.class).toInstance("5");
  205. bind(LongHolder.class);
  206. }
  207. });
  208. assertEquals(5L, (long) injector.getInstance(LongHolder.class).foo);
  209. }
  210. static class LongHolder {
  211. @Inject Long foo;
  212. }
  213. public void testCustomTypeConversion() throws CreationException {
  214. final Date result = new Date();
  215. Injector injector = Guice.createInjector(new AbstractModule() {
  216. @Override protected void configure() {
  217. convertToTypes(Matchers.only(TypeLiteral.get(Date.class)) , mockTypeConverter(result));
  218. bindConstant().annotatedWith(NumericValue.class).to("Today");
  219. bind(DateHolder.class);
  220. }
  221. });
  222. assertSame(result, injector.getInstance(DateHolder.class).date);
  223. Binding<Date> binding = injector.getBinding(Key.get(Date.class, NumericValue.class));
  224. assertTrue(binding instanceof ConvertedConstantBinding<?>);
  225. TypeConverterBinding converterBinding = ((ConvertedConstantBinding<?>)binding).getTypeConverterBinding();
  226. assertEquals("CustomConverter", converterBinding.getTypeConverter().toString());
  227. assertTrue(injector.getTypeConverterBindings().contains(converterBinding));
  228. }
  229. static class InvalidCustomValueModule extends AbstractModule {
  230. @Override protected void configure() {
  231. convertToTypes(Matchers.only(TypeLiteral.get(Date.class)), failingTypeConverter());
  232. bindConstant().annotatedWith(NumericValue.class).to("invalid");
  233. bind(DateHolder.class);
  234. }
  235. }
  236. public void testInvalidCustomValue() throws CreationException {
  237. Module module = new InvalidCustomValueModule();
  238. try {
  239. Guice.createInjector(module);
  240. fail();
  241. } catch (CreationException expected) {
  242. Throwable cause = Iterables.getOnlyElement(expected.getErrorMessages()).getCause();
  243. assertTrue(cause instanceof UnsupportedOperationException);
  244. assertContains(expected.getMessage(),
  245. "1) Error converting 'invalid' (bound at ", getClass().getName(),
  246. getDeclaringSourcePart(getClass()), "to java.util.Date",
  247. "using BrokenConverter which matches only(java.util.Date) ",
  248. "(bound at " + getClass().getName(), getDeclaringSourcePart(getClass()),
  249. "Reason: java.lang.UnsupportedOperationException: Cannot convert",
  250. "at " + DateHolder.class.getName() + ".date(TypeConversionTest.java:");
  251. }
  252. }
  253. static class OuterModule extends AbstractModule {
  254. private final Module converterModule;
  255. OuterModule(Module converterModule) {
  256. this.converterModule = converterModule;
  257. }
  258. @Override protected void configure() {
  259. install(new InnerModule(converterModule));
  260. }
  261. }
  262. static class InnerModule extends AbstractModule {
  263. private final Module converterModule;
  264. InnerModule(Module converterModule) {
  265. this.converterModule = converterModule;
  266. }
  267. @Override protected void configure() {
  268. install(converterModule);
  269. bindConstant().annotatedWith(NumericValue.class).to("foo");
  270. bind(DateHolder.class);
  271. }
  272. }
  273. class ConverterNullModule extends AbstractModule {
  274. @Override protected void configure() {
  275. convertToTypes(Matchers.only(TypeLiteral.get(Date.class)), mockTypeConverter(null));
  276. }
  277. }
  278. public void testNullCustomValue() {
  279. try {
  280. Guice.createInjector(new OuterModule(new ConverterNullModule()));
  281. fail();
  282. } catch (CreationException expected) {
  283. assertContains(expected.getMessage(),
  284. "1) Received null converting 'foo' (bound at ",
  285. getClass().getName(),
  286. getDeclaringSourcePart(getClass()),
  287. asModuleChain(OuterModule.class, InnerModule.class),
  288. "to java.util.Date",
  289. "using CustomConverter which matches only(java.util.Date) ",
  290. "(bound at " + getClass().getName(),
  291. getDeclaringSourcePart(getClass()),
  292. asModuleChain(OuterModule.class, InnerModule.class, ConverterNullModule.class),
  293. "at " + DateHolder.class.getName() + ".date(TypeConversionTest.java:",
  294. asModuleChain(OuterModule.class, InnerModule.class));
  295. }
  296. }
  297. class ConverterCustomModule extends AbstractModule {
  298. @Override protected void configure() {
  299. convertToTypes(Matchers.only(TypeLiteral.get(Date.class)), mockTypeConverter(-1));
  300. }
  301. }
  302. public void testCustomValueTypeMismatch() {
  303. try {
  304. Guice.createInjector(new OuterModule(new ConverterCustomModule()));
  305. fail();
  306. } catch (CreationException expected) {
  307. assertContains(expected.getMessage(),
  308. "1) Type mismatch converting 'foo' (bound at ",
  309. getClass().getName(),
  310. getDeclaringSourcePart(getClass()),
  311. asModuleChain(OuterModule.class, InnerModule.class),
  312. "to java.util.Date",
  313. "using CustomConverter which matches only(java.util.Date) ",
  314. "(bound at " + getClass().getName(),
  315. getDeclaringSourcePart(getClass()),
  316. asModuleChain(OuterModule.class, InnerModule.class, ConverterCustomModule.class),
  317. "Converter returned -1.",
  318. "at " + DateHolder.class.getName() + ".date(TypeConversionTest.java:",
  319. asModuleChain(OuterModule.class, InnerModule.class));
  320. }
  321. }
  322. public void testStringIsConvertedOnlyOnce() {
  323. final TypeConverter converter = new TypeConverter() {
  324. boolean converted = false;
  325. public Object convert(String value, TypeLiteral<?> toType) {
  326. if (converted) {
  327. throw new AssertionFailedError("converted multiple times!");
  328. }
  329. converted = true;
  330. return new Date();
  331. }
  332. };
  333. Injector injector = Guice.createInjector(new AbstractModule() {
  334. @Override protected void configure() {
  335. convertToTypes(Matchers.only(TypeLiteral.get(Date.class)), converter);
  336. bindConstant().annotatedWith(NumericValue.class).to("unused");
  337. }
  338. });
  339. Date first = injector.getInstance(Key.get(Date.class, NumericValue.class));
  340. Date second = injector.getInstance(Key.get(Date.class, NumericValue.class));
  341. assertSame(first, second);
  342. }
  343. class OuterAmbiguousModule extends AbstractModule {
  344. @Override protected void configure() {
  345. install(new InnerAmbiguousModule());
  346. }
  347. }
  348. class InnerAmbiguousModule extends AbstractModule {
  349. @Override protected void configure() {
  350. install(new Ambiguous1Module());
  351. install(new Ambiguous2Module());
  352. bindConstant().annotatedWith(NumericValue.class).to("foo");
  353. bind(DateHolder.class);
  354. }
  355. }
  356. class Ambiguous1Module extends AbstractModule {
  357. @Override protected void configure() {
  358. convertToTypes(Matchers.only(TypeLiteral.get(Date.class)), mockTypeConverter(new Date()));
  359. }
  360. }
  361. class Ambiguous2Module extends AbstractModule {
  362. @Override protected void configure() {
  363. convertToTypes(Matchers.only(TypeLiteral.get(Date.class)), mockTypeConverter(new Date()));
  364. }
  365. }
  366. public void testAmbiguousTypeConversion() {
  367. try {
  368. Guice.createInjector(new OuterAmbiguousModule());
  369. fail();
  370. } catch (CreationException expected) {
  371. assertContains(expected.getMessage(),
  372. "1) Multiple converters can convert 'foo' (bound at ", getClass().getName(),
  373. getDeclaringSourcePart(getClass()),
  374. asModuleChain(OuterAmbiguousModule.class, InnerAmbiguousModule.class),
  375. "to java.util.Date:",
  376. "CustomConverter which matches only(java.util.Date) (bound at "
  377. + Ambiguous1Module.class.getName()
  378. + getDeclaringSourcePart(getClass()),
  379. asModuleChain(
  380. OuterAmbiguousModule.class, InnerAmbiguousModule.class, Ambiguous1Module.class),
  381. "and",
  382. "CustomConverter which matches only(java.util.Date) (bound at "
  383. + Ambiguous2Module.class.getName()
  384. + getDeclaringSourcePart(getClass()),
  385. asModuleChain(
  386. OuterAmbiguousModule.class, InnerAmbiguousModule.class, Ambiguous2Module.class),
  387. "Please adjust your type converter configuration to avoid overlapping matches.",
  388. "at " + DateHolder.class.getName() + ".date(TypeConversionTest.java:");
  389. }
  390. }
  391. TypeConverter mockTypeConverter(final Object result) {
  392. return new TypeConverter() {
  393. public Object convert(String value, TypeLiteral<?> toType) {
  394. return result;
  395. }
  396. @Override public String toString() {
  397. return "CustomConverter";
  398. }
  399. };
  400. }
  401. private static TypeConverter failingTypeConverter() {
  402. return new TypeConverter() {
  403. public Object convert(String value, TypeLiteral<?> toType) {
  404. throw new UnsupportedOperationException("Cannot convert");
  405. }
  406. @Override public String toString() {
  407. return "BrokenConverter";
  408. }
  409. };
  410. }
  411. static class DateHolder {
  412. @Inject @NumericValue Date date;
  413. }
  414. public void testCannotConvertUnannotatedBindings() {
  415. Injector injector = Guice.createInjector(new AbstractModule() {
  416. @Override protected void configure() {
  417. bind(String.class).toInstance("55");
  418. }
  419. });
  420. try {
  421. injector.getInstance(Integer.class);
  422. fail("Converted an unannotated String to an Integer");
  423. } catch (ConfigurationException expected) {
  424. Asserts.assertContains(expected.getMessage(),
  425. "Could not find a suitable constructor in java.lang.Integer.");
  426. }
  427. }
  428. }