/******************************************************************************* * Copyright (c) 2005 - 2007 committers of openArchitectureWare 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: * committers of openArchitectureWare - initial API and implementation *******************************************************************************/ package org.eclipse.emf.mwe.internal.core.debug.processing.handlers; import java.io.IOException; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.Stack; import org.eclipse.emf.mwe.core.debug.model.NameValuePair; import org.eclipse.emf.mwe.core.debug.processing.ElementAdapter; import org.eclipse.emf.mwe.core.debug.processing.EventHandler; import org.eclipse.emf.mwe.internal.core.debug.communication.Connection; import org.eclipse.emf.mwe.internal.core.debug.communication.packages.RequireVarPackage; import org.eclipse.emf.mwe.internal.core.debug.communication.packages.VarDataPackage; import org.eclipse.emf.mwe.internal.core.debug.model.VarValue; import org.eclipse.emf.mwe.internal.core.debug.model.VarValueTO; import org.eclipse.emf.mwe.internal.core.debug.processing.DebugMonitor; import org.eclipse.emf.mwe.internal.core.debug.processing.RuntimeHandler; /** * This class handles the communication of variable names and values with the debug server.<br> * It caches variable values with it's id and handles also cleanup of this cache if values are no longer used. */ public class VariablesRuntimeHandler implements RuntimeHandler, EventHandler, Runnable { private Connection connection; private DebugMonitor monitor; private final Stack<Frame> filteredStackFrames = new Stack<Frame>(); private final Stack<Frame> stackFrames = new Stack<Frame>(); private final List<VarValue> frameCache = new ArrayList<VarValue>(); private final List<VarValue> varCache = new ArrayList<VarValue>(); private int nextId = 0; // ------------------------------------------------------------------------- public void init(final DebugMonitor monitor, final Connection connection) { this.monitor = monitor; this.connection = connection; if (monitor != null) { monitor.addEventHandler(this); } } // ------------------------------------------------------------------------- public void startListener() { Thread thread = new Thread(this, getClass().getSimpleName()); thread.setDaemon(true); thread.start(); } public void run() { try { while (true) { handle((RequireVarPackage) connection.listenForPackage(RequireVarPackage.class)); } } catch (IOException e) { } } private void handle(final RequireVarPackage packet) throws IOException { List<VarValueTO> values; if (packet.varId == 0) { values = getFrameVariables(packet.frameId); } else { values = getSubVariables(packet.frameId, packet.varId); } VarDataPackage varPackage = new VarDataPackage(); if (values != null) { varPackage.valueList = values; } varPackage.refId = packet.getId(); connection.sendPackage(varPackage); } // In case there is no varId in the request the top level variables will be collected. // It depends on the element adapter, which variables will be reported. // The syntaxElement is also registered in the frameCache to find out later if variable values are still in // use. private List<VarValueTO> getFrameVariables(final int frameId) { if (frameId >= filteredStackFrames.size()) { return null; } Frame frame = filteredStackFrames.get(frameId); ElementAdapter adapter = monitor.getAdapter(frame.element); adapter.setContext(frame.context); cleanFramesCache(); VarValue hostValue = new VarValue(frame.element, 0); frameCache.add(hostValue); List<VarValueTO> vars = getVariableTOs(frame.element, hostValue, adapter); return vars; } // In case both frameId and varId are delivered the sub variables of to var with the varId will be collected. // The adapter that suits for the element will be used. That means that there can be different variables // delivered because of different adapters for two frames although the underlying java elements are the same. private List<VarValueTO> getSubVariables(final int frameId, final int varId) { if (filteredStackFrames.size() <= frameId) { return null; } Frame frame = filteredStackFrames.get(frameId); ElementAdapter adapter = monitor.getAdapter(frame.element); adapter.setContext(frame.context); VarValue value = findVarValueById(varId); if (value == null) { return null; } return getVariableTOs(value.element, value, adapter); } // this method handles both the creation and update of variables // The host element is the syntax element if called from getFrameVariables() or an arbitrary java object when // called from getSubVariables(). It is task of the adapter to handle this correctly. // After all new variable values are collected there is a check for old elements that are no longer members // of the current host element. // If such a "removed" element is no longer used in any other element, it will be also removed from the // cache and can be garbage collected. // private List<VarValueTO> getVariableTOs(final Object hostElement, final VarValue hostValue, final ElementAdapter adapter) { List<VarValueTO> list = new ArrayList<VarValueTO>(); Set<VarValue> oldMembers = new HashSet<VarValue>(); oldMembers.addAll(hostValue.members); hostValue.members.clear(); for (NameValuePair entry : adapter.getVariables(hostElement)) { String name = entry.name; Object element = entry.value; VarValueTO varTO = new VarValueTO(name); if (element == null) { varTO.stringRep = "null"; varTO.simpleRep = "null"; } else if (element instanceof String) { varTO.stringRep = (String) element; varTO.simpleRep = "\"" + element + "\""; } else { VarValue value = findOrCreateVarValue(element); value.usedIn.add(hostValue); hostValue.members.add(value); oldMembers.remove(value); varTO.stringRep = adapter.getVariableDetailRep(element); varTO.simpleRep = adapter.getVariableSimpleRep(element); varTO.hasMembers = adapter.checkVariableHasMembers(element); varTO.valueId = value.id; } list.add(varTO); } for (VarValue value : oldMembers) { value.usedIn.remove(hostValue); if (value.usedIn.isEmpty()) { varCache.remove(value); } } return list; } private VarValue findOrCreateVarValue(final Object element) { VarValue value = findVarValueByElement(element); if (value == null) { value = new VarValue(element, ++nextId); varCache.add(value); } return value; } private VarValue findVarValueByElement(final Object element) { for (VarValue cacheEntry : varCache) { if (element == cacheEntry.element) { return cacheEntry; } } return null; } private VarValue findVarValueById(final int id) { for (VarValue cacheEntry : varCache) { if (id == cacheEntry.id) { return cacheEntry; } } return null; } // remove all obsolete syntax elements from the frameCache. // This could be more than one element in case there were many elements on the stack at the last suspend, but // after a RESUME and a later SUSPEND there could be a complete different variable situation on the stack. // Obsolete variable values are also removed from the varCache if they are nowhere else used. private void cleanFramesCache() { Set<VarValue> oldFrameValues = new HashSet<VarValue>(); oldFrameValues.addAll(frameCache); for (VarValue value : oldFrameValues) { if (!filteredStackFrames.contains(value.element)) { frameCache.remove(value); for (VarValue member : value.members) { member.usedIn.remove(value); if (member.usedIn.isEmpty()) { varCache.remove(member); } } } } } // ------------------------------------------------------------------------- // IEventHandler implementation /** * push the syntax element onto the stack * * @see org.eclipse.emf.mwe.internal.core.debug.processing.EventHandler#preTask(java.lang.Object, int) */ public void preTask(final Object element, final Object context, final int state) { Frame frame = new Frame(element, context); ElementAdapter adapter = monitor.getAdapter(element); if(adapter != null) if( adapter.shallAddToCallStack(element) ) filteredStackFrames.push(frame); stackFrames.push(frame); } /** * pop the peek element from the stack * * @see org.eclipse.emf.mwe.internal.core.debug.processing.EventHandler#postTask() */ public void postTask(final Object context) { if(stackFrames.isEmpty()) return; Frame frame = stackFrames.peek(); ElementAdapter adapter = monitor.getAdapter(frame.element); if(adapter != null) if( adapter.shallAddToCallStack(frame.element)) filteredStackFrames.pop(); stackFrames.pop(); } /** * no contribution here * * @see org.eclipse.emf.mwe.internal.core.debug.processing.EventHandler#resumed() */ public void resumed() { } /** * no contribution here * * @see org.eclipse.emf.mwe.internal.core.debug.processing.EventHandler#resumed() */ public void suspended() { } /** * no contribution here * * @see org.eclipse.emf.mwe.internal.core.debug.processing.EventHandler#resumed() */ public void started() { } /** * no contribution here * * @see org.eclipse.emf.mwe.internal.core.debug.processing.EventHandler#resumed() */ public void terminated() { } // ------------------------------------------------------------------------- private class Frame { final Object element; final Object context; private Frame(final Object element, final Object context) { this.element = element; this.context = context; } } }