1 /* JavaConsole -- A java console for the plugin 2 Copyright (C) 2009, 2013 Red Hat 3 4 This file is part of IcedTea. 5 6 IcedTea is free software; you can redistribute it and/or modify 7 it under the terms of the GNU General Public License as published by 8 the Free Software Foundation; either version 2, or (at your option) 9 any later version. 10 11 IcedTea is distributed in the hope that it will be useful, but 12 WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 General Public License for more details. 15 16 You should have received a copy of the GNU General Public License 17 along with IcedTea; see the file COPYING. If not, write to the 18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 19 02110-1301 USA. 20 21 Linking this library statically or dynamically with other modules is 22 making a combined work based on this library. Thus, the terms and 23 conditions of the GNU General Public License cover the whole 24 combination. 25 26 As a special exception, the copyright holders of this library give you 27 permission to link this library with independent modules to produce an 28 executable, regardless of the license terms of these independent 29 modules, and to copy and distribute the resulting executable under 30 terms of your choice, provided that you also meet, for each linked 31 independent module, the terms and conditions of the license of that 32 module. An independent module is a module which is not derived from 33 or based on this library. If you modify this library, you may extend 34 this exception to your version of the library, but you are not 35 obligated to do so. If you do not wish to do so, delete this 36 exception statement from your version. */ 37 package net.sourceforge.jnlp.util.logging; 38 39 import static net.sourceforge.jnlp.runtime.Translator.R; 40 41 import java.awt.BorderLayout; 42 import java.awt.Dimension; 43 import java.awt.GridBagConstraints; 44 import java.awt.GridBagLayout; 45 import java.awt.GridLayout; 46 import java.awt.event.ActionEvent; 47 import java.awt.event.ActionListener; 48 import java.awt.event.WindowAdapter; 49 import java.awt.event.WindowEvent; 50 import java.io.BufferedReader; 51 import java.io.File; 52 import java.io.FileInputStream; 53 import java.io.InputStreamReader; 54 import java.nio.charset.Charset; 55 import java.util.ArrayList; 56 import java.util.Collections; 57 import java.util.List; 58 import java.util.Map; 59 import java.util.Observable; 60 import java.util.Properties; 61 import java.util.Set; 62 import javax.swing.JButton; 63 import javax.swing.JComponent; 64 import javax.swing.JDialog; 65 import javax.swing.JFormattedTextField; 66 import javax.swing.JFrame; 67 import javax.swing.JLabel; 68 import javax.swing.JPanel; 69 import javax.swing.JSpinner; 70 import javax.swing.JSplitPane; 71 import javax.swing.SpinnerNumberModel; 72 import javax.swing.SwingUtilities; 73 import javax.swing.event.ChangeEvent; 74 import javax.swing.event.ChangeListener; 75 import javax.swing.text.DefaultFormatter; 76 import net.sourceforge.jnlp.config.DeploymentConfiguration; 77 import net.sourceforge.jnlp.runtime.JNLPRuntime; 78 import net.sourceforge.jnlp.util.ImageResources; 79 import net.sourceforge.jnlp.util.logging.headers.MessageWithHeader; 80 import net.sourceforge.jnlp.util.logging.headers.ObservableMessagesProvider; 81 import net.sourceforge.jnlp.util.logging.headers.PluginMessage; 82 83 /** 84 * A simple Java console for IcedTeaPlugin and JavaWS 85 * 86 */ 87 public class JavaConsole implements ObservableMessagesProvider { 88 89 final private List<MessageWithHeader> rawData = Collections.synchronizedList(new ArrayList<MessageWithHeader>()); 90 final private List<ConsoleOutputPane> outputs = new ArrayList<ConsoleOutputPane>(); 91 JavaConsole()92 public JavaConsole() { 93 //add middleware, which catches client's application stdout/err 94 //and will submit it into console 95 System.setErr(new TeeOutputStream(System.err, true)); 96 System.setOut(new TeeOutputStream(System.out, false)); 97 //internal stdOut/Err are going throughs outLog/errLog 98 //when console is off, those tees are not installed 99 } 100 101 refreshOutputs()102 private void refreshOutputs() { 103 refreshOutputs(outputsPanel, (Integer)numberOfOutputs.getValue()); 104 } 105 refreshOutputs(JPanel pane, int count)106 private void refreshOutputs(JPanel pane, int count) { 107 pane.removeAll(); 108 while(outputs.size()>count){ 109 getObservable().deleteObserver(outputs.get(outputs.size()-1)); 110 outputs.remove(outputs.size()-1); 111 } 112 while(outputs.size()<count){ 113 ConsoleOutputPane c1 = new ConsoleOutputPane(this); 114 observable.addObserver(c1); 115 outputs.add(c1); 116 } 117 if (count == 0){ 118 pane.add(new JPanel()); 119 } else if (outputs.size() == 1){ 120 pane.add(outputs.get(0)); 121 } else { 122 JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,outputs.get(outputs.size()-2), outputs.get(outputs.size()-1)); 123 splitPane.setDividerLocation(0.5); 124 splitPane.setResizeWeight(0.5); 125 126 for (int i = outputs.size()-3; i>=0; i--){ 127 JSplitPane outerPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, outputs.get(i), splitPane); 128 outerPane.setDividerLocation(0.5); 129 outerPane.setResizeWeight(0.5); 130 splitPane = outerPane; 131 } 132 pane.add(splitPane); 133 134 } 135 pane.validate(); 136 } 137 138 private static class PublicObservable extends Observable { 139 140 @Override setChanged()141 public synchronized void setChanged() { 142 super.setChanged(); 143 } 144 } 145 146 public static interface ClassLoaderInfoProvider { 147 getLoaderInfo()148 public Map<String, String> getLoaderInfo(); 149 } 150 151 private static JavaConsole console; 152 153 private Dimension lastSize; 154 private JDialog consoleWindow; 155 private JPanel contentPanel; 156 private JPanel outputsPanel; 157 private ClassLoaderInfoProvider classLoaderInfoProvider; 158 private JSpinner numberOfOutputs; 159 private PublicObservable observable = new PublicObservable(); 160 private boolean initialized = false; 161 162 private static class JavaConsoleHolder { 163 164 //https://en.wikipedia.org/wiki/Double-checked_locking#Usage_in_Java 165 //https://en.wikipedia.org/wiki/Initialization_on_demand_holder_idiom 166 private static final JavaConsole INSTANCE = new JavaConsole(); 167 } getConsole()168 public static JavaConsole getConsole() { 169 return JavaConsoleHolder.INSTANCE; 170 } 171 isEnabled()172 public static boolean isEnabled() { 173 return isEnabled(JNLPRuntime.getConfiguration()); 174 } 175 isEnabled(DeploymentConfiguration config)176 public static boolean isEnabled(DeploymentConfiguration config) { 177 return !DeploymentConfiguration.CONSOLE_DISABLE.equals(config.getProperty(DeploymentConfiguration.KEY_CONSOLE_STARTUP_MODE)) 178 && !JNLPRuntime.isHeadless(); 179 } 180 canShowOnStartup(boolean isApplication)181 public static boolean canShowOnStartup(boolean isApplication) { 182 return canShowOnStartup(isApplication, JNLPRuntime.getConfiguration()); 183 } 184 canShowOnStartup(boolean isApplication, DeploymentConfiguration config)185 public static boolean canShowOnStartup(boolean isApplication, DeploymentConfiguration config) { 186 if (!isEnabled(config)) { 187 return false; 188 } 189 return DeploymentConfiguration.CONSOLE_SHOW.equals(config.getProperty(DeploymentConfiguration.KEY_CONSOLE_STARTUP_MODE)) 190 || (DeploymentConfiguration.CONSOLE_SHOW_PLUGIN.equals(config.getProperty(DeploymentConfiguration.KEY_CONSOLE_STARTUP_MODE)) 191 && !isApplication) 192 || (DeploymentConfiguration.CONSOLE_SHOW_JAVAWS.equals(config.getProperty(DeploymentConfiguration.KEY_CONSOLE_STARTUP_MODE)) 193 && isApplication); 194 } 195 initializeWindow()196 private void initializeWindow() { 197 if (!initialized){ 198 initialize(); 199 } 200 initializeWindow(lastSize, contentPanel); 201 } 202 initializeWindow(Dimension size, JPanel content)203 private void initializeWindow(Dimension size, JPanel content) { 204 consoleWindow = new JDialog((JFrame) null, R("DPJavaConsole")); 205 consoleWindow.addWindowListener(new WindowAdapter() { 206 207 @Override 208 public void windowClosed(WindowEvent e) { 209 lastSize=consoleWindow.getSize(); 210 } 211 212 }); 213 consoleWindow.setIconImages(ImageResources.INSTANCE.getApplicationImages()); 214 //view is added after console is made visible so no performance impact when hidden/ 215 refreshOutputs(); 216 consoleWindow.add(content); 217 consoleWindow.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); //HIDE_ON_CLOSE can cause shut down deadlock 218 consoleWindow.pack(); 219 if (size!=null){ 220 consoleWindow.setSize(size); 221 } else { 222 consoleWindow.setSize(new Dimension(900, 600)); 223 } 224 consoleWindow.setMinimumSize(new Dimension(300, 300)); 225 226 } 227 228 /** 229 * Initialize the console 230 */ initialize()231 private void initialize() { 232 233 contentPanel = new JPanel(); 234 outputsPanel = new JPanel(); 235 236 outputsPanel.setLayout(new BorderLayout()); 237 contentPanel.setLayout(new GridBagLayout()); 238 239 GridBagConstraints c; 240 c = new GridBagConstraints(); 241 c.fill = GridBagConstraints.BOTH; 242 c.gridheight = 10; 243 c.weighty = 1; 244 245 contentPanel.add(outputsPanel, c); 246 247 /* buttons */ 248 249 c = new GridBagConstraints(); 250 c.gridy = 10; 251 c.gridheight = 1; 252 c.weightx = 0.5; 253 c.weighty = 0; 254 255 JPanel buttonPanel = new JPanel(); 256 buttonPanel.setLayout(new GridLayout(2, 0, 0, 0)); 257 contentPanel.add(buttonPanel, c); 258 259 JButton gcButton = new JButton(R("CONSOLErungc")); 260 buttonPanel.add(gcButton); 261 gcButton.addActionListener(new ActionListener() { 262 263 @Override 264 public void actionPerformed(ActionEvent e) { 265 printMemoryInfo(); 266 OutputController.getLogger().log(OutputController.Level.MESSAGE_ALL, "Performing Garbage Collection...."); 267 System.gc(); 268 OutputController.getLogger().log(OutputController.Level.MESSAGE_ALL, R("ButDone")); 269 printMemoryInfo(); 270 updateModel(); 271 } 272 }); 273 274 JButton finalizersButton = new JButton(R("CONSOLErunFinalizers")); 275 buttonPanel.add(finalizersButton); 276 finalizersButton.addActionListener(new ActionListener() { 277 278 @Override 279 public void actionPerformed(ActionEvent e) { 280 printMemoryInfo(); 281 OutputController.getLogger().log(OutputController.Level.MESSAGE_ALL, R("CONSOLErunningFinalizers")); 282 Runtime.getRuntime().runFinalization(); 283 OutputController.getLogger().log(OutputController.Level.MESSAGE_ALL, R("ButDone")); 284 printMemoryInfo(); 285 updateModel(); 286 } 287 }); 288 289 JButton memoryButton = new JButton(R("CONSOLEmemoryInfo")); 290 buttonPanel.add(memoryButton); 291 memoryButton.addActionListener(new ActionListener() { 292 293 @Override 294 public void actionPerformed(ActionEvent e) { 295 printMemoryInfo(); 296 updateModel(); 297 } 298 }); 299 300 JButton systemPropertiesButton = new JButton(R("CONSOLEsystemProperties")); 301 buttonPanel.add(systemPropertiesButton); 302 systemPropertiesButton.addActionListener(new ActionListener() { 303 304 @Override 305 public void actionPerformed(ActionEvent e) { 306 printSystemProperties(); 307 updateModel(); 308 } 309 }); 310 311 JButton classloadersButton = new JButton(R("CONSOLEclassLoaders")); 312 buttonPanel.add(classloadersButton); 313 classloadersButton.addActionListener(new ActionListener() { 314 315 @Override 316 public void actionPerformed(ActionEvent e) { 317 printClassLoaders(); 318 updateModel(); 319 } 320 }); 321 322 JButton threadListButton = new JButton(R("CONSOLEthreadList")); 323 buttonPanel.add(threadListButton); 324 threadListButton.addActionListener(new ActionListener() { 325 326 @Override 327 public void actionPerformed(ActionEvent e) { 328 printThreadInfo(); 329 updateModel(); 330 } 331 }); 332 333 JLabel numberOfOutputsL = new JLabel(" Number of outputs: "); 334 buttonPanel.add(numberOfOutputsL); 335 numberOfOutputs = new JSpinner(new SpinnerNumberModel(1, 0, 10, 1)); 336 JComponent comp = numberOfOutputs.getEditor(); 337 JFormattedTextField field = (JFormattedTextField) comp.getComponent(0); 338 DefaultFormatter formatter = (DefaultFormatter) field.getFormatter(); 339 formatter.setCommitsOnValidEdit(true); 340 numberOfOutputs.addChangeListener(new ChangeListener() { 341 342 @Override 343 public void stateChanged(ChangeEvent e) { 344 refreshOutputs(); 345 } 346 }); 347 buttonPanel.add(numberOfOutputs); 348 349 JButton closeButton = new JButton(R("ButClose")); 350 buttonPanel.add(closeButton); 351 closeButton.addActionListener(new ActionListener() { 352 353 @Override 354 public void actionPerformed(ActionEvent e) { 355 SwingUtilities.invokeLater(new Runnable() { 356 357 @Override 358 public void run() { 359 hideConsole(); 360 } 361 }); 362 } 363 }); 364 365 JButton cleanButton = new JButton(R("CONSOLEClean")); 366 buttonPanel.add(cleanButton); 367 cleanButton.addActionListener(new ActionListener() { 368 369 @Override 370 public void actionPerformed(ActionEvent e) { 371 synchronized (rawData){ 372 rawData.clear(); 373 updateModel(true); 374 } 375 } 376 }); 377 378 initialized = true; 379 } 380 showConsole()381 public void showConsole() { 382 showConsole(false); 383 } 384 showConsole(boolean modal)385 public void showConsole(boolean modal) { 386 if (consoleWindow == null || !consoleWindow.isVisible()){ 387 initializeWindow(); 388 consoleWindow.setModal(modal); 389 consoleWindow.setVisible(true); 390 } 391 } 392 hideConsole()393 public void hideConsole() { 394 //no need to update when hidden 395 outputsPanel.removeAll();//?? 396 getObservable().deleteObservers(); 397 consoleWindow.setModal(false); 398 consoleWindow.setVisible(false); 399 consoleWindow.dispose(); 400 } 401 showConsoleLater()402 public void showConsoleLater() { 403 showConsoleLater(false); 404 } 405 showConsoleLater(final boolean modal)406 public void showConsoleLater(final boolean modal) { 407 SwingUtilities.invokeLater(new Runnable() { 408 409 @Override 410 public void run() { 411 JavaConsole.getConsole().showConsole(modal); 412 } 413 }); 414 } 415 hideConsoleLater()416 public void hideConsoleLater() { 417 SwingUtilities.invokeLater(new Runnable() { 418 419 @Override 420 public void run() { 421 JavaConsole.getConsole().hideConsole(); 422 } 423 }); 424 } 425 printSystemProperties()426 protected void printSystemProperties() { 427 428 OutputController.getLogger().log(OutputController.Level.MESSAGE_ALL, " ----"); 429 OutputController.getLogger().log(OutputController.Level.MESSAGE_ALL, R("CONSOLEsystemProperties") + ":"); 430 OutputController.getLogger().log(OutputController.Level.MESSAGE_ALL, ""); 431 Properties p = System.getProperties(); 432 Set<Object> keys = p.keySet(); 433 for (Object key : keys) { 434 OutputController.getLogger().log(OutputController.Level.MESSAGE_ALL, key.toString() + ": " + p.get(key)); 435 } 436 437 OutputController.getLogger().log(OutputController.Level.MESSAGE_ALL, " ----"); 438 } 439 setClassLoaderInfoProvider(ClassLoaderInfoProvider clip)440 public void setClassLoaderInfoProvider(ClassLoaderInfoProvider clip) { 441 classLoaderInfoProvider = clip; 442 } 443 printClassLoaders()444 private void printClassLoaders() { 445 if (classLoaderInfoProvider == null) { 446 OutputController.getLogger().log(OutputController.Level.MESSAGE_ALL, R("CONSOLEnoClassLoaders")); 447 } else { 448 OutputController.getLogger().log(OutputController.Level.MESSAGE_ALL, " ----"); 449 OutputController.getLogger().log(OutputController.Level.MESSAGE_ALL, R("CONSOLEclassLoaders") + ": "); 450 Set<String> loaders = classLoaderInfoProvider.getLoaderInfo().keySet(); 451 for (String loader : loaders) { 452 OutputController.getLogger().log(OutputController.Level.MESSAGE_ALL, loader + "\n" 453 + " codebase = " 454 + classLoaderInfoProvider.getLoaderInfo().get(loader)); 455 } 456 OutputController.getLogger().log(OutputController.Level.MESSAGE_ALL, " ----"); 457 } 458 } 459 printMemoryInfo()460 private void printMemoryInfo() { 461 OutputController.getLogger().log(OutputController.Level.MESSAGE_ALL, " ----- "); 462 OutputController.getLogger().log(OutputController.Level.MESSAGE_ALL, " " + R("CONSOLEmemoryInfo") + ":"); 463 OutputController.getLogger().log(OutputController.Level.MESSAGE_ALL, " " + R("CONSOLEmemoryMax") + ": " 464 + String.format("%1$10d", Runtime.getRuntime().maxMemory())); 465 OutputController.getLogger().log(OutputController.Level.MESSAGE_ALL, " " + R("CONSOLEmemoryTotal") + ": " 466 + String.format("%1$10d", Runtime.getRuntime().totalMemory())); 467 OutputController.getLogger().log(OutputController.Level.MESSAGE_ALL, " " + R("CONSOLEmemoryFree") + ": " 468 + String.format("%1$10d", Runtime.getRuntime().freeMemory())); 469 OutputController.getLogger().log(OutputController.Level.MESSAGE_ALL, " ----"); 470 471 } 472 printThreadInfo()473 private void printThreadInfo() { 474 Map<Thread, StackTraceElement[]> map = Thread.getAllStackTraces(); 475 Set<Thread> keys = map.keySet(); 476 for (Thread key : keys) { 477 OutputController.getLogger().log(OutputController.Level.MESSAGE_ALL, R("CONSOLEthread") + " " + key.getId() + ": " + key.getName()); 478 for (StackTraceElement element : map.get(key)) { 479 OutputController.getLogger().log(OutputController.Level.MESSAGE_ALL, " " + element); 480 } 481 482 } 483 } 484 main(String[] args)485 public static void main(String[] args) { 486 487 final JavaConsole cconsole = new JavaConsole(); 488 489 boolean toShowConsole = true; 490 491 for (String arg : args) { 492 if ("--show-console".equals(arg)) { 493 toShowConsole = true; 494 } 495 } 496 497 if (toShowConsole) { 498 cconsole.showConsoleLater(); 499 } 500 501 } 502 503 addMessage(MessageWithHeader m)504 synchronized void addMessage(MessageWithHeader m) { 505 rawData.add(m); 506 updateModel(); 507 } 508 updateModel()509 private synchronized void updateModel() { 510 updateModel(null); 511 } updateModel(Boolean force)512 private synchronized void updateModel(Boolean force) { 513 observable.setChanged(); 514 observable.notifyObservers(force); 515 } 516 517 518 /** 519 * parse plugin message and add it as header+message to data 520 * @param s string to be parsed 521 */ processPluginMessage(String s)522 private void processPluginMessage(String s) { 523 PluginMessage pm = new PluginMessage(s); 524 OutputController.getLogger().log(pm); 525 } 526 527 @Override getData()528 public List<MessageWithHeader> getData() { 529 return rawData; 530 } 531 532 @Override getObservable()533 public Observable getObservable() { 534 return observable; 535 } 536 createPluginReader(final File file)537 public void createPluginReader(final File file) { 538 OutputController.getLogger().log("Starting processing of plugin-debug-to-console " + file.getAbsolutePath()); 539 Thread t = new Thread(new Runnable() { 540 541 @Override 542 public void run() { 543 BufferedReader br = null; 544 try { 545 br = new BufferedReader(new InputStreamReader(new FileInputStream(file), 546 Charset.forName("UTF-8"))); 547 //never ending loop 548 while (true) { 549 try{ 550 String s = br.readLine(); 551 if (s == null) { 552 break; 553 } 554 processPluginMessage(s); 555 }catch(Exception ex){ 556 OutputController.getLogger().log(ex); 557 } 558 } 559 } catch (Exception ex) { 560 OutputController.getLogger().log(ex); 561 if (br != null) { 562 try { 563 br.close(); 564 } catch (Exception exx) { 565 OutputController.getLogger().log(exx); 566 } 567 } 568 } 569 OutputController.getLogger().log("Ended processing of plugin-debug-to-console " + file.getAbsolutePath()); 570 } 571 }, "plugin-debug-to-console reader thread"); 572 t.setDaemon(true); 573 t.start(); 574 575 OutputController.getLogger().log("Started processing of plugin-debug-to-console " + file.getAbsolutePath()); 576 } 577 } 578