/*******************************************************************************
 * Copyright (c) 2000, 2004 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Common Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/cpl-v10.html
 * 
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.jdt.internal.debug.core.model;

 
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;

import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.debug.core.DebugEvent;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.model.IRegisterGroup;
import org.eclipse.debug.core.model.IStackFrame;
import org.eclipse.debug.core.model.IThread;
import org.eclipse.debug.core.model.IVariable;
import org.eclipse.jdt.debug.core.IJavaClassType;
import org.eclipse.jdt.debug.core.IJavaModifiers;
import org.eclipse.jdt.debug.core.IJavaObject;
import org.eclipse.jdt.debug.core.IJavaStackFrame;
import org.eclipse.jdt.debug.core.IJavaThread;
import org.eclipse.jdt.debug.core.IJavaVariable;
import org.eclipse.jdt.internal.debug.core.JDIDebugPlugin;

import com.sun.jdi.AbsentInformationException;
import com.sun.jdi.Field;
import com.sun.jdi.IncompatibleThreadStateException;
import com.sun.jdi.LocalVariable;
import com.sun.jdi.Method;
import com.sun.jdi.NativeMethodException;
import com.sun.jdi.ObjectReference;
import com.sun.jdi.ReferenceType;
import com.sun.jdi.StackFrame;
import com.sun.jdi.Type;
import com.sun.jdi.VirtualMachine;

/**
 * Proxy to a stack frame on the target.
 */

public class JDIStackFrame extends JDIDebugElement implements IJavaStackFrame {

	/**
	 * This frame's depth in the call stack
	 */
	private int fDepth;
	
	/**
	 * Underlying JDI stack frame.
	 */
	private StackFrame fStackFrame;
	/**
	 * The last (previous) underlying method.
	 */
	private Method fLastMethod;
	/**
	 * Containing thread.
	 */
	private JDIThread fThread;
	/**
	 * Visible variables.
	 */
	private List fVariables;
	/**
	 * The method this stack frame is associated with.
	 * Cached lazily on first access.
	 */
	private Method fMethod= null;
	/**
	 * The underlying Object associated with this stack frame.  
	 * Cached lazily on first access.
	 */
	private ObjectReference fThisObject;
	/**
	 * The name of the type in which the method for this stack frame was
	 * declared (implemented).  Cached lazily on first access.
	 */
	private String fDeclaringTypeName;
	/**
	 * The name of the type of the object that received the method call associated
	 * with this stack frame.  Cached lazily on first access.
	 */
	private String fReceivingTypeName;
	/**
	 * Whether the variables need refreshing
	 */
	private boolean fRefreshVariables= true;
	/**
	 * Whether this stack frame has been marked as out of synch. If set
	 * to <code>true</code> this stack frame will stop dynamically
	 * calculating its out of synch state.
	 */
	private boolean fIsOutOfSynch= false;
	/**
	 * The source name debug attribute. Cached lazily on first access.
	 */
	private String fSourceName;
	
	/**
	 * Whether local variable information was available
	 */
	private boolean fLocalsAvailable = true;

	/**
	 * Creates a new stack frame in the given thread.
	 * 
	 * @param thread The parent JDI thread
	 * @param stackFrame The underlying stack frame
	 */
	public JDIStackFrame(JDIThread thread, int depth) {
		super((JDIDebugTarget)thread.getDebugTarget());
		setDepth(depth);
		setThread(thread);
	}
	
	/**
	 * Sets this frame's depth in the call stack.
	 * 
	 * @param depth index in the call stack
	 */
	protected void setDepth(int depth) {
		fDepth = depth;
		clearCachedData();
		fRefreshVariables = true;
	}
	
	/**
	 * Returns this fame's depth in the call stack.
	 * 
	 * @return this frame's depth in the call stack
	 */
	protected int getDepth() {
		return fDepth;
	}
	
	/**
	 * @see IStackFrame#getThread()
	 */
	public IThread getThread() {
		return fThread;
	}

	/**
	 * @see ISuspendResume#canResume()
	 */
	public boolean canResume() {
		return getThread().canResume();
	}

	/**
	 * @see ISuspendResume#canSuspend()
	 */
	public boolean canSuspend() {
		return getThread().canSuspend();
	}

	/**
	 * @see IStep#canStepInto()
	 */
	public boolean canStepInto() {
		try {
			return exists() && isTopStackFrame() && !isObsolete() && getThread().canStepInto();
		} catch (DebugException e) {
			logError(e);
			return false;
		}
	}

	/**
	 * @see IStep#canStepOver()
	 */
	public boolean canStepOver() {
		try {
			return exists() && !isObsolete() && getThread().canStepOver();
		} catch (DebugException e) {
			logError(e);
			return false;
		}
	}

	/**
	 * @see IStep#canStepReturn()
	 */
	public boolean canStepReturn() {
		try {
			if (!exists() || isObsolete() || !getThread().canStepReturn()) {
				return false;
			}
			List frames = ((JDIThread)getThread()).computeStackFrames();
			if (frames != null && !frames.isEmpty()) {
				boolean bottomFrame = this.equals(frames.get(frames.size() - 1));
				boolean aboveObsoleteFrame= false;
				if (!bottomFrame) {
					int index= frames.indexOf(this);
					if (index < frames.size() -1 && ((JDIStackFrame)frames.get(index + 1)).isObsolete()) {
						aboveObsoleteFrame= true;
					}
				}
				return !bottomFrame && !aboveObsoleteFrame;
			}
		} catch (DebugException e) {
			logError(e);
		}
		return false;
	}

	/**
	 * Returns the underlying method associated with this stack frame,
	 * retreiving the method is necessary.
	 */
	public Method getUnderlyingMethod() throws DebugException {
		synchronized (fThread) {
			if (fStackFrame == null || fMethod == null) {
				try {
					fMethod= getUnderlyingStackFrame().location().method();
					fLastMethod = fMethod;
				} catch (RuntimeException e) {
					targetRequestFailed(MessageFormat.format(JDIDebugModelMessages.getString("JDIStackFrame.exception_retrieving_method"), new String[] {e.toString()}), e); //$NON-NLS-1$
				}
			}
			return fMethod;
		}
	}

	/**
	 * @see IStackFrame#getVariables()
	 */
	public IVariable[] getVariables() throws DebugException {
		List list = getVariables0();
		return (IVariable[])list.toArray(new IVariable[list.size()]);
	}
	
	protected List getVariables0() throws DebugException {
		synchronized (fThread) {
			if (fVariables == null) {
				
				// throw exception if native method, so variable view will update
				// with information message
				if (isNative()) {
					requestFailed(JDIDebugModelMessages.getString("JDIStackFrame.Variable_information_unavailable_for_native_methods"), null); //$NON-NLS-1$
				}
				
				Method method= getUnderlyingMethod();
				fVariables= new ArrayList();
				// #isStatic() does not claim to throw any exceptions - so it is not try/catch coded
				if (method.isStatic()) {
					// add statics
					List allFields= null;
					ReferenceType declaringType = method.declaringType();
					try {
						allFields= declaringType.allFields();
					} catch (RuntimeException e) {
						targetRequestFailed(MessageFormat.format(JDIDebugModelMessages.getString("JDIStackFrame.exception_retrieving_fields"),new String[] {e.toString()}), e); //$NON-NLS-1$
						// execution will not reach this line, as 
						// #targetRequestFailed will throw an exception					
						return Collections.EMPTY_LIST;
					}
					if (allFields != null) {
						Iterator fields= allFields.iterator();
						while (fields.hasNext()) {
							Field field= (Field) fields.next();
							if (field.isStatic()) {
								fVariables.add(new JDIFieldVariable((JDIDebugTarget)getDebugTarget(), field, declaringType));
							}
						}
						Collections.sort(fVariables, new Comparator() {
							public int compare(Object a, Object b) {
								JDIFieldVariable v1= (JDIFieldVariable)a;
								JDIFieldVariable v2= (JDIFieldVariable)b;
								try {
									return v1.getName().compareToIgnoreCase(v2.getName());
								} catch (DebugException de) {
									logError(de);
									return -1;
								}
							}
						});
					}
				} else {
					// add "this"
					ObjectReference t= getUnderlyingThisObject();
					if (t != null) {
						fVariables.add(new JDIThisVariable((JDIDebugTarget)getDebugTarget(), t));
					}
				}
				// add locals
				Iterator variables= getUnderlyingVisibleVariables().iterator();
				while (variables.hasNext()) {
					LocalVariable var= (LocalVariable) variables.next();
					fVariables.add(new JDILocalVariable(this, var));
				}
			} else if (fRefreshVariables) {
				updateVariables();
			}
			fRefreshVariables = false;
			return fVariables;
		}
	}

	/**
	 * @see IStackFrame#getName()
	 */
	public String getName() throws DebugException {
		return getMethodName();
	}
	
	/**
	 * @see IJavaStackFrame#getArgumentTypeNames()
	 */
	public List getArgumentTypeNames() throws DebugException {
		try {
			return getUnderlyingMethod().argumentTypeNames();
		} catch (RuntimeException e) {
			targetRequestFailed(MessageFormat.format(JDIDebugModelMessages.getString("JDIStackFrame.exception_retrieving_argument_type_names"), new String[] {e.toString()}), e); //$NON-NLS-1$
			// execution will never reach this line, as
			// #targetRequestFailed will throw an exception			
			return null;
		}
	}

	/**
	 * @see IStackFrame#getLineNumber()
	 */
	public int getLineNumber() throws DebugException {
		if (isSuspended()) {
			try {
				return getUnderlyingStackFrame().location().lineNumber();
			} catch (RuntimeException e) {
				if (getThread().isSuspended()) {
					targetRequestFailed(MessageFormat.format(JDIDebugModelMessages.getString("JDIStackFrame.exception_retrieving_line_number"), new String[] {e.toString()}), e); //$NON-NLS-1$
				}
			}
		}
		return -1;
	}

	/**
	 * @see IStep#isStepping()
	 */
	public boolean isStepping() {
		return getThread().isStepping();
	}

	/**
	 * @see ISuspendResume#isSuspended()
	 */
	public boolean isSuspended() {
		return getThread().isSuspended();
	}

	/**
	 * @see ISuspendResume#resume()
	 */
	public void resume() throws DebugException {
		getThread().resume();
	}

	/**
	 * @see IStep#stepInto()
	 */
	public void stepInto() throws DebugException {
		if (!canStepInto()) {
			return;
		}
		getThread().stepInto();
	}

	/**
	 * @see IStep#stepOver()
	 */
	public void stepOver() throws DebugException {
		if (!canStepOver()) {
			return;
		}
		if (isTopStackFrame()) {
			getThread().stepOver();
		} else {
			((JDIThread)getThread()).stepToFrame(this);
		}
	}

	/**
	 * @see IStep#stepReturn()
	 */
	public void stepReturn() throws DebugException {
		if (!canStepReturn()) {
			return;
		}
		if (isTopStackFrame()) {
			getThread().stepReturn();
		} else {
			List frames = ((JDIThread)getThread()).computeStackFrames();
			int index = frames.indexOf(this);
			if (index >= 0 && index < frames.size() - 1) {
				IStackFrame nextFrame = (IStackFrame)frames.get(index + 1);
				((JDIThread)getThread()).stepToFrame(nextFrame);
			}
		}
	}

	/**
	 * @see ISuspendResume#suspend()
	 */
	public void suspend() throws DebugException {
		getThread().suspend();
	}

	/**
	 * Incrementally updates this stack frames variables.
	 * 
	 * @see JDIDebugElement#targetRequestFailed(String, RuntimeException)
	 */
	protected void updateVariables() throws DebugException {
		if (fVariables == null) {
			return;
		}

		Method method= getUnderlyingMethod();
		int index= 0;
		if (!method.isStatic()) {
			// update "this"
			ObjectReference thisObject;
			try {
				thisObject= getUnderlyingThisObject();
			} catch (DebugException exception) {
				if (!getThread().isSuspended()) {
					thisObject= null;
				} else {
					throw exception;
				}
			}
			JDIThisVariable oldThisObject= null;
			if (!fVariables.isEmpty() && fVariables.get(0) instanceof JDIThisVariable) {
				oldThisObject= (JDIThisVariable) fVariables.get(0);
			}
			if (thisObject == null && oldThisObject != null) {
				// removal of 'this'
				fVariables.remove(0);
				index= 0;
			} else {
				if (oldThisObject == null && thisObject != null) {
					// creation of 'this'
					oldThisObject= new JDIThisVariable((JDIDebugTarget)getDebugTarget(),thisObject);
					fVariables.add(0, oldThisObject);
					index= 1;
				} else {
					if (oldThisObject != null) {
						// 'this' still exists, replace with new 'this' if a different receiver
						if (!oldThisObject.retrieveValue().equals(thisObject)) {
							fVariables.remove(0);
							fVariables.add(0, new JDIThisVariable((JDIDebugTarget)getDebugTarget(),thisObject));
						}
						index= 1;
					}
				}
			}
		}

		List locals= null;
		try {
			locals= getUnderlyingStackFrame().visibleVariables();
		} catch (AbsentInformationException e) {
			locals= Collections.EMPTY_LIST;
		} catch (NativeMethodException e) {
			locals= Collections.EMPTY_LIST;
		} catch (RuntimeException e) {
			targetRequestFailed(MessageFormat.format(JDIDebugModelMessages.getString("JDIStackFrame.exception_retrieving_visible_variables"),new String[] {e.toString()}), e); //$NON-NLS-1$
			// execution will not reach this line, as 
			// #targetRequestFailed will throw an exception			
			return;
		}
		int localIndex= -1;
		while (index < fVariables.size()) {
			Object var= fVariables.get(index);
			if (var instanceof JDILocalVariable) {
				JDILocalVariable local= (JDILocalVariable) fVariables.get(index);
				localIndex= locals.indexOf(local.getLocal());
				if (localIndex >= 0) {
					// update variable with new underling JDI LocalVariable
					local.setLocal((LocalVariable) locals.get(localIndex));
					locals.remove(localIndex);
					index++;
				} else {
					// remove variable
					fVariables.remove(index);
				}
			} else {
				//field variable of a static frame
				index++;
			}
		}

		// add any new locals
		Iterator newOnes= locals.iterator();
		while (newOnes.hasNext()) {
			JDILocalVariable local= new JDILocalVariable(this, (LocalVariable) newOnes.next());
			fVariables.add(local);
		}
	}

	/**
	 * @see IJavaStackFrame#supportsDropToFrame()
	 */
	public boolean supportsDropToFrame() {
		//FIXME 1GH3XDA: ITPDUI:ALL - Drop to frame hangs if after invoke
		JDIThread thread= (JDIThread) getThread();
		JDIDebugTarget target= (JDIDebugTarget)thread.getDebugTarget();
		try {
			if (!target.isAvailable() || !thread.isSuspended() || thread.isTerminated()) {
				return false;
			} 
			boolean j9Support= false;
			boolean jdkSupport= target.canPopFrames();
			VirtualMachine vm = getVM();
			if (vm == null) {
				return false;
			}
			try {
				j9Support= (thread.getUnderlyingThread() instanceof org.eclipse.jdi.hcr.ThreadReference) &&
						((org.eclipse.jdi.hcr.VirtualMachine)vm).canDoReturn();
			} catch (UnsupportedOperationException uoe) {
				j9Support= false;
			}
			
			if (jdkSupport || j9Support) {
				// Also ensure that this frame and no frames above this
				// frame are native. Unable to pop native stack frames.
				List frames= thread.computeStackFrames();
				if (jdkSupport) {
					// JDK 1.4 VMs are currently unable to pop the bottom
					// stack frame.
					if ((frames.size() > 0) && frames.get(frames.size() - 1) == this) {
						return false;
					}
				}
				int index = 0;
				JDIStackFrame frame= null;
				while (index < frames.size()) {
					frame= (JDIStackFrame) frames.get(index);
					index++;
					if (frame.isNative()) {
						return false;
					}
					if (frame.equals(this)) {
						if (jdkSupport) {
							// JDK 1.4 VMs are currently unable to pop the
							// frame directly above a native frame
							if (index < frames.size() && ((JDIStackFrame)frames.get(index)).isNative()) {
								return false;
							}
						}
						return true;
					}
				}
			}
			return false;
		} catch (DebugException e) {
			if (e.getStatus().getException() instanceof IncompatibleThreadStateException || e.getStatus().getCode() == IJavaThread.ERR_THREAD_NOT_SUSPENDED) {
				// if the thread has since resumed, drop is not supported
				return false;
			}
			logError(e);
		} catch (UnsupportedOperationException e) {
			// drop to frame not supported - this is an expected
			// exception for VMs that do not support drop to frame
			return false;
		} catch (RuntimeException e) {
			internalError(e);
		}
		return false;
	}

	/**
	 * @see IJavaStackFrame#dropToFrame()
	 */
	public void dropToFrame() throws DebugException {
		if (supportsDropToFrame()) {
			((JDIThread) getThread()).dropToFrame(this);
		} else {
			notSupported(JDIDebugModelMessages.getString("JDIStackFrame.Drop_to_frame_not_supported")); //$NON-NLS-1$
		}
	}

	public void popFrame() throws DebugException {
		if (supportsDropToFrame()) {
			((JDIThread) getThread()).popFrame(this);
		} else {
			notSupported(JDIDebugModelMessages.getString("JDIStackFrame.pop_frame_not_supported")); //$NON-NLS-1$
		}
	}

	/**
	 * @see IJavaStackFrame#findVariable(String)
	 */
	public IJavaVariable findVariable(String varName) throws DebugException {
		if (isNative()) {
			return null;
		}
		IVariable[] variables = getVariables();
		IJavaVariable thisVariable= null;
		for (int i = 0; i < variables.length; i++) {
			IJavaVariable var = (IJavaVariable) variables[i];
			if (var.getName().equals(varName)) {
				return var;
			}
			if (var instanceof JDIThisVariable) {
				// save for later - check for instance and static vars
				thisVariable= var;
			}
		}

		if (thisVariable != null) {
			IVariable[] thisChildren = thisVariable.getValue().getVariables();
			for (int i = 0; i < thisChildren.length; i++) {
				IJavaVariable var= (IJavaVariable) thisChildren[i];
				if (var.getName().equals(varName)) {
					return var;
				}
			}
		}

		return null;

	}

	/**
	 * Retrieves visible variables in this stack frame
	 * handling any exceptions. Returns an empty list if there are no
	 * variables.
	 * 
	 * @see JDIDebugElement#targetRequestFailed(String, RuntimeException)
	 */
	protected List getUnderlyingVisibleVariables() throws DebugException {
		List variables= Collections.EMPTY_LIST;
		try {
			variables= getUnderlyingStackFrame().visibleVariables();
		} catch (AbsentInformationException e) {
			setLocalsAvailable(false);
		} catch (NativeMethodException e) {
			setLocalsAvailable(false);
		} catch (RuntimeException e) {
			targetRequestFailed(MessageFormat.format(JDIDebugModelMessages.getString("JDIStackFrame.exception_retrieving_visible_variables_2"),new String[] {e.toString()}), e); //$NON-NLS-1$
		}

		return variables;
	}

	/**
	 * Retrieves 'this' from the underlying stack frame.
	 * Returns <code>null</code> for static stack frames.
	 * 
	 * @see JDIDebugElement#targetRequestFailed(String, RuntimeException)
	 */
	protected ObjectReference getUnderlyingThisObject() throws DebugException {
		if (fStackFrame == null || fThisObject == null && !isStatic()) {
			try {
				fThisObject = getUnderlyingStackFrame().thisObject();
			} catch (RuntimeException e) {
				targetRequestFailed(MessageFormat.format(JDIDebugModelMessages.getString("JDIStackFrame.exception_retrieving_this"),new String[] {e.toString()}), e); //$NON-NLS-1$
				// execution will not reach this line, as 
				// #targetRequestFailed will throw an exception			
				return null;
			}
		}
		return fThisObject;
	}
	
	/**
	 * @see IAdaptable#getAdapter(Class)
	 */
	public Object getAdapter(Class adapter) {
		if (adapter == IJavaStackFrame.class || adapter == IJavaModifiers.class) {
			return this;
		}
		return super.getAdapter(adapter);
	}
	
	/**
	 * @see IJavaStackFrame#getSignature()
	 */
	public String getSignature() throws DebugException {
		try {
			return getUnderlyingMethod().signature();
		} catch (RuntimeException e) {
			targetRequestFailed(MessageFormat.format(JDIDebugModelMessages.getString("JDIStackFrame.exception_retrieving_method_signature"), new String[] {e.toString()}), e); //$NON-NLS-1$
			// execution will not reach this line, as 
			// #targetRequestFailed will throw an exception			
			return null;
		}
	}
	
	/**
	 * @see IJavaStackFrame#getDeclaringTypeName()
	 */
	public String getDeclaringTypeName() throws DebugException {
		if (fStackFrame == null || fDeclaringTypeName == null) {
			try {
				if (isObsolete()) {
					fDeclaringTypeName=  JDIDebugModelMessages.getString("JDIStackFrame.<unknown_declaring_type>_1"); //$NON-NLS-1$
				} else {
					fDeclaringTypeName= getUnderlyingMethod().declaringType().name();
				}
			} catch (RuntimeException e) {
				if (getThread().isSuspended()) {
					targetRequestFailed(MessageFormat.format(JDIDebugModelMessages.getString("JDIStackFrame.exception_retrieving_declaring_type"), new String[] {e.toString()}), e); //$NON-NLS-1$
				}
				return JDIDebugModelMessages.getString("JDIStackFrame.<unknown_declaring_type>_1"); //$NON-NLS-1$
			}
		}
		return fDeclaringTypeName;
	}
	
	/**
	 * @see IJavaStackFrame#getReceivingTypeName()
	 */
	public String getReceivingTypeName() throws DebugException {
		if (fStackFrame == null || fReceivingTypeName == null) {
			try {
				if (isObsolete()) {
					fReceivingTypeName=JDIDebugModelMessages.getString("JDIStackFrame.<unknown_receiving_type>_2"); //$NON-NLS-1$
				} else {
					ObjectReference thisObject = getUnderlyingThisObject();
					if (thisObject == null) {
						fReceivingTypeName = getDeclaringTypeName();
					} else {
						fReceivingTypeName = thisObject.referenceType().name();
					}
				}
			} catch (RuntimeException e) {
				if (getThread().isSuspended()) {
					targetRequestFailed(MessageFormat.format(JDIDebugModelMessages.getString("JDIStackFrame.exception_retrieving_receiving_type"), new String[] {e.toString()}), e); //$NON-NLS-1$
				}
				return JDIDebugModelMessages.getString("JDIStackFrame.<unknown_receiving_type>_2"); //$NON-NLS-1$
			}
		}
		return fReceivingTypeName;
	}
	
	/**
	 * @see IJavaStackFrame#getMethodName()
	 */
	public String getMethodName() throws DebugException {
		try {
			return getUnderlyingMethod().name();	
		} catch (RuntimeException e) {
			if (getThread().isSuspended()) {
				targetRequestFailed(MessageFormat.format(JDIDebugModelMessages.getString("JDIStackFrame.exception_retrieving_method_name"), new String[] {e.toString()}), e); //$NON-NLS-1$
			}
			return JDIDebugModelMessages.getString("JDIStackFrame.<unknown_method>_1"); //$NON-NLS-1$
		}
	}
	
	/**
	 * @see IJavaStackFrame#isNative()
	 */
	public boolean isNative() throws DebugException {
		return getUnderlyingMethod().isNative();
	}
	
	/**
	 * @see IJavaStackFrame#isConstructor()
	 */
	public boolean isConstructor() throws DebugException {
		return getUnderlyingMethod().isConstructor();
	}
	
	/**
	 * @see IJavaStackFrame#isStaticInitializer()
	 */
	public boolean isStaticInitializer() throws DebugException {
		return getUnderlyingMethod().isStaticInitializer();
	}
	
	/**
	 * @see IJavaModifiers#isFinal()
	 */
	public boolean isFinal() throws DebugException {
		return getUnderlyingMethod().isFinal();
	}
	
	/**
	 * @see IJavaStackFrame#isSynchronized()
	 */
	public boolean isSynchronized() throws DebugException {
		return getUnderlyingMethod().isSynchronized();
	}
	
	/**
	 * @see IJavaModifiers#isSynthetic()
	 */
	public boolean isSynthetic() throws DebugException {
		return getUnderlyingMethod().isSynthetic();
	}
	
	/**
	 * @see IJavaModifiers#isPublic()
	 */
	public boolean isPublic() throws DebugException {
		return getUnderlyingMethod().isPublic();
	}
	
	/**
	 * @see IJavaModifiers#isPrivate()
	 */
	public boolean isPrivate() throws DebugException {
		return getUnderlyingMethod().isPrivate();
	}
	
	/**
	 * @see IJavaModifiers#isProtected()
	 */
	public boolean isProtected() throws DebugException {
		return getUnderlyingMethod().isProtected();
	}
	
	/**
	 * @see IJavaModifiers#isPackagePrivate()
	 */
	public boolean isPackagePrivate() throws DebugException {
		return getUnderlyingMethod().isPackagePrivate();
	}
	
	/**
	 * @see IJavaModifiers#isStatic()
	 */
	public boolean isStatic() throws DebugException {
		return getUnderlyingMethod().isStatic();
	}
		
	/**
	 * @see IJavaStackFrame#getSourceName()
	 */
	public String getSourceName() throws DebugException {
		if (fStackFrame == null || fSourceName == null) {
			try {
				fSourceName = getUnderlyingStackFrame().location().sourceName();
			} catch (AbsentInformationException e) {
				fSourceName = null;
			} catch (NativeMethodException e) {
				fSourceName = null;
			} catch (RuntimeException e) {
				targetRequestFailed(MessageFormat.format(JDIDebugModelMessages.getString("JDIStackFrame.exception_retrieving_source_name"), new String[] {e.toString()}), e); //$NON-NLS-1$
			}
		}
		return fSourceName;
	}
	
	protected boolean isTopStackFrame() throws DebugException {
		IStackFrame tos = getThread().getTopStackFrame();
		return tos != null && tos.equals(this);
	}
	
	/**
	 * Sets this stack frame to be out of synch.
	 * Note that passing <code>true</code> to this method
	 * marks this stack frame as out of synch permanently (statically).
	 */
	public void setOutOfSynch(boolean outOfSynch) {
		fIsOutOfSynch= outOfSynch;
	}
	
	/**
	 * @see IJavaStackFrame#isOutOfSynch()
	 */
	public boolean isOutOfSynch() throws DebugException {
		if (fIsOutOfSynch) {
			return true;
		}
		// if this frame's thread is not suspended, the out-of-synch info cannot
		// change until it suspends again
		if (getThread().isSuspended()) {
			JDIDebugTarget target= (JDIDebugTarget)getDebugTarget();
			if (target.hasHCROccurred() && target.isOutOfSynch(getUnderlyingMethod().declaringType().name())) {
				return true;
			}
		}
		return false;
	}
	
	/**
	 * @see IJavaStackFrame#isObsolete()
	 */
	public boolean isObsolete() throws DebugException {
		if (!JDIDebugPlugin.isJdiVersionGreaterThanOrEqual(new int[] {1,4}) || !((JDIDebugTarget)getDebugTarget()).hasHCROccurred()) {
			// If no hot code replace has occurred, this frame
			// cannot be obsolete.
			return false;
		}
		// if this frame's thread is not suspended, the obsolete status cannot
		// change until it suspends again
		if (getThread().isSuspended()) {
			try {
				return getUnderlyingMethod().isObsolete();
			} catch (RuntimeException re) {
				targetRequestFailed(MessageFormat.format(JDIDebugModelMessages.getString("JDIStackFrame.Exception_occurred_determining_if_stack_frame_is_obsolete_1"), new String[] {re.toString()}), re); //$NON-NLS-1$
				// execution will not reach this line, as 
				// #targetRequestFailed will throw an exception			
				return true;
			}
		}
		return false;
	}
	
	protected boolean exists() throws DebugException {
		return ((JDIThread)getThread()).computeStackFrames().indexOf(this) != -1;
	}
	
	/**
	 * @see ITerminate#canTerminate()
	 */
	public boolean canTerminate() {
		boolean exists= false;
		try {
			exists= exists();
		} catch (DebugException e) {
			logError(e);
		}
		return exists && getThread().canTerminate() || getDebugTarget().canTerminate();
	}

	/**
	 * @see ITerminate#isTerminated()
	 */
	public boolean isTerminated() {
		return getThread().isTerminated();
	}

	/**
	 * @see ITerminate#terminate()
	 */
	public void terminate() throws DebugException {
		if (getThread().canTerminate()) {
			getThread().terminate();
		} else {
			getDebugTarget().terminate();
		}
	}
	
	/**
	 * Returns this stack frame's underlying JDI frame.
	 * 
	 * @exception DebugException if this stack frame does
	 *  not currently have an underlying frame (is in an
	 *  interim state where this frame's thead has been
	 *  resumed, and is not yet suspended).
	 */
	protected StackFrame getUnderlyingStackFrame() throws DebugException {
		synchronized (fThread) {
			if (fStackFrame == null) {
				int depth= getDepth();
				if (depth == -1) {
					if (fThread.isSuspended()) {
						// re-index stack frames - See Bug 47198
						fThread.computeStackFrames();
						depth = getDepth();
						if (depth == -1) {
							// If depth still -1, then this is an invalid frame
							throw new DebugException(new Status(IStatus.ERROR, JDIDebugPlugin.getUniqueIdentifier(), IStatus.ERROR, JDIDebugModelMessages.getString("JDIStackFrame.25"), null)); //$NON-NLS-1$
						}
					} else {
						throw new DebugException(new Status(IStatus.ERROR, JDIDebugPlugin.getUniqueIdentifier(), IJavaThread.ERR_THREAD_NOT_SUSPENDED, JDIDebugModelMessages.getString("JDIStackFrame.25"), null)); //$NON-NLS-1$
					}
				}
				setUnderlyingStackFrame(((JDIThread)getThread()).getUnderlyingFrame(depth));
			}
			return fStackFrame;
		}
	}
	
	/**
	 * Sets the underlying JDI StackFrame. Called by a thread
	 * when incrementally updating after a step has completed.
	 * 
	 * @param frame The underlying stack frame
	 */
	protected void setUnderlyingStackFrame(StackFrame frame) {
		synchronized (fThread) {
			fStackFrame = frame;
			if (frame == null) {
				fDepth = -1;
				fRefreshVariables = true;
			}
		}
	}
	
	/**
	 * The underlying method that existed before the current underlying
	 * method.  Used only so that equality can be checked on stack frame
	 * after the new one has been set.
	 */
	protected Method getLastMethod() {
		return fLastMethod;
	}

	protected void setThread(JDIThread thread) {
		fThread = thread;
	}

	protected void setVariables(List variables) {
		fVariables = variables;
	}
	
	/**
	 * @see IJavaStackFrame#getLocalVariables()
	 */
	public IJavaVariable[] getLocalVariables() throws DebugException {
		List list = getUnderlyingVisibleVariables();
		IJavaVariable[] locals = new IJavaVariable[list.size()];
		for (int i = 0; i < list.size(); i++) {
			locals[i] = new JDILocalVariable(this, (LocalVariable)list.get(i));
		}
		return locals;
	}

	/**
	 * @see IJavaStackFrame#getThis()
	 */
	public IJavaObject getThis() throws DebugException {
		IJavaObject receiver = null;
		if (!isStatic()) {
			ObjectReference thisObject = getUnderlyingThisObject();
			if (thisObject != null) {
				receiver = (IJavaObject)JDIValue.createValue((JDIDebugTarget)getDebugTarget(), thisObject);
			}
		}
		return receiver;
	}

	/**
	 * Java stack frames do not support registers
	 * 
	 * @see IStackFrame#getRegisterGroups()
	 */
	public IRegisterGroup[] getRegisterGroups() {
		return new IRegisterGroup[0];
	}

	/**
	 * @see IJavaStackFrame#getDeclaringType()
	 */
	public IJavaClassType getDeclaringType() throws DebugException {
		Method method = getUnderlyingMethod();
		try {
			Type type = method.declaringType();
			return (IJavaClassType)JDIType.createType((JDIDebugTarget)getDebugTarget(), type);
		} catch (RuntimeException e) {
			targetRequestFailed(MessageFormat.format(JDIDebugModelMessages.getString("JDIStackFrame.exception_retreiving_declaring_type"), new String[] {e.toString()}), e); //$NON-NLS-1$
		}
		return null;
	}

	/**
	 * Expression level stepping not supported.
	 * 
	 * @see IStackFrame#getCharEnd()
	 */
	public int getCharEnd() {
		return -1;
	}

	/**
	 * Expression level stepping not supported.
	 * 
	 * @see IStackFrame#getCharStart()
	 */
	public int getCharStart() {
		return -1;
	}

	/**
	 * Clears the cached data of this stack frame.
	 * The underlying stack frame has changed in such a way
	 * that the cached data may not be valid.
	 */
	private void clearCachedData() {
		fMethod= null;
		fThisObject= null;
		fDeclaringTypeName= null;
		fReceivingTypeName= null;	
		fSourceName= null;
	}
	
	/**
	 * @see IJavaStackFrame#wereLocalsAvailable()
	 */
	public boolean wereLocalsAvailable() {
		return fLocalsAvailable;
	}
	
	/**
	 * Sets whether locals were available. If the setting is
	 * not the same as the current value, a change event is
	 * fired such that a UI client can update.
	 * 
	 * @param available whether local variable information is
	 * 	available for this stack frame.
	 */
	private void setLocalsAvailable(boolean available) {
		if (available != fLocalsAvailable) {
			fLocalsAvailable = available;
			fireChangeEvent(DebugEvent.STATE);
		}
	}	
	/**
	 * @see IStackFrame#hasRegisterGroups()
	 */
	public boolean hasRegisterGroups() {
		return false;
	}

	/**
	 * @see IStackFrame#hasVariables()
	 */
	public boolean hasVariables() throws DebugException {
		return getVariables0().size() > 0;
	}

	/**
	 * @see org.eclipse.debug.core.model.IFilteredStep#canStepWithFilters()
	 */
	public boolean canStepWithFilters() {
		if (canStepInto()) {
			String[] filters = getJavaDebugTarget().getStepFilters();
			return filters != null && filters.length > 0;
		}
		return false;
	}

	/**
	 * @see org.eclipse.debug.core.model.IFilteredStep#stepWithFilters()
	 */
	public void stepWithFilters() throws DebugException {
		((IJavaThread)getThread()).stepWithFilters();
	}

	/**
	 * @see org.eclipse.jdt.debug.core.IJavaStackFrame#getSourcePath(java.lang.String)
	 */
	public String getSourcePath(String stratum) throws DebugException {
		try {
			return getUnderlyingStackFrame().location().sourcePath(stratum);
		} catch (AbsentInformationException e) {
		} catch (RuntimeException e) {
			targetRequestFailed(MessageFormat.format(JDIDebugModelMessages.getString("JDIStackFrame.exception_retrieving_source_path"), new String[] {e.toString()}), e); //$NON-NLS-1$
		}
		return null;
	}
	
	/**
	 * @see org.eclipse.jdt.debug.core.IJavaStackFrame#getSourcePath()
	 */
	public String getSourcePath() throws DebugException {
		try {
			return getUnderlyingStackFrame().location().sourcePath();
		} catch (AbsentInformationException e) {
		} catch (RuntimeException e) {
			targetRequestFailed(MessageFormat.format(JDIDebugModelMessages.getString("JDIStackFrame.exception_retrieving_source_path"), new String[] {e.toString()}), e); //$NON-NLS-1$
		}
		return null;
	}	

	/*
	 * @see org.eclipse.jdt.debug.core.IJavaStackFrame#getLineNumber(java.lang.String)
	 */
	public int getLineNumber(String stratum) throws DebugException {
		if (isSuspended()) {
			try {
				return getUnderlyingStackFrame().location().lineNumber(stratum);
			} catch (RuntimeException e) {
				if (getThread().isSuspended()) {
					targetRequestFailed(MessageFormat.format(JDIDebugModelMessages.getString("JDIStackFrame.exception_retrieving_line_number"), new String[] {e.toString()}), e); //$NON-NLS-1$
				}
			}
		}
		return -1;
	}

	/*
	 * @see org.eclipse.jdt.debug.core.IJavaStackFrame#getSourceName(java.lang.String)
	 */
	public String getSourceName(String stratum) throws DebugException {
		try {
			return getUnderlyingStackFrame().location().sourceName(stratum);
		} catch (AbsentInformationException e) {
		} catch (NativeMethodException e) {
		} catch (RuntimeException e) {
			targetRequestFailed(MessageFormat.format(JDIDebugModelMessages.getString("JDIStackFrame.exception_retrieving_source_name"), new String[] {e.toString()}), e); //$NON-NLS-1$
		}
		return null;
	}

}
