001/** 002 * The MIT License (MIT) 003 * 004 * Copyright (c) 2017 tools4j.org (Marco Terzer) 005 * 006 * Permission is hereby granted, free of charge, to any person obtaining a copy 007 * of this software and associated documentation files (the "Software"), to deal 008 * in the Software without restriction, including without limitation the rights 009 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 010 * copies of the Software, and to permit persons to whom the Software is 011 * furnished to do so, subject to the following conditions: 012 * 013 * The above copyright notice and this permission notice shall be included in all 014 * copies or substantial portions of the Software. 015 * 016 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 017 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 018 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 019 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 020 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 021 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 022 * SOFTWARE. 023 */ 024package org.tools4j.spockito; 025 026import java.lang.reflect.Type; 027import java.math.BigDecimal; 028import java.math.BigInteger; 029import java.time.*; 030import java.util.*; 031import java.util.function.BiPredicate; 032import java.util.function.Function; 033 034/** 035 * Default value converter implementation for Spockito. All supported conversions are defined as a constant in 036 * {@link Converters}. 037 * <p> 038 * Constom value converter implementations may want to extend this class and add additional converters by overriding 039 * {@link #initConverterFunctions()}. Converters for single types can be registered via 040 * {@link #registerConverterFunction(Class, Function)} and the more generic 041 * {@link #registerConverter(BiPredicate, ValueConverter)}. 042 */ 043public class SpockitoValueConverter implements ValueConverter { 044 045 public static final SpockitoValueConverter DEFAULT_INSTANCE = new SpockitoValueConverter(); 046 047 private final Map<Class<?>, Function<? super String, ?>> convertersByType = new HashMap<>(); 048 private final List<Map.Entry<BiPredicate<Class<?>, ? super Type>, ValueConverter>> convertersByPredicate = new ArrayList<>(); 049 050 public SpockitoValueConverter() { 051 initConverterFunctions(); 052 } 053 054 @Override 055 public <T> T convert(final Class<T> type, final Type genericType, final String value) { 056 if (value == null || "null".equals(value)) { 057 return null; 058 } 059 ValueConverter converter = converterByTypeOrNull(type);; 060 if (converter == null) { 061 converter = converterByPredicateOrNull(type, genericType); 062 } 063 if (converter == null) { 064 throw new IllegalArgumentException("No value converter is defined for type " + typeName(type, genericType)); 065 } 066 try { 067 return converter.convert(type, genericType, value); 068 } catch (final Exception e) { 069 throw new IllegalArgumentException("Conversion to " + typeName(type, genericType) + " failed for value: " + value, e); 070 } 071 } 072 073 /** 074 * Registers converters for individual types via calls to 075 * {@link #registerConverterFunction(Class, Function)} and the more generic 076 * {@link #registerConverter(BiPredicate, ValueConverter)}. All converters registered by this method are defined by 077 * {@link Converters}. 078 */ 079 protected void initConverterFunctions() { 080 registerConverterFunction(Object.class, Converters.OBJECT_CONVERTER); 081 registerConverterFunction(String.class, Converters.STRING_CONVERTER); 082 registerConverterFunction(long.class, Converters.LONG_CONVERTER); 083 registerConverterFunction(int.class, Converters.INTEGER_CONVERTER); 084 registerConverterFunction(short.class, Converters.SHORT_CONVERTER); 085 registerConverterFunction(byte.class, Converters.BYTE_CONVERTER); 086 registerConverterFunction(double.class, Converters.DOUBLE_CONVERTER); 087 registerConverterFunction(float.class, Converters.FLOAT_CONVERTER); 088 registerConverterFunction(char.class, Converters.CHAR_CONVERTER); 089 registerConverterFunction(boolean.class, Converters.BOOLEAN_CONVERTER); 090 registerConverterFunction(BigInteger.class, Converters.BIG_INTEGER_CONVERTER); 091 registerConverterFunction(BigDecimal.class, Converters.BIG_DECIMAL_CONVERTER); 092 registerConverterFunction(LocalDate.class, Converters.LOCAL_DATE_CONVERTER); 093 registerConverterFunction(LocalTime.class, Converters.LOCAL_TIME_CONVERTER); 094 registerConverterFunction(LocalDateTime.class, Converters.LOCAL_DATE_TIME_CONVERTER); 095 registerConverterFunction(ZonedDateTime.class, Converters.ZONED_DATE_TIME_CONVERTER); 096 registerConverterFunction(OffsetDateTime.class, Converters.OFFSET_DATE_TIME_CONVERTER); 097 registerConverterFunction(Instant.class, Converters.INSTANT_CONVERTER); 098 registerConverterFunction(Date.class, Converters.DATE_CONVERTER); 099 registerConverterFunction(java.sql.Date.class, Converters.SQL_DATE_CONVERTER); 100 registerConverterFunction(java.sql.Time.class, Converters.SQL_TIME_CONVERTER); 101 registerConverterFunction(java.sql.Timestamp.class, Converters.SQL_TIMESTAMP_CONVERTER); 102 registerConverterFunction(StringBuilder.class, Converters.STRING_BUILDER_CONVERTER); 103 registerConverterFunction(StringBuffer.class, Converters.STRING_BUFFER_CONVERTER); 104 105 registerConverter((t, g) -> t.isEnum(), Converters.ENUM_CONVERTER); 106 registerConverter((t, g) -> t == Optional.class, new Converters.OptionalConverter(this)); 107 registerConverter((t, g) -> t.isArray(), new Converters.ArrayConverter(this)); 108 registerConverter((t, g) -> Class.class.isAssignableFrom(t), Converters.CLASS_CONVERTER); 109 registerConverter((t, g) -> Collection.class.isAssignableFrom(t), new Converters.CollectionConverter(this)); 110 registerConverter((t, g) -> Map.class.isAssignableFrom(t), new Converters.MapConverter(this)); 111 registerConverter((t, g) -> Converters.BeanConverter.isBeanClass(t), new Converters.BeanConverter(this)); 112 } 113 114 /** 115 * Register a conversion fuction for the specified type. The function is applied for types exactly matching the 116 * given type only (but not to supertypes of this type). 117 * 118 * @param type the target type 119 * @param converter a function converting from string to target type 120 * @param <T> the targe parameter 121 */ 122 protected <T> void registerConverterFunction(final Class<T> type, final Function<? super String, ? extends T> converter) { 123 convertersByType.put(type, converter); 124 if (type.isPrimitive()) { 125 convertersByType.put(Primitives.boxingTypeFor(type), converter); 126 } 127 } 128 129 /** 130 * Register a value converter for some subgroup of types selected for by the given predicate. The two arguments 131 * passed to the applicable predicate are type and generic type of the target value. The predicate returns true if 132 * the specified value converter is applicable for such a value. 133 * <p> 134 * Value converters registered via this method will be queried in registration order. If two converters are 135 * applicable to some value, the first whose predicate returns true will be used. 136 * 137 * @param applicable a predicate returning true if the specified converter can be applied for a value of the 138 * type/generic-type passed to the predicate, and false otherwise 139 * @param converter a value converter from string to target type 140 */ 141 protected void registerConverter(final BiPredicate<Class<?>, ? super Type> applicable, final ValueConverter converter) { 142 convertersByPredicate.add(new AbstractMap.SimpleImmutableEntry<>(applicable, converter)); 143 } 144 145 private ValueConverter converterByTypeOrNull(final Class<?> type) { 146 final Function<? super String, ?> function = convertersByType.get(type); 147 if (function != null) { 148 final Function<? super String, ?> f = function; 149 return new ValueConverter() { 150 @Override 151 public <T> T convert(final Class<T> type, final Type genericType, final String value) { 152 final Class<T> boxingType = type.isPrimitive() ? Primitives.boxingTypeFor(type) : type; 153 return boxingType.cast(f.apply(value)); 154 } 155 }; 156 } 157 return null; 158 } 159 160 private ValueConverter converterByPredicateOrNull(final Class<?> type, final Type genericType) { 161 return convertersByPredicate.stream() 162 .filter(e -> e.getKey().test(type, genericType)) 163 .findFirst() 164 .map(e -> e == null ? null : e.getValue()) 165 .orElse(null); 166 } 167 168 private static String typeName(final Class<?> type, final Type genericType) { 169 return type == genericType || genericType == null ? type.getName() : genericType.toString(); 170 } 171 172}