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.*; 027import java.math.BigDecimal; 028import java.math.BigInteger; 029import java.sql.Time; 030import java.sql.Timestamp; 031import java.time.*; 032import java.util.*; 033import java.util.concurrent.*; 034import java.util.function.Function; 035 036/** 037 * Contains conversion functions and value converters used by {@link SpockitoValueConverter}. 038 */ 039public final class Converters { 040 041 public static final Function<? super String, Object> OBJECT_CONVERTER = Function.identity(); 042 public static final Function<? super String, Long> LONG_CONVERTER = Long::valueOf; 043 public static final Function<? super String, Integer> INTEGER_CONVERTER = Integer::valueOf; 044 public static final Function<? super String, Short> SHORT_CONVERTER = Short::valueOf; 045 public static final Function<? super String, Byte> BYTE_CONVERTER = Byte::valueOf; 046 public static final Function<? super String, Double> DOUBLE_CONVERTER = Double::valueOf; 047 public static final Function<? super String, Float> FLOAT_CONVERTER = Float::valueOf; 048 public static final Function<? super String, Boolean> BOOLEAN_CONVERTER = Boolean::valueOf; 049 050 public static final Function<? super String, BigInteger> BIG_INTEGER_CONVERTER = BigInteger::new; 051 public static final Function<? super String, BigDecimal> BIG_DECIMAL_CONVERTER = BigDecimal::new; 052 public static final Function<? super String, LocalDate> LOCAL_DATE_CONVERTER = LocalDate::parse; 053 public static final Function<? super String, LocalTime> LOCAL_TIME_CONVERTER = LocalTime::parse; 054 public static final Function<? super String, LocalDateTime> LOCAL_DATE_TIME_CONVERTER = LocalDateTime::parse; 055 public static final Function<? super String, ZonedDateTime> ZONED_DATE_TIME_CONVERTER = ZonedDateTime::parse; 056 public static final Function<? super String, OffsetDateTime> OFFSET_DATE_TIME_CONVERTER = OffsetDateTime::parse; 057 public static final Function<? super String, Instant> INSTANT_CONVERTER = Instant::parse; 058 public static final Function<? super String, Date> DATE_CONVERTER = s -> Date.from(Timestamp.valueOf(s).toInstant()); 059 public static final Function<? super String, java.sql.Date> SQL_DATE_CONVERTER = java.sql.Date::valueOf; 060 public static final Function<? super String, Time> SQL_TIME_CONVERTER = Time::valueOf; 061 public static final Function<? super String, Timestamp> SQL_TIMESTAMP_CONVERTER = Timestamp::valueOf; 062 public static final Function<? super String, String> STRING_CONVERTER = s -> Strings.removeStartAndEndChars(s, '\'', '\''); 063 public static final Function<? super String, StringBuilder> STRING_BUILDER_CONVERTER = s -> new StringBuilder(STRING_CONVERTER.apply(s)); 064 public static final Function<? super String, StringBuffer> STRING_BUFFER_CONVERTER = s -> new StringBuffer(STRING_CONVERTER.apply(s)); 065 public static final Function<? super String, Character> CHAR_CONVERTER = s -> { 066 if (s.length() == 1) { 067 return s.charAt(0); 068 } 069 if (s.length() == 3 && s.charAt(0) == '\'' && s.charAt(2) == '\'') { 070 return s.charAt(1); 071 } 072 throw new IllegalArgumentException("Cannot convert string to char: " + s); 073 }; 074 public static final ValueConverter CLASS_CONVERTER = new ValueConverter() { 075 @Override 076 public <T> T convert(final Class<T> type, final Type genericType, final String value) { 077 try { 078 final Class<?> clazz = Class.forName(value); 079 return type.cast(clazz); 080 } catch (final Exception e) { 081 throw new IllegalArgumentException("Cannot convert string to " + type.getName() + ": " + value, e); 082 } 083 } 084 }; 085 public static final ValueConverter ENUM_CONVERTER = new ValueConverter() { 086 @Override 087 public <T> T convert(final Class<T> type, final Type genericType, final String value) { 088 final Enum<?> e = Enum.valueOf(type.asSubclass(Enum.class), value); 089 return type.cast(e); 090 } 091 }; 092 093 /** 094 * Value converter for target type {@link Optional}. 095 */ 096 public static class OptionalConverter implements ValueConverter { 097 private final ValueConverter elementConverter; 098 099 public OptionalConverter(final ValueConverter elementConverter) { 100 this.elementConverter = Objects.requireNonNull(elementConverter); 101 } 102 103 @Override 104 public <T> T convert(final Class<T> type, final Type genericType, final String value) { 105 if (!Optional.class.equals(type)) { 106 throw new IllegalArgumentException("Type must be Optional: " + type.getName()); 107 } 108 final String trimmed = value.trim(); 109 final Object elementValue; 110 if ("empty".equals(trimmed) || trimmed.isEmpty()) { 111 elementValue = null; 112 } else { 113 final ActualType elementType = actualTypeForTypeParam(genericType, 0, 1); 114 elementValue = elementConverter.convert(elementType.rawType, elementType.genericType, trimmed); 115 } 116 return type.cast(Optional.ofNullable(elementValue)); 117 } 118 119 } 120 121 /** 122 * Value converter for target type {@link Collection} or sub-interfaces and implementations of it. 123 */ 124 public static class CollectionConverter implements ValueConverter { 125 private final ValueConverter elementConverter; 126 public CollectionConverter(final ValueConverter elementConverter) { 127 this.elementConverter = Objects.requireNonNull(elementConverter); 128 } 129 130 @Override 131 public <T> T convert(final Class<T> type, final Type genericType, final String value) { 132 if (!Collection.class.isAssignableFrom(type)) { 133 throw new IllegalArgumentException("Type must be a collection: " + type.getName()); 134 } 135 final ActualType elementType = actualTypeForTypeParam(genericType, 0, 1); 136 final List<?> list = toList(elementType, value); 137 if (type.isInstance(list)) { 138 return type.cast(list); 139 } 140 if (type.isAssignableFrom(ArrayList.class)) { 141 return type.cast(new ArrayList<>(list)); 142 } 143 if (type.isAssignableFrom(Vector.class)) { 144 return type.cast(new Vector<>(list)); 145 } 146 if (type.isAssignableFrom(LinkedList.class)) { 147 return type.cast(new LinkedList<>(list)); 148 } 149 if (type.isAssignableFrom(ArrayDeque.class)) { 150 return type.cast(new ArrayDeque<>(list)); 151 } 152 if (type.isAssignableFrom(LinkedHashSet.class)) { 153 return type.cast(new LinkedHashSet<>(list)); 154 } 155 if (type.isAssignableFrom(TreeSet.class)) { 156 return type.cast(new TreeSet<>(list)); 157 } 158 if (type.isAssignableFrom(HashSet.class)) { 159 return type.cast(new HashSet<>(list)); 160 } 161 if (type.isAssignableFrom(EnumSet.class)) { 162 final EnumSet<?> enumSet = enumSet(elementType.rawType.asSubclass(Enum.class), list); 163 return type.cast(enumSet); 164 } 165 if (type.isAssignableFrom(ConcurrentLinkedQueue.class)) { 166 return type.cast(new ConcurrentLinkedQueue<>(list)); 167 } 168 if (type.isAssignableFrom(ConcurrentLinkedDeque.class)) { 169 return type.cast(new ConcurrentLinkedDeque<>(list)); 170 } 171 if (type.isAssignableFrom(ConcurrentSkipListSet.class)) { 172 return type.cast(new ConcurrentSkipListSet<>(list)); 173 } 174 //unsupported collection type 175 throw new IllegalArgumentException("Cannot convert value to " + type.getName() + ": " + value); 176 } 177 178 private List<Object> toList(final ActualType elementType, final String value) { 179 final String plainValue = Strings.removeStartAndEndChars(value, '[', ']'); 180 if (plainValue.trim().isEmpty()) { 181 return Collections.emptyList(); 182 } 183 final String[] parts = parseListValues(plainValue); 184 final List<Object> list = new ArrayList<>(parts.length); 185 for (int i = 0; i < parts.length; i++) { 186 list.add(elementConverter.convert(elementType.rawType, elementType.genericType, parts[i].trim())); 187 } 188 return list; 189 } 190 191 private static <E extends Enum<E>> EnumSet<E> enumSet(final Class<E> enumType, final List<?> list) { 192 final EnumSet<E> set = EnumSet.noneOf(enumType); 193 list.forEach(v -> set.add(enumType.cast(v))); 194 return set; 195 } 196 } 197 198 /** 199 * Value converter for array target types including primitive arrays. 200 */ 201 public static class ArrayConverter implements ValueConverter { 202 private final ValueConverter elementConverter; 203 private final CollectionConverter collectionConverter; 204 public ArrayConverter(final ValueConverter elementConverter) { 205 this.elementConverter = Objects.requireNonNull(elementConverter); 206 this.collectionConverter = new CollectionConverter(elementConverter); 207 } 208 209 @Override 210 public <T> T convert(final Class<T> type, final Type genericType, final String value) { 211 if (!type.isArray()) { 212 throw new IllegalArgumentException("Type must be an array: " + type.getName()); 213 } 214 final Class<?> componentType = type.getComponentType(); 215 final Type genericComponentType; 216 if (genericType instanceof GenericArrayType) { 217 genericComponentType = ((GenericArrayType)genericType).getGenericComponentType(); 218 } else { 219 genericComponentType = componentType; 220 } 221 final Type genericListType = genericListType(genericComponentType); 222 final List<?> list = collectionConverter.convert(List.class, genericListType, value); 223 final Object array = Array.newInstance(componentType, list.size()); 224 for (int i = 0; i < list.size(); i++) { 225 final Object val = list.get(i); 226 Array.set(array, i, val); 227 } 228 return type.cast(array); 229 } 230 } 231 232 /** 233 * Value converter for target type {@link Map} or sub-interfaces and implementations of it. 234 */ 235 public static class MapConverter implements ValueConverter { 236 private final ValueConverter elementConverter; 237 public MapConverter(final ValueConverter elementConverter) { 238 this.elementConverter = Objects.requireNonNull(elementConverter); 239 } 240 241 @Override 242 public <T> T convert(final Class<T> type, final Type genericType, final String value) { 243 final ActualType keyType = actualTypeForTypeParam(genericType, 0, 2); 244 final ActualType valueType = actualTypeForTypeParam(genericType, 1, 2); 245 final Map<?,?> map = toMap(keyType, valueType, value); 246 if (type.isInstance(map)) { 247 return type.cast(map); 248 } 249 if (type.isAssignableFrom(LinkedHashMap.class)) { 250 return type.cast(new LinkedHashMap<>(map)); 251 } 252 if (type.isAssignableFrom(TreeMap.class)) { 253 return type.cast(new TreeMap<>(map)); 254 } 255 if (type.isAssignableFrom(HashMap.class)) { 256 return type.cast(new HashMap<>(map)); 257 } 258 if (type.isAssignableFrom(Hashtable.class)) { 259 return type.cast(new Hashtable<>(map)); 260 } 261 if (type.isAssignableFrom(EnumMap.class)) { 262 final EnumMap<?,?> enumMap = enumMap(keyType.rawType.asSubclass(Enum.class), map); 263 return type.cast(enumMap); 264 } 265 if (type.isAssignableFrom(Properties.class)) { 266 final Properties props = new Properties(); 267 props.putAll(map); 268 return type.cast(props); 269 } 270 if (type.isAssignableFrom(ConcurrentHashMap.class)) { 271 return type.cast(new ConcurrentHashMap<>(map)); 272 } 273 if (type.isAssignableFrom(ConcurrentSkipListMap.class)) { 274 return type.cast(new ConcurrentSkipListMap<>(map)); 275 } 276 //unsupported map type 277 throw new IllegalArgumentException("Cannot convert value to " + type.getName() + ": " + value); 278 } 279 280 private Map<Object, Object> toMap(final ActualType keyType, final ActualType valueType, final String value) { 281 final String plainValue = Strings.removeStartAndEndChars(value, '{', '}'); 282 if (plainValue.trim().isEmpty()) { 283 return Collections.emptyMap(); 284 } 285 final String[] parts = parseListValues(plainValue); 286 final Map<Object, Object> map = new LinkedHashMap<>(); 287 for (int i = 0; i < parts.length; i++) { 288 final String[] keyAndValue = parseKeyValue(parts[i].trim()); 289 if (keyAndValue.length != 2) { 290 throw new IllegalArgumentException("Invalid map key/value pair: " + parts[i]); 291 } 292 try { 293 final Object key = elementConverter.convert(keyType.rawType, keyType.genericType, keyAndValue[0].trim()); 294 final Object val = elementConverter.convert(valueType.rawType, valueType.genericType, keyAndValue[1].trim()); 295 map.put(key, val); 296 } catch (final Exception e) { 297 throw new IllegalArgumentException("Conversion to map key/value failed: " + parts[i], e); 298 } 299 } 300 return map; 301 } 302 303 private static <K extends Enum<K>, V> EnumMap<K, V> enumMap(final Class<K> enumType, final Map<?,V> map) { 304 final EnumMap<K,V> enumMap = new EnumMap(enumType); 305 map.forEach((k,v) -> enumMap.put(enumType.cast(k), v)); 306 return enumMap; 307 } 308 } 309 310 /** 311 * Value converter for Java beans as target type. The beans either have getters and setters to access the fields 312 * or otherwise fields are accessed directly. 313 */ 314 public static class BeanConverter implements ValueConverter { 315 316 private final ValueConverter elementConverter; 317 public BeanConverter(final ValueConverter elementConverter) { 318 this.elementConverter = Objects.requireNonNull(elementConverter); 319 } 320 321 @Override 322 public <T> T convert(final Class<T> type, final Type genericType, final String value) { 323 final T instance = newInstance(type, value); 324 final Map<String, Accessor> accessorByName = new LinkedHashMap<>(); 325 if (hasAccessibleSetters(type)) { 326 inspectSetters(type, accessorByName); 327 } else { 328 inspectFields(type, accessorByName); 329 } 330 if (accessorByName.isEmpty()) { 331 throw new IllegalArgumentException(type.getName() + " is not a bean class, no accessible setters or fields found"); 332 } 333 injectValues(instance, accessorByName, value); 334 return instance; 335 } 336 337 private <T> T newInstance(final Class<T> type, final String value) { 338 try { 339 final Constructor<T> constructor = type.getDeclaredConstructor(); 340 constructor.setAccessible(true); 341 return constructor.newInstance(); 342 } catch (final Exception e) { 343 throw new IllegalArgumentException("Could not instantiate bean " + type.getName(), e); 344 } 345 } 346 347 private void injectValues(final Object instance, final Map<String, Accessor> accessorByName, final String value) { 348 final String plainValue = Strings.removeStartAndEndChars(value, '{', '}'); 349 final String[] parts = parseListValues(plainValue); 350 final Map<String, String> valueByName = new LinkedHashMap<>(); 351 for (int i = 0; i < parts.length; i++) { 352 final String[] nameAndValue = parseKeyValue(parts[i].trim()); 353 if (nameAndValue.length != 2) { 354 throw new IllegalArgumentException("Invalid name/value pair: " + parts[i]); 355 } 356 final String name = normalizeFieldName(nameAndValue[0].trim()); 357 final String val = nameAndValue[1].trim(); 358 valueByName.put(name, val); 359 } 360 for (final Map.Entry<String, Accessor> e : accessorByName.entrySet()) { 361 final String val = valueByName.get(e.getKey()); 362 if (val == null) { 363 throw new IllegalArgumentException("No value found for bean property " + instance.getClass().getName() + "." + e.getKey()); 364 } 365 try { 366 final Class<?> type = e.getValue().type(); 367 final Type genericType = e.getValue().genericType(); 368 final Object convertedVal = elementConverter.convert(type, genericType, val); 369 e.getValue().set(instance, convertedVal); 370 } catch (final Exception ex) { 371 throw new IllegalArgumentException("Could not set bean property " + instance.getClass().getName() + "." + e.getKey() + 372 " to value: " + val, ex); 373 } 374 } 375 } 376 377 public static boolean isBeanClass(final Class<?> clazz) { 378 return isInstantiatable(clazz) && (hasAccessibleFields(clazz) || hasAccessibleSetters(clazz)); 379 } 380 381 private static boolean isInstantiatable(final Class<?> clazz) { 382 return !clazz.isInterface() && !Modifier.isAbstract(clazz.getModifiers()) && hasDefaultConstructor(clazz); 383 } 384 385 private static boolean hasDefaultConstructor(final Class<?> clazz) { 386 try { 387 clazz.getDeclaredConstructor(); 388 return true; 389 } catch (final Exception e) { 390 return false; 391 } 392 } 393 394 private interface Accessor { 395 void set(Object instance, Object value) throws Exception; 396 default Class<?> type() { 397 return Object.class; 398 } 399 default Type genericType() { 400 return Object.class; 401 } 402 static Accessor forType(final Class<?> type, final Type genericType, final Accessor accessor) { 403 return new Accessor() { 404 @Override 405 public void set(final Object instance, final Object value) throws Exception { 406 accessor.set(instance, value); 407 } 408 409 @Override 410 public Class<?> type() { 411 return type; 412 } 413 414 @Override 415 public Type genericType() { 416 return genericType; 417 } 418 }; 419 } 420 } 421 422 private static boolean hasAccessibleSetters(final Class<?> clazz) { 423 return !inspectSetters(clazz, new HashMap<>()).isEmpty(); 424 } 425 private static final Map<String, Accessor> inspectSetters(final Class<?> clazz, final Map<String, Accessor> accessorByName) { 426 if (clazz == null || Object.class.equals(clazz)) { 427 return accessorByName; 428 } 429 for (final Method method : clazz.getDeclaredMethods()) { 430 final int mod = method.getModifiers(); 431 final String name = method.getName(); 432 if (name.length() > 3 && name.startsWith("set") && method.getParameterCount() == 1 && 433 !method.isSynthetic() && !Modifier.isStatic(mod) && !Modifier.isPrivate(mod) && !Modifier.isProtected(mod)) { 434 final String propertyName = normalizeFieldName(name.substring(3)); 435 accessorByName.put(propertyName, Accessor.forType(method.getParameterTypes()[0], method.getGenericParameterTypes()[0], method::invoke)); 436 } 437 } 438 return inspectSetters(clazz.getSuperclass(), accessorByName); 439 } 440 441 private static boolean hasAccessibleFields(final Class<?> clazz) { 442 return !inspectFields(clazz, new HashMap<>()).isEmpty(); 443 } 444 private static final Map<String, Accessor> inspectFields(final Class<?> clazz, final Map<String, Accessor> accessorByName) { 445 if (clazz == null || Object.class.equals(clazz)) { 446 return accessorByName; 447 } 448 for (final Field field : clazz.getDeclaredFields()) { 449 final int mod = field.getModifiers(); 450 if (!field.isSynthetic() && !Modifier.isFinal(mod) && !Modifier.isStatic(mod) && !Modifier.isPrivate(mod) && !Modifier.isProtected(mod)) { 451 accessorByName.put(field.getName(), Accessor.forType(field.getType(), field.getGenericType(), field::set)); 452 } 453 } 454 return inspectFields(clazz.getSuperclass(), accessorByName); 455 } 456 } 457 458 private static String normalizeFieldName(final String name) { 459 if (name.length() > 0 && Character.isUpperCase(name.charAt(0))) { 460 return Character.toLowerCase(name.charAt(0)) + name.substring(1); 461 } 462 return name; 463 } 464 465 private static final String[] parseKeyValue(final String pair) { 466 return Strings.split(pair, Strings.UNESCAPED_EQUAL, Strings.UNESCAPED_COLON); 467 } 468 469 private static final String[] parseListValues(final String pair) { 470 return Strings.split(pair, Strings.UNESCAPED_COMMA, Strings.UNESCAPED_SEMICOLON); 471 } 472 473 private static class ActualType { 474 final Class<?> rawType; 475 final Type genericType; 476 public ActualType(final Class<?> rawType, final Type genericType) { 477 this.rawType = Objects.requireNonNull(rawType); 478 this.genericType = Objects.requireNonNull(genericType); 479 } 480 } 481 private static ActualType actualTypeForTypeParam(final Type type, final int paramIndex, final int paramCount) { 482 if (type instanceof ParameterizedType) { 483 final Type[] actualTypeArgs = ((ParameterizedType) type).getActualTypeArguments(); 484 if (actualTypeArgs.length == paramCount) { 485 Type actualType = actualTypeArgs[paramIndex]; 486 if (actualType instanceof WildcardType) { 487 final Type[] bounds = ((WildcardType) actualType).getUpperBounds(); 488 if (bounds.length == 1) { 489 actualType = bounds[0]; 490 } 491 } 492 if (actualType instanceof Class) { 493 return new ActualType((Class<?>) actualType, actualType); 494 } 495 if (actualType instanceof ParameterizedType) { 496 final ParameterizedType parameterizedType = (ParameterizedType)actualType; 497 if (parameterizedType.getRawType() instanceof Class) { 498 return new ActualType((Class<?>)parameterizedType.getRawType(), parameterizedType); 499 } 500 } 501 } 502 } 503 if (Properties.class.equals(type) && paramCount == 2) { 504 return new ActualType(String.class, String.class); 505 } 506 throw new IllegalArgumentException("Could not derive actual generic type [" + paramIndex + "] for " + type); 507 } 508 509 private static Type genericListType(final Type listElementType) { 510 return new ParameterizedType() { 511 @Override 512 public Type[] getActualTypeArguments() { 513 return new Type[]{listElementType}; 514 } 515 @Override 516 public Type getRawType() { 517 return List.class; 518 } 519 @Override 520 public Type getOwnerType() { 521 return null; 522 } 523 @Override 524 public String toString() { 525 return List.class.getName() + "<" + listElementType + ">"; 526 } 527 }; 528 } 529 530 private Converters() { 531 throw new RuntimeException("No Converters for you!"); 532 } 533}