/*
* Copyright 2006 the original author or authors.
*
* 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 jdave.runner;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import jdave.ExpectationFailedException;
import jdave.Specification;
import jdave.util.Fields;
/**
* @author Joni Freeman
* @author Pekka Enberg
*/
public class ExecutingBehavior extends Behavior {
private final Class<?> contextType;
private final Class<? extends Specification<?>> specType;
private Object context;
public ExecutingBehavior(Method method, Class<? extends Specification<?>> specType, Class<?> contextType) {
super(contextType, method);
this.specType = specType;
this.contextType = contextType;
}
@Override
public void run(final IBehaviorResults results) {
try {
Specification<?> spec = newSpecification();
if (spec.needsThreadLocalIsolation()) {
runInNewThread(results, spec);
} else {
runInCurrentThread(results, spec);
}
} catch (RuntimeException e) {
results.error(method, e);
}
}
private void runInCurrentThread(final IBehaviorResults results, final Specification<?> spec) {
runSpec(results, spec);
}
private void runInNewThread(final IBehaviorResults results, final Specification<?> spec) {
ExecutorService executor = Executors.newSingleThreadExecutor(new ThreadFactory() {
public Thread newThread(Runnable r) {
return new Thread(r);
}
});
executor.submit(new Callable<Void>() {
public Void call() throws Exception {
runSpec(results, spec);
return null;
}
});
executor.shutdown();
try {
executor.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
private void runSpec(IBehaviorResults results, Specification<?> spec) {
boolean error = false;
try {
spec.create();
context = newContext(spec);
method.invoke(context);
spec.verifyMocks();
results.expected(method);
} catch (InvocationTargetException e) {
error = true;
if (e.getCause().getClass().equals(ExpectationFailedException.class)) {
results.unexpected(method, (ExpectationFailedException) e.getCause());
} else {
results.error(method, e.getCause());
}
} catch (ExpectationFailedException e) {
error = true;
results.unexpected(method, e);
} catch (Throwable t) {
error = true;
results.error(method, t);
} finally {
try {
destroy(spec);
} catch (Throwable e) {
// Do not mask the first error.
if (!error) {
throw new RuntimeException(e);
}
}
}
}
private void destroy(Specification<?> spec) throws Exception {
try {
destroyContext();
} finally {
try {
spec.fireAfterContextDestroy(context);
} finally {
spec.destroy();
}
}
}
protected Specification<?> newSpecification() {
try {
return specType.newInstance();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
protected Object newContext(Specification<?> spec) throws Exception {
Object context = newContextInstance(spec);
spec.fireAfterContextInstantiation(context);
Object contextObject = spec.getContextObjectFactory().newContextObject(context);
Fields.set(spec, "be", contextObject);
Fields.set(spec, "context", contextObject);
spec.fireAfterContextCreation(context, contextObject);
return context;
}
protected void destroyContext() throws Exception {
if (context != null) {
invokeDisposer(context);
}
}
private Object newContextInstance(Specification<?> spec) {
try {
Constructor<?> constructor = contextType.getDeclaredConstructor(contextType.getEnclosingClass());
return constructor.newInstance(spec);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private void invokeDisposer(Object context) throws Exception {
Method method;
try {
method = context.getClass().getMethod(DefaultSpecIntrospection.DISPOSER_NAME);
method.invoke(context);
} catch (NoSuchMethodException e) {
// destroy method is not required
}
}
}