AnnotatedConverter.java

package org.cyclopsgroup.caff.conversion;

import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import java.beans.PropertyDescriptor;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;

/**
 * Converter that converts based on rules defined in annotation
 *
 * @author <a href="mailto:jiaqi@cyclopsgroup.org">Jiaqi Guo</a>
 * @param <T> Type of value to converter from/to
 */
public class AnnotatedConverter<T> implements Converter<T> {
  private static class Builder<T> {
    private Annotation annotation;

    @SuppressWarnings("unchecked")
    private Converter<T> toConverter(Class<T> type) {
      if (annotation == null) {
        return new SimpleConverter<T>(type);
      }
      ConversionSupport support =
          annotation.annotationType().getAnnotation(ConversionSupport.class);
      if (support == null) {
        throw new AssertionError(
            "Annotation " + annotation + " is not annotated with " + ConversionSupport.class);
      }
      try {
        ConverterFactory<T> factory = (ConverterFactory<T>) support.factoryType().newInstance();
        Converter<T> converter = factory.getConverterFor(type, annotation);
        return new NullFriendlyConverter<T>(converter);
      } catch (InstantiationException e) {
        throw new IllegalStateException("Can't create converter for " + annotation, e);
      } catch (IllegalAccessException e) {
        throw new IllegalStateException("Can't create converter for " + annotation, e);
      }
    }

    private Builder<T> withAccess(ImmutableList<AnnotatedElement> elements) {
      for (AnnotatedElement access : elements) {
        if (access == null) {
          continue;
        }
        for (Annotation a : access.getAnnotations()) {
          if (a.annotationType().isAnnotationPresent(ConversionSupport.class)) {
            annotation = a;
            return this;
          }
        }
      }
      return this;
    }

    private Builder<T> withAnnotation(Annotation annotation) {
      this.annotation = annotation;
      return this;
    }
  }

  private final Converter<T> proxy;

  /**
   * Constructor with an annotated element with annotations.
   *
   * @param type the kind of bean to convert from/to.
   * @param firstElement is the first element in array to add.
   * @param additionalElements the rest of elements as an array.
   */
  public AnnotatedConverter(
      Class<T> type, AnnotatedElement firstElement, AnnotatedElement... additionalElements) {
    this(type, FluentIterable.of(firstElement).append(additionalElements).toList());
  }

  /**
   * Constructor with given Annotation
   *
   * @param type Value type to convert from/to
   * @param annotation Annotation that defines conversion rule
   */
  public AnnotatedConverter(Class<T> type, Annotation annotation) {
    this.proxy = new Builder<T>().withAnnotation(annotation).toConverter(type);
  }

  /**
   * Constructor with an annotated element with annotations.
   *
   * @param type Type to convert from/to.
   * @param elements Iterable of annotated elements.
   */
  public AnnotatedConverter(Class<T> type, Iterable<AnnotatedElement> elements) {
    this.proxy = new Builder<T>().withAccess(ImmutableList.copyOf(elements)).toConverter(type);
  }

  /**
   * Constructor with a property descriptor
   *
   * @param type Type of value to convert
   * @param descriptor Property descriptor
   */
  public AnnotatedConverter(Class<T> type, PropertyDescriptor descriptor) {
    this.proxy =
        new Builder<T>()
            .withAccess(ImmutableList.of(descriptor.getReadMethod(), descriptor.getWriteMethod()))
            .toConverter(type);
  }

  @Override
  public T fromCharacters(CharSequence text) {
    return proxy.fromCharacters(text);
  }

  @Override
  public CharSequence toCharacters(T value) {
    return proxy.toCharacters(value);
  }
}