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