Package com.googlecode.objectify.impl.translate

Source Code of com.googlecode.objectify.impl.translate.ClassPopulator

package com.googlecode.objectify.impl.translate;

import com.google.appengine.api.datastore.PropertyContainer;
import com.google.common.base.Predicate;
import com.googlecode.objectify.ObjectifyFactory;
import com.googlecode.objectify.annotation.AlsoLoad;
import com.googlecode.objectify.annotation.Id;
import com.googlecode.objectify.annotation.Ignore;
import com.googlecode.objectify.annotation.Index;
import com.googlecode.objectify.annotation.OnLoad;
import com.googlecode.objectify.annotation.OnSave;
import com.googlecode.objectify.annotation.Parent;
import com.googlecode.objectify.annotation.Unindex;
import com.googlecode.objectify.impl.FieldProperty;
import com.googlecode.objectify.impl.MethodProperty;
import com.googlecode.objectify.impl.Path;
import com.googlecode.objectify.impl.Property;
import com.googlecode.objectify.impl.PropertyPopulator;
import com.googlecode.objectify.impl.TypeUtils;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
* <p>Used by translators to populate properties between POJO and PropertiesContainer. Unlike
* translators, this does not create the POJO or container, it just copies translated properties
* between them.</p>
*
* <p>Always excludes the key fields, @Id and @Parent.</p>
*
* @author Jeff Schnitzer <jeff@infohazard.org>
*/
public class ClassPopulator<P> implements Populator<P>
{
  private static final Logger log = Logger.getLogger(ClassPopulator.class.getName());

  /** We do not persist fields with any of these modifiers */
  private static final int NOT_SAVEABLE_MODIFIERS = Modifier.FINAL | Modifier.STATIC;

  /** We don't want to include the key fields in population */
  private static final Predicate<Property> INCLUDED_FIELDS = new Predicate<Property>() {
    @Override
    public boolean apply(Property prop) {
      return prop.getAnnotation(Id.class) == null && prop.getAnnotation(Parent.class) == null;
    }
  };

  /** */
  private final Class<P> clazz;

  /** Populator for the superclass */
  private final Populator<? super P> superPopulator;

  /** Only includes fields declared on this class */
  private final List<PropertyPopulator<Object, Object>> props = new ArrayList<>();

  /** Three-state index instruction for the whole class. Null means "leave it as-is". */
  private final Boolean indexInstruction;

  /** */
  private final List<LifecycleMethod> onSaveMethods = new ArrayList<>();
  private final List<LifecycleMethod> onLoadMethods = new ArrayList<>();

  /**
   */
  public ClassPopulator(Class<P> clazz, CreateContext ctx, Path path) {
    this.clazz = clazz;

    // Recursively climb the superclass chain
    this.superPopulator = ctx.getPopulator(clazz.getSuperclass(), path);

    if (log.isLoggable(Level.FINEST))
      log.finest("Creating class translator for " + clazz.getName() + " at path '"+ path + "'");

    indexInstruction = getIndexInstruction(clazz);

    // Find all the basic properties
    for (Property prop: getDeclaredProperties(ctx.getFactory(), clazz)) {
      if (INCLUDED_FIELDS.apply(prop)) {
        Path propPath = path.extend(prop.getName());
        try {
          Translator<Object, Object> translator = ctx.getTranslator(new TypeKey<>(prop), ctx, propPath);
          PropertyPopulator<Object, Object> tprop = new PropertyPopulator<>(prop, translator);
          props.add(tprop);
        } catch (Exception ex) {
          // Catch any errors during this process and wrap them in an exception that exposes more useful information.
          propPath.throwIllegalState("Error registering " + clazz.getName(), ex);
        }
      }
    }

    // Find the @OnSave methods
    for (Method method: clazz.getDeclaredMethods()) {
      if (method.isAnnotationPresent(OnSave.class))
        onSaveMethods.add(new LifecycleMethod(method));

      if (method.isAnnotationPresent(OnLoad.class))
        onLoadMethods.add(new LifecycleMethod(method));
    }
  }

  /* */
  @Override
  public void load(PropertyContainer node, LoadContext ctx, Path path, final P into) {
    superPopulator.load(node, ctx, path, into);

    ctx.enterContainerContext(into);
    try {
      for (PropertyPopulator<Object, Object> prop: props) {
        prop.load(node, ctx, path, into);
      }
    } finally {
      ctx.exitContainerContext(into);
    }

    // If there are any @OnLoad methods, call them after everything else
    if (!onLoadMethods.isEmpty()) {
      ctx.defer(new Runnable() {
        @Override
        public void run() {
          for (LifecycleMethod method: onLoadMethods)
            method.execute(into);
        }

        @Override
        public String toString() {
          return "(deferred invoke " + clazz + " @OnLoad callbacks on " + into + ")";
        }
      });
    }
  }

  /* */
  @Override
  public void save(P pojo, boolean index, SaveContext ctx, Path path, PropertyContainer into) {
    // Must do @OnSave methods first
    if (!ctx.skipLifecycle() && !onSaveMethods.isEmpty())
      for (LifecycleMethod method: onSaveMethods)
        method.execute(pojo);

    superPopulator.save(pojo, index, ctx, path, into);

    if (indexInstruction != null)
      index = indexInstruction;

    for (PropertyPopulator<Object, Object> prop: props) {
      prop.save(pojo, index, ctx, path, into);
    }
  }

  /**
   * Figure out if there is an index instruction for the whole class.
   * @return true, false, or null (which means no info)
   */
  private Boolean getIndexInstruction(Class<P> clazz) {
    Index ind = clazz.getAnnotation(Index.class);
    Unindex unind = clazz.getAnnotation(Unindex.class);

    if (ind != null && unind != null)
      throw new IllegalStateException("You cannot have @Index and @Unindex on the same class: " + clazz);

    if (ind != null)
      return true;
    else if (unind != null)
      return false;
    else
      return null;
  }

  /**
   * Determine if we should create a Property for the field.  Things we ignore:  static, final, @Ignore, synthetic
   */
  private boolean isOfInterest(Field field) {
    return !field.isAnnotationPresent(Ignore.class)
        && ((field.getModifiers() & NOT_SAVEABLE_MODIFIERS) == 0)
        && !field.isSynthetic()
        && !field.getName().startsWith("bitmap$init")// Scala adds a field bitmap$init$0 and bitmap$init$1 etc
  }

  /**
   * Determine if we should create a Property for the method (ie, @AlsoLoad)
   */
  private  boolean isOfInterest(Method method) {
    for (Annotation[] annos: method.getParameterAnnotations())
      if (TypeUtils.getAnnotation(annos, AlsoLoad.class) != null)
        return true;

    return false;
  }

  /**
   * Get all the persistable fields and methods declared on a class. Ignores superclasses.
   *
   * @return the fields we load and save, including @Id and @Parent fields. All fields will be set accessable
   *  and returned in order of declaration.
   */
  private List<Property> getDeclaredProperties(ObjectifyFactory fact, Class<?> clazz) {
    List<Property> good = new ArrayList<>();

    for (Field field: clazz.getDeclaredFields())
      if (isOfInterest(field))
        good.add(new FieldProperty(fact, clazz, field));

    for (Method method: clazz.getDeclaredMethods())
      if (isOfInterest(method))
        good.add(new MethodProperty(method));

    return good;
  }
}
TOP

Related Classes of com.googlecode.objectify.impl.translate.ClassPopulator

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.