View Javadoc
1   /*
2    * Copyright 2002-2015 the original author or authors.
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  
17  package org.springframework.core.convert.support;
18  
19  import java.awt.Color;
20  import java.awt.SystemColor;
21  import java.lang.annotation.Retention;
22  import java.lang.annotation.RetentionPolicy;
23  import java.util.ArrayList;
24  import java.util.Arrays;
25  import java.util.Collection;
26  import java.util.Collections;
27  import java.util.EnumSet;
28  import java.util.HashMap;
29  import java.util.HashSet;
30  import java.util.Iterator;
31  import java.util.LinkedHashMap;
32  import java.util.LinkedList;
33  import java.util.List;
34  import java.util.Map;
35  import java.util.Set;
36  import java.util.UUID;
37  
38  import org.junit.Test;
39  
40  import org.springframework.core.convert.ConversionFailedException;
41  import org.springframework.core.convert.ConverterNotFoundException;
42  import org.springframework.core.convert.TypeDescriptor;
43  import org.springframework.core.convert.converter.ConditionalConverter;
44  import org.springframework.core.convert.converter.Converter;
45  import org.springframework.core.convert.converter.ConverterFactory;
46  import org.springframework.core.convert.converter.GenericConverter;
47  import org.springframework.core.io.DescriptiveResource;
48  import org.springframework.core.io.Resource;
49  import org.springframework.tests.Assume;
50  import org.springframework.tests.TestGroup;
51  import org.springframework.util.StopWatch;
52  import org.springframework.util.StringUtils;
53  
54  import static org.hamcrest.Matchers.*;
55  import static org.junit.Assert.*;
56  
57  /**
58   * @author Keith Donald
59   * @author Juergen Hoeller
60   * @author Phillip Webb
61   * @author David Haraburda
62   */
63  public class GenericConversionServiceTests {
64  
65  	private GenericConversionService conversionService = new GenericConversionService();
66  
67  
68  	@Test
69  	public void canConvert() {
70  		assertFalse(conversionService.canConvert(String.class, Integer.class));
71  		conversionService.addConverterFactory(new StringToNumberConverterFactory());
72  		assertTrue(conversionService.canConvert(String.class, Integer.class));
73  	}
74  
75  	@Test
76  	public void canConvertAssignable() {
77  		assertTrue(conversionService.canConvert(String.class, String.class));
78  		assertTrue(conversionService.canConvert(Integer.class, Number.class));
79  		assertTrue(conversionService.canConvert(boolean.class, boolean.class));
80  		assertTrue(conversionService.canConvert(boolean.class, Boolean.class));
81  	}
82  
83  	@Test
84  	public void canConvertIllegalArgumentNullTargetType() {
85  		try {
86  			assertFalse(conversionService.canConvert(String.class, null));
87  			fail("Should have failed");
88  		}
89  		catch (IllegalArgumentException ex) {
90  		}
91  		try {
92  			assertFalse(conversionService.canConvert(TypeDescriptor.valueOf(String.class), null));
93  			fail("Should have failed");
94  		}
95  		catch (IllegalArgumentException ex) {
96  		}
97  	}
98  
99  	@Test
100 	public void canConvertNullSourceType() {
101 		assertTrue(conversionService.canConvert(null, Integer.class));
102 		assertTrue(conversionService.canConvert(null, TypeDescriptor.valueOf(Integer.class)));
103 	}
104 
105 	@Test
106 	public void convert() {
107 		conversionService.addConverterFactory(new StringToNumberConverterFactory());
108 		assertEquals(new Integer(3), conversionService.convert("3", Integer.class));
109 	}
110 
111 	@Test
112 	public void convertNullSource() {
113 		assertEquals(null, conversionService.convert(null, Integer.class));
114 	}
115 
116 	@Test(expected = ConversionFailedException.class)
117 	public void convertNullSourcePrimitiveTarget() {
118 		assertEquals(null, conversionService.convert(null, int.class));
119 	}
120 
121 	@Test(expected = ConversionFailedException.class)
122 	public void convertNullSourcePrimitiveTargetTypeDescriptor() {
123 		conversionService.convert(null, TypeDescriptor.valueOf(String.class), TypeDescriptor.valueOf(int.class));
124 	}
125 
126 	@Test(expected = IllegalArgumentException.class)
127 	public void convertNotNullSourceNullSourceTypeDescriptor() {
128 		conversionService.convert("3", null, TypeDescriptor.valueOf(int.class));
129 	}
130 
131 	@Test
132 	public void convertAssignableSource() {
133 		assertEquals(Boolean.FALSE, conversionService.convert(false, boolean.class));
134 		assertEquals(Boolean.FALSE, conversionService.convert(false, Boolean.class));
135 	}
136 
137 	@Test
138 	public void converterNotFound() {
139 		try {
140 			conversionService.convert("3", Integer.class);
141 			fail("Should have thrown an exception");
142 		}
143 		catch (ConverterNotFoundException e) {
144 		}
145 	}
146 
147 	@Test
148 	@SuppressWarnings("rawtypes")
149 	public void addConverterNoSourceTargetClassInfoAvailable() {
150 		try {
151 			conversionService.addConverter(new Converter() {
152 				@Override
153 				public Object convert(Object source) {
154 					return source;
155 				}
156 			});
157 			fail("Should have failed");
158 		}
159 		catch (IllegalArgumentException ex) {
160 		}
161 	}
162 
163 	@Test
164 	public void sourceTypeIsVoid() {
165 		GenericConversionService conversionService = new GenericConversionService();
166 		assertFalse(conversionService.canConvert(void.class, String.class));
167 	}
168 
169 	@Test
170 	public void targetTypeIsVoid() {
171 		GenericConversionService conversionService = new GenericConversionService();
172 		assertFalse(conversionService.canConvert(String.class, void.class));
173 	}
174 
175 	@Test
176 	public void convertNull() {
177 		assertNull(conversionService.convert(null, Integer.class));
178 	}
179 
180 	@Test(expected = IllegalArgumentException.class)
181 	public void convertNullTargetClass() {
182 		assertNull(conversionService.convert("3", (Class<?>) null));
183 		assertNull(conversionService.convert("3", TypeDescriptor.valueOf(String.class), null));
184 	}
185 
186 	@Test(expected = IllegalArgumentException.class)
187 	public void convertNullTypeDescriptor() {
188 		assertNull(conversionService.convert("3", TypeDescriptor.valueOf(String.class), null));
189 	}
190 
191 	@Test(expected = IllegalArgumentException.class)
192 	public void convertWrongSourceTypeDescriptor() {
193 		conversionService.convert("3", TypeDescriptor.valueOf(Integer.class), TypeDescriptor.valueOf(Long.class));
194 	}
195 
196 	@Test
197 	public void convertWrongTypeArgument() {
198 		conversionService.addConverterFactory(new StringToNumberConverterFactory());
199 		try {
200 			conversionService.convert("BOGUS", Integer.class);
201 			fail("Should have failed");
202 		}
203 		catch (ConversionFailedException e) {
204 
205 		}
206 	}
207 
208 	@Test
209 	public void convertSuperSourceType() {
210 		conversionService.addConverter(new Converter<CharSequence, Integer>() {
211 			@Override
212 			public Integer convert(CharSequence source) {
213 				return Integer.valueOf(source.toString());
214 			}
215 		});
216 		Integer result = conversionService.convert("3", Integer.class);
217 		assertEquals(new Integer(3), result);
218 	}
219 
220 	// SPR-8718
221 	@Test(expected = ConverterNotFoundException.class)
222 	public void convertSuperTarget() {
223 		conversionService.addConverter(new ColorConverter());
224 		conversionService.convert("#000000", SystemColor.class);
225 	}
226 
227 	public class ColorConverter implements Converter<String, Color> {
228 		@Override
229 		public Color convert(String source) { if (!source.startsWith("#")) source = "#" + source; return Color.decode(source); }
230 	}
231 
232 	@Test
233 	public void convertObjectToPrimitive() {
234 		assertFalse(conversionService.canConvert(String.class, boolean.class));
235 		conversionService.addConverter(new StringToBooleanConverter());
236 		assertTrue(conversionService.canConvert(String.class, boolean.class));
237 		Boolean b = conversionService.convert("true", boolean.class);
238 		assertEquals(Boolean.TRUE, b);
239 		assertTrue(conversionService.canConvert(TypeDescriptor.valueOf(String.class), TypeDescriptor.valueOf(boolean.class)));
240 		b = (Boolean) conversionService.convert("true", TypeDescriptor.valueOf(String.class), TypeDescriptor.valueOf(boolean.class));
241 		assertEquals(Boolean.TRUE, b);
242 	}
243 
244 	@Test
245 	public void convertObjectToPrimitiveViaConverterFactory() {
246 		assertFalse(conversionService.canConvert(String.class, int.class));
247 		conversionService.addConverterFactory(new StringToNumberConverterFactory());
248 		assertTrue(conversionService.canConvert(String.class, int.class));
249 		Integer three = conversionService.convert("3", int.class);
250 		assertEquals(3, three.intValue());
251 	}
252 
253 	@Test
254 	public void genericConverterDelegatingBackToConversionServiceConverterNotFound() {
255 		conversionService.addConverter(new ObjectToArrayConverter(conversionService));
256 		assertFalse(conversionService.canConvert(String.class, Integer[].class));
257 		try {
258 			conversionService.convert("3,4,5", Integer[].class);
259 			fail("should have failed");
260 		}
261 		catch (ConverterNotFoundException ex) {
262 		}
263 	}
264 
265 	@Test
266 	public void testListToIterableConversion() {
267 		GenericConversionService conversionService = new GenericConversionService();
268 		List<Object> raw = new ArrayList<Object>();
269 		raw.add("one");
270 		raw.add("two");
271 		Object converted = conversionService.convert(raw, Iterable.class);
272 		assertSame(raw, converted);
273 	}
274 
275 	@Test
276 	public void testListToObjectConversion() {
277 		GenericConversionService conversionService = new GenericConversionService();
278 		List<Object> raw = new ArrayList<Object>();
279 		raw.add("one");
280 		raw.add("two");
281 		Object converted = conversionService.convert(raw, Object.class);
282 		assertSame(raw, converted);
283 	}
284 
285 	@Test
286 	public void testMapToObjectConversion() {
287 		GenericConversionService conversionService = new GenericConversionService();
288 		Map<Object, Object> raw = new HashMap<Object, Object>();
289 		raw.put("key", "value");
290 		Object converted = conversionService.convert(raw, Object.class);
291 		assertSame(raw, converted);
292 	}
293 
294 	@Test
295 	public void testInterfaceToString() {
296 		GenericConversionService conversionService = new GenericConversionService();
297 		conversionService.addConverter(new MyBaseInterfaceToStringConverter());
298 		conversionService.addConverter(new ObjectToStringConverter());
299 		Object converted = conversionService.convert(new MyInterfaceImplementer(), String.class);
300 		assertEquals("RESULT", converted);
301 	}
302 
303 	@Test
304 	public void testInterfaceArrayToStringArray() {
305 		GenericConversionService conversionService = new GenericConversionService();
306 		conversionService.addConverter(new MyBaseInterfaceToStringConverter());
307 		conversionService.addConverter(new ArrayToArrayConverter(conversionService));
308 		String[] converted = conversionService.convert(new MyInterface[] {new MyInterfaceImplementer()}, String[].class);
309 		assertEquals("RESULT", converted[0]);
310 	}
311 
312 	@Test
313 	public void testObjectArrayToStringArray() {
314 		GenericConversionService conversionService = new GenericConversionService();
315 		conversionService.addConverter(new MyBaseInterfaceToStringConverter());
316 		conversionService.addConverter(new ArrayToArrayConverter(conversionService));
317 		String[] converted = conversionService.convert(new MyInterfaceImplementer[] {new MyInterfaceImplementer()}, String[].class);
318 		assertEquals("RESULT", converted[0]);
319 	}
320 
321 	@Test
322 	public void testStringArrayToResourceArray() {
323 		GenericConversionService conversionService = new DefaultConversionService();
324 		conversionService.addConverter(new MyStringArrayToResourceArrayConverter());
325 		Resource[] converted = conversionService.convert(new String[] {"x1", "z3"}, Resource[].class);
326 		assertEquals(2, converted.length);
327 		assertEquals("1", converted[0].getDescription());
328 		assertEquals("3", converted[1].getDescription());
329 	}
330 
331 	@Test
332 	public void testStringArrayToIntegerArray() {
333 		GenericConversionService conversionService = new DefaultConversionService();
334 		conversionService.addConverter(new MyStringArrayToIntegerArrayConverter());
335 		Integer[] converted = conversionService.convert(new String[] {"x1", "z3"}, Integer[].class);
336 		assertEquals(2, converted.length);
337 		assertEquals(1, converted[0].intValue());
338 		assertEquals(3, converted[1].intValue());
339 	}
340 
341 	@Test
342 	public void testStringToIntegerArray() {
343 		GenericConversionService conversionService = new DefaultConversionService();
344 		conversionService.addConverter(new MyStringToIntegerArrayConverter());
345 		Integer[] converted = conversionService.convert("x1,z3", Integer[].class);
346 		assertEquals(2, converted.length);
347 		assertEquals(1, converted[0].intValue());
348 		assertEquals(3, converted[1].intValue());
349 	}
350 
351 	@Test
352 	public void testWildcardMap() throws Exception {
353 		GenericConversionService conversionService = new DefaultConversionService();
354 		Map<String, String> input = new LinkedHashMap<String, String>();
355 		input.put("key", "value");
356 		Object converted = conversionService.convert(input, TypeDescriptor.forObject(input), new TypeDescriptor(getClass().getField("wildcardMap")));
357 		assertEquals(input, converted);
358 	}
359 
360 	@Test
361 	public void testListOfList() {
362 		GenericConversionService service = new DefaultConversionService();
363 		List<String> list1 = Arrays.asList("Foo", "Bar");
364 		List<String> list2 = Arrays.asList("Baz", "Boop");
365 		List<List<String>> list = Arrays.asList(list1, list2);
366 		String result = service.convert(list, String.class);
367 		assertNotNull(result);
368 		assertEquals("Foo,Bar,Baz,Boop", result);
369 	}
370 
371 	@Test
372 	public void testStringToString() {
373 		GenericConversionService service = new DefaultConversionService();
374 		String value = "myValue";
375 		String result = service.convert(value, String.class);
376 		assertSame(value, result);
377 	}
378 
379 	@Test
380 	public void testStringToObject() {
381 		GenericConversionService service = new DefaultConversionService();
382 		String value = "myValue";
383 		Object result = service.convert(value, Object.class);
384 		assertSame(value, result);
385 	}
386 
387 	@Test
388 	public void testIgnoreCopyConstructor() {
389 		GenericConversionService service = new DefaultConversionService();
390 		WithCopyConstructor value = new WithCopyConstructor();
391 		Object result = service.convert(value, WithCopyConstructor.class);
392 		assertSame(value, result);
393 	}
394 
395 	@Test
396 	public void testConvertUUID() {
397 		GenericConversionService service = new DefaultConversionService();
398 		UUID uuid = UUID.randomUUID();
399 		String convertToString = service.convert(uuid, String.class);
400 		UUID convertToUUID = service.convert(convertToString, UUID.class);
401 		assertEquals(uuid, convertToUUID);
402 	}
403 
404 	@Test
405 	public void testPerformance1() {
406 		Assume.group(TestGroup.PERFORMANCE);
407 		GenericConversionService conversionService = new DefaultConversionService();
408 		StopWatch watch = new StopWatch("integer->string conversionPerformance");
409 		watch.start("convert 4,000,000 with conversion service");
410 		for (int i = 0; i < 4000000; i++) {
411 			conversionService.convert(3, String.class);
412 		}
413 		watch.stop();
414 		watch.start("convert 4,000,000 manually");
415 		for (int i = 0; i < 4000000; i++) {
416 			new Integer(3).toString();
417 		}
418 		watch.stop();
419 		System.out.println(watch.prettyPrint());
420 	}
421 
422 	@Test
423 	public void testPerformance2() throws Exception {
424 		Assume.group(TestGroup.PERFORMANCE);
425 		GenericConversionService conversionService = new DefaultConversionService();
426 		StopWatch watch = new StopWatch("list<string> -> list<integer> conversionPerformance");
427 		watch.start("convert 4,000,000 with conversion service");
428 		List<String> source = new LinkedList<String>();
429 		source.add("1");
430 		source.add("2");
431 		source.add("3");
432 		TypeDescriptor td = new TypeDescriptor(getClass().getField("list"));
433 		for (int i = 0; i < 1000000; i++) {
434 			conversionService.convert(source, TypeDescriptor.forObject(source), td);
435 		}
436 		watch.stop();
437 		watch.start("convert 4,000,000 manually");
438 		for (int i = 0; i < 4000000; i++) {
439 			List<Integer> target = new ArrayList<Integer>(source.size());
440 			for (String element : source) {
441 				target.add(Integer.valueOf(element));
442 			}
443 		}
444 		watch.stop();
445 		System.out.println(watch.prettyPrint());
446 	}
447 
448 	@Test
449 	public void testPerformance3() throws Exception {
450 		Assume.group(TestGroup.PERFORMANCE);
451 		GenericConversionService conversionService = new DefaultConversionService();
452 		StopWatch watch = new StopWatch("map<string, string> -> map<string, integer> conversionPerformance");
453 		watch.start("convert 4,000,000 with conversion service");
454 		Map<String, String> source = new HashMap<String, String>();
455 		source.put("1", "1");
456 		source.put("2", "2");
457 		source.put("3", "3");
458 		TypeDescriptor td = new TypeDescriptor(getClass().getField("map"));
459 		for (int i = 0; i < 1000000; i++) {
460 			conversionService.convert(source, TypeDescriptor.forObject(source), td);
461 		}
462 		watch.stop();
463 		watch.start("convert 4,000,000 manually");
464 		for (int i = 0; i < 4000000; i++) {
465 			Map<String, Integer> target = new HashMap<String, Integer>(source.size());
466 			for (Map.Entry<String, String> entry : source.entrySet()) {
467 				target.put(entry.getKey(), Integer.valueOf(entry.getValue()));
468 			}
469 		}
470 		watch.stop();
471 		System.out.println(watch.prettyPrint());
472 	}
473 
474 	@Test
475 	public void emptyListToArray() {
476 		conversionService.addConverter(new CollectionToArrayConverter(conversionService));
477 		conversionService.addConverterFactory(new StringToNumberConverterFactory());
478 		List<String> list = new ArrayList<String>();
479 		TypeDescriptor sourceType = TypeDescriptor.forObject(list);
480 		TypeDescriptor targetType = TypeDescriptor.valueOf(String[].class);
481 		assertTrue(conversionService.canConvert(sourceType, targetType));
482 		assertEquals(0, ((String[]) conversionService.convert(list, sourceType, targetType)).length);
483 	}
484 
485 	@Test
486 	public void emptyListToObject() {
487 		conversionService.addConverter(new CollectionToObjectConverter(conversionService));
488 		conversionService.addConverterFactory(new StringToNumberConverterFactory());
489 		List<String> list = new ArrayList<String>();
490 		TypeDescriptor sourceType = TypeDescriptor.forObject(list);
491 		TypeDescriptor targetType = TypeDescriptor.valueOf(Integer.class);
492 		assertTrue(conversionService.canConvert(sourceType, targetType));
493 		assertNull(conversionService.convert(list, sourceType, targetType));
494 	}
495 
496 	@Test
497 	public void stringToArrayCanConvert() {
498 		conversionService.addConverter(new StringToArrayConverter(conversionService));
499 		assertFalse(conversionService.canConvert(String.class, Integer[].class));
500 		conversionService.addConverterFactory(new StringToNumberConverterFactory());
501 		assertTrue(conversionService.canConvert(String.class, Integer[].class));
502 	}
503 
504 	@Test
505 	public void stringToCollectionCanConvert() throws Exception {
506 		conversionService.addConverter(new StringToCollectionConverter(conversionService));
507 		assertTrue(conversionService.canConvert(String.class, Collection.class));
508 		TypeDescriptor targetType = new TypeDescriptor(getClass().getField("integerCollection"));
509 		assertFalse(conversionService.canConvert(TypeDescriptor.valueOf(String.class), targetType));
510 		conversionService.addConverterFactory(new StringToNumberConverterFactory());
511 		assertTrue(conversionService.canConvert(TypeDescriptor.valueOf(String.class), targetType));
512 	}
513 
514 	@Test
515 	public void testConvertiblePairsInSet() {
516 		Set<GenericConverter.ConvertiblePair> set = new HashSet<GenericConverter.ConvertiblePair>();
517 		set.add(new GenericConverter.ConvertiblePair(Number.class, String.class));
518 		assert set.contains(new GenericConverter.ConvertiblePair(Number.class, String.class));
519 	}
520 
521 	@Test
522 	public void testConvertiblePairEqualsAndHash() {
523 		GenericConverter.ConvertiblePair pair = new GenericConverter.ConvertiblePair(Number.class, String.class);
524 		GenericConverter.ConvertiblePair pairEqual = new GenericConverter.ConvertiblePair(Number.class, String.class);
525 		assertEquals(pair, pairEqual);
526 		assertEquals(pair.hashCode(), pairEqual.hashCode());
527 	}
528 
529 	@Test
530 	public void testConvertiblePairDifferentEqualsAndHash() {
531 		GenericConverter.ConvertiblePair pair = new GenericConverter.ConvertiblePair(Number.class, String.class);
532 		GenericConverter.ConvertiblePair pairOpposite = new GenericConverter.ConvertiblePair(String.class, Number.class);
533 		assertFalse(pair.equals(pairOpposite));
534 		assertFalse(pair.hashCode() == pairOpposite.hashCode());
535 	}
536 
537 	@Test
538 	public void convertPrimitiveArray() {
539 		GenericConversionService conversionService = new DefaultConversionService();
540 		byte[] byteArray = new byte[] { 1, 2, 3 };
541 		Byte[] converted = conversionService.convert(byteArray, Byte[].class);
542 		assertTrue(Arrays.equals(converted, new Byte[] {1, 2, 3}));
543 	}
544 
545 	@Test
546 	public void canConvertIllegalArgumentNullTargetTypeFromClass() {
547 		try {
548 			conversionService.canConvert(String.class, null);
549 			fail("Did not thow IllegalArgumentException");
550 		}
551 		catch (IllegalArgumentException ex) {
552 		}
553 	}
554 
555 	@Test
556 	public void canConvertIllegalArgumentNullTargetTypeFromTypeDescriptor() {
557 		try {
558 			conversionService.canConvert(TypeDescriptor.valueOf(String.class), null);
559 			fail("Did not thow IllegalArgumentException");
560 		}
561 		catch(IllegalArgumentException ex) {
562 		}
563 	}
564 
565 	@Test
566 	@SuppressWarnings({ "rawtypes" })
567 	public void convertHashMapValuesToList() {
568 		GenericConversionService conversionService = new DefaultConversionService();
569 		Map<String, Integer> hashMap = new LinkedHashMap<String, Integer>();
570 		hashMap.put("1", 1);
571 		hashMap.put("2", 2);
572 		List converted = conversionService.convert(hashMap.values(), List.class);
573 		assertEquals(Arrays.asList(1, 2), converted);
574 	}
575 
576 	@Test
577 	public void removeConvertible() {
578 		conversionService.addConverter(new ColorConverter());
579 		assertTrue(conversionService.canConvert(String.class, Color.class));
580 		conversionService.removeConvertible(String.class, Color.class);
581 		assertFalse(conversionService.canConvert(String.class, Color.class));
582 	}
583 
584 	@Test
585 	public void conditionalConverter() {
586 		GenericConversionService conversionService = new GenericConversionService();
587 		MyConditionalConverter converter = new MyConditionalConverter();
588 		conversionService.addConverter(new ColorConverter());
589 		conversionService.addConverter(converter);
590 		assertEquals(Color.BLACK, conversionService.convert("#000000", Color.class));
591 		assertTrue(converter.getMatchAttempts() > 0);
592 	}
593 
594 	@Test
595 	public void conditionalConverterFactory() {
596 		GenericConversionService conversionService = new GenericConversionService();
597 		MyConditionalConverterFactory converter = new MyConditionalConverterFactory();
598 		conversionService.addConverter(new ColorConverter());
599 		conversionService.addConverterFactory(converter);
600 		assertEquals(Color.BLACK, conversionService.convert("#000000", Color.class));
601 		assertTrue(converter.getMatchAttempts() > 0);
602 		assertTrue(converter.getNestedMatchAttempts() > 0);
603 	}
604 
605 	@Test
606 	public void shouldNotSupportNullConvertibleTypesFromNonConditionalGenericConverter() {
607 		GenericConversionService conversionService = new GenericConversionService();
608 		GenericConverter converter = new GenericConverter() {
609 			@Override
610 			public Set<ConvertiblePair> getConvertibleTypes() {
611 				return null;
612 			}
613 			@Override
614 			public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
615 				return null;
616 			}
617 		};
618 		try {
619 			conversionService.addConverter(converter);
620 			fail("Did not throw");
621 		}
622 		catch (IllegalStateException ex) {
623 			assertEquals("Only conditional converters may return null convertible types", ex.getMessage());
624 		}
625 	}
626 
627 	@Test
628 	public void conditionalConversionForAllTypes() {
629 		GenericConversionService conversionService = new GenericConversionService();
630 		MyConditionalGenericConverter converter = new MyConditionalGenericConverter();
631 		conversionService.addConverter(converter);
632 		assertEquals((Integer) 3, conversionService.convert(3, Integer.class));
633 		assertThat(converter.getSourceTypes().size(), greaterThan(2));
634 		Iterator<TypeDescriptor> iterator = converter.getSourceTypes().iterator();
635 		while(iterator.hasNext()) {
636 			assertEquals(Integer.class, iterator.next().getType());
637 		}
638 	}
639 
640 	@Test
641 	public void convertOptimizeArray() {
642 		// SPR-9566
643 		GenericConversionService conversionService = new DefaultConversionService();
644 		byte[] byteArray = new byte[] { 1, 2, 3 };
645 		byte[] converted = conversionService.convert(byteArray, byte[].class);
646 		assertSame(byteArray, converted);
647 	}
648 
649 	@Test
650 	public void convertCannotOptimizeArray() {
651 		GenericConversionService conversionService = new GenericConversionService();
652 		conversionService.addConverter(new Converter<Byte, Byte>() {
653 			@Override
654 			public Byte convert(Byte source) {
655 				return (byte) (source + 1);
656 			}
657 		});
658 		DefaultConversionService.addDefaultConverters(conversionService);
659 		byte[] byteArray = new byte[] { 1, 2, 3 };
660 		byte[] converted = conversionService.convert(byteArray, byte[].class);
661 		assertNotSame(byteArray, converted);
662 		assertTrue(Arrays.equals(new byte[] {2, 3, 4}, converted));
663 	}
664 
665 	@Test
666 	public void testEnumToStringConversion() {
667 		conversionService.addConverter(new EnumToStringConverter(conversionService));
668 		String result = conversionService.convert(MyEnum.A, String.class);
669 		assertEquals("A", result);
670 	}
671 
672 	@Test
673 	public void testSubclassOfEnumToString() throws Exception {
674 		conversionService.addConverter(new EnumToStringConverter(conversionService));
675 		String result = conversionService.convert(EnumWithSubclass.FIRST, String.class);
676 		assertEquals("FIRST", result);
677 	}
678 
679 	@Test
680 	public void testEnumWithInterfaceToStringConversion() {
681 		// SPR-9692
682 		conversionService.addConverter(new EnumToStringConverter(conversionService));
683 		conversionService.addConverter(new MyEnumInterfaceToStringConverter<MyEnum>());
684 		String result = conversionService.convert(MyEnum.A, String.class);
685 		assertEquals("1", result);
686 	}
687 
688 	@Test
689 	public void testStringToEnumWithInterfaceConversion() {
690 		conversionService.addConverterFactory(new StringToEnumConverterFactory());
691 		conversionService.addConverterFactory(new StringToMyEnumInterfaceConverterFactory());
692 		assertEquals(MyEnum.A, conversionService.convert("1", MyEnum.class));
693 	}
694 
695 	@Test
696 	public void testStringToEnumWithBaseInterfaceConversion() {
697 		conversionService.addConverterFactory(new StringToEnumConverterFactory());
698 		conversionService.addConverterFactory(new StringToMyEnumBaseInterfaceConverterFactory());
699 		assertEquals(MyEnum.A, conversionService.convert("base1", MyEnum.class));
700 	}
701 
702 	@Test
703 	public void testStringToEnumSet() throws Exception {
704 		DefaultConversionService.addDefaultConverters(conversionService);
705 		assertEquals(EnumSet.of(MyEnum.A),
706 				conversionService.convert("A", TypeDescriptor.valueOf(String.class), new TypeDescriptor(getClass().getField("enumSet"))));
707 	}
708 
709 	@Test
710 	public void convertNullAnnotatedStringToString() throws Exception {
711 		DefaultConversionService.addDefaultConverters(conversionService);
712 		String source = null;
713 		TypeDescriptor sourceType = new TypeDescriptor(getClass().getField("annotatedString"));
714 		TypeDescriptor targetType = TypeDescriptor.valueOf(String.class);
715 		conversionService.convert(source, sourceType, targetType);
716 	}
717 
718 	@Test
719 	public void multipleCollectionTypesFromSameSourceType() throws Exception {
720 		conversionService.addConverter(new MyStringToRawCollectionConverter());
721 		conversionService.addConverter(new MyStringToGenericCollectionConverter());
722 		conversionService.addConverter(new MyStringToStringCollectionConverter());
723 		conversionService.addConverter(new MyStringToIntegerCollectionConverter());
724 
725 		assertEquals(Collections.singleton("testX"),
726 				conversionService.convert("test", TypeDescriptor.valueOf(String.class), new TypeDescriptor(getClass().getField("stringCollection"))));
727 		assertEquals(Collections.singleton(4),
728 				conversionService.convert("test", TypeDescriptor.valueOf(String.class), new TypeDescriptor(getClass().getField("integerCollection"))));
729 		assertEquals(Collections.singleton(4),
730 				conversionService.convert("test", TypeDescriptor.valueOf(String.class), new TypeDescriptor(getClass().getField("rawCollection"))));
731 		assertEquals(Collections.singleton(4),
732 				conversionService.convert("test", TypeDescriptor.valueOf(String.class), new TypeDescriptor(getClass().getField("genericCollection"))));
733 		assertEquals(Collections.singleton(4),
734 				conversionService.convert("test", TypeDescriptor.valueOf(String.class), new TypeDescriptor(getClass().getField("rawCollection"))));
735 		assertEquals(Collections.singleton("testX"),
736 				conversionService.convert("test", TypeDescriptor.valueOf(String.class), new TypeDescriptor(getClass().getField("stringCollection"))));
737 	}
738 
739 	@Test
740 	public void adaptedCollectionTypesFromSameSourceType() throws Exception {
741 		conversionService.addConverter(new MyStringToStringCollectionConverter());
742 
743 		assertEquals(Collections.singleton("testX"),
744 				conversionService.convert("test", TypeDescriptor.valueOf(String.class), new TypeDescriptor(getClass().getField("stringCollection"))));
745 		assertEquals(Collections.singleton("testX"),
746 				conversionService.convert("test", TypeDescriptor.valueOf(String.class), new TypeDescriptor(getClass().getField("genericCollection"))));
747 		assertEquals(Collections.singleton("testX"),
748 				conversionService.convert("test", TypeDescriptor.valueOf(String.class), new TypeDescriptor(getClass().getField("rawCollection"))));
749 		assertEquals(Collections.singleton("testX"),
750 				conversionService.convert("test", TypeDescriptor.valueOf(String.class), new TypeDescriptor(getClass().getField("genericCollection"))));
751 		assertEquals(Collections.singleton("testX"),
752 				conversionService.convert("test", TypeDescriptor.valueOf(String.class), new TypeDescriptor(getClass().getField("stringCollection"))));
753 		assertEquals(Collections.singleton("testX"),
754 				conversionService.convert("test", TypeDescriptor.valueOf(String.class), new TypeDescriptor(getClass().getField("rawCollection"))));
755 
756 		try {
757 			conversionService.convert("test", TypeDescriptor.valueOf(String.class), new TypeDescriptor(getClass().getField("integerCollection")));
758 			fail("Should have thrown ConverterNotFoundException");
759 		}
760 		catch (ConverterNotFoundException ex) {
761 			// expected
762 		}
763 	}
764 
765 	@Test
766 	public void genericCollectionAsSource() throws Exception {
767 		conversionService.addConverter(new MyStringToGenericCollectionConverter());
768 
769 		assertEquals(Collections.singleton("testX"),
770 				conversionService.convert("test", TypeDescriptor.valueOf(String.class), new TypeDescriptor(getClass().getField("stringCollection"))));
771 		assertEquals(Collections.singleton("testX"),
772 				conversionService.convert("test", TypeDescriptor.valueOf(String.class), new TypeDescriptor(getClass().getField("genericCollection"))));
773 		assertEquals(Collections.singleton("testX"),
774 				conversionService.convert("test", TypeDescriptor.valueOf(String.class), new TypeDescriptor(getClass().getField("rawCollection"))));
775 
776 		// The following is unpleasant but a consequence of the generic collection converter above...
777 		assertEquals(Collections.singleton("testX"),
778 				conversionService.convert("test", TypeDescriptor.valueOf(String.class), new TypeDescriptor(getClass().getField("integerCollection"))));
779 	}
780 
781 	@Test
782 	public void rawCollectionAsSource() throws Exception {
783 		conversionService.addConverter(new MyStringToRawCollectionConverter());
784 
785 		assertEquals(Collections.singleton("testX"),
786 				conversionService.convert("test", TypeDescriptor.valueOf(String.class), new TypeDescriptor(getClass().getField("stringCollection"))));
787 		assertEquals(Collections.singleton("testX"),
788 				conversionService.convert("test", TypeDescriptor.valueOf(String.class), new TypeDescriptor(getClass().getField("genericCollection"))));
789 		assertEquals(Collections.singleton("testX"),
790 				conversionService.convert("test", TypeDescriptor.valueOf(String.class), new TypeDescriptor(getClass().getField("rawCollection"))));
791 
792 		// The following is unpleasant but a consequence of the raw collection converter above...
793 		assertEquals(Collections.singleton("testX"),
794 				conversionService.convert("test", TypeDescriptor.valueOf(String.class), new TypeDescriptor(getClass().getField("integerCollection"))));
795 	}
796 
797 
798 	@Retention(RetentionPolicy.RUNTIME)
799 	public static @interface ExampleAnnotation {
800 	}
801 
802 
803 	private interface MyBaseInterface {
804 	}
805 
806 
807 	private interface MyInterface extends MyBaseInterface {
808 	}
809 
810 
811 	private static class MyInterfaceImplementer implements MyInterface {
812 	}
813 
814 
815 	private static class MyBaseInterfaceToStringConverter implements Converter<MyBaseInterface, String> {
816 
817 		@Override
818 		public String convert(MyBaseInterface source) {
819 			return "RESULT";
820 		}
821 	}
822 
823 
824 	private static class MyStringArrayToResourceArrayConverter implements Converter<String[], Resource[]>	{
825 
826 		@Override
827 		public Resource[] convert(String[] source) {
828 			Resource[] result = new Resource[source.length];
829 			for (int i = 0; i < source.length; i++) {
830 				result[i] = new DescriptiveResource(source[i].substring(1));
831 			}
832 			return result;
833 		}
834 	}
835 
836 
837 	private static class MyStringArrayToIntegerArrayConverter implements Converter<String[], Integer[]>	{
838 
839 		@Override
840 		public Integer[] convert(String[] source) {
841 			Integer[] result = new Integer[source.length];
842 			for (int i = 0; i < source.length; i++) {
843 				result[i] = Integer.parseInt(source[i].substring(1));
844 			}
845 			return result;
846 		}
847 	}
848 
849 
850 	private static class MyStringToIntegerArrayConverter implements Converter<String, Integer[]>	{
851 
852 		@Override
853 		public Integer[] convert(String source) {
854 			String[] srcArray = StringUtils.commaDelimitedListToStringArray(source);
855 			Integer[] result = new Integer[srcArray.length];
856 			for (int i = 0; i < srcArray.length; i++) {
857 				result[i] = Integer.parseInt(srcArray[i].substring(1));
858 			}
859 			return result;
860 		}
861 	}
862 
863 
864 	public static class WithCopyConstructor {
865 
866 		public WithCopyConstructor() {
867 		}
868 
869 		public WithCopyConstructor(WithCopyConstructor value) {
870 		}
871 	}
872 	private static class MyConditionalConverter implements Converter<String, Color>, ConditionalConverter {
873 
874 		private int matchAttempts = 0;
875 
876 		@Override
877 		public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
878 			matchAttempts++;
879 			return false;
880 		}
881 
882 		@Override
883 		public Color convert(String source) {
884 			throw new IllegalStateException();
885 		}
886 
887 		public int getMatchAttempts() {
888 			return matchAttempts;
889 		}
890 	}
891 
892 
893 	private static class MyConditionalGenericConverter implements GenericConverter, ConditionalConverter {
894 
895 		private List<TypeDescriptor> sourceTypes = new ArrayList<TypeDescriptor>();
896 
897 		@Override
898 		public Set<ConvertiblePair> getConvertibleTypes() {
899 			return null;
900 		}
901 
902 		@Override
903 		public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
904 			sourceTypes.add(sourceType);
905 			return false;
906 		}
907 
908 		@Override
909 		public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
910 			return null;
911 		}
912 
913 		public List<TypeDescriptor> getSourceTypes() {
914 			return sourceTypes;
915 		}
916 	}
917 
918 
919 	private static class MyConditionalConverterFactory implements ConverterFactory<String, Color>, ConditionalConverter {
920 
921 		private MyConditionalConverter converter = new MyConditionalConverter();
922 
923 		private int matchAttempts = 0;
924 
925 		@Override
926 		public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
927 			matchAttempts++;
928 			return true;
929 		}
930 
931 		@Override
932 		@SuppressWarnings("unchecked")
933 		public <T extends Color> Converter<String, T> getConverter(Class<T> targetType) {
934 			return (Converter<String, T>) converter;
935 		}
936 
937 		public int getMatchAttempts() {
938 			return matchAttempts;
939 		}
940 
941 		public int getNestedMatchAttempts() {
942 			return converter.getMatchAttempts();
943 		}
944 	}
945 
946 
947 	interface MyEnumBaseInterface {
948 
949 		String getBaseCode();
950 	}
951 
952 
953 	interface MyEnumInterface extends MyEnumBaseInterface {
954 
955 		String getCode();
956 	}
957 
958 
959 	public static enum MyEnum implements MyEnumInterface {
960 
961 		A("1"),
962 		B("2"),
963 		C("3");
964 
965 		private String code;
966 
967 		MyEnum(String code) {
968 			this.code = code;
969 		}
970 
971 		@Override
972 		public String getCode() {
973 			return code;
974 		}
975 
976 		@Override
977 		public String getBaseCode() {
978 			return "base" + code;
979 		}
980 	}
981 
982 
983 	public enum EnumWithSubclass {
984 
985 		FIRST {
986 			@Override
987 			public String toString() {
988 				return "1st";
989 			}
990 		}
991 	}
992 
993 
994 	public static class MyStringToRawCollectionConverter implements Converter<String, Collection> {
995 
996 		@Override
997 		public Collection convert(String source) {
998 			return Collections.singleton(source + "X");
999 		}
1000 	}
1001 
1002 
1003 	public static class MyStringToGenericCollectionConverter implements Converter<String, Collection<?>> {
1004 
1005 		@Override
1006 		public Collection<?> convert(String source) {
1007 			return Collections.singleton(source + "X");
1008 		}
1009 	}
1010 
1011 
1012 	private static class MyEnumInterfaceToStringConverter<T extends MyEnumInterface> implements Converter<T, String> {
1013 
1014 		@Override
1015 		public String convert(T source) {
1016 			return source.getCode();
1017 		}
1018 	}
1019 
1020 
1021 	private static class StringToMyEnumInterfaceConverterFactory implements ConverterFactory<String, MyEnumInterface> {
1022 
1023 		@SuppressWarnings("unchecked")
1024 		public <T extends MyEnumInterface> Converter<String, T> getConverter(Class<T> targetType) {
1025 			return new StringToMyEnumInterfaceConverter(targetType);
1026 		}
1027 
1028 		private static class StringToMyEnumInterfaceConverter<T extends Enum<?> & MyEnumInterface> implements Converter<String, T> {
1029 			private final Class<T> enumType;
1030 
1031 			public StringToMyEnumInterfaceConverter(Class<T> enumType) {
1032 				this.enumType = enumType;
1033 			}
1034 
1035 			public T convert(String source) {
1036 				for (T value : enumType.getEnumConstants()) {
1037 					if (value.getCode().equals(source)) {
1038 						return value;
1039 					}
1040 				}
1041 				return null;
1042 			}
1043 		}
1044 	}
1045 
1046 
1047 	private static class StringToMyEnumBaseInterfaceConverterFactory implements ConverterFactory<String, MyEnumBaseInterface> {
1048 
1049 		@SuppressWarnings("unchecked")
1050 		public <T extends MyEnumBaseInterface> Converter<String, T> getConverter(Class<T> targetType) {
1051 			return new StringToMyEnumBaseInterfaceConverter(targetType);
1052 		}
1053 
1054 		private static class StringToMyEnumBaseInterfaceConverter<T extends Enum<?> & MyEnumBaseInterface> implements Converter<String, T> {
1055 
1056 			private final Class<T> enumType;
1057 
1058 			public StringToMyEnumBaseInterfaceConverter(Class<T> enumType) {
1059 				this.enumType = enumType;
1060 			}
1061 
1062 			public T convert(String source) {
1063 				for (T value : enumType.getEnumConstants()) {
1064 					if (value.getBaseCode().equals(source)) {
1065 						return value;
1066 					}
1067 				}
1068 				return null;
1069 			}
1070 		}
1071 	}
1072 
1073 
1074 	public static class MyStringToStringCollectionConverter implements Converter<String, Collection<String>> {
1075 
1076 		@Override
1077 		public Collection<String> convert(String source) {
1078 			return Collections.singleton(source + "X");
1079 		}
1080 	}
1081 
1082 
1083 	public static class MyStringToIntegerCollectionConverter implements Converter<String, Collection<Integer>> {
1084 
1085 		@Override
1086 		public Collection<Integer> convert(String source) {
1087 			return Collections.singleton(source.length());
1088 		}
1089 	}
1090 
1091 
1092 	@ExampleAnnotation
1093 	public String annotatedString;
1094 
1095 	public List<Integer> list;
1096 
1097 	public Map<String, Integer> map;
1098 
1099 	public Map<String, ?> wildcardMap;
1100 
1101 	public EnumSet<MyEnum> enumSet;
1102 
1103 	public Collection rawCollection;
1104 
1105 	public Collection<?> genericCollection;
1106 
1107 	public Collection<String> stringCollection;
1108 
1109 	public Collection<Integer> integerCollection;
1110 
1111 }