All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.xj4.lifecycle.LifecycleDescriptor Maven / Gradle / Ivy

The newest version!
/**
 * Copyright 2008 Peach Jean Solutions
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.xj4.lifecycle;

import org.apache.commons.collections15.CollectionUtils;
import org.apache.commons.collections15.ListUtils;
import org.apache.commons.collections15.Transformer;
import org.apache.commons.lang.StringUtils;
import org.xj4.XJ4TestClass;
import org.xj4.util.ServiceLoader;
import org.xj4.spi.TestWrapper;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.*;

/**
 * Refers to one specific field.
 *
 * Note: this class has a natural ordering that is inconsistent with equals. Ordering depends solely on dependencies.
 *
 * @author Jared Bunting 
 */
class LifecycleDescriptor implements Comparable {
  private XJ4TestClass testClass;
  private Field field;
  private ServiceLoader providers;
  private List> dependentClasses;
  private Map, LifecycleDescriptor> dependencyMap;
  private final List lifecycles;

  public LifecycleDescriptor(XJ4TestClass testClass, Field field, ServiceLoader providers) {
    this.testClass = testClass;
    this.field = field;
    this.providers = providers;
    this.lifecycles = retrieveLifecycles();
    this.dependentClasses = determineDependentClasses();
    this.dependencyMap = new HashMap, LifecycleDescriptor>();
  }

  public List> getDependentClasses() {
    return dependentClasses;
  }

  /**
   * Defines which lifecycle descriptor to use to define a particular dependency.
   * @param type the dependency
   * @param descriptor the descriptor to use
   */
  public void satisfyDependency(Class type, LifecycleDescriptor descriptor) {
    if(!dependentClasses.contains(type)) {
      throw new IllegalStateException("This descriptor is not dependent on " + type.getName());
    }
    this.dependencyMap.put(type, descriptor);
  }

  /**
   * Generates test wrappers for this lifecycle.  This is where the management happens (in the wrappers).
   * @param target the test object
   * @return a list of test wrappers
   */
  public List generateTestWrappers(final Object target) {
    List wrappers = new ArrayList();
    for(Lifecycle lifecycle: retrieveLifecycles()) {
      wrappers.add(new LifecycleTestWrapper(lifecycle, target));
    }
    return wrappers;
  }

  /**
   * Returns true if the field handled by this lifecycle descriptor will satisfy a given dependency.
   * @param dependentType the dependency to test for
   * @return true if it will satisfy, false otherwise
   */
  public boolean satisfiesDependency(Class dependentType) {
    return dependentType.isAssignableFrom(field.getType());
  }

  private class LifecycleTestWrapper implements TestWrapper {
    private Lifecycle lifecycle;
    private Object target;
    private Dependencies dependencies;

    private LifecycleTestWrapper(Lifecycle lifecycle, Object target) {
      this.lifecycle = lifecycle;
      this.target = target;
      this.dependencies = new Dependencies() {
        private Collection> deps =
                Arrays.asList(determineDependentClasses(LifecycleTestWrapper.this.lifecycle));
        
        public  T retrieve(Class type) {
          if(!deps.contains(type)) {
            throw new IllegalArgumentException(type.getName() + " was not declared as a dependency.");
          }
          try {
            return (T) dependencyMap.get(type).field.get(LifecycleTestWrapper.this.target);
          } catch (IllegalAccessException e) {
            throw new IllegalStateException("Fields must be accessible.", e);
          }
        }
      };
    }

    public void doBefore() throws Throwable {
      lifecycle.start(testClass, field, target, dependencies);
    }

    public void doAfter() throws Throwable {
      lifecycle.stop(testClass, field, target, dependencies);
    }
  }

  public boolean isInstance() {
    return !Modifier.isStatic(field.getModifiers());    
  }

  @SuppressWarnings({"ThrowableInstanceNeverThrown"})
  public void validate(List errors) {
    if(!field.isAnnotationPresent(Manage.class)) {
      errors.add(new Exception(String.format("Field %s is not managed!", field.getName())));
      return;
    }
    field.setAccessible(true);
    if(lifecycles.isEmpty()) {
      errors.add(new Exception(String.format("No lifecycle providers could be located for managed field %s.", field.getName())));
    }
    if(!dependencyMap.keySet().containsAll(dependentClasses)) {
      Collection unsatisfiedDependencies = CollectionUtils.transformedCollection(
              CollectionUtils.subtract(dependentClasses, dependencyMap.keySet()),
              new Transformer, String>() {
                public String transform(Class aClass) {
                  return aClass.getName();
                }
              });
      errors.add(new Exception(String.format("There are unsatisfied dependencies: ",
              StringUtils.join(unsatisfiedDependencies, ", "))));
    }
    boolean isStatic = Modifier.isStatic(field.getModifiers());
    for(Lifecycle lifecycle : lifecycles) {
      if(isStatic && !lifecycle.supportsClassLevel()) {
        errors.add(new Exception(String.format("Lifecycle type %s does not support static fields.", lifecycle.getClass().getName())));
      }
      if(!isStatic && !lifecycle.supportsInstanceLevel()) {
        errors.add(new Exception(String.format("Lifecycle type %s does not support instance fields.", lifecycle.getClass().getName())));
      }
      if(!lifecycle.requiredType().isAssignableFrom(field.getType())) {
        errors.add(new Exception(String.format("Lifecycle type %s only operates on fields of type %s.", lifecycle.getClass().getName(), lifecycle.requiredType())));
      }
      lifecycle.validate(errors, field);
    }
  }

  /**
   * Retrieves the lifecycles that are applicable
   * @return
   */
  protected List retrieveLifecycles() {
    Class[] lifecycleTypes = getLifecycleClasses();
    List lifecycles = new ArrayList();
    for(Class lifecycleType : lifecycleTypes) {
      try {
        lifecycles.add(lifecycleType.newInstance());
      } catch(Exception ex) {
        throw new IllegalArgumentException("Lifecycle type " + lifecycleType.getName() + "is not instantiable.", ex);
      }
    }
    if(lifecycleTypes.length == 0) {
      LifecycleProvider provider = providers.selectFirst(new LifecycleProviderSelector(field));
      if(provider != null) {
        lifecycles.addAll(provider.handle(field));
      }
    }
    return lifecycles;
  }

  private List> determineDependentClasses() {
    List> dependentClasses = new ArrayList>();
    for(Lifecycle lifecycle: lifecycles) {
      Class[] classes = { };
      classes = determineDependentClasses(lifecycle);
      Collections.addAll(dependentClasses, classes);
    }
    return ListUtils.unmodifiableList(dependentClasses);
  }

  private Class[] determineDependentClasses(Lifecycle lifecycle) {
    if(lifecycle.getClass().isAnnotationPresent(Requires.class)) {
      Requires requires = lifecycle.getClass().getAnnotation(Requires.class);
      return requires.value();
    } else {
      return new Class[0];
    }
  }

  private Class[] getLifecycleClasses() {
    Manage manage = field.getAnnotation(Manage.class);
    if(manage == null) {
      return new Class[0];
    }
    return manage.value().length != 0 ? manage.value() : manage.lifecycle();
  }

  public int compareTo(LifecycleDescriptor lifecycleDescriptor) {
    boolean otherDependent = lifecycleDescriptor.dependsOn(this, null);
    boolean thisDependent = this.dependsOn(lifecycleDescriptor, null);
    if(otherDependent == true) {
      if(thisDependent == true) {
        throw new IllegalStateException("There is a mutual dependency issue - seems like this situation should have " +
                "already caused a CircularDependencyException - an xj4 developer should check the code.");
      } else {
        return -1;
      }
    } else {
      if(thisDependent == true) {
        return 1;
      } else {
        return 0;
      }
    }
  }

  public boolean dependsOn(LifecycleDescriptor lifecycleDescriptor, Stack depStack) {
    if(depStack != null && depStack.contains(this)) {
      throw new CircularDependencyException(this, depStack);
    }
    if(dependencyMap.containsValue(lifecycleDescriptor)) {
      return true;
    }
    if(depStack == null) {
      depStack = new Stack();
    }
    try {
      depStack.push(this);
      for(LifecycleDescriptor dep: dependencyMap.values()) {
        if(dep.dependsOn(lifecycleDescriptor, depStack)) {
          return true;
        }
      }
    } finally {
      depStack.pop();
    }
    return false;
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy