/**
* Copyright 2010-2014 Jingjing Li.
*
* This file is part of jplot2d.
*
* jplot2d is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or any later version.
*
* jplot2d is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Lesser Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with jplot2d. If not, see .
*/
package org.jplot2d.env;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;
import org.jplot2d.element.Element;
import org.jplot2d.element.impl.ComponentEx;
import org.jplot2d.element.impl.ElementEx;
import org.jplot2d.element.impl.InvokeStep;
import org.jplot2d.element.impl.Joinable;
/**
* This InvocationHandler intercept calls on proxy objects, and wrap the calls with environment lock.
*
* @author Jingjing Li
*/
public class ElementIH implements InvocationHandler {
private final InterfaceInfo iinfo;
/**
* Guarded by EnvLock
*/
private ElementEx impl;
/**
* This field must be read within Environment Global LOCK
*/
protected volatile Environment environment;
/**
* @param impl
* @param clazz
*/
public ElementIH(ElementEx impl, Class> clazz) {
this.impl = impl;
iinfo = InterfaceInfo.loadInterfaceInfo(clazz);
}
/**
* This method replace the impl with the given impl, when undoing or redoing.
*
* MUST called within environment begin-end block.
*
* @param impl
*/
void replaceImpl(ElementEx impl) {
this.impl = impl;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
/* quick equals */
if (method.getName().equals("equals")) {
if (proxy == args[0]) {
return Boolean.TRUE;
}
if (args[0] == null) {
return Boolean.FALSE;
}
}
/* listener methods */
if (iinfo.isListenerMethod(method)) {
try {
return method.invoke(impl, args);
} catch (InvocationTargetException ex) {
throw ex.getCause();
}
}
/* ElementEx */
if (method.getName().equals("getEnvironment")) {
synchronized (Environment.getGlobalLock()) {
return environment;
}
}
if (method.getName().equals("setEnvironment")) {
assert Thread.holdsLock(Environment.getGlobalLock());
environment = (Environment) args[0];
return null;
}
if (method.getName().equals("getImpl")) {
return impl;
}
/* getComponent(int n) */
if (iinfo.isGetCompMethod(method)) {
return invokeGetCompMethod(method, args);
}
/* getComponents() */
if (iinfo.isGetCompArrayMethod(method)) {
return invokeGetCompArrayMethod(method, args);
}
/* addComponent(Component comp) */
if (iinfo.isAddCompMethod(method)) {
if (args[0] instanceof Object[]) {
invokeAddCompArrayMethod(method, args);
} else {
invokeAddCompMethod(method, args);
}
return null;
}
/* removeComponent(Component comp) */
if (iinfo.isRemoveCompMethod(method)) {
invokeRemoveCompMethod(method, args);
return null;
}
/* set(Element) as a join reference. For example, axis join an axis tick manager */
if (iinfo.isJoinElementMethod(method)) {
invokeJoinElementMethod(method, args);
return null;
}
/* set(Element element) as a reference. For example, layer reference an axis range manager */
if (iinfo.isRefElementMethod(method)) {
invokeSetRefElementMethod(method, args);
return null;
}
/* set(Element, Element) as references. For example, layer reference 2 axis range managers */
if (iinfo.isRef2ElementMethod(method)) {
invokeSetRef2ElementMethod(method, args);
return null;
}
/* addComponent(Component comp, Element e1, Elemente2) */
if (iinfo.isAddRef2Method(method)) {
invokeAddRefMethod(method, args, 2);
return null;
}
environment.begin();
try {
if (method.getName().equals("equals")) {
return proxy == args[0];
}
if (method.getName().equals("hashCode")) {
return impl.hashCode();
}
if (method.getName().equals("toString")) {
return "Proxy@" + System.identityHashCode(proxy) + "(" + impl.toString() + ")";
}
/* property getter */
if (iinfo.isPropReadMethod(method)) {
return invokeGetter(method);
}
} finally {
environment.end();
}
environment.beginCommand(method.getName());
try {
if (iinfo.isPropWriteMethod(method)) {
/* property setter method */
boolean propChanged = invokeSetter(method, args);
if (propChanged) {
// fire PropertyChanged
environment.elementPropertyChanged((ElementEx) impl);
}
} else {
/* other method */
invokeOther(method, args);
// fire PropertyChanged
environment.elementPropertyChanged((ElementEx) impl);
}
} finally {
environment.endCommand();
}
return null;
}
private Object invokeGetCompMethod(Method method, Object[] args) throws Throwable {
synchronized (Environment.getGlobalLock()) {
environment.begin();
}
try {
ElementEx compImpl = (ElementEx) method.invoke(impl, args);
return environment.getProxy(compImpl);
} catch (InvocationTargetException e) {
throw e.getCause();
} finally {
environment.end();
}
}
private Object invokeGetCompArrayMethod(Method method, Object[] args) throws Throwable {
synchronized (Environment.getGlobalLock()) {
environment.begin();
}
try {
Object compImpls = method.invoke(impl, args);
int length = Array.getLength(compImpls);
Object result = Array.newInstance(method.getReturnType().getComponentType(), length);
for (int i = 0; i < length; i++) {
Array.set(result, i, environment.getProxy((ElementEx) Array.get(compImpls, i)));
}
return result;
} catch (InvocationTargetException e) {
throw e.getCause();
} finally {
environment.end();
}
}
/**
* Called when adding a array of child components. The array of child components is the 1st argument of the calling
* method.
*
* @param method
* @param args
* the arguments
* @throws Throwable
*/
private void invokeAddCompArrayMethod(Method method, Object[] args) throws Throwable {
if (((Object[]) args[0]).length == 0) {
return;
}
Environment penv;
Environment cenv;
synchronized (Environment.getGlobalLock()) {
// local safe copy
penv = environment;
Object[] arg0 = (Object[]) args[0];
cenv = ((Element) arg0[0]).getEnvironment();
for (int i = 1; i < arg0.length; i++) {
if (cenv != ((Element) arg0[i]).getEnvironment()) {
throw new IllegalArgumentException("The components to be added must belong to the same envoriment.");
}
}
Class> arg0LcType = method.getParameterTypes()[0].getComponentType();
Object cimpls = Array.newInstance(arg0LcType, arg0.length);
cenv.beginCommand(method.getName());
for (int i = 0; i < arg0.length; i++) {
ComponentEx cimpl = (ComponentEx) ((ElementAddition) arg0[i]).getImpl();
if (cimpl.getParent() != null) {
cenv.endCommand();
throw new IllegalArgumentException(
"At lease one of the components to be added already has a parent.");
}
Array.set(cimpls, i, cimpl);
}
penv.beginCommand(method.getName());
logCommand(method, args);
Object[] cargs = args.clone();
cargs[0] = cimpls;
try {
method.invoke(impl, cargs);
} catch (InvocationTargetException e) {
penv.endCommand();
cenv.endCommand();
throw e.getCause();
}
// update environment for all adding components
for (Element proxy : cenv.proxyMap.values()) {
((ElementAddition) proxy).setEnvironment(penv);
}
for (int i = 0; i < Array.getLength(cimpls); i++) {
penv.componentAdded((ComponentEx) Array.get(cimpls, i), cenv);
}
}
penv.endCommand();
cenv.endCommand();
}
/**
* Called when adding a child component. The child component is the 1st argument of the calling method.
*
* @param method
* @param args
* the arguments
* @throws Throwable
*/
private void invokeAddCompMethod(Method method, Object[] args) throws Throwable {
ElementAddition cproxy = (ElementAddition) args[0];
ComponentEx cimpl;
Environment penv;
Environment cenv;
synchronized (Environment.getGlobalLock()) {
// local safe copy
penv = environment;
cenv = ((Element) args[0]).getEnvironment();
cenv.beginCommand(method.getName());
cimpl = (ComponentEx) cproxy.getImpl();
if (cimpl.getParent() != null) {
cenv.endCommand();
throw new IllegalArgumentException("The component to be added already has a parent.");
}
penv.beginCommand(method.getName());
logCommand(method, args);
Object[] cargs = args.clone();
cargs[0] = cimpl;
try {
method.invoke(impl, cargs);
} catch (InvocationTargetException e) {
penv.endCommand();
cenv.endCommand();
throw e.getCause();
}
// update environment for all adding components
for (Element proxy : cenv.proxyMap.values()) {
((ElementAddition) proxy).setEnvironment(penv);
}
penv.componentAdded(cimpl, cenv);
}
penv.endCommand();
cenv.endCommand();
}
/**
* @param method
* @param object
* the component to added
* @throws Throwable
*/
private void invokeRemoveCompMethod(Method method, Object[] args) throws Throwable {
ElementAddition cproxy = (ElementAddition) args[0];
Throwable throwable = null;
Environment penv;
Environment cenv;
synchronized (Environment.getGlobalLock()) {
penv = environment;
penv.beginCommand(method.getName());
ComponentEx cimpl = (ComponentEx) cproxy.getImpl();
if (cimpl.getParent() != impl) {
throwable = new IllegalArgumentException("The component to be removed dosn't belong to this container.");
} else {
// check removable
Map mooringMap = cimpl.getMooringMap();
if (mooringMap.size() > 0) {
String msg = "The removing is not allowed, because some element is required.\n";
for (Map.Entry me : mooringMap.entrySet()) {
msg += "\t" + me.getKey() + " is required by " + me.getValue() + "\n";
}
throwable = new IllegalStateException(msg);
} else {
logCommand(method, args);
// notify environment a component is going to be removed
penv.componentRemoving(cimpl);
// remove the component
Object[] cargs = args.clone();
cargs[0] = cimpl;
try {
method.invoke(impl, cargs);
} catch (InvocationTargetException e) {
throwable = e.getCause();
}
}
}
// finish the removing
if (throwable == null) {
cenv = penv.componentRemoved(cimpl);
// update environment for the removing component
for (Element proxy : cenv.proxyMap.values()) {
((ElementAddition) proxy).setEnvironment(cenv);
}
}
}
penv.endCommand();
if (throwable != null) {
throw throwable;
}
}
/**
* Sets args[0] as a join reference. Such as axis join a axis tick manager, or axis tick manager join a axis range
* manager, or axis range manager join a axis lock group.
*
* @param method
* @param object
* the component to added
* @throws Throwable
*/
private void invokeJoinElementMethod(Method method, Object[] args) throws Throwable {
if (args[0] == null) {
throw new IllegalArgumentException("Null is not a valid argument.");
}
// the join target belongs to a dummy env, bring it to this env.
boolean add = false;
ElementEx cimpl;
Environment env;
Environment cenv;
synchronized (Environment.getGlobalLock()) {
env = environment;
cenv = ((Element) args[0]).getEnvironment();
cenv.beginCommand(method.getName());
cimpl = ((ElementAddition) args[0]).getImpl();
if (env != cenv) {
if (cimpl instanceof Joinable) {
if (((Joinable) cimpl).getPrim() != null) {
cenv.endCommand();
throw new IllegalArgumentException(
"The element to be referenced has been referenced by element from another environment.");
}
} else {
cenv.endCommand();
throw new IllegalArgumentException("The argument is not referencable.");
}
add = true;
env.beginCommand(method.getName());
}
Method reader = iinfo.getPropReadMethodByWriteMethod(method);
Object oldRef = invokeGetter(reader);
if (cimpl == oldRef) {
if (add) {
cenv.endCommand();
}
env.endCommand();
return;
}
logCommand(method, args);
// join the new child
Object[] cargs = args.clone();
cargs[0] = cimpl;
try {
method.invoke(impl, cargs);
} catch (InvocationTargetException e) {
env.endCommand();
if (add) {
cenv.endCommand();
}
throw e.getCause();
}
// update environment for all adding components
for (Element proxy : cenv.proxyMap.values()) {
((ElementAddition) proxy).setEnvironment(env);
}
if (add) {
env.elementAdded(cimpl, cenv);
}
// remove the old child if it's orphan
if (oldRef != null && ((Joinable) oldRef).getPrim() == null) {
Environment nenv = env.componentRemoved((ElementEx) oldRef);
// update environment for the removing component
for (Element proxy : nenv.proxyMap.values()) {
((ElementAddition) proxy).setEnvironment(nenv);
}
}
}
env.endCommand();
if (add) {
cenv.endCommand();
}
}
/**
* Called when setting a reference to the element args[0]. The referred component must belong to the same
* environment. For example, layer refer to x axis range manager.
*
* @param method
* @param args
* the arguments
* @throws Throwable
*/
private void invokeSetRefElementMethod(Method method, Object[] args) throws Throwable {
if (args[0] == null) {
throw new IllegalArgumentException("Null is not a valid argument.");
}
Element cimpl;
Environment env;
synchronized (Environment.getGlobalLock()) {
// local safe copy
env = environment;
Environment cenv = ((Element) args[0]).getEnvironment();
if (env != cenv) {
throw new IllegalArgumentException("Must belongd to the same environment");
}
env.beginCommand(method.getName());
cimpl = ((ElementAddition) args[0]).getImpl();
}
logCommand(method, args);
Object[] cargs = args.clone();
cargs[0] = cimpl;
try {
method.invoke(impl, cargs);
} catch (InvocationTargetException e) {
throw e.getCause();
} finally {
env.endCommand();
}
}
/**
* Called when setting a reference to the element args[0] and args[1]. The referred component must belong to the
* same environment. For example, layer refer to x axis range manager and y axis range manager.
*
* @param method
* @param args
* the arguments
* @throws Throwable
*/
private void invokeSetRef2ElementMethod(Method method, Object[] args) throws Throwable {
if (args[0] == null || args[1] == null) {
throw new IllegalArgumentException("Null is not a valid argument.");
}
Element cimpl0, cimpl1;
Environment env;
synchronized (Environment.getGlobalLock()) {
// local safe copy
env = environment;
if (((Element) args[0]).getEnvironment() != env) {
throw new IllegalArgumentException("Must belongd to the same environment");
}
if (((Element) args[1]).getEnvironment() != env) {
throw new IllegalArgumentException("Must belongd to the same environment");
}
env.beginCommand(method.getName());
cimpl0 = ((ElementAddition) args[0]).getImpl();
cimpl1 = ((ElementAddition) args[1]).getImpl();
}
logCommand(method, args);
Object[] cargs = args.clone();
cargs[0] = cimpl0;
cargs[1] = cimpl1;
try {
method.invoke(impl, cargs);
} catch (InvocationTargetException e) {
throw e.getCause();
} finally {
env.endCommand();
}
}
/**
* Called when adding a component and set n references at the same time.
*
* @param method
* @param args
* @param nref
* @throws Throwable
*/
private void invokeAddRefMethod(Method method, Object[] args, int nref) throws Throwable {
ElementAddition cproxy = (ElementAddition) args[0];
ComponentEx cimpl;
Object[] cargs = args.clone();
Environment penv;
Environment cenv;
synchronized (Environment.getGlobalLock()) {
// local safe copy
penv = environment;
cenv = ((Element) args[0]).getEnvironment();
cenv.beginCommand(method.getName());
cimpl = (ComponentEx) cproxy.getImpl();
if (cimpl.getParent() != null) {
cenv.endCommand();
throw new IllegalArgumentException("The component to be added already has a parent.");
}
penv.beginCommand(method.getName());
logCommand(method, args);
// update environment for all adding components
for (Element proxy : cenv.proxyMap.values()) {
((ElementAddition) proxy).setEnvironment(penv);
}
for (int i = 1; i <= nref; i++) {
if (((Element) args[i]).getEnvironment() != penv) {
throw new IllegalArgumentException("The argument " + args[i]
+ " must belong to the environment of the container.");
}
cargs[i] = (args[i] == null) ? null : ((ElementAddition) args[i]).getImpl();
}
}
cargs[0] = cimpl;
try {
method.invoke(impl, cargs);
penv.componentAdded(cimpl, cenv);
} catch (InvocationTargetException e) {
throw e.getCause();
} finally {
penv.endCommand();
cenv.endCommand();
}
}
protected Object invokeGetter(Method method) throws Throwable {
try {
return method.invoke(impl);
} catch (InvocationTargetException e) {
throw e.getCause();
}
}
/**
* This method invoke reader 1st, only when the setting value is different, the setter method is called.
*
* @param method
* the setter method
* @param args
* arguments
* @return true if the setter method is called.
* @throws Throwable
*/
protected boolean invokeSetter(Method method, Object[] args) throws Throwable {
if (iinfo.isPropWriteDisabled(method)) {
throw new UnsupportedOperationException("The property setter " + method.getName() + " is unsupported.");
}
// the old value is retrieved from concrete engine
Object oldValue = null;
Method reader = iinfo.getPropReadMethodByWriteMethod(method);
oldValue = invokeGetter(reader);
if (args[0] == oldValue) {
return false;
}
if (args[0] != null && args[0].equals(oldValue)) {
return false;
}
logCommand(method, args);
try {
method.invoke(impl, args[0]);
return true;
} catch (InvocationTargetException e) {
throw e.getCause();
} finally {
}
}
protected Object invokeOther(Method method, Object[] args) throws Throwable {
logCommand(method, args);
try {
return method.invoke(impl, args);
} catch (InvocationTargetException e) {
throw e.getCause();
}
}
/**
* Log a command invocation. The logging is protected by environment.
*
* @param method
* the command method
* @param args
* plain java object or Element proxy object
*/
private void logCommand(Method method, Object[] args) {
StringBuilder sb = new StringBuilder();
fillElementExpString(sb, environment, impl);
sb.append(".");
sb.append(method.getName());
sb.append("(");
if (args != null && args.length > 0) {
fillArgString(sb, args[0]);
for (int i = 1; i < args.length; i++) {
sb.append(", ");
fillArgString(sb, args[i]);
}
}
sb.append(")");
if (environment.cmdLogger != null) {
environment.cmdLogger.log(sb.toString());
}
}
/**
* Fills the argument object into the given stringBuilder
*
* @param sb
* the string builder to fill in
* @param arg
* the argument object
*/
private static void fillArgString(StringBuilder sb, Object arg) {
if (arg instanceof Element) {
fillElementExpString(sb, ((Element) arg).getEnvironment(), ((ElementAddition) arg).getImpl());
} else if (arg instanceof Element[]) {
sb.append("[");
if (((Element[]) arg).length > 0) {
fillArgString(sb, ((Element[]) arg)[0]);
for (int i = 1; i < ((Element[]) arg).length; i++) {
sb.append(", ");
fillArgString(sb, ((Element[]) arg)[i]);
}
}
sb.append("]");
} else if (arg instanceof String) {
sb.append("\"");
sb.append(arg);
sb.append("\"");
} else {
sb.append(arg);
}
}
/**
* Fills into a string builder to represent how to get the object from it's ancestor proxy.
*
* @param sb
* the string builder to fill in
* @param env
* the environment that contains the giving obj
* @param obj
* the object unpacked from proxy
*/
private static void fillElementExpString(StringBuilder sb, Environment env, Object obj) {
if (obj instanceof ElementEx) {
ElementEx parent;
if (obj instanceof Joinable) {
parent = ((Joinable) obj).getPrim();
} else {
parent = ((ElementEx) obj).getParent();
}
InvokeStep ivs = null;
if (parent != null) {
ivs = ((ElementEx) obj).getInvokeStepFormParent();
}
if (ivs != null) {
fillElementExpString(sb, env, parent);
sb.append(".");
sb.append(ivs.getMethod().getName());
sb.append("(");
if (ivs.getIndex() >= 0) {
sb.append(ivs.getIndex());
}
sb.append(")");
return;
} else {
sb.append(String.valueOf(env.getProxy((ElementEx) obj)));
return;
}
} else {
sb.append(obj.toString());
return;
}
}
}
Ads help maintain this website.