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.awt.*;
  39 import java.awt.event.*;
  40 import javax.swing.*;
  41 
  42 import com.sun.jdi.*;
  43 import com.sun.jdi.request.*;
  44 
  45 import com.sun.tools.example.debug.bdi.*;
  46 
  47 public class SourceTool extends JPanel {
  48 
  49     private static final long serialVersionUID = -5461299294186395257L;
  50 
  51     private Environment env;
  52 
  53     private ExecutionManager runtime;
  54     private ContextManager context;
  55     private SourceManager sourceManager;
  56 
  57     private JList list;
  58     private ListModel sourceModel;
  59 
  60     // Information on source file that is on display, or failed to be
  61     // displayed due to inaccessible source.  Used to update display
  62     // when sourcepath is changed.
  63 
  64     private String sourceName;          // relative path name, if showSourceFile
  65     private Location sourceLocn;        // location, if showSourceForLocation
  66     private CommandInterpreter interpreter;
  67 
  68     public SourceTool(Environment env) {
  69 
  70         super(new BorderLayout());
  71 
  72         this.env = env;
  73 
  74         runtime = env.getExecutionManager();
  75         sourceManager = env.getSourceManager();
  76         this.context = env.getContextManager();
  77         this.interpreter = new CommandInterpreter(env, true);
  78 
  79         sourceModel = new DefaultListModel();  // empty
  80 
  81         list = new JList(sourceModel);
  82         list.setCellRenderer(new SourceLineRenderer());
  83 
  84         list.setPrototypeCellValue(SourceModel.prototypeCellValue);
  85 
  86         SourceToolListener listener = new SourceToolListener();
  87         context.addContextListener(listener);
  88         runtime.addSpecListener(listener);
  89         sourceManager.addSourceListener(listener);
  90 
  91         MouseListener squeek = new STMouseListener();
  92         list.addMouseListener(squeek);
  93 
  94         add(new JScrollPane(list));
  95     }
  96 
  97     public void setTextFont(Font f) {
  98         list.setFont(f);
  99         list.setPrototypeCellValue(SourceModel.prototypeCellValue);
 100     }
 101 
 102     private class SourceToolListener
 103                implements ContextListener, SourceListener, SpecListener
 104     {
 105 
 106         // ContextListener
 107 
 108         @Override
 109         public void currentFrameChanged(CurrentFrameChangedEvent e) {
 110             showSourceContext(e.getThread(), e.getIndex());
 111         }
 112 
 113             // Clear source view.
 114             //      sourceModel = new DefaultListModel();  // empty
 115 
 116         // SourceListener
 117 
 118         @Override
 119         public void sourcepathChanged(SourcepathChangedEvent e) {
 120             // Reload source view if its contents depend
 121             // on the source path.
 122             if (sourceName != null) {
 123                 showSourceFile(sourceName);
 124             } else if (sourceLocn != null) {
 125                 showSourceForLocation(sourceLocn);
 126             }
 127         }
 128 
 129         // SpecListener
 130 
 131         @Override
 132         public void breakpointSet(SpecEvent e) {
 133             breakpointResolved(e);
 134         }
 135 
 136         @Override
 137         public void breakpointDeferred(SpecEvent e) { }
 138 
 139         @Override
 140         public void breakpointDeleted(SpecEvent e) {
 141             BreakpointRequest req = (BreakpointRequest)e.getEventRequest();
 142             Location loc = req.location();
 143             if (loc != null) {
 144                 try {
 145                     SourceModel sm = sourceManager.sourceForLocation(loc);
 146                     sm.showBreakpoint(loc.lineNumber(), false);
 147                     showSourceForLocation(loc);
 148                 } catch (Exception exc) {
 149                 }
 150             }
 151         }
 152 
 153         @Override
 154         public void breakpointResolved(SpecEvent e) {
 155             BreakpointRequest req = (BreakpointRequest)e.getEventRequest();
 156             Location loc = req.location();
 157             try {
 158                 SourceModel sm = sourceManager.sourceForLocation(loc);
 159                 sm.showBreakpoint(loc.lineNumber(), true);
 160                 showSourceForLocation(loc);
 161             } catch (Exception exc) {
 162             }
 163         }
 164 
 165         @Override
 166         public void breakpointError(SpecErrorEvent e) {
 167             breakpointDeleted(e);
 168         }
 169 
 170         @Override
 171         public void watchpointSet(SpecEvent e) {
 172         }
 173         @Override
 174         public void watchpointDeferred(SpecEvent e) {
 175         }
 176         @Override
 177         public void watchpointDeleted(SpecEvent e) {
 178         }
 179         @Override
 180         public void watchpointResolved(SpecEvent e) {
 181         }
 182         @Override
 183         public void watchpointError(SpecErrorEvent e) {
 184         }
 185 
 186         @Override
 187         public void exceptionInterceptSet(SpecEvent e) {
 188         }
 189         @Override
 190         public void exceptionInterceptDeferred(SpecEvent e) {
 191         }
 192         @Override
 193         public void exceptionInterceptDeleted(SpecEvent e) {
 194         }
 195         @Override
 196         public void exceptionInterceptResolved(SpecEvent e) {
 197         }
 198         @Override
 199         public void exceptionInterceptError(SpecErrorEvent e) {
 200         }
 201     }
 202 
 203     private void showSourceContext(ThreadReference thread, int index) {
 204         //### Should use ThreadInfo here.
 205         StackFrame frame = null;
 206         if (thread != null) {
 207             try {
 208                 frame = thread.frame(index);
 209             } catch (IncompatibleThreadStateException e) {}
 210         }
 211         if (frame == null) {
 212             return;
 213         }
 214         Location locn = frame.location();
 215         /*****
 216         if (!showSourceForLocation(locn)) {
 217             env.notice("Could not display source for "
 218                        + Utils.locationString(locn));
 219         }
 220         *****/
 221         showSourceForLocation(locn);
 222     }
 223 
 224     public boolean showSourceForLocation(Location locn) {
 225         sourceName = null;
 226         sourceLocn = locn;
 227         int lineNo = locn.lineNumber();
 228         if (lineNo != -1) {
 229             SourceModel source = sourceManager.sourceForLocation(locn);
 230             if (source != null) {
 231                 showSourceAtLine(source, lineNo-1);
 232                 return true;
 233             }
 234         }
 235         // Here if we could not display source.
 236         showSourceUnavailable();
 237         return false;
 238     }
 239 
 240     public boolean showSourceFile(String fileName) {
 241         sourceLocn = null;
 242         File file;
 243         if (!fileName.startsWith(File.separator)) {
 244             sourceName = fileName;
 245             SearchPath sourcePath = sourceManager.getSourcePath();
 246             file = sourcePath.resolve(fileName);
 247             if (file == null) {
 248                 //env.failure("Source not found on current source path.");
 249                 showSourceUnavailable();
 250                 return false;
 251             }
 252         } else {
 253             sourceName = null;  // Absolute pathname does not depend on sourcepath.
 254             file = new File(fileName);
 255         }
 256         SourceModel source = sourceManager.sourceForFile(file);
 257         if (source != null) {
 258             showSource(source);
 259             return true;
 260         }
 261         showSourceUnavailable();
 262         return false;
 263     }
 264 
 265     private void showSource(SourceModel model) {
 266         setViewModel(model);
 267     }
 268 
 269     private void showSourceAtLine(SourceModel model, int lineNo) {
 270         setViewModel(model);
 271         if (model.isActuallySource && (lineNo < model.getSize())) {
 272             list.setSelectedIndex(lineNo);
 273             if (lineNo+4 < model.getSize()) {
 274                 list.ensureIndexIsVisible(lineNo+4);  // give some context
 275             }
 276             list.ensureIndexIsVisible(lineNo);
 277         }
 278     }
 279 
 280     private void showSourceUnavailable() {
 281         SourceModel model = new SourceModel("[Source code is not available]");
 282         setViewModel(model);
 283     }
 284 
 285     private void setViewModel(SourceModel model) {
 286         if (model != sourceModel) {
 287             // install new model
 288             list.setModel(model);
 289             sourceModel = model;
 290         }
 291     }
 292 
 293     private class SourceLineRenderer extends DefaultListCellRenderer {
 294 
 295         @Override
 296         public Component getListCellRendererComponent(JList list,
 297                                                       Object value,
 298                                                       int index,
 299                                                       boolean isSelected,
 300                                                       boolean cellHasFocus) {
 301 
 302             //### Should set background highlight and/or icon if breakpoint on this line.
 303             // Configures "this"
 304             super.getListCellRendererComponent(list, value, index,
 305                                                isSelected, cellHasFocus);
 306 
 307             SourceModel.Line line = (SourceModel.Line)value;
 308 
 309             //### Tab expansion is now done when source file is read in,
 310             //### to speed up display.  This costs a lot of space, slows
 311             //### down source file loading, and has not been demonstrated
 312             //### to yield an observable improvement in display performance.
 313             //### Measurements may be appropriate here.
 314             //String sourceLine = expandTabs((String)value);
 315             setText(line.text);
 316             if (line.hasBreakpoint) {
 317                 setIcon(Icons.stopSignIcon);
 318             } else if (line.isExecutable()) {
 319                 setIcon(Icons.execIcon);
 320             } else {
 321                 setIcon(Icons.blankIcon);
 322             }
 323 
 324 
 325             return this;
 326         }
 327 
 328         @Override
 329         public Dimension getPreferredSize() {
 330             Dimension dim = super.getPreferredSize();
 331             return new Dimension(dim.width, dim.height-5);
 332         }
 333 
 334     }
 335 
 336     private class STMouseListener extends MouseAdapter implements MouseListener {
 337         @Override
 338         public void mousePressed(MouseEvent e) {
 339             if (e.isPopupTrigger()) {
 340                 showPopupMenu((Component)e.getSource(),
 341                               e.getX(), e.getY());
 342             }
 343         }
 344 
 345         @Override
 346         public void mouseReleased(MouseEvent e) {
 347             if (e.isPopupTrigger()) {
 348                 showPopupMenu((Component)e.getSource(),
 349                               e.getX(), e.getY());
 350             }
 351         }
 352 
 353         private void showPopupMenu(Component invoker, int x, int y) {
 354             JList list = (JList)invoker;
 355             int ln = list.getSelectedIndex() + 1;
 356             SourceModel.Line line =
 357                 (SourceModel.Line)list.getSelectedValue();
 358             JPopupMenu popup = new JPopupMenu();
 359 
 360             if (line == null) {
 361                 popup.add(new JMenuItem("please select a line"));
 362             } else if (line.isExecutable()) {
 363                 String className = line.refType.name();
 364                 if (line.hasBreakpoint()) {
 365                     popup.add(commandItem("Clear Breakpoint",
 366                                           "clear " + className +
 367                                           ":" + ln));
 368                 } else {
 369                     popup.add(commandItem("Set Breakpoint",
 370                                           "stop at " + className +
 371                                           ":" + ln));
 372                 }
 373             } else {
 374                 popup.add(new JMenuItem("not an executable line"));
 375             }
 376 
 377             popup.show(invoker,
 378                        x + popup.getWidth()/2, y + popup.getHeight()/2);
 379         }
 380 
 381         private JMenuItem commandItem(String label, final String cmd) {
 382             JMenuItem item = new JMenuItem(label);
 383             item.addActionListener(new ActionListener() {
 384                 @Override
 385                 public void actionPerformed(ActionEvent e) {
 386                     interpreter.executeCommand(cmd);
 387                 }
 388             });
 389             return item;
 390         }
 391     }
 392 }