1 /*
   2  * Copyright (c) 1998, 2011, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 /*
  27  * This source code is provided to illustrate the usage of a given feature
  28  * or technique and has been deliberately simplified. Additional steps
  29  * required for a production-quality application, such as security checks,
  30  * input validation and proper error handling, might not be present in
  31  * this sample code.
  32  */
  33 
  34 
  35 package com.sun.tools.example.debug.gui;
  36 
  37 import java.io.*;
  38 import java.util.*;
  39 
  40 import com.sun.jdi.*;
  41 import com.sun.tools.example.debug.event.*;
  42 import com.sun.tools.example.debug.bdi.*;
  43 
  44 public class ContextManager {
  45 
  46     private ClassManager classManager;
  47     private ExecutionManager runtime;
  48 
  49     private String mainClassName;
  50     private String vmArguments;
  51     private String commandArguments;
  52     private String remotePort;
  53 
  54     private ThreadReference currentThread;
  55 
  56     private boolean verbose;
  57 
  58     private ArrayList<ContextListener> contextListeners = new ArrayList<ContextListener>();
  59 
  60     public ContextManager(Environment env) {
  61         classManager = env.getClassManager();
  62         runtime = env.getExecutionManager();
  63         mainClassName = "";
  64         vmArguments = "";
  65         commandArguments = "";
  66         currentThread = null;
  67 
  68         ContextManagerListener listener = new ContextManagerListener();
  69         runtime.addJDIListener(listener);
  70         runtime.addSessionListener(listener);
  71     }
  72 
  73     // Program execution defaults.
  74 
  75     //### Should there be change listeners for these?
  76     //### They would be needed if we expected a dialog to be
  77     //### synchronized with command input while it was open.
  78 
  79     public String getMainClassName() {
  80         return mainClassName;
  81     }
  82 
  83     public void setMainClassName(String mainClassName) {
  84         this.mainClassName = mainClassName;
  85     }
  86 
  87     public String getVmArguments() {
  88         return processClasspathDefaults(vmArguments);
  89     }
  90 
  91     public void setVmArguments(String vmArguments) {
  92         this.vmArguments = vmArguments;
  93     }
  94 
  95     public String getProgramArguments() {
  96         return commandArguments;
  97     }
  98 
  99     public void setProgramArguments(String commandArguments) {
 100         this.commandArguments = commandArguments;
 101     }
 102 
 103     public String getRemotePort() {
 104         return remotePort;
 105     }
 106 
 107     public void setRemotePort(String remotePort) {
 108         this.remotePort = remotePort;
 109 
 110     }
 111 
 112 
 113     // Miscellaneous debugger session preferences.
 114 
 115     public boolean getVerboseFlag() {
 116         return verbose;
 117     }
 118 
 119     public void setVerboseFlag(boolean verbose) {
 120         this.verbose = verbose;
 121     }
 122 
 123 
 124     // Thread focus.
 125 
 126     public ThreadReference getCurrentThread() {
 127         return currentThread;
 128     }
 129 
 130     public void setCurrentThread(ThreadReference t) {
 131         if (t != currentThread) {
 132             currentThread = t;
 133             notifyCurrentThreadChanged(t);
 134         }
 135     }
 136 
 137     public void setCurrentThreadInvalidate(ThreadReference t) {
 138         currentThread = t;
 139         notifyCurrentFrameChanged(runtime.threadInfo(t),
 140                                   0, true);
 141     }
 142 
 143     public void invalidateCurrentThread() {
 144         notifyCurrentFrameChanged(null, 0, true);
 145     }
 146 
 147 
 148     // If a view is displaying the current thread, it may
 149     // choose to indicate which frame is current in the
 150     // sense of the command-line UI.  It may also "warp" the
 151     // selection to that frame when changed by an 'up' or 'down'
 152     // command. Hence, a notifier is provided.
 153 
 154     /******
 155     public int getCurrentFrameIndex() {
 156         return getCurrentFrameIndex(currentThreadInfo);
 157     }
 158     ******/
 159 
 160     public int getCurrentFrameIndex(ThreadReference t) {
 161         return getCurrentFrameIndex(runtime.threadInfo(t));
 162     }
 163 
 164     //### Used in StackTraceTool.
 165     public int getCurrentFrameIndex(ThreadInfo tinfo) {
 166         if (tinfo == null) {
 167             return 0;
 168         }
 169         Integer currentFrame = (Integer)tinfo.getUserObject();
 170         if (currentFrame == null) {
 171             return 0;
 172         } else {
 173             return currentFrame.intValue();
 174         }
 175     }
 176 
 177     public int moveCurrentFrameIndex(ThreadReference t, int count) throws VMNotInterruptedException {
 178         return setCurrentFrameIndex(t,count, true);
 179     }
 180 
 181     public int setCurrentFrameIndex(ThreadReference t, int newIndex) throws VMNotInterruptedException {
 182         return setCurrentFrameIndex(t, newIndex, false);
 183     }
 184 
 185     public int setCurrentFrameIndex(int newIndex) throws VMNotInterruptedException {
 186         if (currentThread == null) {
 187             return 0;
 188         } else {
 189             return setCurrentFrameIndex(currentThread, newIndex, false);
 190         }
 191     }
 192 
 193     private int setCurrentFrameIndex(ThreadReference t, int x, boolean relative) throws VMNotInterruptedException {
 194         boolean sameThread = t.equals(currentThread);
 195         ThreadInfo tinfo = runtime.threadInfo(t);
 196         if (tinfo == null) {
 197             return 0;
 198         }
 199         int maxIndex = tinfo.getFrameCount()-1;
 200         int oldIndex = getCurrentFrameIndex(tinfo);
 201         int newIndex = relative? oldIndex + x : x;
 202         if (newIndex > maxIndex) {
 203             newIndex = maxIndex;
 204         } else  if (newIndex < 0) {
 205             newIndex = 0;
 206         }
 207         if (!sameThread || newIndex != oldIndex) {  // don't recurse
 208             setCurrentFrameIndex(tinfo, newIndex);
 209         }
 210         return newIndex - oldIndex;
 211     }
 212 
 213     private void setCurrentFrameIndex(ThreadInfo tinfo, int index) {
 214         tinfo.setUserObject(index);
 215         //### In fact, the value may not have changed at this point.
 216         //### We need to signal that the user attempted to change it,
 217         //### however, so that the selection can be "warped" to the
 218         //### current location.
 219         notifyCurrentFrameChanged(tinfo.thread(), index);
 220     }
 221 
 222     public StackFrame getCurrentFrame() throws VMNotInterruptedException {
 223         return getCurrentFrame(runtime.threadInfo(currentThread));
 224     }
 225 
 226     public StackFrame getCurrentFrame(ThreadReference t) throws VMNotInterruptedException {
 227         return getCurrentFrame(runtime.threadInfo(t));
 228     }
 229 
 230     public StackFrame getCurrentFrame(ThreadInfo tinfo) throws VMNotInterruptedException {
 231         int index = getCurrentFrameIndex(tinfo);
 232         try {
 233             // It is possible, though unlikely, that the VM was interrupted
 234             // before the thread created its Java stack.
 235             return tinfo.getFrame(index);
 236         } catch (FrameIndexOutOfBoundsException e) {
 237             return null;
 238         }
 239     }
 240 
 241     public void addContextListener(ContextListener cl) {
 242         contextListeners.add(cl);
 243     }
 244 
 245     public void removeContextListener(ContextListener cl) {
 246         contextListeners.remove(cl);
 247     }
 248 
 249     //### These notifiers are fired only in response to USER-INITIATED changes
 250     //### to the current thread and current frame.  When the current thread is set automatically
 251     //### after a breakpoint hit or step completion, no event is generated.  Instead,
 252     //### interested parties are expected to listen for the BreakpointHit and StepCompleted
 253     //### events.  This convention is unclean, and I believe that it reflects a defect in
 254     //### in the current architecture.  Unfortunately, however, we cannot guarantee the
 255     //### order in which various listeners receive a given event, and the handlers for
 256     //### the very same events that cause automatic changes to the current thread may also
 257     //### need to know the current thread.
 258 
 259     private void notifyCurrentThreadChanged(ThreadReference t) {
 260         ThreadInfo tinfo = null;
 261         int index = 0;
 262         if (t != null) {
 263             tinfo = runtime.threadInfo(t);
 264             index = getCurrentFrameIndex(tinfo);
 265         }
 266         notifyCurrentFrameChanged(tinfo, index, false);
 267     }
 268 
 269     private void notifyCurrentFrameChanged(ThreadReference t, int index) {
 270         notifyCurrentFrameChanged(runtime.threadInfo(t),
 271                                   index, false);
 272     }
 273 
 274     private void notifyCurrentFrameChanged(ThreadInfo tinfo, int index,
 275                                            boolean invalidate) {
 276         ArrayList<ContextListener> l =  new ArrayList<ContextListener>(contextListeners);
 277         CurrentFrameChangedEvent evt =
 278             new CurrentFrameChangedEvent(this, tinfo, index, invalidate);
 279         for (int i = 0; i < l.size(); i++) {
 280             l.get(i).currentFrameChanged(evt);
 281         }
 282     }
 283 
 284     private class ContextManagerListener extends JDIAdapter
 285                        implements SessionListener, JDIListener {
 286 
 287         // SessionListener
 288 
 289         @Override
 290         public void sessionStart(EventObject e) {
 291             invalidateCurrentThread();
 292         }
 293 
 294         @Override
 295         public void sessionInterrupt(EventObject e) {
 296             setCurrentThreadInvalidate(currentThread);
 297         }
 298 
 299         @Override
 300         public void sessionContinue(EventObject e) {
 301             invalidateCurrentThread();
 302         }
 303 
 304         // JDIListener
 305 
 306         @Override
 307         public void locationTrigger(LocationTriggerEventSet e) {
 308             setCurrentThreadInvalidate(e.getThread());
 309         }
 310 
 311         @Override
 312         public void exception(ExceptionEventSet e) {
 313             setCurrentThreadInvalidate(e.getThread());
 314         }
 315 
 316         @Override
 317         public void vmDisconnect(VMDisconnectEventSet e) {
 318             invalidateCurrentThread();
 319         }
 320 
 321     }
 322 
 323 
 324     /**
 325      * Add a -classpath argument to the arguments passed to the exec'ed
 326      * VM with the contents of CLASSPATH environment variable,
 327      * if -classpath was not already specified.
 328      *
 329      * @param javaArgs the arguments to the VM being exec'd that
 330      *                 potentially has a user specified -classpath argument.
 331      * @return a javaArgs whose -classpath option has been added
 332      */
 333 
 334     private String processClasspathDefaults(String javaArgs) {
 335         if (javaArgs.indexOf("-classpath ") == -1) {
 336             StringBuilder munged = new StringBuilder(javaArgs);
 337             SearchPath classpath = classManager.getClassPath();
 338             if (classpath.isEmpty()) {
 339                 String envcp = System.getProperty("env.class.path");
 340                 if ((envcp != null) && (envcp.length() > 0)) {
 341                     munged.append(" -classpath " + envcp);
 342                 }
 343             } else {
 344                 munged.append(" -classpath " + classpath.asString());
 345             }
 346             return munged.toString();
 347         } else {
 348             return javaArgs;
 349         }
 350     }
 351 
 352     private String appendPath(String path1, String path2) {
 353         if (path1 == null || path1.length() == 0) {
 354             return path2 == null ? "." : path2;
 355         } else if (path2 == null || path2.length() == 0) {
 356             return path1;
 357         } else {
 358             return path1  + File.pathSeparator + path2;
 359         }
 360     }
 361 
 362 }