PropertyValueReference.java

package org.cyclopsgroup.caff.ref;

import com.google.common.base.Preconditions;
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;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import javax.annotation.Nullable;

/**
 * Implementation of value holder based on reader and writer methods
 *
 * @author <a href="mailto:jiaqi@cyclopsgroup.org">Jiaqi Guo</a>
 * @param <T> Type of owner object
 */
class PropertyValueReference<T> extends ValueReference<T> {
  private final String name;
  private final Method reader;
  private final boolean readerPublic;
  private final Class<?> type;
  private final Method writer;
  private final boolean writerPublic;

  /**
   * @param descriptor Property descriptor of property
   */
  PropertyValueReference(PropertyDescriptor descriptor) {
    this(
        descriptor.getName(),
        descriptor.getPropertyType(),
        descriptor.getReadMethod(),
        descriptor.getWriteMethod());
  }

  PropertyValueReference(
      String name, Class<?> type, @Nullable Method reader, @Nullable Method writer) {
    this.name = Preconditions.checkNotNull(name, "Property name is required.");
    this.type = Preconditions.checkNotNull(type, "Property type is required.");
    this.reader = reader;
    this.readerPublic = reader != null && (reader.getModifiers() & Modifier.PUBLIC) > 0;
    this.writer = writer;
    this.writerPublic = writer != null && (writer.getModifiers() & Modifier.PUBLIC) > 0;
  }

  @Override
  public <A extends Annotation> A getAnnotation(Class<A> annotationType) {
    return Arrays.asList(reader, writer).stream()
        .filter(m -> m != null)
        .map(m -> m.getAnnotation(annotationType))
        .filter(a -> a != null)
        .findAny()
        .orElse(null);
  }

  @Override
  public ImmutableList<AnnotatedElement> getAnontatedElements() {
    return FluentIterable.<AnnotatedElement>of(reader, writer).filter(e -> e != null).toList();
  }

  @Override
  public final String getName() {
    return name;
  }

  @Override
  public Class<?> getType() {
    return type;
  }

  @Override
  public boolean isReadable() {
    return reader != null;
  }

  @Override
  public boolean isWritable() {
    return writer != null;
  }

  @Override
  public Object readValue(T owner) {
    if (reader == null) {
      throw new IllegalStateException("Property " + name + " isn't readable");
    }
    if (!readerPublic && !reader.isAccessible()) {
      reader.setAccessible(true);
    }
    try {
      return reader.invoke(owner);
    } catch (IllegalAccessException | InvocationTargetException e) {
      throw new AccessFailureException("Can't read property " + name + " from object " + owner, e);
    }
  }

  @Override
  public void writeValue(Object value, T owner) {
    if (writer == null) {
      throw new IllegalStateException("Property " + name + " isn't writable");
    }
    if (value == null && type.isPrimitive()) {
      return;
    }
    if (!writerPublic && !writer.isAccessible()) {
      writer.setAccessible(true);
    }
    try {
      writer.invoke(owner, value);
    } catch (IllegalAccessException | InvocationTargetException e) {
      throw new IllegalStateException(
          "Can't set property " + name + " of object " + owner + " to " + value, e);
    }
  }
}