Package com.google.appengine.tools.mapreduce.impl

Source Code of com.google.appengine.tools.mapreduce.impl.BigQueryFieldUtil

package com.google.appengine.tools.mapreduce.impl;

import com.google.appengine.tools.mapreduce.BigQueryDataField;
import com.google.appengine.tools.mapreduce.BigQueryFieldMode;
import com.google.appengine.tools.mapreduce.BigQueryIgnore;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.base.Strings;
import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;

import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Field;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.Member;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
* Utility class for processing {@link Field} and BigQuery data fields.
*/
public final class BigQueryFieldUtil {

  // To ignore the 'this' field present in inner classes.
  private static final String THIS_FIELD_PREFIX = "this$";

  private static final Set<Class<?>> UNSUPPORTED_TYPES =
      new ImmutableSet.Builder<Class<?>>().add(Object.class).build();

  private BigQueryFieldUtil() {
    // utility class
  }

  /**
   * Filters to identify the class {@link Field}s that should not be marshalled to bigquery fields.
   */
  @SuppressWarnings("unchecked")
  private static Predicate<? super Field> ignoreFieldsFilter = Predicates.not(Predicates.or(
      withAnnotation(BigQueryIgnore.class), withModifier(Modifier.TRANSIENT),
      withModifier(Modifier.STATIC), withPrefix(THIS_FIELD_PREFIX)));

  /**
   * @param field - {@link Field} of a class.
   * @return {@link BigQueryDataField} annotated name. If annotation is not present then returns the
   *         name of the field.
   */
  public static String getFieldName(Field field) {
    BigQueryDataField bq = field.getAnnotation(BigQueryDataField.class);
    if (bq != null && !Strings.isNullOrEmpty(bq.name())) {
      return bq.name();
    }
    return field.getName();
  }

  /**
   * @param field - {@link Field} of a class.
   * @return {@link BigQueryDataField} annotation description. Null if annotation is not present.
   */
  public static String getFieldDescription(Field field) {
    BigQueryDataField bq = field.getAnnotation(BigQueryDataField.class);
    if (bq == null || bq.description().equals("")) {
      return null;
    }
    return bq.description();
  }

  /**
   * @param field - {@link Field} of a class.
   * @return {@link BigQueryDataField} annotation mode.
   */
  public static String getFieldMode(Field field) {
    BigQueryDataField bq = field.getAnnotation(BigQueryDataField.class);
    if (bq == null) {
      if (field.getType().isPrimitive()) {
        return BigQueryFieldMode.REQUIRED.getValue();
      }
      return null;
    }
    return bq.mode().getValue();
  }

  /**
   * @param parameterizedType
   * @return runtime type of the generic parameter.
   */
  public static Type getParameterType(ParameterizedType parameterizedType) {
    return parameterizedType.getActualTypeArguments()[0];
  }

  /**
   * @param type
   * @return true if the type can be assigned to a {@link Collection} class
   */
  public static boolean isCollection(Class<?> type) {
    return Collection.class.isAssignableFrom(type);
  }

  /**
   * @param type
   * @return true if the field is a parameterized generic type.
   */
  public static boolean isGenericType(Type type) {
    if (isParameterized(type)) {
      return true;
    }
    // check for case when the type is defined to be parameterized but is used in raw form
    TypeVariable<?>[] typeParameters = ((Class<?>) type).getTypeParameters();
    return typeParameters.length > 0;
  }

  /**
   * @param type
   * @return true if the parameterized type had a parameter declared at compile time
   */
  public static boolean isParameterized(Type type) {
    return type instanceof ParameterizedType;
  }

  /**
   * @param type
   * @return true if it's a collection or an array type.
   */
  public static boolean isCollectionOrArray(Class<?> type) {
    return isCollection(type) || type.isArray();
  }

  /**
   * Returns a set of all the non-transient, non-static fields without the annotation
   * {@link BigQueryIgnore}.
   */
  public static Set<Field> getFieldsToSerialize(Class<?> type) {
    return getAllFields(type, ignoreFieldsFilter);

  }

  /**
   * Bigquery schema cannot be generated in following cases 1. Field is an interface or an abstract
   * type. These types can lead to inconsistent schema at runtime. 2. Generic types. 3. {@link Map}
   * 4. Type having reference to itself as a field.
   */
  static void validateTypeForSchemaMarshalling(Class<?> type) {
    if (isNonCollectionInterface(type) || isAbstract(type)) {
      throw new IllegalArgumentException("Cannot marshal " + type.getSimpleName()
          + ". Interfaces and abstract class cannot be cannot be marshalled into consistent BigQuery data.");
    }
    if (!isCollection(type) && isGenericType(type)) {
      throw new IllegalArgumentException("Cannot marshal " + type.getSimpleName()
          + ". Parameterized type other than Collection<T> cannot be marshalled into consistent BigQuery data.");
    }
    if (Map.class.isAssignableFrom(type)) {
      throw new IllegalArgumentException(
          "Cannot marshal a map into BigQuery data " + type.getSimpleName());
    }
    if (UNSUPPORTED_TYPES.contains(type)) {
      throw new IllegalArgumentException(
          "Type cannot be marshalled into bigquery schema. " + type.getSimpleName());
    }
  }

  private static boolean isNonCollectionInterface(Class<?> type) {
    return !isCollection(type) && type.isInterface();
  }

  private static boolean isAbstract(Class<?> type) {
    // primitive types and Collection interface are abstract. So added this extra check.
    return !type.isPrimitive() && !isCollectionOrArray(type)
        && Modifier.isAbstract(type.getModifiers());
  }

  /**
   * A field of type {@link Collection} must be parameterized for marshalling it into bigquery data
   * as a raw field can lead to ambiguous bigquery table definitions.
   */
  public static void validateCollection(Field field) {
    if (!isParameterized(field.getGenericType())) {
      throw new IllegalArgumentException("Cannot marshal a non-parameterized Collection field "
          + field.getName() + " into BigQuery data");
    }
  }

  /**
   * A field of type Collection<Collection> cannot be marshalled into bigquery data format as
   * parameterized types nested more than one level cannot be determined at runtime. So cannot be
   * marshalled.
   */
  public static void validateNestedRepeatedType(Class<?> parameterType, Field field) {
    if (isCollectionOrArray(parameterType)) {
      throw new IllegalArgumentException(
          " Cannot marshal a nested collection or array field " + field.getName());
    }
  }

  /**
   * Parameterized types nested more than one level cannot be determined at runtime. So cannot be
   * marshalled.
   */
  public static void validateNestedParameterizedType(Type parameterType) {
    if (isParameterized(parameterType)
        || GenericArrayType.class.isAssignableFrom(parameterType.getClass())) {
      throw new IllegalArgumentException(
          "Invalid field. Cannot marshal fields of type Collection<GenericType> or GenericType[].");
    }
  }

  /**
   * Returns type of the parameter or component type for the repeated field depending on whether it
   * is a collection or an array.
   */
  public static Class<?> getParameterTypeOfRepeatedField(Field field) {
    Class<?> componentType = null;
    if (isCollection(field.getType())) {
      validateCollection(field);
      Type parameterType = getParameterType((ParameterizedType) field.getGenericType());
      validateNestedParameterizedType(parameterType);
      componentType = (Class<?>) parameterType;
    } else if (field.getType().isArray()) {
      componentType = field.getType().getComponentType();
      return componentType;
    } else {
      throw new IllegalArgumentException("Unsupported repeated type " + field.getType()
          + " Allowed repeated fields are Collection<T> and arrays");
    }
    validateNestedRepeatedType(componentType, field);
    return componentType;
  }

  static Object getFieldValue(Field field, Object object) {
    try {
      field.setAccessible(true);
      return field.get(object);
    } catch (IllegalArgumentException | IllegalAccessException e) {
      throw new RuntimeException("Failed to read the value of field " + field.getName(), e);
    }
  }

  /**
   * Returns true if the field is annotated as a required bigquery field.
   */
  static boolean isFieldRequired(Field field) {
    BigQueryDataField bqAnnotation = field.getAnnotation(BigQueryDataField.class);

    return (bqAnnotation != null && bqAnnotation.mode().equals(BigQueryFieldMode.REQUIRED))
        || field.getType().isPrimitive();
  }

  /**
   * Returns if a {@link BigqueryFieldMarshaller} exists for the field either in the map provided or
   * the internally maintained list.
   */
  static BigqueryFieldMarshaller findMarshaller(Field field,
      Map<Field, BigqueryFieldMarshaller> marshallers) {
    BigqueryFieldMarshaller marshaller = null;
    if (marshallers != null) {
      marshaller = marshallers.get(field);
    }
    if (marshaller == null) {
      marshaller = BigqueryFieldMarshallers.getMarshaller(field.getType());
    }
    return marshaller;
  }

  private static Set<Field> getAllFields(final Class<?> type, Predicate<? super Field> predicate) {
    Set<Field> result = new HashSet<>();
    for (Class<?> t : getAllSuperTypes(type))
      Collections.addAll(result, t.getDeclaredFields());

    return ImmutableSet.copyOf(Collections2.filter(result, predicate));
  }

  private static Set<Class<?>> getAllSuperTypes(final Class<?> type) {
    Set<Class<?>> result = Sets.newHashSet();
    if (type != null) {
      result.add(type);
      result.addAll(getAllSuperTypes(type.getSuperclass()));
      for (Class<?> inter : type.getInterfaces()) {
        result.addAll(getAllSuperTypes(inter));
      }
    }
    return result;
  }

  private static <T extends AnnotatedElement> Predicate<T> withAnnotation(
      final Class<? extends Annotation> annotation) {
    return new Predicate<T>() {
      @Override
      public boolean apply(T input) {
        return input != null && input.isAnnotationPresent(annotation);
      }
    };
  }

  private static <T extends Member> Predicate<T> withModifier(final int mod) {
    return new Predicate<T>() {
      @Override
      public boolean apply(T input) {
        return input != null && (input.getModifiers() & mod) != 0;
      }
    };
  }

  private static <T extends Member> Predicate<T> withPrefix(final String prefix) {
    return new Predicate<T>() {
      @Override
      public boolean apply(T input) {
        return input != null && input.getName().startsWith(prefix);
      }
    };
  }
}
TOP

Related Classes of com.google.appengine.tools.mapreduce.impl.BigQueryFieldUtil

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.