/******************************************************************************* * Copyright © 2008, 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.javascript.internal.model; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.StringTokenizer; import org.eclipse.core.resources.IMarker; import org.eclipse.core.resources.IMarkerDelta; import org.eclipse.core.runtime.CoreException; import org.eclipse.debug.core.DebugEvent; import org.eclipse.debug.core.DebugException; import org.eclipse.debug.core.DebugPlugin; import org.eclipse.debug.core.IBreakpointManagerListener; import org.eclipse.debug.core.ILaunch; import org.eclipse.debug.core.model.IBreakpoint; import org.eclipse.debug.core.model.IDebugTarget; import org.eclipse.debug.core.model.IMemoryBlock; import org.eclipse.debug.core.model.IProcess; import org.eclipse.debug.core.model.IStackFrame; import org.eclipse.debug.core.model.ISuspendResume; import org.eclipse.debug.core.model.IThread; import org.eclipse.debug.core.model.IValue; import org.eclipse.edt.debug.core.IEGLDebugCoreConstants; import org.eclipse.edt.debug.core.IEGLDebugTarget; import org.eclipse.edt.debug.core.IEGLVariable; import org.eclipse.edt.debug.core.breakpoints.EGLBreakpoint; import org.eclipse.edt.debug.internal.ui.actions.BreakpointUtils; import org.eclipse.edt.ide.debug.javascript.internal.server.DebugContext; import org.eclipse.edt.ide.debug.javascript.internal.utils.RUIDebugUtil; import org.eclipse.edt.ide.rui.server.EvServer; import org.eclipse.osgi.util.NLS; public class RUIDebugTarget extends RUIDebugElement implements IEGLDebugTarget, IBreakpointManagerListener, ISuspendResume { public final static String VAR_VAR = "var_"; //$NON-NLS-1$ public final static String VAR_VALUE = VAR_VAR + "Value"; //$NON-NLS-1$ public final static String VAR_SET_VALUE = VAR_VAR + "SetValue"; //$NON-NLS-1$ public final static String VAR_VARIABLES = VAR_VAR + "Variables"; //$NON-NLS-1$ private static final IStackFrame[] EMPTY_FRAMES = {}; private ILaunch fLaunch; private DebugContext context; private RUIThread fThread; private IThread[] fThreads; private boolean fTerminated; private boolean fTerminating; private boolean fSuspended; private boolean fSuspending; private List<RUIStackFrame> fStackFrames; private List<RUIStackFrame> fOldStackFrames; private String debugCmd; private boolean steppingIn; private LocaleInfo localeInfo; private IVariableQueryCompletion completion; private Object completionSynchObj = new Object(); public RUIDebugTarget( ILaunch launch ) { super( null ); fLaunch = launch; initialize(); } @Override public Object getAdapter( Class adapter ) { if ( adapter == RUIDebugTarget.class || adapter == IEGLDebugTarget.class || adapter == IDebugTarget.class ) { return this; } return super.getAdapter( adapter ); } private void initialize() { fThread = new RUIThread( this ); fThreads = new IThread[] { fThread }; DebugPlugin.getDefault().getBreakpointManager().addBreakpointListener( this ); DebugPlugin.getDefault().getBreakpointManager().addBreakpointManagerListener( this ); fireCreationEvent(); } public void setContext( DebugContext context ) { this.context = context; } @Override public String getName() throws DebugException { return NLS.bind( RUIDebugMessages.rui_debug_target_label, new Object[] { getClass().getName(), "localhost", Integer.valueOf( EvServer.getInstance().getPortNumber() ) } ); //$NON-NLS-1$ } @Override public IProcess getProcess() { return null; } @Override public ILaunch getLaunch() { return fLaunch; } RUIThread getCurrentThread() { return (RUIThread)fThreads[ 0 ]; } @Override public IThread[] getThreads() throws DebugException { return fThreads; } @Override public boolean hasThreads() throws DebugException { return true; } @Override public boolean supportsBreakpoint( IBreakpoint breakpoint ) { return breakpoint instanceof EGLBreakpoint; } @Override public boolean canTerminate() { return !isTerminated() && !isTerminating(); } @Override public boolean isTerminated() { return fTerminated; } public boolean isTerminating() { return fTerminating; } @Override public void terminate() throws DebugException { fTerminating = true; if ( isSuspended() ) { if ( !setDebugCommand( "disconnect" ) ) //$NON-NLS-1$ { context.disconnectDebugger(); } } else if ( context != null ) { context.disconnectDebugger(); } context.debugTerminationCleanup(); } public void terminated() { fSuspending = false; fSuspended = false; fTerminating = false; fTerminated = true; steppingIn = false; try { fireTerminateEvent(); fireEvent( new DebugEvent( this, DebugEvent.CHANGE ) ); DebugPlugin.getDefault().getBreakpointManager().removeBreakpointListener( this ); DebugPlugin.getDefault().getBreakpointManager().removeBreakpointManagerListener( this ); } catch ( Exception e ) { // Can happen when workbench is closed before this target is terminated. } } public void windowClosed() { // If suspended at a breakpoint, let the atLine AJAX request be completed so that // the browser doesn't manually terminate it, flashing an error message in the // window. if ( !fTerminating && isSuspended() && debugCmd == null ) { // do not call setDebugCmd - browser closing/refresh trumps everything else. debugCmd = "silentTerminate"; //$NON-NLS-1$ int numWaits = 0; while ( debugCmd != null && numWaits < 20 ) { try { Thread.sleep( 1 ); } catch ( Exception e ) { } numWaits++; } } terminated(); } @Override public boolean canResume() { return !isTerminated() && !isTerminating() && isSuspended(); } @Override public boolean canSuspend() { return !isSuspending() && !isSuspended() && !isTerminated(); } @Override public boolean isSuspended() { return !isTerminated() && fSuspended; } public boolean isSuspending() { return !isTerminated() && !isTerminating() && fSuspending; } @Override public void breakpointAdded( IBreakpoint breakpoint ) { if ( supportsBreakpoint( breakpoint ) && context != null ) { String bpPath = BreakpointUtils.getRelativeBreakpointPath( breakpoint ); if ( bpPath != null && bpPath.length() > 0 ) { String file = RUIDebugUtil.encodeValue( bpPath ); String line = Integer.toString( breakpoint.getMarker().getAttribute( IMarker.LINE_NUMBER, -1 ) ); String enabled = Boolean.toString( breakpoint.getMarker().getAttribute( IBreakpoint.ENABLED, true ) ); boolean runToLine = breakpoint.getMarker().getAttribute( IEGLDebugCoreConstants.RUN_TO_LINE, false ); if ( isSuspended() ) { String command = runToLine ? "addSingleUseBreakpoint " + file + "," + line //$NON-NLS-1$ //$NON-NLS-2$ : "addBreakpoint " + file + "," + line + "," + enabled; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ if ( !setDebugCommand( command ) ) { context.addBreakpoint( file, line, enabled, runToLine ); } } else { context.addBreakpoint( file, line, enabled, runToLine ); } } } } @Override public void breakpointRemoved( IBreakpoint breakpoint, IMarkerDelta delta ) { if ( supportsBreakpoint( breakpoint ) && context != null ) { String bpPath = BreakpointUtils.getRelativeBreakpointPath( breakpoint ); if ( bpPath != null && bpPath.length() > 0 ) { String file = RUIDebugUtil.encodeValue( bpPath ); String line = Integer.toString( breakpoint.getMarker().getAttribute( IMarker.LINE_NUMBER, -1 ) ); if ( isSuspended() ) { if ( !setDebugCommand( "removeBreakpoint " + file + "," + line ) ) //$NON-NLS-1$ //$NON-NLS-2$ { context.removeBreakpoint( file, line ); } } else { context.removeBreakpoint( file, line ); } } } } @Override public void breakpointChanged( IBreakpoint breakpoint, IMarkerDelta delta ) { if ( supportsBreakpoint( breakpoint ) && context != null ) { IMarker marker = breakpoint.getMarker(); boolean currentEnable = marker.getAttribute( IBreakpoint.ENABLED, true ); boolean oldEnable; int oldLine; if ( delta != null ) { oldEnable = delta.getAttribute( IBreakpoint.ENABLED, true ); oldLine = delta.getAttribute( IMarker.LINE_NUMBER, -1 ); } else { // If there is no delta then need to assume that enablement state changed. oldEnable = !currentEnable; oldLine = marker.getAttribute( IMarker.LINE_NUMBER, -1 ); } // We don't report changes to line numbers because RUI doesn't support hotswapping. The current generated // code being run will be using the old line numbers. if ( currentEnable != oldEnable ) { String bpPath = BreakpointUtils.getRelativeBreakpointPath( breakpoint ); if ( bpPath != null && bpPath.length() > 0 ) { String file = RUIDebugUtil.encodeValue( bpPath ); String line = Integer.toString( oldLine ); String enabled = Boolean.toString( currentEnable ); if ( isSuspended() ) { if ( !setDebugCommand( "changeBreakpoint " + file + "," + line + "," + enabled ) ) //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ { context.changeBreakpoint( file, line, enabled ); } } else { context.changeBreakpoint( file, line, enabled ); } } } } } /** * Called when Eclipse breakpoints as a whole are enabled or disabled. */ @Override public void breakpointManagerEnablementChanged( boolean enabled ) { if ( context != null ) { String state = Boolean.toString( enabled ); if ( isSuspended() ) { if ( !setDebugCommand( "breakpointEnablement " + state ) ) //$NON-NLS-1$ { context.breakpointManagerChanged( state ); } } else { context.breakpointManagerChanged( state ); } } } /** * Returns whether breakpoints have been enabled / disabled globally by Eclipse * * @return true if breakpoint manager is enabled, false otherwise. */ protected boolean breakpointsEnabled() { return DebugPlugin.getDefault().getBreakpointManager().isEnabled(); } @Override public boolean canDisconnect() { return false; } @Override public void disconnect() throws DebugException { } @Override public boolean isDisconnected() { return false; } @Override public IMemoryBlock getMemoryBlock( long startAddress, long length ) throws DebugException { return null; } @Override public boolean supportsStorageRetrieval() { return false; } public IStackFrame[] getStackFrames() { if ( !isSuspended() || fStackFrames == null || fStackFrames.size() == 0 ) { return EMPTY_FRAMES; } return (IStackFrame[])fStackFrames.toArray( new IStackFrame[ fStackFrames.size() ] ); } public void stepOver( RUIStackFrame frame ) { steppingIn = false; setDebugCommand( "stepOver " + frame.getId() ); //$NON-NLS-1$ } public void stepIn() { steppingIn = true; setDebugCommand( "stepIn" ); //$NON-NLS-1$ } public void stepOut( RUIStackFrame frame ) { steppingIn = false; setDebugCommand( "stepOut " + frame.getId() ); //$NON-NLS-1$ } /** * @return true if the command was set, false otherwise (meaning we're no longer suspended). */ private synchronized boolean setDebugCommand( String str ) { while ( debugCmd != null ) { // Don't clobber other commands, like step-over! try { if ( !isSuspended() ) { // While the command was waiting, a prior command caused it to no longer be suspended. // We return false so that the terminate button will know the command wasn't set, so it must // use its alternate method of termination. return false; } Thread.sleep( 10 ); } catch ( Exception e ) { } } debugCmd = str; return true; } public IValue getVariableValue( final RUIVariable variable ) { if ( !isSuspended() ) { variable.getCurrValue().setValue( "" ); //$NON-NLS-1$ return variable.getCurrValue(); } synchronized ( completionSynchObj ) { final String[] result = new String[ 2 ]; completion = new IVariableQueryCompletion() { @Override public void completed( Object value ) { if ( value instanceof String[] ) { result[ 0 ] = ((String[])value)[ 0 ]; result[ 1 ] = ((String[])value)[ 1 ]; } } @Override public RUIVariable getVariable() { return variable; } }; setDebugCommand( VAR_VALUE + " " + variable.getStackFrame().getId() + " " + variable.getIndex() ); //$NON-NLS-1$ //$NON-NLS-2$ int timeLeft = 4000; // wait up to 4 seconds for a response. while ( result[ 0 ] == null ) { try { if ( fTerminating || fTerminated || timeLeft == 0 ) { // If we sit here waiting forever, we break the variables view. completion.completed( new String[] { "", "" } ); //$NON-NLS-1$ //$NON-NLS-2$ debugCmd = null; // reset so new requests can be made. } else { Thread.sleep( 10 ); timeLeft -= 10; } } catch ( Exception e ) { result[ 0 ] = ""; //$NON-NLS-1$ } } completion = null; variable.getCurrValue().setValue( result[ 0 ] ); if ( result[ 1 ] != null && result[ 1 ].trim().length() != 0 ) { variable.setType( result[ 1 ].trim() ); } } return variable.getCurrValue(); } public boolean setVariableValue( final RUIVariable variable, final String LHS, final String RHS, final String getter, final String setter ) { if ( isSuspended() ) { synchronized ( completionSynchObj ) { final String[] result = new String[ 1 ]; completion = new IVariableQueryCompletion() { @Override public void completed( Object value ) { if ( value instanceof String ) { result[ 0 ] = (String)value; } } @Override public RUIVariable getVariable() { return variable; } }; setDebugCommand( VAR_SET_VALUE + " " + variable.getStackFrame().getId() + " " + variable.getIndex() + " " + LHS + " " + RHS + " " //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ + getter + " " + setter ); //$NON-NLS-1$ int timeLeft = 4000; // wait up to 4 seconds for a response. while ( result[ 0 ] == null ) { try { if ( fTerminating || fTerminated || timeLeft == 0 ) { // If we sit here waiting forever, we break the variables view. completion.completed( "0" ); //$NON-NLS-1$ debugCmd = null; // reset so new requests can be made. } else { Thread.sleep( 10 ); timeLeft -= 10; } } catch ( Exception e ) { result[ 0 ] = ""; //$NON-NLS-1$ } } completion = null; if ( result[ 0 ] != null && result[ 0 ].length() != 0 && result[ 0 ].charAt( 0 ) == '1' ) { // 1 means variable was successfully updated. return true; } } } return false; } @Override public void suspend() throws DebugException { if ( isSuspended() || context == null ) { return; } fSuspending = true; fireEvent( new DebugEvent( this, DebugEvent.CHANGE ) ); } public void suspend( String reason ) { fSuspending = false; fSuspended = true; int detail = getSuspendDetail( reason ); if ( detail != DebugEvent.STEP_END ) { steppingIn = false; } fThread.fireSuspendEvent( detail ); fireEvent( new DebugEvent( getCurrentThread(), DebugEvent.CHANGE ) ); fireEvent( new DebugEvent( this, DebugEvent.CHANGE ) ); } public IEGLVariable[] getVariables( final RUIVariable variable ) { if ( !isSuspended() ) { return new IEGLVariable[ 0 ]; } final Object[] result; synchronized ( completionSynchObj ) { result = new Object[ 1 ]; completion = new IVariableQueryCompletion() { @Override public void completed( Object value ) { if ( value instanceof IEGLVariable[] ) { result[ 0 ] = value; } } @Override public RUIVariable getVariable() { return variable; } }; setDebugCommand( VAR_VARIABLES + " " + variable.getStackFrame().getId() + " " + variable.getIndex() ); //$NON-NLS-1$ //$NON-NLS-2$ int timeLeft = 4000; // wait up to 4 seconds for a response. while ( result[ 0 ] == null ) { try { if ( fTerminating || fTerminated || timeLeft == 0 ) { // If we sit here waiting forever, we break the variables view. completion.completed( new RUIVariable[ 0 ] ); debugCmd = null; // reset so new requests can be made. } else { Thread.sleep( 10 ); timeLeft -= 10; } } catch ( Exception e ) { result[ 0 ] = new RUIVariable[ 0 ]; } } completion = null; } return (IEGLVariable[])result[ 0 ]; } private int getSuspendDetail( String reason ) { if ( "breakpoint".equals( reason ) ) //$NON-NLS-1$ { return DebugEvent.BREAKPOINT; } if ( "step".equals( reason ) ) //$NON-NLS-1$ { return DebugEvent.STEP_END; } return 0; } @Override public void resume() throws DebugException { steppingIn = false; setDebugCommand( "resume" ); //$NON-NLS-1$ } public void resume( String reason ) { fSuspended = false; int detail = getResumeDetail( reason ); if ( detail != DebugEvent.STEP_INTO ) { steppingIn = false; } fThread.fireResumeEvent( detail ); fireEvent( new DebugEvent( this, DebugEvent.CHANGE ) ); } private int getResumeDetail( String reason ) { if ( "clientRequest".equals( reason ) ) //$NON-NLS-1$ { return DebugEvent.CLIENT_REQUEST; } if ( "stepOver".equals( reason ) ) //$NON-NLS-1$ { return DebugEvent.STEP_OVER; } if ( "stepIn".equals( reason ) ) //$NON-NLS-1$ { return DebugEvent.STEP_INTO; } if ( "stepOut".equals( reason ) ) //$NON-NLS-1$ { return DebugEvent.STEP_RETURN; } return 0; } /* * (non-Javadoc) * @see org.eclipse.debug.core.model.IDebugElement#getDebugTarget() */ @Override public IDebugTarget getDebugTarget() { return this; } public void parseStack( String stackStr ) { fStackFrames = new ArrayList<RUIStackFrame>(); StringTokenizer stackStrings = new StringTokenizer( stackStr, "|" ); //$NON-NLS-1$ while ( stackStrings.hasMoreTokens() ) { String nextStack = stackStrings.nextToken(); StringTokenizer stackProperties = new StringTokenizer( nextStack, "," ); //$NON-NLS-1$ String nextFile = RUIDebugUtil.decodeValue( stackProperties.nextToken() ); String nextFunctionName = stackProperties.nextToken(); int nextLineNum = Integer.parseInt( stackProperties.nextToken() ); int nextFrameId = Integer.parseInt( stackProperties.nextToken() ); RUIStackFrame newFrame = new RUIStackFrame( getCurrentThread(), nextFrameId, RUIDebugUtil.getProgramNameFromFile( nextFile ), nextFile, nextFunctionName, nextLineNum ); // process the variable info boolean first = true; while ( stackProperties.hasMoreTokens() ) { String varName = stackProperties.nextToken(); String jsName = stackProperties.nextToken(); String getterName = stackProperties.nextToken(); String setterName = stackProperties.nextToken(); String varIndex = stackProperties.nextToken(); String varType = getTypeFromTokenizer( stackProperties ); boolean kids = Integer.parseInt( stackProperties.nextToken() ) != 0; RUIVariable var = new RUIVariable( newFrame, null, varName, jsName, getterName, setterName, first ? "this" : varName, varIndex, varType, kids ); //$NON-NLS-1$ newFrame.addVariable( var ); first = false; } if ( (nextLineNum != -1 || fStackFrames.size() != 0) && (nextLineNum != -1 || !"<<undefined>>".equals( nextFile )) ) //$NON-NLS-1$ { // There can be 1 or more "init" frames with no real line number. Skip them. fStackFrames.add( 0, newFrame ); } } // Check if we can re-use any to save the variables. if ( fOldStackFrames != null && fOldStackFrames.size() != 0 ) { for ( int i = 0, size = fStackFrames.size(); i < size; i++ ) { RUIStackFrame newFrame = fStackFrames.get( i ); RUIStackFrame oldFrame = findMatchingOldStackFrame( newFrame, size - i - 1 ); if ( oldFrame != null ) { oldFrame.initialize( newFrame ); fStackFrames.set( i, oldFrame ); } } } } /** * Checks if we have an old stack frame at a certain depth (from the bottom of the call stack) that was previously used for the given stack frame * * @param depth The distance from the bottom of the call stack, 0 indicates the bottom call stack entry */ private RUIStackFrame findMatchingOldStackFrame( RUIStackFrame newFrame, int depth ) { if ( fOldStackFrames == null || fOldStackFrames.size() == 0 ) { return null; } // Determine the array index that is depth entries from the end of the array int index = fOldStackFrames.size() - depth - 1; if ( index < 0 ) { return null; } RUIStackFrame oldFrame = (RUIStackFrame)fOldStackFrames.get( index ); if ( oldFrame.getProgramName().equals( newFrame.getProgramName() ) && oldFrame.getFunctionName().equals( newFrame.getFunctionName() ) ) { return oldFrame; } return null; } /** * Parses the variable type from a tokenizer. Some types, such as "num(1,0)", will be broken into two tokens when the delimiter is a comma. * * @param tok The tokenizer. * @return the variable type. */ private String getTypeFromTokenizer( StringTokenizer tok ) { String type = tok.nextToken(); if ( type.indexOf( '(' ) != -1 && type.indexOf( ')' ) == -1 ) { type += "," + tok.nextToken(); //$NON-NLS-1$ } return type; } public String handleAtLine( Map args ) { // Tell the debug target to suspend. This updates the UI String reason = (String)args.get( "suspendReason" ); //$NON-NLS-1$ if ( reason != null ) { if ( "breakpoint".equals( reason ) ) //$NON-NLS-1$ { // Look for the breakpoint that we hit. IBreakpoint bp = findBreakpointFromArgs( args ); if ( bp != null ) { getCurrentThread().setBreakpoints( new IBreakpoint[] { bp } ); } } suspend( reason ); } // Sleep until there is a command to report. It will be "resume" or "step" etc... while ( isSuspended() && debugCmd == null ) { try { Thread.sleep( 10 ); } catch ( Exception e ) { } } String temp = debugCmd; debugCmd = null; if ( temp == null || !temp.startsWith( RUIDebugTarget.VAR_VAR ) ) { if ( fStackFrames != null && fStackFrames.size() != 0 ) { fOldStackFrames = fStackFrames; } fStackFrames = null; } return temp; } private IBreakpoint findBreakpointFromArgs( final Map args ) { int line; String file = (String)args.get( "file" ); //$NON-NLS-1$ try { line = Integer.parseInt( (String)args.get( "line" ) ); //$NON-NLS-1$ } catch ( NumberFormatException nfe ) { line = -1; } IBreakpoint[] breakpoints = DebugPlugin.getDefault().getBreakpointManager() .getBreakpoints( IEGLDebugCoreConstants.EGL_JAVA_MODEL_PRESENTATION_ID ); for ( int i = 0; i < breakpoints.length; i++ ) { IBreakpoint breakpoint = breakpoints[ i ]; if ( supportsBreakpoint( breakpoint ) ) { try { if ( breakpoint.isEnabled() && line == breakpoint.getMarker().getAttribute( IMarker.LINE_NUMBER, -1 ) && file.equals( BreakpointUtils.getRelativeBreakpointPath( breakpoint ) ) ) { return breakpoint; } } catch ( CoreException ce ) { } } } return null; } public boolean needsNewStack() { return fStackFrames == null || fStackFrames.size() == 0; } public void varValue( Map args ) { if ( completion != null ) { String value = (String)args.get( "value" ); //$NON-NLS-1$ String type = (String)args.get( "type" ); //$NON-NLS-1$ value = (value == null ? "" //$NON-NLS-1$ : value); type = (type == null ? "" //$NON-NLS-1$ : type); completion.completed( new String[] { value, type } ); } } public void varSetValue( Map args ) { if ( completion != null ) { completion.completed( args.get( "success" ) ); //$NON-NLS-1$ } } public void varVariables( String value ) { if ( completion != null ) { List<IEGLVariable> list = new ArrayList<IEGLVariable>(); if ( value != null ) { StringTokenizer varTokenizer = new StringTokenizer( value, "," ); //$NON-NLS-1$ RUIVariable parent = completion.getVariable(); RUIStackFrame frame = parent.getStackFrame(); while ( varTokenizer.hasMoreTokens() ) { String varName = varTokenizer.nextToken(); String jsName = varTokenizer.nextToken(); String getterName = varTokenizer.nextToken(); String setterName = varTokenizer.nextToken(); String varIndex = varTokenizer.nextToken(); String varType = getTypeFromTokenizer( varTokenizer ); boolean kids = Integer.parseInt( varTokenizer.nextToken() ) != 0; RUIVariable var = new RUIVariable( frame, parent, varName, jsName, getterName, setterName, parent.getQualifiedName() + "." //$NON-NLS-1$ + varName, varIndex, varType, kids ); list.add( var ); } } completion.completed( (IEGLVariable[])list.toArray( new IEGLVariable[ list.size() ] ) ); } } public void setVarValue() { if ( completion != null ) { completion.completed( null ); } } public boolean isStepInto() { return steppingIn; } public LocaleInfo getLocaleInfo() { return this.localeInfo; } public void setLocaleInfo( Map args ) { if ( this.localeInfo == null ) { this.localeInfo = new LocaleInfo(); } String val = (String)args.get( "decimalSymbol" ); //$NON-NLS-1$ if ( val != null ) { this.localeInfo.decimalSymbol = val; } val = (String)args.get( "currencySymbol" ); //$NON-NLS-1$ if ( val != null ) { this.localeInfo.currencySymbol = val; } } public class LocaleInfo { public String decimalSymbol; public String currencySymbol; } }