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.converter;
018
019import java.lang.reflect.Array;
020import java.lang.reflect.ParameterizedType;
021import java.lang.reflect.Type;
022
023import java.util.Collection;
024import java.util.Objects;
025
026import java.util.function.Function;
027
028import java.util.regex.Pattern;
029
030import javax.enterprise.inject.Vetoed;
031
032import org.microbean.settings.Converter;
033import org.microbean.settings.Value;
034
035/**
036 * An abstract class that can serve as the base class for
037 * implementations of, but which deliberately does not itself
038 * implement, the {@link Converter} interface.
039 *
040 * @author <a href="https://about.me/lairdnelson"
041 * target="_parent">Laird Nelson</a>
042 *
043 * @see Converter
044 *
045 * @see #convert(Value)
046 */
047@Vetoed
048public abstract class ArrayConverter<T> {
049
050
051  /*
052   * Static fields.
053   */
054
055  
056  private static final long serialVersionUID = 1L;
057
058
059  /*
060   * Instance fields.
061   */
062
063  
064  private final Class<?> arrayComponentType;
065  
066  private final Converter<? extends Collection<? extends T>> collectionConverter;
067
068
069  /*
070   * Constructors.
071   */
072
073  
074  /**
075   * Creates a new {@link ArrayConverter}.
076   *
077   * @param collectionConverter a {@link Converter} that returns
078   * {@link Collection}s whose elements are of the appropriate type
079   * from its {@link Converter#convert(Value)} method; must not be
080   * {@code null}
081   *
082   * @exception NullPointerException if {@code collectionConverter} is
083   * {@code null}
084   *
085   * @see #convert(Value)
086   */
087  protected ArrayConverter(final Converter<? extends Collection<? extends T>> collectionConverter) {
088    super();
089    Type genericSuperclass = this.getClass();
090    while (genericSuperclass instanceof Class) {
091      genericSuperclass = ((Class<?>)genericSuperclass).getGenericSuperclass();
092    }
093    assert genericSuperclass instanceof ParameterizedType : "Unexpected genericSuperclass: " + genericSuperclass;
094    final ParameterizedType genericSupertype = (ParameterizedType)genericSuperclass;
095    final Type[] actualTypeArguments = genericSupertype.getActualTypeArguments();
096    assert actualTypeArguments != null;
097    assert actualTypeArguments.length == 1;
098    final Type firstTypeArgument = actualTypeArguments[0];
099    assert firstTypeArgument instanceof Class;
100    this.arrayComponentType = (Class<?>)firstTypeArgument;
101    this.collectionConverter = Objects.requireNonNull(collectionConverter);
102  }
103
104
105  /*
106   * Instance methods.
107   */
108  
109
110  /**
111   * Converts the supplied {@link Value} into an appropriately-typed
112   * array and returns the result.
113   *
114   * @param value the {@link Value} to convert; may be {@code null}
115   *
116   * @return an appropriately-typed array, or {@code null}
117   *
118   * @nullability This method does and its overrides are permitted to
119   * return {@code null}.
120   *
121   * @idempotency No guarantees are made about the idempotency of this
122   * method or its overrides.
123   *
124   * @threadsafety This method is and its overrides must be safe for
125   * concurrent use by multiple threads.
126   *
127   * @see Converter#convert(Value)
128   */
129  public T[] convert(final Value value) {
130    final Collection<? extends T> collection = this.collectionConverter.convert(value);
131    final T[] returnValue;
132    if (collection == null) {
133      returnValue = null;
134    } else {
135      @SuppressWarnings("unchecked")
136      final T[] targetArray = (T[])Array.newInstance(this.arrayComponentType, collection.size());
137      returnValue = collection.toArray(targetArray);
138    }
139    return returnValue;
140  }
141
142}