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 extends Lifecycle>[] lifecycleTypes = getLifecycleClasses();
List lifecycles = new ArrayList();
for(Class extends Lifecycle> 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 extends Lifecycle>[] 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