/*
* Beanfabrics Framework Copyright (C) by Michael Karneim, beanfabrics.org
* Use is subject to license terms. See license.txt.
*/
// TODO javadoc - remove this comment only when the class and all non-public
// methods and fields are documented
package org.beanfabrics.support;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.UndeclaredThrowableException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.beanfabrics.Path;
import org.beanfabrics.PathObservation;
import org.beanfabrics.model.IOperationPM;
import org.beanfabrics.model.OperationPM;
import org.beanfabrics.model.PresentationModel;
import org.beanfabrics.util.ReflectionUtil;
/**
* @author Michael Karneim
*/
// TODO (mk) UNIT TEST
public class OperationSupport implements Support {
public static OperationSupport get(PresentationModel model) {
Supportable s = (Supportable)model;
OperationSupport support = s.getSupportMap().get(OperationSupport.class);
if (support == null) {
support = new OperationSupport(model);
s.getSupportMap().put(OperationSupport.class, support);
}
return support;
}
private final PresentationModel model;
private Map<Method, ExecutionMethodSupport> map = new HashMap<Method, ExecutionMethodSupport>();
public OperationSupport(PresentationModel model) {
this.model = model;
}
public void setup(Method method) {
if (map.containsKey(method) == false) {
ExecutionMethodSupport support = support(model, method);
map.put(method, support);
}
}
private static ExecutionMethodSupport support(PresentationModel pModel, Method m) {
ExecutionMethodSupport result = new ExecutionMethodSupport(pModel, m);
return result;
}
private static class ExecutionMethodSupport {
private final PresentationModel owner;
private final List<PathObservation> obervations = new LinkedList<PathObservation>();
private final Method annotatedMethod;
private final org.beanfabrics.model.ExecutionMethod executionMethod = new org.beanfabrics.model.ExecutionMethod() {
public boolean execute()
throws Throwable {
return callAnnotatedMethod();
}
};
private ExecutionMethodSupport(PresentationModel owner, Method annotatedMethod) {
Operation anno = annotatedMethod.getAnnotation(Operation.class);
if (annotatedMethod.getParameterTypes() != null && annotatedMethod.getParameterTypes().length > 0) {
throw new IllegalArgumentException("method '" + annotatedMethod.getName() + "' must not declare any parameter when annotated with @ExecutionMethod");
}
if (annotatedMethod.getReturnType() != null) {
Class<?> retType = annotatedMethod.getReturnType();
if (!retType.equals(Void.TYPE) && !retType.equals(Boolean.TYPE) && !retType.equals(Boolean.class)) {
throw new IllegalArgumentException("method '" + annotatedMethod.getName() + "' must not declare any return type other than 'void', 'Boolean' or 'boolean' when annotated with @ExecutionMethod");
}
}
this.owner = owner;
this.annotatedMethod = annotatedMethod;
if (anno.path().length == 1 && "#default".equals(anno.path()[0])) {
// "default" handling
if (owner instanceof IOperationPM) {
Path path = Path.parse(Path.THIS_PATH_ELEMENT);
this.obervations.add(new OperationTargetObservation(owner, path));
} else {
Path path = Path.parse(annotatedMethod.getName());
this.obervations.add(new OperationTargetObservation(owner, path));
}
} else {
for (int i = 0; i < anno.path().length; ++i) {
Path path = Path.parse(anno.path()[i]);
this.obervations.add(new OperationTargetObservation(owner, path));
}
}
}
private class OperationTargetObservation extends PathObservation {
private PresentationModel currentTarget = null;
public OperationTargetObservation(PresentationModel root, Path path) {
super(root, path);
this.addPropertyChangeListener(new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent evt) {
addExecutionToTarget();
}
});
this.addExecutionToTarget();
}
public void addExecutionToTarget() {
PresentationModel newTarget = this.getTarget();
if (newTarget == this.currentTarget) {
return; // nothing to do
}
if (this.currentTarget != null && (this.currentTarget instanceof IOperationPM)) {
// TODO (mk) merge IOperationPM and OperationPM
if (this.currentTarget instanceof OperationPM) {
((OperationPM)this.currentTarget).removeExecutionMethod(ExecutionMethodSupport.this.executionMethod);
}
}
this.currentTarget = newTarget;
if (this.currentTarget != null && (this.currentTarget instanceof IOperationPM)) {
// TODO (mk) merge IOperationPM and OperationPM
if (this.currentTarget instanceof OperationPM) {
((OperationPM)this.currentTarget).addExecutionMethod(ExecutionMethodSupport.this.executionMethod);
}
}
}
}
private boolean callAnnotatedMethod()
throws Throwable {
try {
Object result = ReflectionUtil.invokeMethod(owner, annotatedMethod, (Object[])null);
if (result == null) {
return true;
} else {
return ((Boolean)result).booleanValue();
}
} catch (IllegalArgumentException e) {
throw new UndeclaredThrowableException(e);
} catch (IllegalAccessException e) {
throw new UndeclaredThrowableException(e, "IllegalAccessException on invoke '" + annotatedMethod + "'");
} catch (InvocationTargetException e) {
if (e.getTargetException() != null) {
throw e.getTargetException();
}
throw new UndeclaredThrowableException(e);
}
}
}
}