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}