1 /*
2  * Copyright (c) 2006, 2013, 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 package com.sun.tools.jconsole;
27 
28 import java.beans.PropertyChangeEvent;
29 import java.beans.PropertyChangeListener;
30 import java.util.ArrayList;
31 import java.util.List;
32 import javax.swing.JPanel;
33 import javax.swing.SwingWorker;
34 
35 /**
36  * A JConsole plugin class.  JConsole uses the
37  * {@link java.util.ServiceLoader service provider}
38  * mechanism to search the JConsole plugins.
39  * Users can provide their JConsole plugins in a jar file
40  * containing a file named
41  *
42  * <blockquote><pre>
43  * META-INF/services/com.sun.tools.jconsole.JConsolePlugin</pre></blockquote>
44  *
45  * <p> This file contains one line for each plugin, for example,
46  *
47  * <blockquote><pre>
48  * com.sun.example.JTop</pre></blockquote>
49  * <p> which is the fully qualified class name of the class implementing
50  * {@code JConsolePlugin}.
51  *
52  * <p> To load the JConsole plugins in JConsole, run:
53  *
54  * <blockquote><pre>
55  * jconsole -pluginpath &lt;plugin-path&gt; </pre></blockquote>
56  *
57  * <p> where {@code <plugin-path>} specifies the paths of JConsole
58  * plugins to look up which can be a directory or a jar file. Multiple
59  * paths are separated by the path separator character of the platform.
60  *
61  * <p> When a new JConsole window is created for a connection,
62  * an instance of each {@code JConsolePlugin} will be created.
63  * The {@code JConsoleContext} object is not available at its
64  * construction time.
65  * JConsole will set the {@link JConsoleContext} object for
66  * a plugin after the plugin object is created.  It will then
67  * call its {@link #getTabs getTabs} method and add the returned
68  * tabs to the JConsole window.
69  *
70  * @see java.util.ServiceLoader
71  *
72  * @since 1.6
73  */
74 public abstract class JConsolePlugin {
75     private volatile JConsoleContext context = null;
76     private List<PropertyChangeListener> listeners = null;
77 
78     /**
79      * Constructor.
80      */
JConsolePlugin()81     protected JConsolePlugin() {
82     }
83 
84     /**
85      * Sets the {@link JConsoleContext JConsoleContext} object representing
86      * the connection to an application.  This method will be called
87      * only once after the plugin is created and before the {@link #getTabs}
88      * is called. The given {@code context} can be in any
89      * {@link JConsoleContext#getConnectionState connection state} when
90      * this method is called.
91      *
92      * @param context a {@code JConsoleContext} object
93      */
setContext(JConsoleContext context)94     public final synchronized void setContext(JConsoleContext context) {
95         this.context = context;
96         if (listeners != null) {
97             for (PropertyChangeListener l : listeners) {
98                 context.addPropertyChangeListener(l);
99             }
100             // throw away the listener list
101             listeners = null;
102         }
103     }
104 
105     /**
106      * Returns the {@link JConsoleContext JConsoleContext} object representing
107      * the connection to an application.  This method may return {@code null}
108      * if it is called before the {@link #setContext context} is initialized.
109      *
110      * @return the {@link JConsoleContext JConsoleContext} object representing
111      *         the connection to an application.
112      */
getContext()113     public final JConsoleContext getContext() {
114         return context;
115     }
116 
117     /**
118      * Returns the tabs to be added in JConsole window.
119      * <p>
120      * The returned map contains one entry for each tab
121      * to be added in the tabbed pane in a JConsole window with
122      * the tab name as the key
123      * and the {@link JPanel} object as the value.
124      * This method returns an empty map if no tab is added by this plugin.
125      * This method will be called from the <i>Event Dispatch Thread</i>
126      * once at the new connection time.
127      *
128      * @return a map of a tab name and a {@link JPanel} object
129      *         representing the tabs to be added in the JConsole window;
130      *         or an empty map.
131      */
getTabs()132     public abstract java.util.Map<String, JPanel> getTabs();
133 
134     /**
135      * Returns a {@link SwingWorker} to perform
136      * the GUI update for this plugin at the same interval
137      * as JConsole updates the GUI.
138      * <p>
139      * JConsole schedules the GUI update at an interval specified
140      * for a connection.  This method will be called at every
141      * update to obtain a {@code SwingWorker} for each plugin.
142      * <p>
143      * JConsole will invoke the {@link SwingWorker#execute execute()}
144      * method to schedule the returned {@code SwingWorker} for execution
145      * if:
146      * <ul>
147      *   <li> the {@code SwingWorker} object has not been executed
148      *        (i.e. the {@link SwingWorker#getState} method
149      *        returns {@link javax.swing.SwingWorker.StateValue#PENDING PENDING}
150      *        state); and</li>
151      *   <li> the {@code SwingWorker} object returned in the previous
152      *        update has completed the task if it was not {@code null}
153      *        (i.e. the {@link SwingWorker#isDone SwingWorker.isDone} method
154      *        returns {@code true}).</li>
155      * </ul>
156      * <br>
157      * Otherwise, {@code SwingWorker} object will not be scheduled to work.
158      *
159      * <p>
160      * A plugin can schedule its own GUI update and this method
161      * will return {@code null}.
162      *
163      * @return a {@code SwingWorker} to perform the GUI update; or
164      *         {@code null}.
165      */
newSwingWorker()166     public abstract SwingWorker<?,?> newSwingWorker();
167 
168     /**
169      * Dispose this plugin. This method is called by JConsole to inform
170      * that this plugin will be discarded and that it should free
171      * any resources that it has allocated.
172      * The {@link #getContext JConsoleContext} can be in any
173      * {@link JConsoleContext#getConnectionState connection state} when
174      * this method is called.
175      */
dispose()176     public void dispose() {
177         // Default nop implementation
178     }
179 
180     /**
181      * Adds a {@link PropertyChangeListener PropertyChangeListener}
182      * to the {@link #getContext JConsoleContext} object for this plugin.
183      * This method is a convenient method for this plugin to register
184      * a listener when the {@code JConsoleContext} object may or
185      * may not be available.
186      *
187      * <p>For example, a plugin constructor can
188      * call this method to register a listener to listen to the
189      * {@link JConsoleContext.ConnectionState connectionState}
190      * property changes and the listener will be added to the
191      * {@link JConsoleContext#addPropertyChangeListener JConsoleContext}
192      * object when it is available.
193      *
194      * @param listener  The {@code PropertyChangeListener} to be added
195      *
196      * @throws NullPointerException if {@code listener} is {@code null}.
197      */
addContextPropertyChangeListener(PropertyChangeListener listener)198     public final void addContextPropertyChangeListener(PropertyChangeListener listener) {
199         if (listener == null) {
200             throw new NullPointerException("listener is null");
201         }
202 
203         if (context == null) {
204             // defer registration of the listener until setContext() is called
205             synchronized (this) {
206                 // check again if context is not set
207                 if (context == null) {
208                     // maintain a listener list to be added later
209                     if (listeners == null) {
210                         listeners = new ArrayList<PropertyChangeListener>();
211                     }
212                     listeners.add(listener);
213                     return;
214                 }
215             }
216         }
217         context.addPropertyChangeListener(listener);
218     }
219 
220     /**
221      * Removes a {@link PropertyChangeListener PropertyChangeListener}
222      * from the listener list of the {@link #getContext JConsoleContext}
223      * object for this plugin.
224      * If {@code listener} was never added, no exception is
225      * thrown and no action is taken.
226      *
227      * @param listener the {@code PropertyChangeListener} to be removed
228      *
229      * @throws NullPointerException if {@code listener} is {@code null}.
230      */
removeContextPropertyChangeListener(PropertyChangeListener listener)231     public final void removeContextPropertyChangeListener(PropertyChangeListener listener) {
232         if (listener == null) {
233             throw new NullPointerException("listener is null");
234         }
235 
236         if (context == null) {
237             // defer registration of the listener until setContext() is called
238             synchronized (this) {
239                 // check again if context is not set
240                 if (context == null) {
241                     if (listeners != null) {
242                         listeners.remove(listener);
243                     }
244                     return;
245                 }
246             }
247         }
248         context.removePropertyChangeListener(listener);
249     }
250 }
251