/******************************************************************************* * Copyright (c) 2012 Pivotal Software, Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Pivotal Software, Inc. - initial API and implementation *******************************************************************************/ package org.grails.ide.eclipse.groovy.debug.core.evaluation; import groovy.lang.MetaClass; import groovy.lang.MetaMethod; import groovy.lang.MetaProperty; import groovy.lang.Script; import java.util.List; import org.codehaus.groovy.ast.ClassNode; import org.eclipse.debug.core.DebugException; import org.eclipse.jdt.debug.core.IJavaClassObject; import org.eclipse.jdt.debug.core.IJavaFieldVariable; import org.eclipse.jdt.debug.core.IJavaObject; import org.eclipse.jdt.debug.core.IJavaValue; /** * A {@link MetaClass} that delegates all operations to a metaclass * running in the debugged application. * @author Andrew Eisenberg * @since 2.5.1 */ public class JDIMetaClass implements MetaClass { private final MetaClass origMetaClass; private final IJavaObject jdiMetaClass; private final JDITargetDelegate delegate; public JDIMetaClass(MetaClass origMetaClass, IJavaObject jdiMetaClass, JDITargetDelegate delegate) { this.origMetaClass = origMetaClass; this.jdiMetaClass = jdiMetaClass; this.delegate = delegate; } public JDIMetaClass(IJavaObject object, JDITargetDelegate delegate) throws DebugException { this.origMetaClass = null; this.jdiMetaClass = delegate.getMetaClass(object); this.delegate = delegate; } public JDIMetaClass(MetaClass metaClass, JDITargetDelegate delegate) throws DebugException { this.origMetaClass = metaClass; this.jdiMetaClass = delegate.createMetaClassInDebuggedApplication(metaClass); this.delegate = delegate; } public MetaClass getOrigMetaClass() { return origMetaClass; } @SuppressWarnings("rawtypes") public List respondsTo(Object obj, String name, Object[] argTypes) { throw new IllegalArgumentException("Not implemented (Groovy-Eclipse)"); } @SuppressWarnings("rawtypes") public List respondsTo(Object obj, String name) { throw new IllegalArgumentException("Not implemented (Groovy-Eclipse)"); } public MetaProperty hasProperty(Object obj, String name) { throw new IllegalArgumentException("Not implemented (Groovy-Eclipse)"); } public MetaProperty getMetaProperty(String name) { throw new IllegalArgumentException("Not implemented (Groovy-Eclipse)"); } public MetaMethod getStaticMetaMethod(String name, Object[] args) { throw new IllegalArgumentException("Not implemented (Groovy-Eclipse)"); } public MetaMethod getMetaMethod(String name, Object[] args) { throw new IllegalArgumentException("Not implemented (Groovy-Eclipse)"); } public Class<? extends MetaClass> getTheClass() { return origMetaClass != null ? origMetaClass.getTheClass() : Object.class; } public Object invokeConstructor(Object[] arguments) { try { IJavaValue result = jdiMetaClass.sendMessage("invokeConstructor", //$NON-NLS-1$ "([Ljava/lang/Object;)Ljava/lang/Object;", //$NON-NLS-1$ new IJavaValue[] { delegate.toJDIObject(arguments) }, delegate.getThread(), false); return delegate.createProxyFor(result); } catch (DebugException e) { throw new RuntimeException(e); } } public Object invokeMethod(Object object, String methodName, Object[] arguments) { try { IJavaValue result = jdiMetaClass.sendMessage("invokeMethod", //$NON-NLS-1$ "(Ljava/lang/Object;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/Object;", //$NON-NLS-1$ new IJavaValue[] { getClosureDelegate(delegate.toJDIObject(object)), delegate.getTarget().newValue(methodName), delegate.toJDIObject(arguments) }, delegate.getThread(), false); if (!result.isNull() && methodName.equals("asBoolean") && result.getJavaType().getName().equals("java.lang.Boolean") && arguments.length == 0) { // assume this is part of an if, while statement, or an elvis operator, etc return JDITargetDelegate.convertToBoolean(result); } else if (!result.isNull() && methodName.equals("iterator")) { // assume this is part of a for loop return delegate.convertToIterator(result, new JDIMetaClass((IJavaObject) result, delegate)); } else { return delegate.createProxyFor(result); } } catch (DebugException e) { throw new RuntimeException(e); } } public Object invokeMethod(Object object, String methodName, Object arguments) { IJavaValue jdiMethodName = null; IJavaValue jdiArguments = null; IJavaValue jdiMethodTarget = null; try { jdiMethodTarget = getClosureDelegate(delegate.toJDIObject(object)); jdiMethodName = delegate.getTarget().newValue(methodName); jdiArguments = delegate.toJDIObject(arguments); } catch (DebugException e) { throw new RuntimeException(e); } // if the method target is the running script and current stack frame is static, // then must call invoke static method instead if (object instanceof Script && jdiMethodTarget instanceof IJavaClassObject) { return invokeStaticMethod(object, methodName, arguments instanceof Object[] ? (Object[]) arguments : new Object[] { arguments }); } // must try to invoke the method twice. First using the closure delegate, and second not // the first will handle method invocations in most situations, but will fail on calls // to 'print' or other DGMs when inside of closures try { IJavaValue result = jdiMetaClass.sendMessage("invokeMethod", //$NON-NLS-1$ "(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/Object;", //$NON-NLS-1$ new IJavaValue[] { jdiMethodTarget, jdiMethodName, jdiArguments }, delegate.getThread(), false); return delegate.createProxyFor(result); } catch (DebugException e) { try { IJavaValue result = jdiMetaClass.sendMessage("invokeMethod", //$NON-NLS-1$ "(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/Object;", //$NON-NLS-1$ new IJavaValue[] { delegate.toJDIObject(object), jdiMethodName, jdiArguments }, delegate.getThread(), false); return delegate.createProxyFor(result); } catch (DebugException e2) { throw new RuntimeException(e2); } } } public Object invokeStaticMethod(Object object, String methodName, Object[] arguments) { try { IJavaObject newValue = (IJavaObject) delegate.getTarget().newValue(methodName); delegate.disableCollection(newValue); IJavaValue result = jdiMetaClass.sendMessage("invokeStaticMethod", //$NON-NLS-1$ "(Ljava/lang/Object;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/Object;", //$NON-NLS-1$ new IJavaValue[] { getClosureDelegate(delegate.toJDIObject(object)), newValue, delegate.toJDIObject(arguments) }, delegate.getThread(), false); return delegate.createProxyFor(result); } catch (DebugException e) { throw new RuntimeException(e); } } public Object getProperty(Object object, String property) { try { IJavaValue target = delegate.toJDIObject(object); IJavaObject targetMetaClass = jdiMetaClass; // if we are using a closure, then we should // try the delegate first instead of the current type // except owner and delegates should be grabbed from the actual closure object if (!property.equals("owner") && !property.equals("delegate") && isClosureType(target)) { IJavaValue closureTarget = getClosureDelegate(target); try { Object maybeProperty = getProperty(closureTarget, property); if (maybeProperty != null) { return maybeProperty; } } catch (RuntimeException e) { // ignore since could be thrown for normal reasons // and if there really is a problem, then we will hit it // again below // probably a MissingPropertyException } } if (target instanceof IJavaObject) { // try to shortcut and grab the field directly if it exists IJavaObject jdiObject = (IJavaObject) target; IJavaFieldVariable field = jdiObject.getField(property, false); if (field != null) { return delegate.createProxyFor((IJavaValue) field.getValue()); } // now try to see if there is a getProperty method that is explicitly defined try { return delegate.createProxyFor(jdiObject.sendMessage("getProperty", "(Ljava/lang/String;)Ljava/lang/Object;", new IJavaValue[] { delegate.getTarget().newValue(property) }, delegate.getThread(), false)); } catch (DebugException e) { // continue on our merry way. Likely that the method just isn't there } } IJavaValue propertyValue = targetMetaClass.sendMessage("getProperty", "(Ljava/lang/Object;Ljava/lang/String;)Ljava/lang/Object;", //$NON-NLS-1$ //$NON-NLS-2$ new IJavaValue[] { target, delegate.getTarget().newValue(property) }, delegate.getThread(), false); return delegate.createProxyFor(propertyValue); } catch (DebugException e) { // probably a MissingPropertyException throw new RuntimeException(e); } } /** * Returns the closure owner if this jdoObject represents a closure object * on the running app, or else return the object * @param jdiObject * @return * @throws DebugException */ private IJavaValue getClosureDelegate(IJavaValue jdiValue) throws DebugException { IJavaObject jdiObject = (IJavaObject) jdiValue; IJavaFieldVariable delegateField = jdiObject.getField("delegate", false); if (delegateField != null) { jdiValue = (IJavaValue) delegateField.getValue(); } return jdiValue; } /** * @param jdiValue * @return * @throws DebugException */ protected boolean isClosureType(IJavaValue jdiValue) throws DebugException { return jdiValue instanceof IJavaObject && !jdiValue.isNull() && ( jdiValue.getJavaType().getName().indexOf("$_closure") > 0 || jdiValue.getJavaType().getName().indexOf("$_run_closure") > 0 ); } // FIXADE in static frames, this must behave differently public void setProperty(Object object, String property, Object newValue) { try { IJavaObject propertyValue = (IJavaObject) delegate.getTarget().newValue(property); delegate.disableCollection(propertyValue); IJavaObject jdiObject = (IJavaObject) delegate.toJDIObject(newValue); delegate.disableCollection(jdiObject); jdiMetaClass.sendMessage("setProperty", "(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/Object;)V", //$NON-NLS-1$ //$NON-NLS-2$ new IJavaValue[] { delegate.toJDIObject(object), propertyValue, jdiObject }, delegate.getThread(), false); } catch (DebugException e) { throw new RuntimeException(e); } } public Object getAttribute(Object object, String attribute) { try { IJavaValue value = jdiMetaClass.sendMessage("getAttribute", "(Ljava/lang/Object;Ljava/lang/String;)Ljava/lang/Object;", //$NON-NLS-1$ //$NON-NLS-2$ new IJavaValue[] { delegate.toJDIObject(object), delegate.getTarget().newValue(attribute) }, delegate.getThread(), false); return delegate.createProxyFor(value); } catch (DebugException e) { throw new RuntimeException(e); } } public void setAttribute(Object object, String attribute, Object newValue) { try { jdiMetaClass.sendMessage("setAttribute", "(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/Object;)V", //$NON-NLS-1$ //$NON-NLS-2$ new IJavaValue[] { delegate.toJDIObject(object), delegate.getTarget().newValue(attribute), delegate.toJDIObject(newValue) }, delegate.getThread(), false); } catch (DebugException e) { throw new RuntimeException(e); } } public Object invokeMethod(@SuppressWarnings("rawtypes") Class sender, Object receiver, String methodName, Object[] arguments, boolean isCallToSuper, boolean fromInsideClass) { throw new IllegalArgumentException("Not implemented (Groovy-Eclipse)"); } public Object getProperty(@SuppressWarnings("rawtypes") Class sender, Object receiver, String property, boolean isCallToSuper, boolean fromInsideClass) { throw new IllegalArgumentException("Not implemented (Groovy-Eclipse)"); } public void setProperty(@SuppressWarnings("rawtypes") Class sender, Object receiver, String property, Object value, boolean isCallToSuper, boolean fromInsideClass) { throw new IllegalArgumentException("Not implemented (Groovy-Eclipse)"); } public Object invokeMissingMethod(Object instance, String methodName, Object[] arguments) { throw new IllegalArgumentException("Not implemented (Groovy-Eclipse)"); } public Object invokeMissingProperty(Object instance, String propertyName, Object optionalValue, boolean isGetter) { throw new IllegalArgumentException("Not implemented (Groovy-Eclipse)"); } public Object getAttribute(@SuppressWarnings("rawtypes") Class sender, Object receiver, String messageName, boolean useSuper) { throw new IllegalArgumentException("Not implemented (Groovy-Eclipse)"); } public void setAttribute(@SuppressWarnings("rawtypes") Class sender, Object receiver, String messageName, Object messageValue, boolean useSuper, boolean fromInsideClass) { throw new IllegalArgumentException("Not implemented (Groovy-Eclipse)"); } public void initialize() { // no-op? } @SuppressWarnings("rawtypes") public List getProperties() { throw new IllegalArgumentException("Not implemented (Groovy-Eclipse)"); } @SuppressWarnings("rawtypes") public List getMethods() { throw new IllegalArgumentException("Not implemented (Groovy-Eclipse)"); } public ClassNode getClassNode() { throw new IllegalArgumentException("Not implemented (Groovy-Eclipse)"); } @SuppressWarnings("rawtypes") public List getMetaMethods() { throw new IllegalArgumentException("Not implemented (Groovy-Eclipse)"); } public int selectConstructorAndTransformArguments(int numberOfConstructors, Object[] arguments) { throw new IllegalArgumentException("Not implemented (Groovy-Eclipse)"); } public MetaMethod pickMethod(String methodName, @SuppressWarnings("rawtypes") Class[] arguments) { throw new IllegalArgumentException("Not implemented (Groovy-Eclipse)"); } }