001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*- 002 * 003 * Copyright © 2020 microBean™. 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 014 * implied. See the License for the specific language governing 015 * permissions and limitations under the License. 016 */ 017package org.microbean.settings; 018 019import java.beans.PropertyEditor; 020import java.beans.PropertyEditorManager; 021 022import java.io.Serializable; 023 024import java.math.BigDecimal; 025import java.math.BigInteger; 026 027import java.lang.reflect.Array; 028import java.lang.reflect.Constructor; 029import java.lang.reflect.Executable; 030import java.lang.reflect.GenericDeclaration; 031import java.lang.reflect.Method; 032import java.lang.reflect.Modifier; 033import java.lang.reflect.ParameterizedType; 034import java.lang.reflect.Type; 035import java.lang.reflect.TypeVariable; 036 037import java.net.URI; 038import java.net.URL; 039 040import java.time.Duration; 041import java.time.Instant; 042import java.time.LocalDate; 043import java.time.LocalDateTime; 044import java.time.LocalTime; 045import java.time.MonthDay; 046import java.time.OffsetDateTime; 047import java.time.OffsetTime; 048import java.time.Period; 049import java.time.Year; 050import java.time.YearMonth; 051import java.time.ZonedDateTime; 052 053import java.util.ArrayList; 054import java.util.Calendar; 055import java.util.Collection; 056import java.util.Date; 057import java.util.HashMap; 058import java.util.HashSet; 059import java.util.Map; 060import java.util.Objects; 061import java.util.Optional; 062import java.util.Set; 063 064import java.util.concurrent.ConcurrentHashMap; 065 066import java.util.logging.Level; 067 068import java.util.regex.Pattern; 069 070import javax.enterprise.util.TypeLiteral; 071 072import org.microbean.settings.converter.*; 073 074/** 075 * A hub for the centralized conversion of {@link Value}s. 076 * 077 * <p>{@link Converters} instances compute {@link Converter}s on 078 * demand in many cases and are normally used in standalone 079 * environments.</p> 080 * 081 * @author <a href="https://about.me/lairdnelson" 082 * target="_parent">Laird Nelson</a> 083 */ 084public final class Converters implements ConverterProvider { 085 086 087 /* 088 * Static fields. 089 */ 090 091 092 private static final Map<Class<?>, Class<?>> wrapperClasses; 093 094 static { 095 wrapperClasses = new HashMap<>(); 096 wrapperClasses.put(boolean.class, Boolean.class); 097 wrapperClasses.put(byte.class, Byte.class); 098 wrapperClasses.put(char.class, Character.class); 099 wrapperClasses.put(double.class, Double.class); 100 wrapperClasses.put(float.class, Float.class); 101 wrapperClasses.put(int.class, Integer.class); 102 wrapperClasses.put(long.class, Long.class); 103 wrapperClasses.put(short.class, Short.class); 104 } 105 106 private static final Pattern backslashCommaPattern = Pattern.compile("\\\\,"); 107 108 private static final Pattern splitPattern = Pattern.compile("(?<!\\\\),"); 109 110 111 /* 112 * Instance fields. 113 */ 114 115 116 private final Map<Type, Converter<?>> converters; 117 118 119 /* 120 * Constructors. 121 */ 122 123 124 /** 125 * Creates a new {@link Converters} instance. 126 */ 127 public Converters() { 128 super(); 129 this.converters = new ConcurrentHashMap<>(); 130 installDefaultConverters(this); 131 } 132 133 /** 134 * Creates a new {@link Converters} instance. 135 * 136 * @param converters a {@link Collection} of {@link Converter}s that 137 * will be {@linkplain #putConverter(TypeLiteral, Converter) 138 * installed} into this {@link Converters} instance; may be {@code 139 * null} 140 * 141 * @exception java.util.ConcurrentModificationException if some 142 * other thread modifies the supplied {@link Collection} while 143 * iteration is in progress 144 */ 145 public Converters(final Collection<? extends Converter<?>> converters) { 146 super(); 147 this.converters = new ConcurrentHashMap<>(); 148 if (converters != null && !converters.isEmpty()) { 149 for (final Converter<?> converter : converters) { 150 if (converter != null) { 151 final Type conversionType = getConversionType(converter); 152 assert conversionType != null; 153 this.putConverter(conversionType, converter); 154 } 155 } 156 } 157 158 } 159 160 161 /* 162 * Instance methods. 163 */ 164 165 166 /** 167 * Returns a {@link Converter} capable of {@linkplain 168 * Converter#convert(Value) converting} {@link Value}s into objects 169 * of the supplied {@code type}. 170 * 171 * @param <T> the conversion type 172 * 173 * @param type a {@link TypeLiteral} describing the conversion type; 174 * must not be {@code null} 175 * 176 * @return a non-{@code null} {@link Converter} capable of 177 * {@linkplain Converter#convert(Value) converting} {@link Value}s 178 * into objects of the proper type 179 * 180 * @exception NullPointerException if {@code type} is {@code null} 181 * 182 * @exception IllegalArgumentException if no {@link Converter} is 183 * available for the supplied {@code type} 184 * 185 * @idempotency Because {@link Converter}s can be {@linkplain 186 * #putConverter(TypeLiteral, Converter) added to this 187 * <code>Converters</code> instance}, this method may return 188 * different {@link Converter} instances over time given the same 189 * {@code type}. 190 * 191 * @nullability This method never returns {@code null}. 192 * 193 * @threadsafety This method is safe for concurrent use by multiple 194 * threads. 195 */ 196 @Override 197 public final <T> Converter<? extends T> getConverter(final TypeLiteral<T> type) { 198 @SuppressWarnings("unchecked") 199 final Converter<? extends T> returnValue = (Converter<? extends T>)this.getConverter(type.getType()); 200 return returnValue; 201 } 202 203 /** 204 * Returns a {@link Converter} capable of {@linkplain 205 * Converter#convert(Value) converting} {@link Value}s into objects 206 * of the supplied {@code type}. 207 * 208 * @param <T> the conversion type 209 * 210 * @param type a {@link Class} describing the conversion type; must 211 * not be {@code null} 212 * 213 * @return a non-{@code null} {@link Converter} capable of 214 * {@linkplain Converter#convert(Value) converting} {@link Value}s 215 * into objects of the proper type 216 * 217 * @exception NullPointerException if {@code type} is {@code null} 218 * 219 * @exception IllegalArgumentException if no {@link Converter} is 220 * available for the supplied {@code type} 221 * 222 * @idempotency Because {@link Converter}s can be {@linkplain 223 * #putConverter(Class, Converter) added to this 224 * <code>Converters</code> instance}, this method may return 225 * different {@link Converter} instances over time given the same 226 * {@code type}. 227 * 228 * @nullability This method never returns {@code null}. 229 * 230 * @threadsafety This method is safe for concurrent use by multiple 231 * threads. 232 */ 233 @Override 234 public final <T> Converter<? extends T> getConverter(final Class<T> type) { 235 @SuppressWarnings("unchecked") 236 final Converter<? extends T> returnValue = (Converter<? extends T>)this.getConverter((Type)Objects.requireNonNull(type)); 237 return returnValue; 238 } 239 240 /** 241 * Returns a {@link Converter} capable of {@linkplain 242 * Converter#convert(Value) converting} {@link Value}s into objects 243 * of the supplied {@code type}. 244 * 245 * @param type a {@link Type} describing the conversion type; must 246 * not be {@code null} 247 * 248 * @return a non-{@code null} {@link Converter} capable of 249 * {@linkplain Converter#convert(Value) converting} {@link Value}s 250 * into objects of the proper type 251 * 252 * @exception NullPointerException if {@code type} is {@code null} 253 * 254 * @exception IllegalArgumentException if no {@link Converter} is 255 * available for the supplied {@code type} 256 * 257 * @idempotency Because {@link Converter}s can be {@linkplain 258 * #putConverter(Class, Converter) added to this 259 * <code>Converters</code> instance}, this method may return 260 * different {@link Converter} instances over time given the same 261 * {@code type}. 262 * 263 * @nullability This method never returns {@code null}. 264 * 265 * @threadsafety This method is safe for concurrent use by multiple 266 * threads. 267 */ 268 @Override 269 public final Converter<?> getConverter(final Type type) { 270 final Converter<?> returnValue = this.converters.computeIfAbsent(Objects.requireNonNull(type), this::computeConverter); 271 if (returnValue == null) { 272 throw new IllegalArgumentException("No converter available for " + type); 273 } 274 return returnValue; 275 } 276 277 /** 278 * Given a {@link Type}, creates and returns a new suitable {@link 279 * Converter}. 280 * 281 * @param <T> the conversion type 282 * 283 * @param type the {@link Type} for which a new {@link Converter} 284 * should be returned; must not be {@code null} 285 * 286 * @return a new {@link Converter}, or {@code null} 287 * 288 * @exception NullPointerException if {@code type} is {@code null} 289 * 290 * @exception IllegalArgumentException if {@code type} designates a 291 * concrete {@link Collection} class without a zero-argument 292 * constructor 293 * 294 * @idempotency Repeated invocations of this method will return new 295 * but otherwise equal instances of the same kind of result given 296 * the same parameter values. 297 * 298 * @nullability This method may return {@code null}. 299 * 300 * @threadsafety This method is safe for concurrent use by multiple 301 * threads. 302 */ 303 private final <T> Converter<? extends T> computeConverter(final Type type) { 304 Objects.requireNonNull(type); 305 Converter<? extends T> returnValue; 306 if (CharSequence.class.equals(type) || 307 String.class.equals(type) || 308 Serializable.class.equals(type) || 309 Object.class.equals(type)) { 310 @SuppressWarnings("unchecked") 311 final Converter<? extends T> temp = (Converter<? extends T>)new StringConverter(); 312 returnValue = temp; 313 314 } else if (Boolean.class.equals(type) || boolean.class.equals(type)) { 315 @SuppressWarnings("unchecked") 316 final Converter<? extends T> temp = (Converter<? extends T>)new BooleanConverter(); 317 returnValue = temp; 318 319 } else if (URI.class.equals(type)) { 320 @SuppressWarnings("unchecked") 321 final Converter<? extends T> temp = (Converter<? extends T>)new URIConverter(); 322 returnValue = temp; 323 324 } else if (URL.class.equals(type)) { 325 @SuppressWarnings("unchecked") 326 final Converter<? extends T> temp = (Converter<? extends T>)new URLConverter(); 327 returnValue = temp; 328 329 } else if (Class.class.equals(type)) { 330 @SuppressWarnings("unchecked") 331 final Converter<? extends T> temp = (Converter<? extends T>)new ClassConverter<Object>(); 332 returnValue = temp; 333 334 } else if (type instanceof ParameterizedType) { 335 final ParameterizedType parameterizedType = (ParameterizedType)type; 336 final Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); 337 assert actualTypeArguments != null; 338 assert actualTypeArguments.length > 0; 339 final Type rawType = parameterizedType.getRawType(); 340 assert rawType instanceof Class : "!(parameterizedType.getRawType() instanceof Class): " + rawType; 341 final Class<?> conversionClass = (Class<?>)rawType; 342 assert !conversionClass.isArray(); 343 344 if (Optional.class.isAssignableFrom(conversionClass)) { 345 assert actualTypeArguments.length == 1; 346 final Type firstTypeArgument = actualTypeArguments[0]; 347 returnValue = new Converter<T>() { 348 private static final long serialVersionUID = 1L; 349 @Override 350 @SuppressWarnings("unchecked") 351 public final T convert(final Value value) { 352 return (T)Optional.ofNullable(Converters.this.convert(value, firstTypeArgument)); // XXX recursive call 353 } 354 }; 355 356 } else if (Class.class.isAssignableFrom(conversionClass)) { 357 returnValue = new Converter<T>() { 358 private static final long serialVersionUID = 1L; 359 @Override 360 public final T convert(final Value value) { 361 @SuppressWarnings("unchecked") 362 final T returnValue = (T)Converters.this.convert(value, (Type)conversionClass); // XXX recursive call 363 return returnValue; 364 } 365 }; 366 367 } else if (Collection.class.isAssignableFrom(conversionClass)) { 368 returnValue = new Converter<T>() { 369 private static final long serialVersionUID = 1L; 370 @Override 371 public final T convert(final Value value) { 372 final T returnValue; 373 if (value == null) { 374 returnValue = null; 375 } else { 376 final String stringValue = value.get(); 377 if (stringValue == null) { 378 returnValue = null; 379 } else { 380 Collection<Object> container = null; 381 if (conversionClass.isInterface()) { 382 if (Set.class.isAssignableFrom(conversionClass)) { 383 container = new HashSet<>(); 384 } else { 385 container = new ArrayList<>(); 386 } 387 } else { 388 try { 389 @SuppressWarnings("unchecked") 390 final Collection<Object> temp = (Collection<Object>)conversionClass.getDeclaredConstructor().newInstance(); 391 container = temp; 392 } catch (final ReflectiveOperationException reflectiveOperationException) { 393 throw new IllegalArgumentException(stringValue, reflectiveOperationException); 394 } 395 } 396 assert container != null; 397 final Type firstTypeArgument = actualTypeArguments[0]; 398 final String[] parts = split(stringValue); 399 assert parts != null; 400 assert parts.length > 0; 401 for (final String part : parts) { 402 final Object scalar = Converters.this.convert(new Value(value, part), firstTypeArgument); // XXX recursive call 403 container.add(scalar); 404 } 405 @SuppressWarnings("unchecked") 406 final T temp = (T)container; 407 returnValue = temp; 408 } 409 } 410 return returnValue; 411 } 412 }; 413 } else { 414 throw new IllegalArgumentException("Unhandled conversion type: " + type); 415 } 416 417 } else if (type instanceof Class) { 418 final Class<?> conversionClass = (Class<?>)type; 419 if (conversionClass.isArray()) { 420 returnValue = new Converter<T>() { 421 private static final long serialVersionUID = 1L; 422 @Override 423 public final T convert(final Value value) { 424 final T returnValue; 425 if (value == null) { 426 returnValue = null; 427 } else { 428 final String stringValue = value.get(); 429 if (stringValue == null) { 430 returnValue = null; 431 } else { 432 final String[] parts = split(stringValue); 433 assert parts != null; 434 @SuppressWarnings("unchecked") 435 final T container = (T)Array.newInstance(conversionClass.getComponentType(), parts.length); 436 for (int i = 0; i < parts.length; i++) { 437 final Object scalar = Converters.this.convert(new Value(value, parts[i]), conversionClass.getComponentType()); // XXX recursive call 438 Array.set(container, i, scalar); 439 } 440 returnValue = container; 441 } 442 } 443 return returnValue; 444 } 445 }; 446 447 } else { 448 final Class<?> cls; 449 if (conversionClass.isPrimitive()) { 450 cls = wrapperClasses.get(conversionClass); 451 assert cls != null; 452 } else { 453 cls = conversionClass; 454 } 455 returnValue = getConverterFromStaticMethod(cls, "of", String.class); 456 if (returnValue == null) { 457 returnValue = getConverterFromStaticMethod(cls, "of", CharSequence.class); 458 if (returnValue == null) { 459 returnValue = getConverterFromStaticMethod(cls, "valueOf", String.class); 460 if (returnValue == null) { 461 returnValue = getConverterFromStaticMethod(cls, "valueOf", CharSequence.class); 462 if (returnValue == null) { 463 returnValue = getConverterFromStaticMethod(cls, "parse", String.class); 464 if (returnValue == null) { 465 returnValue = getConverterFromStaticMethod(cls, "parse", CharSequence.class); 466 if (returnValue == null) { 467 @SuppressWarnings("unchecked") 468 final Class<T> temp = (Class<T>)cls; 469 returnValue = getConverterFromConstructor(temp, String.class); 470 if (returnValue == null) { 471 returnValue = getConverterFromConstructor(temp, CharSequence.class); 472 if (returnValue == null) { 473 final PropertyEditor editor = PropertyEditorManager.findEditor(cls); 474 if (editor != null) { 475 returnValue = new PropertyEditorConverter<T>(temp, editor); 476 } 477 } 478 } 479 } 480 } 481 } 482 } 483 } 484 } 485 } 486 } else { 487 returnValue = null; 488 } 489 return returnValue; 490 } 491 492 /** 493 * Installs the supplied {@link Converter} under the supplied {@link 494 * Class} and returns any {@link Converter} previously installed 495 * under a {@link Type} equal to that {@link Class}. 496 * 497 * @param <T> the conversion type 498 * 499 * @param type the {@link Class} describing the conversion type of 500 * the supplied {@link Converter}; must not be {@code null} 501 * 502 * @param converter the {@link Converter} to install; must not be 503 * {@code null} 504 * 505 * @return the {@link Converter} previously installed under a {@link 506 * Type} equal to the supplied {@link Class}, or {@code null} 507 * 508 * @exception NullPointerException if {@code type} or {@code 509 * converter} is {@code null} 510 * 511 * @idempotency This method is not idempotent. 512 * 513 * @nullability This method may return {@code null}. 514 * 515 * @threadsafety This method is safe for concurrent use by multiple 516 * threads. 517 * 518 * @see #getConverter(Class) 519 */ 520 public final <T> Converter<? extends T> putConverter(final Class<T> type, final Converter<? extends T> converter) { 521 @SuppressWarnings("unchecked") 522 final Converter<? extends T> returnValue = (Converter<? extends T>)this.putConverter((Type)type, converter); 523 return returnValue; 524 } 525 526 /** 527 * Installs the supplied {@link Converter} under the supplied {@link 528 * TypeLiteral}'s {@link TypeLiteral#getType() Type} and returns any 529 * {@link Converter} previously installed under a {@link Type} equal 530 * to that {@link Type}. 531 * 532 * @param <T> the conversion type 533 * 534 * @param type the {@link TypeLiteral} describing the conversion 535 * type of the supplied {@link Converter}; must not be {@code null} 536 * 537 * @param converter the {@link Converter} to install; must not be 538 * {@code null} 539 * 540 * @return the {@link Converter} previously installed under a {@link 541 * Type} equal to the supplied {@link TypeLiteral}'s {@link 542 * TypeLiteral#getType() Type}, or {@code null} 543 * 544 * @exception NullPointerException if {@code type} or {@code 545 * converter} is {@code null} 546 * 547 * @idempotency This method is not idempotent. 548 * 549 * @nullability This method may return {@code null}. 550 * 551 * @threadsafety This method is safe for concurrent use by multiple 552 * threads. 553 * 554 * @see #getConverter(TypeLiteral) 555 */ 556 public final <T> Converter<? extends T> putConverter(final TypeLiteral<T> type, final Converter<? extends T> converter) { 557 @SuppressWarnings("unchecked") 558 final Converter<? extends T> returnValue = (Converter<? extends T>)this.putConverter(type.getType(), converter); 559 return returnValue; 560 } 561 562 /** 563 * Installs the supplied {@link Converter} under the supplied {@link 564 * Type} and returns any {@link Converter} previously installed 565 * under a {@link Type} equal to that {@link Type}. 566 * 567 * @param type the {@link Type} describing the conversion type of 568 * the supplied {@link Converter}; must not be {@code null} 569 * 570 * @param converter the {@link Converter} to install; must not be 571 * {@code null} 572 * 573 * @return the {@link Converter} previously installed under a {@link 574 * Type} equal to the supplied {@link Type}, or {@code null} 575 * 576 * @exception NullPointerException if {@code type} or {@code 577 * converter} is {@code null} 578 * 579 * @idempotency This method is not idempotent. 580 * 581 * @nullability This method may return {@code null}. 582 * 583 * @threadsafety This method is safe for concurrent use by multiple 584 * threads. 585 * 586 * @see #getConverter(Type) 587 */ 588 private final Converter<?> putConverter(final Type type, final Converter<?> converter) { 589 return this.converters.put(Objects.requireNonNull(type), Objects.requireNonNull(converter)); 590 } 591 592 /** 593 * Uninstalls and returns any {@link Converter} stored under a key 594 * equal to the supplied {@code key}. 595 * 596 * @param <T> the conversion type 597 * 598 * @param key the key designating the {@link Converter} to remove; 599 * must not be {@code null} 600 * 601 * @return the {@link Converter} that was uninstalled, or {@code 602 * null} 603 * 604 * @exception NullPointerException if {@code key} is {@code null} 605 * 606 * @idempotency This method will uninstall and return any {@link 607 * Converter} stored under a key equal to the supplied {@code key}, 608 * and, if then invoked with the same {@code key}, will return 609 * {@code null} thereafter. 610 * 611 * @nullability This method may return {@code null}. 612 * 613 * @threadsafety This method is safe for concurrent use by multiple 614 * threads. 615 */ 616 public final <T> Converter<? extends T> removeConverter(final Class<T> key) { 617 @SuppressWarnings("unchecked") 618 final Converter<? extends T> returnValue = (Converter<? extends T>)this.removeConverter((Object)key); 619 return returnValue; 620 } 621 622 /** 623 * Uninstalls and returns any {@link Converter} stored under a key 624 * equal to the supplied {@code key}. 625 * 626 * @param <T> the conversion type 627 * 628 * @param key the key designating the {@link Converter} to remove; 629 * must not be {@code null} 630 * 631 * @return the {@link Converter} that was uninstalled, or {@code 632 * null} 633 * 634 * @exception NullPointerException if {@code key} is {@code null} 635 * 636 * @idempotency This method will uninstall and return any {@link 637 * Converter} stored under a key equal to the supplied {@code key}, 638 * and, if then invoked with the same {@code key}, will return 639 * {@code null} thereafter. 640 * 641 * @nullability This method may return {@code null}. 642 * 643 * @threadsafety This method is safe for concurrent use by multiple 644 * threads. 645 */ 646 public final <T> Converter<? extends T> removeConverter(final TypeLiteral<T> key) { 647 @SuppressWarnings("unchecked") 648 final Converter<? extends T> returnValue = (Converter<? extends T>)this.removeConverter((Object)key.getType()); 649 return returnValue; 650 } 651 652 /** 653 * Uninstalls and returns any {@link Converter} stored under a key 654 * equal to the supplied {@code key}. 655 * 656 * @param key the key designating the {@link Converter} to remove; 657 * must not be {@code null} 658 * 659 * @return the {@link Converter} that was uninstalled, or {@code 660 * null} 661 * 662 * @exception NullPointerException if {@code key} is {@code null} 663 * 664 * @idempotency This method will uninstall and return any {@link 665 * Converter} stored under a key equal to the supplied {@code key}, 666 * and, if then invoked with the same {@code key}, will return 667 * {@code null} thereafter. 668 * 669 * @nullability This method may return {@code null}. 670 * 671 * @threadsafety This method is safe for concurrent use by multiple 672 * threads. 673 */ 674 private final Converter<?> removeConverter(final Object key) { 675 return this.converters.remove(Objects.requireNonNull(key)); 676 } 677 678 /** 679 * Converts the supplied {@link Value} to an object of the proper 680 * type and returns the result of the conversion using a {@link 681 * Converter} {@linkplain #putConverter(Class, Converter) previously 682 * installed} under a {@link Type} equal to the supplied {@link 683 * Class}. 684 * 685 * @param <T> the conversion type 686 * 687 * @param value the {@link Value} to convert; may be {@code null} 688 * 689 * @param type the {@link Class} designating the {@link 690 * Converter} to use; must not be {@code null} 691 * 692 * @return the result of the conversion, which may be {@code null} 693 * 694 * @exception NullPointerException if {@code type} is {@code null} 695 * 696 * @exception IllegalArgumentException if conversion fails because 697 * of a problem with the supplied {@link Value} 698 * 699 * @exception ConversionException if conversion fails for any other 700 * reason 701 * 702 * @idempotency This method may return different results when 703 * supplied with the same parameter values over repeated 704 * invocations. 705 * 706 * @nullability This method may return {@code null}. 707 * 708 * @threadsafety This method is safe for concurrent use by multiple 709 * threads. 710 */ 711 public final <T> T convert(final Value value, final Class<T> type) { 712 @SuppressWarnings("unchecked") 713 final T returnValue = (T)this.convert(value, (Type)type); 714 return returnValue; 715 } 716 717 /** 718 * Converts the supplied {@link Value} to an object of the proper 719 * type and returns the result of the conversion using a {@link 720 * Converter} {@linkplain #putConverter(Class, Converter) previously 721 * installed} under a {@link Type} equal to the supplied {@link 722 * TypeLiteral}'s {@link TypeLiteral#getType() Type}. 723 * 724 * @param <T> the conversion type 725 * 726 * @param value the {@link Value} to convert; may be {@code null} 727 * 728 * @param type the {@link TypeLiteral} designating the {@link 729 * Converter} to use; must not be {@code null} 730 * 731 * @return the result of the conversion, which may be {@code null} 732 * 733 * @exception NullPointerException if {@code type} is {@code null} 734 * 735 * @exception IllegalArgumentException if conversion fails because 736 * of a problem with the supplied {@link Value} 737 * 738 * @exception ConversionException if conversion fails for any other 739 * reason 740 * 741 * @idempotency This method may return different results when 742 * supplied with the same parameter values over repeated 743 * invocations. 744 * 745 * @nullability This method may return {@code null}. 746 * 747 * @threadsafety This method is safe for concurrent use by multiple 748 * threads. 749 */ 750 public final <T> T convert(final Value value, final TypeLiteral<T> type) { 751 @SuppressWarnings("unchecked") 752 final T returnValue = (T)this.convert(value, type.getType()); 753 return returnValue; 754 } 755 756 /** 757 * Converts the supplied {@link Value} to an object of the proper 758 * type and returns the result of the conversion using a {@link 759 * Converter} {@linkplain #putConverter(Class, Converter) previously 760 * installed} under a {@link Type} equal to the supplied {@code 761 * type}. 762 * 763 * @param value the {@link Value} to convert; may be {@code null} 764 * 765 * @param type the {@link Type} designating the {@link Converter} to 766 * use; must not be {@code null} 767 * 768 * @return the result of the conversion, which may be {@code null} 769 * 770 * @exception NullPointerException if {@code type} is {@code null} 771 * 772 * @exception IllegalArgumentException if conversion fails because 773 * of a problem with the supplied {@link Value} 774 * 775 * @exception ConversionException if conversion fails for any other 776 * reason 777 * 778 * @idempotency This method may return different results when 779 * supplied with the same parameter values over repeated 780 * invocations. 781 * 782 * @nullability This method may return {@code null}. 783 * 784 * @threadsafety This method is safe for concurrent use by multiple 785 * threads. 786 */ 787 private final Object convert(final Value value, final Type type) { 788 return this.getConverter(type).convert(value); 789 } 790 791 792 /* 793 * Static methods. 794 */ 795 796 797 /** 798 * Returns a new {@link ExecutableBasedConverter} adapting the 799 * designated {@code static} method. 800 * 801 * @param <T> the conversion type 802 * 803 * @param methodHostClass the {@link Class} whose {@link 804 * Class#getMethod(String, Class...)} method will be called; must 805 * not be {@code null} 806 * 807 * @param methodName the name of the {@code static} method to supply 808 * to a new {@link ExecutableBasedConverter}; must not be {@code 809 * null} 810 * 811 * @param soleParameterType the {@link Class} of the single 812 * parameter of the method in question; must not be {@code null}; an 813 * invocation of {@link Class#isAssignableFrom(Class)} on {@link 814 * CharSequence CharSequence.class} with {@code soleParameterType} 815 * as its parameter value must return {@code true} 816 * 817 * @return a suitable {@link Converter}, or {@code null} 818 * 819 * @exception NullPointerException if either {@code 820 * methodHostClass}, {@code methodName} or {@code soleParameterType} 821 * is {@code null} 822 * 823 * @exception IllegalArgumentException if {@code methodHostClass} 824 * designates an array-typed or primitive class 825 * 826 * @nullability This method may return {@code null}. 827 * 828 * @idempotency Repeated invocations of this method will return the 829 * same result given the same parameter values. 830 * 831 * @threadsafety This method is safe for concurrent use by multiple 832 * threads. 833 */ 834 private static final <T> Converter<T> getConverterFromStaticMethod(Class<?> methodHostClass, 835 final String methodName, 836 final Class<? extends CharSequence> soleParameterType) { 837 Objects.requireNonNull(methodHostClass); 838 Objects.requireNonNull(methodName); 839 Objects.requireNonNull(soleParameterType); 840 if (methodHostClass.isArray()) { 841 throw new IllegalArgumentException("methodHostClass.isArray(): " + methodHostClass.getName()); 842 } else if (methodHostClass.isPrimitive()) { 843 throw new IllegalArgumentException("methodHostClass.isPrimitive(): " + methodHostClass.getName()); 844 } 845 Converter<T> returnValue = null; 846 final Method method; 847 Method temp = null; 848 try { 849 temp = methodHostClass.getMethod(methodName, soleParameterType); 850 } catch (final NoSuchMethodException noSuchMethodException) { 851 852 } finally { 853 method = temp; 854 } 855 if (method != null && Modifier.isStatic(method.getModifiers()) && methodHostClass.isAssignableFrom(method.getReturnType())) { 856 returnValue = new ExecutableBasedConverter<>(method); 857 } 858 return returnValue; 859 } 860 861 /** 862 * Returns a new {@link ExecutableBasedConverter} adapting the 863 * designated {@link Constructor}. 864 * 865 * @param <T> the conversion type 866 * 867 * @param constructorHostClass the {@link Class} whose {@link 868 * Class#getConstructor(Class...)} method will be called; must not 869 * be {@code null} 870 * 871 * @param soleParameterType the {@link Class} of the single 872 * parameter of the {@link Constructor} in question; must not be 873 * {@code null}; an invocation of {@link 874 * Class#isAssignableFrom(Class)} on {@link CharSequence 875 * CharSequence.class} with {@code soleParameterType} as its 876 * parameter value must return {@code true} 877 * 878 * @return a suitable {@link Converter}, or {@code null} 879 * 880 * @exception NullPointerException if either {@code 881 * constructorHostClass} or {@code soleParameterType} is {@code 882 * null} 883 * 884 * @exception IllegalArgumentException if {@code 885 * constructorHostClass} designates an array-typed or primitive 886 * class 887 * 888 * @nullability This method may return {@code null}. 889 * 890 * @idempotency Repeated invocations of this method will return the 891 * same result given the same parameter values. 892 * 893 * @threadsafety This method is safe for concurrent use by multiple 894 * threads. 895 */ 896 private static final <T> Converter<T> getConverterFromConstructor(Class<T> constructorHostClass, final Class<? extends CharSequence> soleParameterType) { 897 Objects.requireNonNull(constructorHostClass); 898 Objects.requireNonNull(soleParameterType); 899 if (constructorHostClass.isPrimitive()) { 900 throw new IllegalArgumentException("constructorHostClass.isPrimitive(): " + constructorHostClass.getName()); 901 } else if (constructorHostClass.isArray()) { 902 throw new IllegalArgumentException("constructorHostClass.isArray(): " + constructorHostClass.getName()); 903 } 904 905 Converter<T> returnValue = null; 906 final Constructor<T> constructor; 907 Constructor<T> temp = null; 908 try { 909 temp = constructorHostClass.getConstructor(soleParameterType); 910 } catch (final NoSuchMethodException noSuchMethodException) { 911 912 } finally { 913 constructor = temp; 914 } 915 if (constructor != null) { 916 returnValue = new ExecutableBasedConverter<>(constructor); 917 } 918 return returnValue; 919 } 920 921 private static final String[] split(final String text) { 922 final String[] returnValue; 923 if (text == null) { 924 returnValue = new String[0]; 925 } else { 926 returnValue = splitPattern.split(text); 927 assert returnValue != null; 928 for (int i = 0; i < returnValue.length; i++) { 929 returnValue[i] = backslashCommaPattern.matcher(returnValue[i]).replaceAll(","); 930 } 931 } 932 return returnValue; 933 } 934 935 private static final Type getConversionType(final Converter<?> converter) { 936 final Type returnValue = getConversionType(Objects.requireNonNull(converter).getClass(), null, null); 937 assert returnValue != null; 938 return returnValue; 939 } 940 941 // can return null during recursive invocations only 942 private static final Type getConversionType(final Type type, Set<Type> seen, Map<TypeVariable<?>, Type> reifiedTypes) { 943 Type returnValue = null; 944 Objects.requireNonNull(type); 945 if (seen == null) { 946 seen = new HashSet<>(); 947 } 948 if (!seen.contains(type)) { 949 seen.add(type); 950 951 if (reifiedTypes == null) { 952 reifiedTypes = new HashMap<>(); 953 } 954 955 if (type instanceof Class) { 956 final Class<?> c = (Class<?>)type; 957 if (Converter.class.isAssignableFrom(c)) { 958 final Type[] genericInterfaces = c.getGenericInterfaces(); 959 assert genericInterfaces != null; 960 for (final Type genericInterface : genericInterfaces) { 961 returnValue = getConversionType(genericInterface, seen, reifiedTypes); // XXX recursive call 962 if (returnValue != null) { 963 break; 964 } 965 } 966 if (returnValue == null) { 967 returnValue = getConversionType(c.getSuperclass(), seen, reifiedTypes); // XXX recursive call 968 } 969 } 970 971 } else if (type instanceof ParameterizedType) { 972 final ParameterizedType pt = (ParameterizedType)type; 973 final Type rawType = pt.getRawType(); 974 assert rawType instanceof GenericDeclaration; 975 final TypeVariable<?>[] typeParameters = ((GenericDeclaration)rawType).getTypeParameters(); 976 assert typeParameters != null; 977 final Type[] actualTypeArguments = pt.getActualTypeArguments(); 978 assert actualTypeArguments != null; 979 assert actualTypeArguments.length == typeParameters.length; 980 if (actualTypeArguments.length > 0) { 981 for (int i = 0; i < actualTypeArguments.length; i++) { 982 Type actualTypeArgument = actualTypeArguments[i]; 983 if (actualTypeArgument instanceof Class || 984 actualTypeArgument instanceof ParameterizedType) { 985 reifiedTypes.put(typeParameters[i], actualTypeArgument); 986 } else if (actualTypeArgument instanceof TypeVariable) { 987 final Type reifiedType = reifiedTypes.get((TypeVariable)actualTypeArgument); 988 if (reifiedType == null) { 989 reifiedTypes.put((TypeVariable)actualTypeArgument, Object.class); 990 actualTypeArgument = Object.class; 991 } else { 992 actualTypeArgument = reifiedType; 993 } 994 assert actualTypeArgument != null; 995 assert (actualTypeArgument instanceof ParameterizedType || actualTypeArgument instanceof Class) : "Unexpected actualTypeArgument: " + actualTypeArgument; 996 reifiedTypes.put(typeParameters[i], actualTypeArgument); 997 } 998 } 999 } 1000 if (Converter.class.equals(rawType)) { 1001 assert actualTypeArguments.length == 1; 1002 final Type typeArgument = actualTypeArguments[0]; 1003 if (typeArgument instanceof Class || 1004 typeArgument instanceof ParameterizedType) { 1005 returnValue = typeArgument; 1006 } else if (typeArgument instanceof TypeVariable) { 1007 final TypeVariable<?> typeVariable = (TypeVariable<?>)typeArgument; 1008 returnValue = reifiedTypes.get(typeVariable); 1009 assert returnValue instanceof ParameterizedType || returnValue instanceof Class : "Unexpected returnValue: " + returnValue; 1010 } else { 1011 throw new IllegalArgumentException("Unhandled conversion type: " + typeArgument); 1012 } 1013 } else { 1014 returnValue = getConversionType(rawType, seen, reifiedTypes); // XXX recursive call 1015 } 1016 1017 } else { 1018 throw new IllegalArgumentException("Unhandled type: " + type); 1019 } 1020 1021 } 1022 return returnValue; 1023 } 1024 1025 private static final void installDefaultConverters(final Converters converters) { 1026 final StringConverter stringConverter = new StringConverter(); 1027 converters.putConverter(BigDecimal.class, new BigDecimalConverter()); 1028 converters.putConverter(BigInteger.class, new BigIntegerConverter()); 1029 converters.putConverter(Boolean.class, new BooleanConverter()); 1030 converters.putConverter(Byte.class, new ByteConverter()); 1031 converters.putConverter(Calendar.class, new CalendarConverter()); 1032 converters.putConverter(CharSequence.class, stringConverter); 1033 converters.putConverter(Date.class, new DateConverter()); 1034 converters.putConverter(Double.class, new DoubleConverter()); 1035 converters.putConverter(Duration.class, new DurationConverter()); 1036 converters.putConverter(Float.class, new FloatConverter()); 1037 converters.putConverter(Instant.class, new InstantConverter()); 1038 converters.putConverter(Integer.class, new IntegerConverter()); 1039 converters.putConverter(Level.class, new LevelConverter()); 1040 converters.putConverter(LocalDate.class, new LocalDateConverter()); 1041 converters.putConverter(LocalDateTime.class, new LocalDateTimeConverter()); 1042 converters.putConverter(LocalTime.class, new LocalTimeConverter()); 1043 converters.putConverter(Long.class, new LongConverter()); 1044 converters.putConverter(MonthDay.class, new MonthDayConverter()); 1045 converters.putConverter(Object.class, stringConverter); 1046 converters.putConverter(OffsetDateTime.class, new OffsetDateTimeConverter()); 1047 converters.putConverter(OffsetTime.class, new OffsetTimeConverter()); 1048 converters.putConverter(Period.class, new PeriodConverter()); 1049 converters.putConverter(Short.class, new ShortConverter()); 1050 converters.putConverter(String.class, stringConverter); 1051 converters.putConverter(StringBuffer.class, new StringBufferConverter()); 1052 converters.putConverter(StringBuilder.class, new StringBuilderConverter()); 1053 converters.putConverter(URI.class, new URIConverter()); 1054 converters.putConverter(URL.class, new URLConverter()); 1055 converters.putConverter(Year.class, new YearConverter()); 1056 converters.putConverter(YearMonth.class, new YearMonthConverter()); 1057 converters.putConverter(ZonedDateTime.class, new ZonedDateTimeConverter()); 1058 } 1059 1060}