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 String[] parts = Strings.UNESCAPED_COMMA.split(plainValue); 184 if (parts.length == 1) { 185 parts = Strings.UNESCAPED_SEMICOLON.split(plainValue); 186 } 187 final List<Object> list = new ArrayList<>(parts.length); 188 for (int i = 0; i < parts.length; i++) { 189 list.add(elementConverter.convert(elementType.rawType, elementType.genericType, parts[i].trim())); 190 } 191 return list; 192 } 193 194 private static <E extends Enum<E>> EnumSet<E> enumSet(final Class<E> enumType, final List<?> list) { 195 final EnumSet<E> set = EnumSet.noneOf(enumType); 196 list.forEach(v -> set.add(enumType.cast(v))); 197 return set; 198 } 199 } 200 201 /** 202 * Value converter for array target types including primitive arrays. 203 */ 204 public static class ArrayConverter implements ValueConverter { 205 private final ValueConverter elementConverter; 206 private final CollectionConverter collectionConverter; 207 public ArrayConverter(final ValueConverter elementConverter) { 208 this.elementConverter = Objects.requireNonNull(elementConverter); 209 this.collectionConverter = new CollectionConverter(elementConverter); 210 } 211 212 @Override 213 public <T> T convert(final Class<T> type, final Type genericType, final String value) { 214 if (!type.isArray()) { 215 throw new IllegalArgumentException("Type must be an array: " + type.getName()); 216 } 217 final Class<?> componentType = type.getComponentType(); 218 final Type genericComponentType; 219 if (genericType instanceof GenericArrayType) { 220 genericComponentType = ((GenericArrayType)genericType).getGenericComponentType(); 221 } else { 222 genericComponentType = componentType; 223 } 224 final Type genericListType = genericListType(genericComponentType); 225 final List<?> list = collectionConverter.convert(List.class, genericListType, value); 226 final Object array = Array.newInstance(componentType, list.size()); 227 for (int i = 0; i < list.size(); i++) { 228 final Object val = list.get(i); 229 Array.set(array, i, val); 230 } 231 return type.cast(array); 232 } 233 } 234 235 /** 236 * Value converter for target type {@link Map} or sub-interfaces and implementations of it. 237 */ 238 public static class MapConverter implements ValueConverter { 239 private final ValueConverter elementConverter; 240 public MapConverter(final ValueConverter elementConverter) { 241 this.elementConverter = Objects.requireNonNull(elementConverter); 242 } 243 244 @Override 245 public <T> T convert(final Class<T> type, final Type genericType, final String value) { 246 final ActualType keyType = actualTypeForTypeParam(genericType, 0, 2); 247 final ActualType valueType = actualTypeForTypeParam(genericType, 1, 2); 248 final Map<?,?> map = toMap(keyType, valueType, value); 249 if (type.isInstance(map)) { 250 return type.cast(map); 251 } 252 if (type.isAssignableFrom(LinkedHashMap.class)) { 253 return type.cast(new LinkedHashMap<>(map)); 254 } 255 if (type.isAssignableFrom(TreeMap.class)) { 256 return type.cast(new TreeMap<>(map)); 257 } 258 if (type.isAssignableFrom(HashMap.class)) { 259 return type.cast(new HashMap<>(map)); 260 } 261 if (type.isAssignableFrom(Hashtable.class)) { 262 return type.cast(new Hashtable<>(map)); 263 } 264 if (type.isAssignableFrom(EnumMap.class)) { 265 final EnumMap<?,?> enumMap = enumMap(keyType.rawType.asSubclass(Enum.class), map); 266 return type.cast(enumMap); 267 } 268 if (type.isAssignableFrom(Properties.class)) { 269 final Properties props = new Properties(); 270 props.putAll(map); 271 return type.cast(props); 272 } 273 if (type.isAssignableFrom(ConcurrentHashMap.class)) { 274 return type.cast(new ConcurrentHashMap<>(map)); 275 } 276 if (type.isAssignableFrom(ConcurrentSkipListMap.class)) { 277 return type.cast(new ConcurrentSkipListMap<>(map)); 278 } 279 //unsupported map type 280 throw new IllegalArgumentException("Cannot convert value to " + type.getName() + ": " + value); 281 } 282 283 private Map<Object, Object> toMap(final ActualType keyType, final ActualType valueType, final String value) { 284 final String plainValue = Strings.removeStartAndEndChars(value, '{', '}'); 285 if (plainValue.trim().isEmpty()) { 286 return Collections.emptyMap(); 287 } 288 String[] parts = Strings.UNESCAPED_COMMA.split(plainValue); 289 if (parts.length == 1) { 290 parts = Strings.UNESCAPED_SEMICOLON.split(plainValue); 291 } 292 final Map<Object, Object> map = new LinkedHashMap<>(); 293 for (int i = 0; i < parts.length; i++) { 294 final String[] keyAndValue = parseKeyValue(parts[i].trim()); 295 if (keyAndValue.length != 2) { 296 throw new IllegalArgumentException("Invalid map key/value pair: " + parts[i]); 297 } 298 try { 299 final Object key = elementConverter.convert(keyType.rawType, keyType.genericType, keyAndValue[0].trim()); 300 final Object val = elementConverter.convert(valueType.rawType, valueType.genericType, keyAndValue[1].trim()); 301 map.put(key, val); 302 } catch (final Exception e) { 303 throw new IllegalArgumentException("Conversion to map key/value failed: " + parts[i], e); 304 } 305 } 306 return map; 307 } 308 309 private static <K extends Enum<K>, V> EnumMap<K, V> enumMap(final Class<K> enumType, final Map<?,V> map) { 310 final EnumMap<K,V> enumMap = new EnumMap(enumType); 311 map.forEach((k,v) -> enumMap.put(enumType.cast(k), v)); 312 return enumMap; 313 } 314 } 315 316 /** 317 * Value converter for Java beans as target type. The beans either have getters and setters to access the fields 318 * or otherwise fields are accessed directly. 319 */ 320 public static class BeanConverter implements ValueConverter { 321 322 private final ValueConverter elementConverter; 323 public BeanConverter(final ValueConverter elementConverter) { 324 this.elementConverter = Objects.requireNonNull(elementConverter); 325 } 326 327 @Override 328 public <T> T convert(final Class<T> type, final Type genericType, final String value) { 329 final T instance = newInstance(type, value); 330 final Map<String, Accessor> accessorByName = new LinkedHashMap<>(); 331 if (hasAccessibleSetters(type)) { 332 inspectSetters(type, accessorByName); 333 } else { 334 inspectFields(type, accessorByName); 335 } 336 if (accessorByName.isEmpty()) { 337 throw new IllegalArgumentException(type.getName() + " is not a bean class, no accessible setters or fields found"); 338 } 339 injectValues(instance, accessorByName, value); 340 return instance; 341 } 342 343 private <T> T newInstance(final Class<T> type, final String value) { 344 try { 345 final Constructor<T> constructor = type.getDeclaredConstructor(); 346 constructor.setAccessible(true); 347 return constructor.newInstance(); 348 } catch (final Exception e) { 349 throw new IllegalArgumentException("Could not instantiate bean " + type.getName(), e); 350 } 351 } 352 353 private void injectValues(final Object instance, final Map<String, Accessor> accessorByName, final String value) { 354 final String plainValue = Strings.removeStartAndEndChars(value, '{', '}'); 355 String[] parts = Strings.UNESCAPED_COMMA.split(plainValue); 356 if (parts.length == 1) { 357 parts = Strings.UNESCAPED_SEMICOLON.split(plainValue); 358 } 359 final Map<String, String> valueByName = new LinkedHashMap<>(); 360 for (int i = 0; i < parts.length; i++) { 361 final String[] nameAndValue = parseKeyValue(parts[i].trim()); 362 if (nameAndValue.length != 2) { 363 throw new IllegalArgumentException("Invalid name/value pair: " + parts[i]); 364 } 365 final String name = normalizeFieldName(nameAndValue[0].trim()); 366 final String val = nameAndValue[1].trim(); 367 valueByName.put(name, val); 368 } 369 for (final Map.Entry<String, Accessor> e : accessorByName.entrySet()) { 370 final String val = valueByName.get(e.getKey()); 371 if (val == null) { 372 throw new IllegalArgumentException("No value found for bean property " + instance.getClass().getName() + "." + e.getKey()); 373 } 374 try { 375 final Class<?> type = e.getValue().type(); 376 final Type genericType = e.getValue().genericType(); 377 final Object convertedVal = elementConverter.convert(type, genericType, val); 378 e.getValue().set(instance, convertedVal); 379 } catch (final Exception ex) { 380 throw new IllegalArgumentException("Could not set bean property " + instance.getClass().getName() + "." + e.getKey() + 381 " to value: " + val, ex); 382 } 383 } 384 } 385 386 public static boolean isBeanClass(final Class<?> clazz) { 387 return isInstantiatable(clazz) && (hasAccessibleFields(clazz) || hasAccessibleSetters(clazz)); 388 } 389 390 private static boolean isInstantiatable(final Class<?> clazz) { 391 return !clazz.isInterface() && !Modifier.isAbstract(clazz.getModifiers()) && hasDefaultConstructor(clazz); 392 } 393 394 private static boolean hasDefaultConstructor(final Class<?> clazz) { 395 try { 396 clazz.getDeclaredConstructor(); 397 return true; 398 } catch (final Exception e) { 399 return false; 400 } 401 } 402 403 private interface Accessor { 404 void set(Object instance, Object value) throws Exception; 405 default Class<?> type() { 406 return Object.class; 407 } 408 default Type genericType() { 409 return Object.class; 410 } 411 static Accessor forType(final Class<?> type, final Type genericType, final Accessor accessor) { 412 return new Accessor() { 413 @Override 414 public void set(final Object instance, final Object value) throws Exception { 415 accessor.set(instance, value); 416 } 417 418 @Override 419 public Class<?> type() { 420 return type; 421 } 422 423 @Override 424 public Type genericType() { 425 return genericType; 426 } 427 }; 428 } 429 } 430 431 private static boolean hasAccessibleSetters(final Class<?> clazz) { 432 return !inspectSetters(clazz, new HashMap<>()).isEmpty(); 433 } 434 private static final Map<String, Accessor> inspectSetters(final Class<?> clazz, final Map<String, Accessor> accessorByName) { 435 if (clazz == null || Object.class.equals(clazz)) { 436 return accessorByName; 437 } 438 for (final Method method : clazz.getDeclaredMethods()) { 439 final int mod = method.getModifiers(); 440 final String name = method.getName(); 441 if (name.length() > 3 && name.startsWith("set") && method.getParameterCount() == 1 && 442 !method.isSynthetic() && !Modifier.isStatic(mod) && !Modifier.isPrivate(mod) && !Modifier.isProtected(mod)) { 443 final String propertyName = normalizeFieldName(name.substring(3)); 444 accessorByName.put(propertyName, Accessor.forType(method.getParameterTypes()[0], method.getGenericParameterTypes()[0], method::invoke)); 445 } 446 } 447 return inspectSetters(clazz.getSuperclass(), accessorByName); 448 } 449 450 private static boolean hasAccessibleFields(final Class<?> clazz) { 451 return !inspectFields(clazz, new HashMap<>()).isEmpty(); 452 } 453 private static final Map<String, Accessor> inspectFields(final Class<?> clazz, final Map<String, Accessor> accessorByName) { 454 if (clazz == null || Object.class.equals(clazz)) { 455 return accessorByName; 456 } 457 for (final Field field : clazz.getDeclaredFields()) { 458 final int mod = field.getModifiers(); 459 if (!field.isSynthetic() && !Modifier.isFinal(mod) && !Modifier.isStatic(mod) && !Modifier.isPrivate(mod) && !Modifier.isProtected(mod)) { 460 accessorByName.put(field.getName(), Accessor.forType(field.getType(), field.getGenericType(), field::set)); 461 } 462 } 463 return inspectFields(clazz.getSuperclass(), accessorByName); 464 } 465 } 466 467 private static String normalizeFieldName(final String name) { 468 if (name.length() > 0 && Character.isUpperCase(name.charAt(0))) { 469 return Character.toLowerCase(name.charAt(0)) + name.substring(1); 470 } 471 return name; 472 } 473 474 private static final String[] parseKeyValue(final String pair) { 475 final String[] split = Strings.UNESCAPED_EQUAL.split(pair); 476 if (split.length == 1) { 477 return Strings.UNESCAPED_COLON.split(pair); 478 } 479 return split; 480 } 481 482 private static class ActualType { 483 final Class<?> rawType; 484 final Type genericType; 485 public ActualType(final Class<?> rawType, final Type genericType) { 486 this.rawType = Objects.requireNonNull(rawType); 487 this.genericType = Objects.requireNonNull(genericType); 488 } 489 } 490 private static ActualType actualTypeForTypeParam(final Type type, final int paramIndex, final int paramCount) { 491 if (type instanceof ParameterizedType) { 492 final Type[] actualTypeArgs = ((ParameterizedType) type).getActualTypeArguments(); 493 if (actualTypeArgs.length == paramCount) { 494 Type actualType = actualTypeArgs[paramIndex]; 495 if (actualType instanceof WildcardType) { 496 final Type[] bounds = ((WildcardType) actualType).getUpperBounds(); 497 if (bounds.length == 1) { 498 actualType = bounds[0]; 499 } 500 } 501 if (actualType instanceof Class) { 502 return new ActualType((Class<?>) actualType, actualType); 503 } 504 if (actualType instanceof ParameterizedType) { 505 final ParameterizedType parameterizedType = (ParameterizedType)actualType; 506 if (parameterizedType.getRawType() instanceof Class) { 507 return new ActualType((Class<?>)parameterizedType.getRawType(), parameterizedType); 508 } 509 } 510 } 511 } 512 if (Properties.class.equals(type) && paramCount == 2) { 513 return new ActualType(String.class, String.class); 514 } 515 throw new IllegalArgumentException("Could not derive actual generic type [" + paramIndex + "] for " + type); 516 } 517 518 private static Type genericListType(final Type listElementType) { 519 return new ParameterizedType() { 520 @Override 521 public Type[] getActualTypeArguments() { 522 return new Type[]{listElementType}; 523 } 524 @Override 525 public Type getRawType() { 526 return List.class; 527 } 528 @Override 529 public Type getOwnerType() { 530 return null; 531 } 532 @Override 533 public String toString() { 534 return List.class.getName() + "<" + listElementType + ">"; 535 } 536 }; 537 } 538 539 private Converters() { 540 throw new RuntimeException("No Converters for you!"); 541 } 542}