/******************************************************************************* * Copyright © 2011, 2013 IBM Corporation and others. * 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: * IBM Corporation - initial API and implementation * *******************************************************************************/ package org.eclipse.edt.debug.core.java.variables; import java.util.ArrayList; import java.util.List; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IConfigurationElement; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.Platform; import org.eclipse.debug.core.DebugEvent; import org.eclipse.debug.core.DebugException; import org.eclipse.debug.core.model.IValue; import org.eclipse.debug.core.model.IVariable; import org.eclipse.edt.debug.core.EDTDebugCorePlugin; import org.eclipse.edt.debug.core.java.IEGLJavaStackFrame; import org.eclipse.edt.debug.core.java.IEGLJavaThread; import org.eclipse.edt.debug.core.java.IEGLJavaValue; import org.eclipse.edt.debug.core.java.IEGLJavaVariable; import org.eclipse.edt.debug.core.java.SMAPVariableInfo; import org.eclipse.edt.debug.internal.core.java.EGLJavaFunctionContainerVariable; import org.eclipse.edt.debug.internal.core.java.EGLJavaVariable; import org.eclipse.edt.debug.internal.core.java.variables.DefaultVariableAdapter; import org.eclipse.jdt.debug.core.IEvaluationRunnable; import org.eclipse.jdt.debug.core.IJavaClassObject; import org.eclipse.jdt.debug.core.IJavaClassType; import org.eclipse.jdt.debug.core.IJavaDebugTarget; import org.eclipse.jdt.debug.core.IJavaFieldVariable; import org.eclipse.jdt.debug.core.IJavaInterfaceType; import org.eclipse.jdt.debug.core.IJavaObject; import org.eclipse.jdt.debug.core.IJavaReferenceType; import org.eclipse.jdt.debug.core.IJavaStackFrame; import org.eclipse.jdt.debug.core.IJavaThread; import org.eclipse.jdt.debug.core.IJavaType; import org.eclipse.jdt.debug.core.IJavaValue; import org.eclipse.jdt.debug.core.IJavaVariable; /** * Utility class for EGL variables. */ public class VariableUtil { /** * Use this when an empty variable array would otherwise be created. */ public static final IVariable[] EMPTY_VARIABLES = {}; /** * The ID for the variable adapters extension point. */ public static final String EXTENSION_POINT_VARIABLE_ADAPTERS = "javaVariableAdapters"; //$NON-NLS-1$ /** * The variable adapters. */ private static IVariableAdapter[] variableAdapters; private VariableUtil() { // No instances. } /** * Given a set of Java variables, and SMAP variable information, this will determine which Java variables should be displayed. Any variables to be * displayed will be wrapped inside an EGLJavaVariable. * * @param javaVariables The Java variables, not null. * @param currVariables The currently cached EGL variables, not null. * @param prevVariables The EGL variables from the last time we processed variables, possibly null. * @param infos The variable information from the SMAP, not null. * @param frame The active EGL-wrapped stack frame, not null. * @param skipLocals True if local variables should be omitted. * @param parent The parent of the variables being created, possibly null. * @return the filtered, EGL-wrapped variables. * @throws DebugException */ public static List<IEGLJavaVariable> filterAndWrapVariables( IVariable[] javaVariables, IEGLJavaStackFrame frame, boolean skipLocals, IEGLJavaValue parent ) throws DebugException { List<IEGLJavaVariable> newEGLVariables = new ArrayList<IEGLJavaVariable>( javaVariables.length ); SMAPVariableInfo[] infos = parent == null ? frame.getSMAPVariableInfos() : parent.getSMAPVariableInfos(); String javaFrameSignature = frame.getJavaStackFrame().getMethodName() + ";" + frame.getJavaStackFrame().getSignature(); //$NON-NLS-1$ int currentLine = frame.getLineNumber(); int frameStartLine = frame.getSMAPFunctionInfo() == null ? -1 : frame.getSMAPFunctionInfo().lineDeclared; for ( int i = 0; i < javaVariables.length; i++ ) { IJavaVariable javaVar = (IJavaVariable)javaVariables[ i ]; String javaName = javaVar.getName(); if ( javaVar.isLocal() ) { if ( !skipLocals ) { SMAPVariableInfo matchingInfo = null; for ( int j = 0; j < infos.length; j++ ) { SMAPVariableInfo info = infos[ j ]; if ( info.javaName.equals( javaName ) ) { // Validate the signature and make sure the info is in scope. There could be multiple // local variables of different types with the same name, in different scopes within the // function. if ( info.javaMethodSignature != null && info.javaMethodSignature.equals( javaFrameSignature ) && (currentLine >= info.lineDeclared || currentLine == frameStartLine) ) { if ( matchingInfo == null || matchingInfo.lineDeclared < info.lineDeclared ) { matchingInfo = info; } // Don't break - keep looping to see if there's a better match. We want the info // with the highest line number that's within scope. } } } if ( matchingInfo != null ) { IEGLJavaVariable var = frame.getCorrespondingVariable( createEGLVariable( javaVar, matchingInfo, frame, parent ), parent ); newEGLVariables.add( var ); } } } else { if ( "this".equals( javaName ) ) //$NON-NLS-1$ { IEGLJavaVariable var = frame.getCorrespondingVariable( new EGLJavaFunctionContainerVariable( frame.getDebugTarget(), javaVar, frame ), parent ); newEGLVariables.add( var ); } else { SMAPVariableInfo matchingInfo = null; for ( int j = 0; j < infos.length; j++ ) { SMAPVariableInfo info = infos[ j ]; if ( info.javaName.equals( javaName ) ) { // Make sure it has no signature if ( info.javaMethodSignature == null ) { matchingInfo = info; break; } } } if ( matchingInfo != null ) { IEGLJavaVariable var = frame.getCorrespondingVariable( createEGLVariable( javaVar, matchingInfo, frame, parent ), parent ); newEGLVariables.add( var ); } } } } return newEGLVariables; } /** * Creates an EGL wrapper around the given Java variable. Contributed IVariableAdapters are consulted before doing basic wrapping. * * @param javaVariable The Java variable. * @param info The variable information from the SMAP. * @param frame The EGL frame. * @param parentVariable The parent of this variable, possibly null. * @return */ public static IEGLJavaVariable createEGLVariable( IJavaVariable javaVariable, SMAPVariableInfo info, IEGLJavaStackFrame frame, IEGLJavaValue parent ) { // Consult the adapters. for ( IVariableAdapter adapter : getVariableAdapters() ) { IEGLJavaVariable variable = adapter.adapt( javaVariable, frame, info, parent ); if ( variable != null ) { return variable; } } return new EGLJavaVariable( frame.getDebugTarget(), javaVariable, info, frame, parent ); } /** * @return the qualified name of the variable with '|' as the delimeter, e.g. parentVarName|childVarName * @throws DebugException */ public static String getQualifiedName( IEGLJavaVariable var ) throws DebugException { if ( var == null ) { return null; } if ( var instanceof EGLJavaFunctionContainerVariable ) { return "this"; //$NON-NLS-1$ } IEGLJavaValue parent = var.getParentValue(); if ( parent == null || parent.getParentVariable() == null ) { return var.getName(); } StringBuilder buf = new StringBuilder( 100 ); buf.append( getQualifiedName( parent.getParentVariable() ) ); buf.append( '|' ); buf.append( var.getName() ); return buf.toString(); } /** * @return true if the variable is an instance of <code>typeName</code> */ public static boolean isInstanceOf( IJavaVariable variable, String typeName ) { IJavaValue javaValue = null; try { IValue value = variable.getValue(); if ( value instanceof IJavaValue ) { javaValue = (IJavaValue)value; IJavaType javaType = javaValue.isNull() ? variable.getJavaType() : javaValue.getJavaType(); if ( javaType instanceof IJavaClassType ) { return isInstanceOf( (IJavaClassType)javaType, typeName, false ); } } } catch ( DebugException e ) { } // Maybe the type matches. try { String type = javaValue == null || javaValue.isNull() ? variable.getReferenceTypeName() : javaValue.getReferenceTypeName(); return typeName.equals( type ); } catch ( DebugException e ) { } return false; } public static boolean isInstanceOf( IJavaClassType type, String typeName, boolean skipInterfaces ) throws DebugException { if ( typeName.equals( type.getName() ) ) { return true; } if ( !skipInterfaces ) { for ( IJavaInterfaceType iface : type.getAllInterfaces() ) { if ( typeName.equals( iface.getName() ) ) { return true; } } } IJavaClassType superType = type.getSuperclass(); if ( superType != null ) { if ( isInstanceOf( superType, typeName, true ) ) { return true; } } return false; } /** * Invokes a static method in the remote VM. * * @param thread The EGL thread. * @param frame The Java frame. * @param className The class name contaning the static method. * @param methodName The name of the static method. * @param methodSignature The signature of the static method. * @param args Arguments to be passed to the method, possibly null. * @return the resulting value, or null if the evaluation failed. */ public static IJavaValue invokeStaticMethod( IEGLJavaThread thread, IJavaStackFrame frame, String className, String methodName, String methodSignature, IJavaValue[] args ) { synchronized ( thread.getEvaluationLock() ) { waitForEvaluation( thread.getJavaThread() ); IJavaDebugTarget target = (IJavaDebugTarget)frame.getDebugTarget(); try { IJavaReferenceType context; IJavaObject thisObj = frame.getThis(); if ( thisObj == null ) { context = frame.getReferenceType(); } else { context = (IJavaReferenceType)thisObj.getJavaType(); } IJavaObject classLoader = context.getClassLoaderObject(); IJavaClassObject classObj = null; IJavaType[] types = target.getJavaTypes( className ); if ( types != null && types.length > 0 ) { for ( int i = 0; i < types.length; i++ ) { if ( types[ i ] instanceof IJavaReferenceType && isClassLoaderCompatible( classLoader, ((IJavaReferenceType)types[ i ]).getClassLoaderObject(), thread ) ) { classObj = (IJavaClassObject)((IJavaReferenceType)types[ i ]).getClassObject(); break; } } } if ( classObj == null ) { // Class not yet loaded. final IJavaValue name = target.newValue( className ); ((IJavaObject)name).disableCollection(); try { IJavaClassType javaLangClass = getJavaLangClass( target ); if ( javaLangClass != null ) { IJavaValue[] forNameArgs = new IJavaValue[] { name, target.newValue( true ), classLoader == null ? target.nullValue() : classLoader }; classObj = (IJavaClassObject)runSendMessage( thread, javaLangClass, forNameArgs, "forName", //$NON-NLS-1$ "(Ljava/lang/String;ZLjava/lang/ClassLoader;)Ljava/lang/Class;" ); //$NON-NLS-1$ } } catch ( CoreException e ) { } finally { ((IJavaObject)name).enableCollection(); } } if ( classObj != null ) { IJavaType instType = classObj.getInstanceType(); if ( instType instanceof IJavaClassType ) { return runSendMessage( thread, (IJavaClassType)instType, args, methodName, methodSignature ); } } } catch ( CoreException e ) { EDTDebugCorePlugin.log( e ); } } return null; } /** * @return the IJavaClassType for java.lang.Class, or null if it hasn't been loaded yet. * @throws CoreException */ public static IJavaClassType getJavaLangClass( IJavaDebugTarget target ) throws CoreException { IJavaType[] types = target.getJavaTypes( "java.lang.Class" ); //$NON-NLS-1$ if ( types == null || types.length != 1 ) { return null; } return (IJavaClassType)types[ 0 ]; } /** * @return true if cl2 is the same as, or a parent of, cl1, or if either is null. */ public static boolean isClassLoaderCompatible( IJavaObject cl1, IJavaObject cl2, IEGLJavaThread thread ) throws CoreException { if ( cl1 == null || cl2 == null ) { return true; } IJavaObject toTry = cl1; while ( toTry != null ) { if ( toTry.equals( cl2 ) ) { return true; } toTry = getParentLoader( toTry, thread ); } return false; } /** * Returns the parent class loader of the given class loader object or <code>null</code> if none. * * @param loader class loader object * @return parent class loader or <code>null</code> * @throws CoreException */ public static IJavaObject getParentLoader( IJavaObject cl, IEGLJavaThread thread ) throws CoreException { // Some classloaders have their parent in a 'parent' field. This is much faster than sending a message. IJavaFieldVariable parent = cl.getField( "parent", false ); //$NON-NLS-1$ if ( parent != null ) { IJavaValue value = (IJavaValue)parent.getValue(); return value.isNull() ? null : (IJavaObject)value; } IJavaValue value = runSendMessage( thread, cl, null, "getParent", "()Ljava/lang/ClassLoader;", false ); //$NON-NLS-1$ //$NON-NLS-2$ return value.isNull() ? null : (IJavaObject)value; } /** * Runs "sendMessage" on the receiver in a synchronized manner. This should be used instead of directly invoking sendMessage because only one * request may be done at a time. * * @param thread The EGL thread. * @param receiver The object on which to send the message. * @param args The arguments to be passed to the method, possibly null. * @param methodName The name of the method to invoke. * @param methodSignature The signature of the method to invoke. * @param superSend <code>true</code> if the method lookup should begin in this object's superclass * @return the result of invoking the method. * @throws DebugException */ public static IJavaValue runSendMessage( IEGLJavaThread thread, final IJavaObject receiver, final IJavaValue[] args, final String methodName, final String methodSignature, final boolean superSend ) throws DebugException { // Each thread can only run one evaluation at a time. If we try to run a second, JDT throws an error. final IJavaValue[] result = new IJavaValue[ 1 ]; synchronized ( thread.getEvaluationLock() ) { final IJavaThread javaThread = thread.getJavaThread(); IEvaluationRunnable eval = new IEvaluationRunnable() { public void run( IJavaThread thread, IProgressMonitor monitor ) throws DebugException { result[ 0 ] = receiver.sendMessage( methodName, methodSignature, args, thread, superSend ); } }; waitForEvaluation( javaThread ); javaThread.runEvaluation( eval, null, DebugEvent.EVALUATION_IMPLICIT, false ); } return result[ 0 ]; } /** * Runs "sendMessage" on the receiver in a synchronized manner. This should be used instead of directly invoking sendMessage because only one * request may be done at a time. * * @param thread The EGL thread. * @param receiver The type on which to send the message. * @param args The arguments to be passed to the method, possibly null. * @param methodName The name of the method to invoke. * @param methodSignature The signature of the method to invoke. * @return the result of invoking the method. * @throws DebugException */ public static IJavaValue runSendMessage( IEGLJavaThread thread, final IJavaClassType receiver, final IJavaValue[] args, final String methodName, final String methodSignature ) throws DebugException { // Each thread can only run one evaluation at a time. If we try to run a second, JDT throws an error. final IJavaValue[] result = new IJavaValue[ 1 ]; synchronized ( thread.getEvaluationLock() ) { final IJavaThread javaThread = thread.getJavaThread(); IEvaluationRunnable eval = new IEvaluationRunnable() { public void run( IJavaThread thread, IProgressMonitor monitor ) throws DebugException { result[ 0 ] = receiver.sendMessage( methodName, methodSignature, args, thread ); } }; waitForEvaluation( javaThread ); javaThread.runEvaluation( eval, null, DebugEvent.EVALUATION_IMPLICIT, false ); } return result[ 0 ]; } /** * Waits until it's safe to run an evaluation. * * @param thread The Java thread. */ private static void waitForEvaluation( IJavaThread thread ) { while ( thread.isPerformingEvaluation() || !thread.isSuspended() ) { try { Thread.sleep( 1 ); } catch ( InterruptedException e ) { return; } } } /** * @return the variable adapter extensions. */ public static synchronized IVariableAdapter[] getVariableAdapters() { if ( variableAdapters == null ) { IConfigurationElement[] elements = Platform.getExtensionRegistry().getConfigurationElementsFor( EDTDebugCorePlugin.PLUGIN_ID, EXTENSION_POINT_VARIABLE_ADAPTERS ); List<IVariableAdapter> adapters = new ArrayList<IVariableAdapter>( elements.length ); for ( IConfigurationElement element : elements ) { try { Object o = element.createExecutableExtension( "class" ); //$NON-NLS-1$ if ( o instanceof IVariableAdapter ) { adapters.add( (IVariableAdapter)o ); } } catch ( CoreException e ) { EDTDebugCorePlugin.log( e ); } } variableAdapters = new IVariableAdapter[ adapters.size() + 1 ]; adapters.toArray( variableAdapters ); variableAdapters[ variableAdapters.length - 1 ] = new DefaultVariableAdapter(); } return variableAdapters; } public static void dispose() { if ( variableAdapters != null ) { for ( IVariableAdapter adapter : variableAdapters ) { adapter.dispose(); } variableAdapters = null; } } }