/*
 * Copyright (c) 2006, 2007, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package com.sun.tools.jconsole;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JPanel;
import javax.swing.SwingWorker;

/**
 * A JConsole plugin class.  JConsole uses the
 * <a href="{@docRoot}/../../../../api/java/util/ServiceLoader.html">
 * service provider</a> mechanism to search the JConsole plugins.
 * Users can provide their JConsole plugins in a jar file
 * containing a file named
 *
 * <blockquote><pre>
 * META-INF/services/com.sun.tools.jconsole.JConsolePlugin</pre></blockquote>
 *
 * <p> This file contains one line for each plugin, for example,
 *
 * <blockquote><pre>
 * com.sun.example.JTop</pre></blockquote>
 * <p> which is the fully qualified class name of the class implementing
 * {@code JConsolePlugin}.
 *
 * <p> To load the JConsole plugins in JConsole, run:
 *
 * <blockquote><pre>
 * jconsole -pluginpath &lt;plugin-path&gt; </pre></blockquote>
 *
 * <p> where <tt>&lt;plugin-path&gt;</tt> specifies the paths of JConsole
 * plugins to look up which can be a directory or a jar file. Multiple
 * paths are separated by the path separator character of the platform.
 *
 * <p> When a new JConsole window is created for a connection,
 * an instance of each {@code JConsolePlugin} will be created.
 * The {@code JConsoleContext} object is not available at its
 * construction time.
 * JConsole will set the {@link JConsoleContext} object for
 * a plugin after the plugin object is created.  It will then
 * call its {@link #getTabs getTabs} method and add the returned
 * tabs to the JConsole window.
 *
 * @see <a href="{@docRoot}/../../../../api/java/util/ServiceLoader.html">
 * java.util.ServiceLoader</a>
 *
 * @since 1.6
 */
public abstract class JConsolePlugin {
    private volatile JConsoleContext context = null;
    private List<PropertyChangeListener> listeners = null;

    /**
     * Constructor.
     */
    protected JConsolePlugin() {
    }

    /**
     * Sets the {@link JConsoleContext JConsoleContext} object representing
     * the connection to an application.  This method will be called
     * only once after the plugin is created and before the {@link #getTabs}
     * is called. The given {@code context} can be in any
     * {@link JConsoleContext#getConnectionState connection state} when
     * this method is called.
     *
     * @param context a {@code JConsoleContext} object
     */
    public final synchronized void setContext(JConsoleContext context) {
        this.context = context;
        if (listeners != null) {
            for (PropertyChangeListener l : listeners) {
                context.addPropertyChangeListener(l);
            }
            // throw away the listener list
            listeners = null;
        }
    }

    /**
     * Returns the {@link JConsoleContext JConsoleContext} object representing
     * the connection to an application.  This method may return <tt>null</tt>
     * if it is called before the {@link #setContext context} is initialized.
     *
     * @return the {@link JConsoleContext JConsoleContext} object representing
     *         the connection to an application.
     */
    public final JConsoleContext getContext() {
        return context;
    }

    /**
     * Returns the tabs to be added in JConsole window.
     * <p>
     * The returned map contains one entry for each tab
     * to be added in the tabbed pane in a JConsole window with
     * the tab name as the key
     * and the {@link JPanel} object as the value.
     * This method returns an empty map if no tab is added by this plugin.
     * This method will be called from the <i>Event Dispatch Thread</i>
     * once at the new connection time.
     *
     * @return a map of a tab name and a {@link JPanel} object
     *         representing the tabs to be added in the JConsole window;
     *         or an empty map.
     */
    public abstract java.util.Map<String, JPanel> getTabs();

    /**
     * Returns a {@link SwingWorker} to perform
     * the GUI update for this plugin at the same interval
     * as JConsole updates the GUI.
     * <p>
     * JConsole schedules the GUI update at an interval specified
     * for a connection.  This method will be called at every
     * update to obtain a {@code SwingWorker} for each plugin.
     * <p>
     * JConsole will invoke the {@link SwingWorker#execute execute()}
     * method to schedule the returned {@code SwingWorker} for execution
     * if:
     * <ul>
     *   <li> the <tt>SwingWorker</tt> object has not been executed
     *        (i.e. the {@link SwingWorker#getState} method
     *        returns {@link javax.swing.SwingWorker.StateValue#PENDING PENDING}
     *        state); and</li>
     *   <li> the <tt>SwingWorker</tt> object returned in the previous
     *        update has completed the task if it was not <tt>null</tt>
     *        (i.e. the {@link SwingWorker#isDone SwingWorker.isDone} method
     *        returns <tt>true</tt>).</li>
     * </ul>
     * <br>
     * Otherwise, <tt>SwingWorker</tt> object will not be scheduled to work.
     *
     * <p>
     * A plugin can schedule its own GUI update and this method
     * will return <tt>null</tt>.
     *
     * @return a <tt>SwingWorker</tt> to perform the GUI update; or
     *         <tt>null</tt>.
     */
    public abstract SwingWorker<?,?> newSwingWorker();

    /**
     * Dispose this plugin. This method is called by JConsole to inform
     * that this plugin will be discarded and that it should free
     * any resources that it has allocated.
     * The {@link #getContext JConsoleContext} can be in any
     * {@link JConsoleContext#getConnectionState connection state} when
     * this method is called.
     */
    public void dispose() {
        // Default nop implementation
    }

    /**
     * Adds a {@link PropertyChangeListener PropertyChangeListener}
     * to the {@link #getContext JConsoleContext} object for this plugin.
     * This method is a convenient method for this plugin to register
     * a listener when the {@code JConsoleContext} object may or
     * may not be available.
     *
     * <p>For example, a plugin constructor can
     * call this method to register a listener to listen to the
     * {@link JConsoleContext.ConnectionState connectionState}
     * property changes and the listener will be added to the
     * {@link JConsoleContext#addPropertyChangeListener JConsoleContext}
     * object when it is available.
     *
     * @param listener  The {@code PropertyChangeListener} to be added
     *
     * @throws NullPointerException if {@code listener} is {@code null}.
     */
    public final void addContextPropertyChangeListener(PropertyChangeListener listener) {
        if (listener == null) {
            throw new NullPointerException("listener is null");
        }

        if (context == null) {
            // defer registration of the listener until setContext() is called
            synchronized (this) {
                // check again if context is not set
                if (context == null) {
                    // maintain a listener list to be added later
                    if (listeners == null) {
                        listeners = new ArrayList<PropertyChangeListener>();
                    }
                    listeners.add(listener);
                    return;
                }
            }
        }
        context.addPropertyChangeListener(listener);
    }

    /**
     * Removes a {@link PropertyChangeListener PropertyChangeListener}
     * from the listener list of the {@link #getContext JConsoleContext}
     * object for this plugin.
     * If {@code listener} was never added, no exception is
     * thrown and no action is taken.
     *
     * @param listener the {@code PropertyChangeListener} to be removed
     *
     * @throws NullPointerException if {@code listener} is {@code null}.
     */
    public final void removeContextPropertyChangeListener(PropertyChangeListener listener) {
        if (listener == null) {
            throw new NullPointerException("listener is null");
        }

        if (context == null) {
            // defer registration of the listener until setContext() is called
            synchronized (this) {
                // check again if context is not set
                if (context == null) {
                    if (listeners != null) {
                        listeners.remove(listener);
                    }
                    return;
                }
            }
        }
        context.removePropertyChangeListener(listener);
    }
}
