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
26package com.sun.tools.jconsole;
27
28import java.beans.PropertyChangeEvent;
29import java.beans.PropertyChangeListener;
30import java.util.ArrayList;
31import java.util.List;
32import javax.swing.JPanel;
33import 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 */
74public abstract class JConsolePlugin {
75    private volatile JConsoleContext context = null;
76    private List<PropertyChangeListener> listeners = null;
77
78    /**
79     * Constructor.
80     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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