/******************************************************************************* * Copyright (c) 2009 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 * Zend Technologies *******************************************************************************/ package org.eclipse.php.internal.debug.core.zend.model; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.eclipse.debug.core.DebugException; import org.eclipse.debug.core.model.*; import org.eclipse.php.internal.debug.core.model.PHPDebugElement; import org.eclipse.php.internal.debug.core.zend.debugger.Expression; import org.eclipse.php.internal.debug.core.zend.debugger.ExpressionValue; /** * PHP stack frame. */ public class PHPStackFrame extends PHPDebugElement implements IStackFrame { private static class PHPVariableContainer { IVariable[] locals = null; /** * @param varName * @return * @throws DebugException */ public IVariable findVariable(String varName) throws DebugException { if (locals != null) { final IVariable variable = findVariable(varName, locals); if (variable != null) { return variable; } } return null; } private static IVariable findVariable(String varName, IVariable[] vars) throws DebugException { for (int i = 0; i < vars.length; i++) { final IVariable var = vars[i]; if (var.getName().equals(varName)) { return var; } } return null; } } private static final Pattern LAMBDA_FUNC_PATTERN = Pattern.compile("(.*)\\((\\d+)\\) : runtime-created function"); //$NON-NLS-1$ private PHPThread fThread; private String fFunctionName; private String fFileName; private String fResolvedFileName; private int fLineNumber; private int fDepth; private Expression[] fLocalVariables; private PHPVariableContainer fCurrentVariables; private PHPVariableContainer fPreviousVariables; /** * Create new PHP stack frame * * @param thread * Debug thread * @param fileName * Current file name * @param funcName * Current function name * @param lineNumber * Current line number * @param depth * Stack layer depth * @param resolvedFileName * Resolved file name * @param localVariables * All local function variables */ public PHPStackFrame(IThread thread, String fileName, String resolvedFileName, String funcName, int lineNumber, int depth, Expression[] localVariables) { super((PHPDebugTarget) thread.getDebugTarget()); baseInit(thread, fileName, resolvedFileName, funcName, lineNumber, depth, localVariables); } private void baseInit(IThread thread, String fileName, String resolvedFileName, String funcName, int lineNumber, int depth, Expression[] localVariables) { Matcher matcher = LAMBDA_FUNC_PATTERN.matcher(fileName); if (matcher.matches()) { fileName = matcher.group(1); lineNumber = Integer.parseInt(matcher.group(2)); } fFunctionName = funcName; fFileName = fileName; fResolvedFileName = resolvedFileName; fLineNumber = lineNumber; fDepth = depth; fThread = (PHPThread) thread; fLocalVariables = localVariables; } protected void update(int lineNumber, Expression[] localVariables) throws DebugException { this.fLineNumber = lineNumber; // Reset state this.fPreviousVariables = fCurrentVariables; this.fCurrentVariables = null; // Set new locals this.fLocalVariables = localVariables; } /** * Merges incoming variable. Merge is done by means of checking if related * child variable existed in "one step back" state of a frame. If related * variable existed, it is updated with the use of the most recent * descriptor and returned instead of the incoming one. * * @param variable * @param descriptor * @return merged variable */ protected IVariable merge(IVariable variable) { if (fPreviousVariables == null) return variable; if (!(variable instanceof PHPVariable)) return variable; PHPVariable incoming = (PHPVariable) variable; if (incoming.getFullName().isEmpty()) return incoming; for (IVariable stored : fPreviousVariables.locals) { if (stored instanceof PHPVariable) { PHPVariable previous = (PHPVariable) stored; if (previous.getFullName().equals(incoming.getFullName())) { ((PHPVariable) stored).update(incoming.getExpression()); return stored; } } } return variable; } /** * Returns this stack frame's unique identifier within its thread * * @return this stack frame's unique identifier within its thread */ protected int getDepth() { return fDepth; } /* * (non-Javadoc) * * @see org.eclipse.debug.core.model.IStackFrame#getThread() */ public IThread getThread() { return fThread; } /* * (non-Javadoc) * * @see org.eclipse.debug.core.model.IStackFrame#getVariables() */ public synchronized IVariable[] getVariables() throws DebugException { Expression[] localVariables = ExpressionValue.sort(fLocalVariables); if (fCurrentVariables == null) { fCurrentVariables = new PHPVariableContainer(); fCurrentVariables.locals = new PHPVariable[localVariables.length]; for (int i = 0; i < localVariables.length; i++) { PHPVariable incoming = new PHPVariable((PHPDebugTarget) fThread.getDebugTarget(), localVariables[i]); fCurrentVariables.locals[i] = merge(incoming); } } return fCurrentVariables.locals; } public synchronized IVariable findVariable(String varName) throws DebugException { if (fCurrentVariables == null) { getVariables(); } if (fCurrentVariables != null) { return (IVariable) fCurrentVariables.findVariable(varName); } return null; } /* * (non-Javadoc) * * @see org.eclipse.debug.core.model.IStackFrame#hasVariables() */ public boolean hasVariables() throws DebugException { return ((PHPDebugTarget) getDebugTarget()).getVariables(this).length > 0; } /* * (non-Javadoc) * * @see org.eclipse.debug.core.model.IStackFrame#getLineNumber() */ public int getLineNumber() throws DebugException { return fLineNumber; } /* * (non-Javadoc) * * @see org.eclipse.debug.core.model.IStackFrame#getCharStart() */ public int getCharStart() throws DebugException { return -1; } /* * (non-Javadoc) * * @see org.eclipse.debug.core.model.IStackFrame#getCharEnd() */ public int getCharEnd() throws DebugException { return -1; } /* * (non-Javadoc) * * @see org.eclipse.debug.core.model.IStackFrame#getName() */ public String getName() throws DebugException { return fFunctionName; } /* * (non-Javadoc) * * @see org.eclipse.debug.core.model.IStackFrame#getRegisterGroups() */ public IRegisterGroup[] getRegisterGroups() throws DebugException { return new IRegisterGroup[0]; } /* * (non-Javadoc) * * @see org.eclipse.debug.core.model.IStackFrame#hasRegisterGroups() */ public boolean hasRegisterGroups() throws DebugException { return false; } /* * (non-Javadoc) * * @see org.eclipse.debug.core.model.IStep#canStepInto() */ public boolean canStepInto() { return getThread().canStepInto(); } /* * (non-Javadoc) * * @see org.eclipse.debug.core.model.IStep#canStepOver() */ public boolean canStepOver() { return getThread().canStepOver(); } /* * (non-Javadoc) * * @see org.eclipse.debug.core.model.IStep#canStepReturn() */ public boolean canStepReturn() { return getThread().canStepReturn(); } /* * (non-Javadoc) * * @see org.eclipse.debug.core.model.IStep#isStepping() */ public boolean isStepping() { return getThread().isStepping(); } /* * (non-Javadoc) * * @see org.eclipse.debug.core.model.IStep#stepInto() */ public void stepInto() throws DebugException { getThread().stepInto(); } /* * (non-Javadoc) * * @see org.eclipse.debug.core.model.IStep#stepOver() */ public void stepOver() throws DebugException { getThread().stepOver(); } /* * (non-Javadoc) * * @see org.eclipse.debug.core.model.IStep#stepReturn() */ public void stepReturn() throws DebugException { getThread().stepReturn(); } /* * (non-Javadoc) * * @see org.eclipse.debug.core.model.ISuspendResume#canResume() */ public boolean canResume() { return getThread().canResume(); } /* * (non-Javadoc) * * @see org.eclipse.debug.core.model.ISuspendResume#canSuspend() */ public boolean canSuspend() { return getThread().canSuspend(); } /* * (non-Javadoc) * * @see org.eclipse.debug.core.model.ISuspendResume#isSuspended() */ public boolean isSuspended() { return getThread().isSuspended(); } /* * (non-Javadoc) * * @see org.eclipse.debug.core.model.ISuspendResume#resume() */ public void resume() throws DebugException { getThread().resume(); } /* * (non-Javadoc) * * @see org.eclipse.debug.core.model.ISuspendResume#suspend() */ public void suspend() throws DebugException { getThread().suspend(); } /* * (non-Javadoc) * * @see org.eclipse.debug.core.model.ITerminate#canTerminate() */ public boolean canTerminate() { return getThread().canTerminate(); } /* * (non-Javadoc) * * @see org.eclipse.debug.core.model.ITerminate#isTerminated() */ public boolean isTerminated() { return getThread().isTerminated(); } /* * (non-Javadoc) * * @see org.eclipse.debug.core.model.ITerminate#terminate() */ public void terminate() throws DebugException { getThread().terminate(); } public int checkLineNumber() throws DebugException { return fLineNumber; } /** * Returns the name of the source file this stack frame is associated with. * * @return the name of the source file this stack frame is associated with */ public String getSourceName() { return fResolvedFileName; } /** * Returns the file name with full path. * * @return the file name with full path */ public String getAbsoluteFileName() { return fFileName; } public Expression[] getStackVariables() { return fLocalVariables; } public void setStackVariables(Expression[] variables) { fLocalVariables = variables; } }