/* * Copyright 1998-2008 Sun Microsystems, Inc. All Rights Reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Sun designates this * particular file as subject to the "Classpath" exception as provided * by Sun in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, * CA 95054 USA or visit www.sun.com if you need additional information or * have any questions. */ package org.visage.tools.debug.tty; import com.sun.jdi.*; import com.sun.jdi.request.StepRequest; import com.sun.jdi.request.MethodEntryRequest; import com.sun.jdi.request.MethodExitRequest; import java.util.*; import java.io.*; class Env { private EventRequestSpecList specList = new EventRequestSpecList(this); private boolean exitDebuggerVM = true; private VMConnection connection; private SourceMapper sourceMapper = new SourceMapper(""); private List<String> excludes; private static final int SOURCE_CACHE_SIZE = 5; private List<SourceCode> sourceCache = new LinkedList<SourceCode>(); private HashMap<String, Value> savedValues = new HashMap<String, Value>(); private Method atExitMethod; private MessageOutput messageOutput = new MessageOutput(); // This is a list of all known ThreadInfo objects. It survives // env.invalidateAllThreadInfo, unlike the other thread related fields below. private final List<ThreadInfo> threads = Collections.synchronizedList(new ArrayList<ThreadInfo>()); private boolean gotInitialThreads = false; private ThreadInfo currentThread = null; private ThreadGroupReference currentThreadGroup = null; void init(String connectSpec, boolean openNow, int flags) { connection = new VMConnection(this, connectSpec, flags); if (!connection.isLaunch() || openNow) { connection.open(); } } EventRequestSpecList getSpecList() { return specList; } void setExitDebuggerVM(boolean flag) { exitDebuggerVM = flag; } boolean getExitDebuggerVM() { return exitDebuggerVM; } VMConnection connection() { return connection; } VirtualMachine vm() { return connection.vm(); } MessageOutput messageOutput() { return messageOutput; } void printPrompt() { messageOutput().printPrompt(getCurrentThreadInfo()); } void fatalError(String messageKey) { messageOutput().fatalError(messageKey); shutdown(); } void shutdown() { shutdown(null); } void shutdown(String message) { invalidateAllThreadInfo(); threads.clear(); if (connection != null) { try { connection.disposeVM(); } catch (VMDisconnectedException e) { // Shutting down after the VM has gone away. This is // not an error, and we just ignore it. } } if (message != null) { messageOutput().lnprint(message); messageOutput().println(); } if (exitDebuggerVM) { System.exit(0); } } void setSourcePath(String srcPath) { sourceMapper = new SourceMapper(srcPath); sourceCache.clear(); } void setSourcePath(List<String> srcList) { sourceMapper = new SourceMapper(srcList); sourceCache.clear(); } String getSourcePath() { return sourceMapper.getSourcePath(); } private List<String> excludes() { if (excludes == null) { setExcludes("java.*, javax.*, sun.*, com.sun.*"); } return excludes; } String excludesString() { StringBuffer buffer = new StringBuffer(); for (String pattern : excludes()) { buffer.append(pattern); buffer.append(","); } return buffer.toString(); } void addExcludes(StepRequest request) { for (String pattern : excludes()) { request.addClassExclusionFilter(pattern); } } void addExcludes(MethodEntryRequest request) { for (String pattern : excludes()) { request.addClassExclusionFilter(pattern); } } void addExcludes(MethodExitRequest request) { for (String pattern : excludes()) { request.addClassExclusionFilter(pattern); } } void setExcludes(String excludeString) { StringTokenizer t = new StringTokenizer(excludeString, " ,;"); List<String> list = new ArrayList<String>(); while (t.hasMoreTokens()) { list.add(t.nextToken()); } excludes = list; } Method atExitMethod() { return atExitMethod; } void setAtExitMethod(Method mmm) { atExitMethod = mmm; } /** * Return a Reader cooresponding to the source of this location. * Return null if not available. * Note: returned reader must be closed. */ BufferedReader sourceReader(Location location) { return sourceMapper.sourceReader(location); } synchronized String sourceLine(Location location, int lineNumber) throws IOException { if (lineNumber == -1) { throw new IllegalArgumentException(); } try { String fileName = location.sourceName(); Iterator<SourceCode> iter = sourceCache.iterator(); SourceCode code = null; while (iter.hasNext()) { SourceCode candidate = iter.next(); if (candidate.fileName().equals(fileName)) { code = candidate; iter.remove(); break; } } if (code == null) { BufferedReader reader = sourceReader(location); if (reader == null) { throw new FileNotFoundException(fileName); } code = new SourceCode(fileName, reader); if (sourceCache.size() == SOURCE_CACHE_SIZE) { sourceCache.remove(sourceCache.size() - 1); } } sourceCache.add(0, code); return code.sourceLine(lineNumber); } catch (AbsentInformationException e) { throw new IllegalArgumentException(); } } /** Return a description of an object. */ String description(ObjectReference ref) { ReferenceType clazz = ref.referenceType(); long id = ref.uniqueID(); if (clazz == null) { return toHex(id); } else { return MessageOutput.format("object description and hex id", new Object [] {clazz.name(), toHex(id)}); } } /** Convert a long to a hexadecimal string. */ static String toHex(long n) { char s1[] = new char[16]; char s2[] = new char[18]; /* Store digits in reverse order. */ int i = 0; do { long d = n & 0xf; s1[i++] = (char)((d < 10) ? ('0' + d) : ('a' + d - 10)); } while ((n >>>= 4) > 0); /* Now reverse the array. */ s2[0] = '0'; s2[1] = 'x'; int j = 2; while (--i >= 0) { s2[j++] = s1[i]; } return new String(s2, 0, j); } /** Convert hexadecimal strings to longs. */ static long fromHex(String hexStr) { String str = hexStr.startsWith("0x") ? hexStr.substring(2).toLowerCase() : hexStr.toLowerCase(); if (hexStr.length() == 0) { throw new NumberFormatException(); } long ret = 0; for (int i = 0; i < str.length(); i++) { int c = str.charAt(i); if (c >= '0' && c <= '9') { ret = (ret * 16) + (c - '0'); } else if (c >= 'a' && c <= 'f') { ret = (ret * 16) + (c - 'a' + 10); } else { throw new NumberFormatException(); } } return ret; } ReferenceType getReferenceTypeFromToken(String idToken) { ReferenceType cls = null; if (Character.isDigit(idToken.charAt(0))) { cls = null; } else if (idToken.startsWith("*.")) { // This notation saves typing by letting the user omit leading // package names. The first // loaded class whose name matches this limited regular // expression is selected. idToken = idToken.substring(1); for (ReferenceType type : vm().allClasses()) { if (type.name().endsWith(idToken)) { cls = type; break; } } } else { // It's a class name List<ReferenceType> classes = vm().classesByName(idToken); if (classes.size() > 0) { // TO DO: handle multiples cls = classes.get(0); } } return cls; } Set<String> getSaveKeys() { return savedValues.keySet(); } Value getSavedValue(String key) { return savedValues.get(key); } void setSavedValue(String key, Value value) { savedValues.put(key, value); } // Current thread/threadGroup methods private ThreadInfo createThreadInfo(ThreadReference thread) { if (thread == null) { fatalError("Internal error: null ThreadInfo created"); } return new ThreadInfo(thread); } private void initThreads() { if (!gotInitialThreads) { for (ThreadReference thread : vm().allThreads()) { threads.add(createThreadInfo(thread)); } gotInitialThreads = true; } } void addThread(ThreadReference thread) { synchronized (threads) { initThreads(); ThreadInfo ti = createThreadInfo(thread); // Guard against duplicates. Duplicates can happen during // initialization when a particular thread might be added both // by a thread start event and by the initial call to threads() if (getThreadInfo(thread) == null) { threads.add(ti); } } } void removeThread(ThreadReference thread) { if (thread.equals(currentThread)) { // Current thread has died. // Be careful getting the thread name. If its death happens // as part of VM termination, it may be too late to get the // information, and an exception will be thrown. String currentThreadName; try { currentThreadName = "\"" + thread.name() + "\""; } catch (Exception e) { currentThreadName = ""; } setCurrentThread(null); messageOutput().println(); messageOutput().println("Current thread died. Execution continuing...", currentThreadName); } threads.remove(getThreadInfo(thread)); } List<ThreadInfo> threads() { synchronized(threads) { initThreads(); // Make a copy to allow iteration without synchronization return new ArrayList<ThreadInfo>(threads); } } void invalidateAllThreadInfo() { currentThread = null; currentThreadGroup = null; synchronized (threads) { for (ThreadInfo ti : threads()) { ti.invalidate(); } } } void setThreadGroup(ThreadGroupReference tg) { currentThreadGroup = tg; } void setCurrentThread(ThreadReference tr) { if (tr == null) { setCurrentThreadInfo(null); } else { ThreadInfo tinfo = getThreadInfo(tr); setCurrentThreadInfo(tinfo); } } void setCurrentThreadInfo(ThreadInfo tinfo) { currentThread = tinfo; if (currentThread != null) { currentThread.invalidate(); } } /** * Get the ThreadInfo object. * * @return the ThreadInfo for the currentThread thread. */ ThreadInfo getCurrentThreadInfo() { return currentThread; } ThreadGroupReference getCurrentThreadGroup() { if (currentThreadGroup == null) { // Current current thread group defaults to the first top level // thread group. setThreadGroup(vm().topLevelThreadGroups().get(0)); } return currentThreadGroup; } ThreadInfo getThreadInfo(long id) { ThreadInfo retInfo = null; synchronized (threads) { for (ThreadInfo ti : threads()) { if (ti.getThread().uniqueID() == id) { retInfo = ti; break; } } } return retInfo; } ThreadInfo getThreadInfo(ThreadReference tr) { return getThreadInfo(tr.uniqueID()); } ThreadInfo getThreadInfo(String idToken) { ThreadInfo tinfo = null; if (idToken.startsWith("t@")) { idToken = idToken.substring(2); } try { long threadId = Long.decode(idToken).longValue(); tinfo = getThreadInfo(threadId); } catch (NumberFormatException e) { tinfo = null; } return tinfo; } static class SourceCode { private String fileName; private List<String> sourceLines = new ArrayList<String>(); SourceCode(String fileName, BufferedReader reader) throws IOException { this.fileName = fileName; try { String line = reader.readLine(); while (line != null) { sourceLines.add(line); line = reader.readLine(); } } finally { reader.close(); } } String fileName() { return fileName; } String sourceLine(int number) { int index = number - 1; // list is 0-indexed if (index >= sourceLines.size()) { return null; } else { return sourceLines.get(index); } } } }