/*
 * @(#)GaugeMonitor.java	1.73 04/05/18
 *
 * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
 * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */

package javax.management.monitor;


// java imports
//
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

// RI imports
//
import javax.management.ObjectName;
import javax.management.MBeanNotificationInfo;
import javax.management.AttributeNotFoundException;
import javax.management.InstanceNotFoundException;
import javax.management.MBeanException;
import javax.management.ReflectionException;

/**
 * Defines a monitor MBean designed to observe the values of a gauge attribute.
 *
 * <P> A gauge monitor observes an attribute that is continuously
 * variable with time.  A gauge monitor sends notifications as
 * follows:
 *
 * <UL>
 *
 * <LI> if the attribute value is increasing and becomes equal to or
 * greater than the high threshold value, a {@link
 * MonitorNotification#THRESHOLD_HIGH_VALUE_EXCEEDED threshold high
 * notification} is sent.  The notify high flag must be set to
 * <CODE>true</CODE>
 *
 * <BR>Subsequent crossings of the high threshold value do not cause
 * further notifications unless the attribute value becomes equal to
 * or less than the low threshold value.
 *
 * <LI> if the attribute value is decreasing and becomes equal to or
 * less than the low threshold value, a {@link
 * MonitorNotification#THRESHOLD_LOW_VALUE_EXCEEDED threshold low
 * notification} is sent.  The notify low flag must be set to
 * <CODE>true</CODE>.
 *
 * <BR>Subsequent crossings of the low threshold value do not cause
 * further notifications unless the attribute value becomes equal to
 * or greater than the high threshold value.
 *
 * </UL>
 *
 * This provides a hysteresis mechanism to avoid repeated triggering
 * of notifications when the attribute value makes small oscillations
 * around the high or low threshold value.
 *
 * <P> If the gauge difference mode is used, the value of the derived
 * gauge is calculated as the difference between the observed gauge
 * values for two successive observations.
 *
 * <BR>The derived gauge value (V[t]) is calculated using the following method:
 * <UL>
 * <LI>V[t] = gauge[t] - gauge[t-GP]
 * </UL>
 *
 * This implementation of the gauge monitor requires the observed
 * attribute to be of the type integer or floating-point
 * (<CODE>Byte</CODE>, <CODE>Integer</CODE>, <CODE>Short</CODE>,
 * <CODE>Long</CODE>, <CODE>Float</CODE>, <CODE>Double</CODE>).
 *
 * @version     1.73     05/18/04
 * @author      Sun Microsystems, Inc
 *
 * @since 1.5
 */
public class GaugeMonitor extends Monitor implements GaugeMonitorMBean {


    /*
     * ------------------------------------------
     *  PRIVATE VARIABLES
     * ------------------------------------------
     */

    private static final Integer INTEGER_ZERO = new Integer(0);

    /**
     * Gauge high threshold.
     * <BR>The default value is a null Integer object.
     */
    private Number highThreshold = INTEGER_ZERO;

    /**
     * Gauge low threshold.
     * <BR>The default value is a null Integer object.
     */
    private Number lowThreshold = INTEGER_ZERO;

    /**
     * Flag indicating if the gauge monitor notifies when exceeding
     * the high threshold.
     *
     * <BR>The default value is set to <CODE>false</CODE>.
     */
    private boolean notifyHigh = false;

    /**
     * Flag indicating if the gauge monitor notifies when exceeding
     * the low threshold.
     *
     * <BR>The default value is set to <CODE>false</CODE>.
     */
    private boolean notifyLow = false;

    /**
     * Flag indicating if the gauge difference mode is used.  If the
     * gauge difference mode is used, the derived gauge is the
     * difference between two consecutive observed values.  Otherwise,
     * the derived gauge is directly the value of the observed
     * attribute.
     *
     * <BR>The default value is set to <CODE>false</CODE>.
     */
    private boolean differenceMode = false;

    /**
     * Derived gauges.
     * <BR>Each element in this array corresponds to an observed
     * object in the list.
     */
    private Number derivedGauge[] = new Number[capacityIncrement];

    /**
     * Derived gauge timestamps.
     * <BR>Each element in this array corresponds to an observed
     * object in the list.
     */
    private long derivedGaugeTimestamp[] = new long[capacityIncrement];

    /**
     * Scan gauge values captured by the previous observation.
     * <BR>Each element in this array corresponds to an observed
     * object in the list.
     */
    private Number previousScanGauge[] = new Number[capacityIncrement];

    /**
     * This attribute is used to handle the hysteresis mechanism.
     * <BR>Each element in this array corresponds to an observed
     * object in the list.
     */
    private int status[] = new int[capacityIncrement];

    /**
     * This attribute is used to keep the derived gauge type.
     * <BR>Each element in this array corresponds to an observed
     * object in the list.
     */
    private int type[] = new int[capacityIncrement];

    // Flags needed to implement the hysteresis mechanism.
    //
    private static final int RISING             = 0;
    private static final int FALLING            = 1;
    private static final int RISING_OR_FALLING  = 2;

    // Flags needed to keep trace of the derived gauge type.
    // Integer + floating-point types are allowed.
    //
    private static final int INTEGER    = 0;
    private static final int BYTE       = 1;
    private static final int SHORT      = 2;
    private static final int LONG       = 3;
    private static final int FLOAT      = 4;
    private static final int DOUBLE     = 5;

    // New flags defining possible gauge monitor errors.

    // Flag denoting that a notification has occurred after changing
    // the high/low threshold.  This flag is used to check that the
    // high/low threshold type is the same as the gauge and that the
    // threshold high value is greater than the threshold low value at
    // the first notification time.
    //
    private static final int THRESHOLD_ERROR_NOTIFIED   = 16;

    /**
     * Timer.
     */
    private Timer timer = null;



    // TRACES & DEBUG
    //---------------

    String makeDebugTag() {
        return "GaugeMonitor";
    }

    /*
     * ------------------------------------------
     *  CONSTRUCTORS
     * ------------------------------------------
     */

    /**
     * Default constructor.
     */
    public GaugeMonitor() {
      dbgTag = makeDebugTag();
    }

    /*
     * ------------------------------------------
     *  PUBLIC METHODS
     * ------------------------------------------
     */

    /**
     * Starts the gauge monitor.
     */
    public void start() {

        if (isTraceOn()) {
            trace("start", "start the gauge monitor");
        }

	synchronized(this) {
	    if (isActive) {
		if (isTraceOn()) {
		    trace("start", "the gauge monitor is already activated");
		}

		return;
	    }

            isActive = true;

            // Reset values.
            //
            for (int i = 0; i < elementCount; i++) {
                status[i] = RISING_OR_FALLING;
                previousScanGauge[i] = null;
            }

            // Start the AlarmClock.
            //
            timer = new Timer();
            timer.schedule(new GaugeAlarmClock(this), getGranularityPeriod(),
			   getGranularityPeriod());
	}
    }

    /**
     * Stops the gauge monitor.
     */
    /* This method is not synchronized, because if it were there could
       be a deadlock with a thread that attempted to get the lock on
       the monitor before being interrupted or noticing that it had
       been interrupted.  */
    public void stop() {
	trace("stop", "stop the gauge monitor");

	synchronized(this) {
	    if (!isActive) {
		if (isTraceOn()) {
		    trace("stop", "the counter monitor is already started");
		}

		return;
	    }

            isActive = false;

            // Stop the AlarmClock.
            //
            if (timer != null) {
                timer.cancel();
                timer = null;
            }
	}
    }

    // GETTERS AND SETTERS
    //--------------------

    /**
     * Sets the granularity period (in milliseconds).
     * <BR>The default value of the granularity period is 10 seconds.
     *
     * @param period The granularity period value.
     * @exception java.lang.IllegalArgumentException The granularity
     * period is less than or equal to zero.
     *
     * @see Monitor#setGranularityPeriod(long)
     */
    public synchronized void setGranularityPeriod(long period)
	    throws IllegalArgumentException {
        super.setGranularityPeriod(period);

        // Reschedule timer task if timer is already running
        //
	if (isActive()) {
	    timer.cancel();
	    timer = new Timer();
	    timer.schedule(new GaugeAlarmClock(this), getGranularityPeriod(),
			   getGranularityPeriod());
        }
    }

    /**
     * Gets the derived gauge of the specified object, if this object is
     * contained in the set of observed MBeans, or <code>null</code> otherwise.
     *
     * @param object the name of the MBean.
     *
     * @return The derived gauge of the specified object.
     *
     * @since.unbundled JMX 1.2
     */
    public synchronized Number getDerivedGauge(ObjectName object) {
        int index = indexOf(object);
        if (index != -1)
            return derivedGauge[index];
        else
            return null;
    }

    /**
     * Gets the derived gauge timestamp of the specified object, if
     * this object is contained in the set of observed MBeans, or
     * <code>null</code> otherwise.
     *
     * @param object the name of the MBean.
     *
     * @return The derived gauge timestamp of the specified object.
     *
     * @since.unbundled JMX 1.2
     */
    public synchronized long getDerivedGaugeTimeStamp(ObjectName object) {
        int index = indexOf(object);
        if (index != -1)
            return derivedGaugeTimestamp[index];
        else
            return 0;
    }

    /**
     * Returns the derived gauge of the first object in the set of
     * observed MBeans.
     *
     * @return The derived gauge.
     * @deprecated As of JMX 1.2, replaced by {@link #getDerivedGauge(ObjectName)}
     */
    @Deprecated
    public synchronized Number getDerivedGauge() {
        return derivedGauge[0];
    }

    /**
     * Gets the derived gauge timestamp of the first object in the set
     * of observed MBeans.
     *
     * @return The derived gauge timestamp.
     * @deprecated As of JMX 1.2, replaced by
     * {@link #getDerivedGaugeTimeStamp(ObjectName)}
     */
    @Deprecated
    public synchronized long getDerivedGaugeTimeStamp() {
        return derivedGaugeTimestamp[0];
    }

    /**
     * Gets the high threshold value common to all observed MBeans.
     *
     * @return The high threshold value.
     */
    public synchronized Number getHighThreshold() {
        return highThreshold;
    }

    /**
     * Gets the low threshold value common to all observed MBeans.
     *
     * @return The low threshold value.
     */
    public synchronized Number getLowThreshold() {
        return lowThreshold;
    }

    /**
     * Sets the high and the low threshold values common to all
     * observed MBeans.
     *
     * @param highValue The high threshold value.
     * @param lowValue The low threshold value.
     *
     * @exception IllegalArgumentException The specified high/low
     * threshold is null or the low threshold is greater than the high
     * threshold or the high threshold and the low threshold are not
     * of the same type.
     */
    public synchronized void setThresholds(Number highValue, Number lowValue)
	    throws IllegalArgumentException {

        if ((highValue == null) || (lowValue == null)) {
            throw new IllegalArgumentException("Null threshold value");
        }
        if (highValue.getClass() != lowValue.getClass()) {
            throw new IllegalArgumentException("Different type " +
					       "threshold values");
        }

        if (isFirstStrictlyGreaterThanLast(lowValue, highValue,
					   highValue.getClass().getName())) {
            throw new IllegalArgumentException("High threshold less than " +
					       "low threshold");
        }

        highThreshold = highValue;
        lowThreshold = lowValue;
        for (int i = 0; i < elementCount; i++) {
            resetAlreadyNotified(i, THRESHOLD_ERROR_NOTIFIED);

            // Reset values.
            //
            status[i] = RISING_OR_FALLING;
        }
    }

    /**
     * Gets the high notification's on/off switch value common to all
     * observed MBeans.
     *
     * @see #setNotifyHigh
     *
     * @return <CODE>true</CODE> if the gauge monitor notifies when
     * exceeding the high threshold, <CODE>false</CODE> otherwise.
     */
    public synchronized boolean getNotifyHigh() {
        return notifyHigh;
    }

    /**
     * Sets the high notification's on/off switch value common to all
     * observed MBeans.
     *
     * @param value The high notification's on/off switch value.
     *
     * @see #getNotifyHigh
     */
    public synchronized void setNotifyHigh(boolean value) {
        notifyHigh = value;
    }

    /**
     * Gets the low notification's on/off switch value common to all
     * observed MBeans.
     *
     * @return <CODE>true</CODE> if the gauge monitor notifies when
     * exceeding the low threshold, <CODE>false</CODE> otherwise.
     *
     * @see #setNotifyLow
     */
    public synchronized boolean getNotifyLow() {
        return notifyLow;
    }

    /**
     * Sets the low notification's on/off switch value common to all
     * observed MBeans.
     *
     * @param value The low notification's on/off switch value.
     *
     * @see #getNotifyLow
     */
    public synchronized void setNotifyLow(boolean value) {
        notifyLow = value;
    }

    /**
     * Gets the difference mode flag value common to all observed MBeans.
     *
     * @return <CODE>true</CODE> if the difference mode is used,
     * <CODE>false</CODE> otherwise.
     *
     * @see #setDifferenceMode
     */
    public synchronized boolean getDifferenceMode() {
        return differenceMode;
    }

    /**
     * Sets the difference mode flag value common to all observed MBeans.
     *
     * @param value The difference mode flag value.
     *
     * @see #getDifferenceMode
     */
    public synchronized void setDifferenceMode(boolean value) {
        differenceMode = value;

        // Reset values.
        //
        for (int i = 0; i < elementCount; i++) {
            status[i] = RISING_OR_FALLING;
            previousScanGauge[i] = null;
        }
    }

   /**
     * Returns a <CODE>NotificationInfo</CODE> object containing the
     * name of the Java class of the notification and the notification
     * types sent by the gauge monitor.
     */
    public MBeanNotificationInfo[] getNotificationInfo() {
        String[] types  = { MonitorNotification.RUNTIME_ERROR,
                            MonitorNotification.OBSERVED_OBJECT_ERROR,
                            MonitorNotification.OBSERVED_ATTRIBUTE_ERROR,
                            MonitorNotification.OBSERVED_ATTRIBUTE_TYPE_ERROR,
                            MonitorNotification.THRESHOLD_ERROR,
                            MonitorNotification.THRESHOLD_HIGH_VALUE_EXCEEDED,
                            MonitorNotification.THRESHOLD_LOW_VALUE_EXCEEDED};
        MBeanNotificationInfo[] notifsInfo = {
	    new MBeanNotificationInfo(types,
				      "javax.management.monitor.MonitorNotification",
				      "Notifications sent by the GaugeMonitor MBean")
	};
        return notifsInfo;
    }

    /*
     * ------------------------------------------
     *  PRIVATE METHODS
     * ------------------------------------------
     */

    /**
     * Updates the derived gauge and the derived gauge timestamp attributes
     * of the observed object at the specified index.
     *
     * @param scanGauge The value of the observed attribute.
     * @param index The index of the observed object.
     * @return <CODE>true</CODE> if the derived gauge value is valid,
     * <CODE>false</CODE> otherwise.  The derived gauge value is
     * invalid when the differenceMode flag is set to
     * <CODE>true</CODE> and it is the first notification (so we
     * haven't 2 consecutive values to update the derived gauge).
     */
    private synchronized boolean updateDerivedGauge(Object scanGauge,
						    int index) {

        boolean is_derived_gauge_valid;

        derivedGaugeTimestamp[index] = System.currentTimeMillis();

        // The gauge difference mode is used.
        //
        if (differenceMode) {

            // The previous scan gauge has been initialized.
            //
            if (previousScanGauge[index] != null) {
                setDerivedGaugeWithDifference((Number)scanGauge, index);
                is_derived_gauge_valid = true;
            }
            // The previous scan gauge has not been initialized.
            // We cannot update the derived gauge...
            //
            else {
                is_derived_gauge_valid = false;
            }
            previousScanGauge[index] = (Number)scanGauge;
        }
        // The gauge difference mode is not used.
        //
        else {
            derivedGauge[index] = (Number)scanGauge;
            is_derived_gauge_valid = true;
        }

        return is_derived_gauge_valid;
    }

    /**
     * Updates the notification attribute of the observed object at the
     * specified index and notifies the listeners only once if the notify flag
     * is set to <CODE>true</CODE>.
     * @param index The index of the observed object.
     */
    private void updateNotifications(int index) {
	boolean sendNotify = false;
	String notifType = null;
	long timeStamp = 0;
	String msg = null;
	Object derGauge = null;
	Object trigger = null;

        // Send high notification if notifyHigh is true.
        // Send low notification if notifyLow is true.
        //
	synchronized(this) {
	    if (status[index] == RISING_OR_FALLING) {
		if (isFirstGreaterThanLast(derivedGauge[index], highThreshold,
					   type[index])) {
		    if (notifyHigh) {
			sendNotify = true;
			notifType =
			    MonitorNotification.THRESHOLD_HIGH_VALUE_EXCEEDED;
			timeStamp = derivedGaugeTimestamp[index];
			msg = "";
			derGauge = derivedGauge[index];
			trigger = highThreshold;
		    }
		    status[index] = FALLING;
		} else if (isFirstGreaterThanLast(lowThreshold,
						  derivedGauge[index],
						  type[index])) {
		    if (notifyLow) {
			sendNotify = true;
			notifType =
			    MonitorNotification.THRESHOLD_LOW_VALUE_EXCEEDED;
			timeStamp = derivedGaugeTimestamp[index];
			msg ="";
			derGauge = derivedGauge[index];
			trigger = lowThreshold;
		    }
		    status[index] = RISING;
		}
	    } else {
		if (status[index] == RISING) {
		    if (isFirstGreaterThanLast(derivedGauge[index],
					       highThreshold,
					       type[index])) {
			if (notifyHigh) {
			    sendNotify = true;
			    notifType =
				MonitorNotification.THRESHOLD_HIGH_VALUE_EXCEEDED;
			    timeStamp = derivedGaugeTimestamp[index];
			    msg = "";
			    derGauge = derivedGauge[index];
			    trigger = highThreshold;
			}
			status[index] = FALLING;
		    }
		} else if (status[index] == FALLING) {
		    if (isFirstGreaterThanLast(lowThreshold,
					       derivedGauge[index],
					       type[index])) {
			if (notifyLow) {
			    sendNotify = true;
			    notifType =
				MonitorNotification.THRESHOLD_LOW_VALUE_EXCEEDED;
			    timeStamp = derivedGaugeTimestamp[index];
			    msg = "";
			    derGauge = derivedGauge[index];
			    trigger = lowThreshold;
			}
			status[index] = RISING;
		    }
		}
	    }
	}

	if (sendNotify) {
	    sendNotification(notifType, timeStamp, msg, derGauge, trigger, index);
	}
    }

    /**
     * Tests if the threshold high and threshold low are both of the
     * same type as the gauge.  Both integer and floating-point types
     * are allowed.
     *
     * Note:
     *   If the optional lowThreshold or highThreshold have not been
     *   initialized, their default value is an Integer object with
     *   a value equal to zero.
     *
     * @param index The index of the observed object.
     * @return <CODE>true</CODE> if type is the same,
     * <CODE>false</CODE> otherwise.
     */
    private synchronized boolean isThresholdTypeValid(int index) {

        switch(type[index]) {
        case INTEGER:
            return (((highThreshold == INTEGER_ZERO) ||
		     (highThreshold instanceof Integer)) &&
		    ((lowThreshold == INTEGER_ZERO) ||
		     (lowThreshold instanceof Integer)));
        case BYTE:
            return (((highThreshold == INTEGER_ZERO) ||
		     (highThreshold instanceof Byte)) &&
		    ((lowThreshold == INTEGER_ZERO) ||
		     (lowThreshold instanceof Byte)));
        case SHORT:
            return (((highThreshold == INTEGER_ZERO) ||
		     (highThreshold instanceof Short)) &&
		    ((lowThreshold == INTEGER_ZERO) ||
		     (lowThreshold instanceof Short)));
        case LONG:
            return (((highThreshold == INTEGER_ZERO) ||
		     (highThreshold instanceof Long)) &&
		    ((lowThreshold == INTEGER_ZERO) ||
		     (lowThreshold instanceof Long)));
        case FLOAT:
            return (((highThreshold == INTEGER_ZERO) ||
		     (highThreshold instanceof Float)) &&
		    ((lowThreshold == INTEGER_ZERO) ||
		     (lowThreshold instanceof Float)));
        case DOUBLE:
            return (((highThreshold == INTEGER_ZERO) ||
		     (highThreshold instanceof Double)) &&
		    ((lowThreshold == INTEGER_ZERO) ||
		     (lowThreshold instanceof Double)));
        default:
            // Should never occur...
            if (isDebugOn()) {
                debug("isThresholdTypeValid", "The threshold type is invalid");
            }
	    return false;
        }
    }

    /**
     * Sets the derived gauge when the differenceMode flag is set to
     * <CODE>true</CODE>.  Both integer and floating-point types are
     * allowed.
     *
     * @param scanGauge The value of the observed attribute.
     * @param index The index of the observed object.
     */
    private synchronized void setDerivedGaugeWithDifference(Number scanGauge,
							    int index) {
	Number prev = previousScanGauge[index];
	Number der;
        switch (type[index]) {
        case INTEGER:
            der = new Integer(((Integer)scanGauge).intValue() -
			      ((Integer)prev).intValue());
            break;
        case BYTE:
            der = new Byte((byte)(((Byte)scanGauge).byteValue() -
				  ((Byte)prev).byteValue()));
            break;
        case SHORT:
            der = new Short((short)(((Short)scanGauge).shortValue() -
				    ((Short)prev).shortValue()));
            break;
        case LONG:
            der = new Long(((Long)scanGauge).longValue() -
			   ((Long)prev).longValue());
            break;
        case FLOAT:
            der = new Float(((Float)scanGauge).floatValue() -
			    ((Float)prev).floatValue());
            break;
        case DOUBLE:
            der = new Double(((Double)scanGauge).doubleValue() -
			     ((Double)prev).doubleValue());
        default:
            // Should never occur...
            if (isDebugOn()) {
                debug("setDerivedGaugeWithDifference",
		      "the threshold type is invalid");
            }
	    return;
        }
	derivedGauge[index] = der;
    }

    /**
     * Tests if the first specified Number is greater than or equal to
     * the last.  Both integer and floating-point types are allowed.
     *
     * @param greater The first Number to compare with the second.
     * @param less The second Number to compare with the first.
     * @param type The number type.
     * @return <CODE>true</CODE> if the first specified Number is
     * greater than or equal to the last, <CODE>false</CODE>
     * otherwise.
     */
    private boolean isFirstGreaterThanLast(Number greater,
					   Number less, int type) {

        switch(type) {
        case INTEGER:
        case BYTE:
        case SHORT:
        case LONG:
            return (greater.longValue() >= less.longValue());
        case FLOAT:
        case DOUBLE:
            return (greater.doubleValue() >= less.doubleValue());
        default:
            // Should never occur...
            if (isDebugOn()) {
                debug("isFirstGreaterThanLast",
		      "the threshold type is invalid");
            }
	    return false;
        }
    }

    /**
     * Tests if the first specified Number is strictly greater than the last.
     * Both integer and floating-point types are allowed.
     *
     * @param greater The first Number to compare with the second.
     * @param less The second Number to compare with the first.
     * @param className The number class name.
     * @return <CODE>true</CODE> if the first specified Number is
     * strictly greater than the last, <CODE>false</CODE> otherwise.
     */
    private boolean isFirstStrictlyGreaterThanLast(Number greater,
						   Number less,
						   String className) {

        if (className.equals("java.lang.Integer") ||
            className.equals("java.lang.Byte") ||
            className.equals("java.lang.Short") ||
            className.equals("java.lang.Long")) {

            return (greater.longValue() > less.longValue());
        }
        else if (className.equals("java.lang.Float") ||
                 className.equals("java.lang.Double")) {

            return (greater.doubleValue() > less.doubleValue());
        }
        else {
            // Should never occur...
            if (isDebugOn()) {
                debug("isFirstStrictlyGreaterThanLast",
		      "the threshold type is invalid");
            }
	    return false;
        }
    }

    /*
     * ------------------------------------------
     *  PACKAGE METHODS
     * ------------------------------------------
     */

    /**
     * This method is called by the gauge monitor each time
     * the granularity period has been exceeded.
     * @param index The index of the observed object.
     */
    void notifyAlarmClock(int index) {
	long timeStamp = 0;
	String msg = null;
	Object derGauge = null;

        Object  scan_gauge = null;
        String  notif_type = null;

	synchronized(this) {
            if (!isActive())
		return;

	    // Check if the observed object and observed attribute are valid.
	    //

	    // Check that neither the observed object nor the observed
	    // attribute are null.  If the observed object or observed
	    // attribute is null, this means that the monitor started
	    // before a complete initialization and nothing is done.
	    //
	    if ((getObservedObject(index) == null) ||
		(getObservedAttribute() == null))
		return;

	    // Check that the observed object is registered in the
	    // MBean server and that the observed attribute belongs to
	    // the observed object.
	    //
	    try {
		scan_gauge = server.getAttribute(getObservedObject(index),
						 getObservedAttribute());
		if (scan_gauge == null)
		    return;
	    } catch (NullPointerException np_ex) {
		if (alreadyNotified(index, RUNTIME_ERROR_NOTIFIED))
		    return;
		else {
		    notif_type = MonitorNotification.RUNTIME_ERROR;
		    setAlreadyNotified(index, RUNTIME_ERROR_NOTIFIED);
		    msg =
			"The gauge monitor must be registered in " +
			"the MBean server.";
		}
	    } catch (InstanceNotFoundException inf_ex) {
		if (alreadyNotified(index, OBSERVED_OBJECT_ERROR_NOTIFIED))
		    return;
		else {
		    notif_type = MonitorNotification.OBSERVED_OBJECT_ERROR;
		    setAlreadyNotified(index,
				       OBSERVED_OBJECT_ERROR_NOTIFIED);
		    msg =
			"The observed object must be registered in " +
			"the MBean server.";
		}
	    } catch (AttributeNotFoundException anf_ex) {
		if (alreadyNotified(index,
				    OBSERVED_ATTRIBUTE_ERROR_NOTIFIED))
		    return;
		else {
		    notif_type =
			MonitorNotification.OBSERVED_ATTRIBUTE_ERROR;
		    setAlreadyNotified(index,
				       OBSERVED_ATTRIBUTE_ERROR_NOTIFIED);
		    msg =
			"The observed attribute must be accessible in " +
			"the observed object.";
		}
	    } catch (MBeanException mb_ex) {
		if (alreadyNotified(index, RUNTIME_ERROR_NOTIFIED))
		    return;
		else {
		    notif_type = MonitorNotification.RUNTIME_ERROR;
		    setAlreadyNotified(index, RUNTIME_ERROR_NOTIFIED);
		    msg = mb_ex.getMessage();
		}
	    } catch (ReflectionException ref_ex) {
		if (alreadyNotified(index,
				    OBSERVED_ATTRIBUTE_ERROR_NOTIFIED))
		    return;
		else {
		    notif_type =
			MonitorNotification.OBSERVED_ATTRIBUTE_ERROR;
		    setAlreadyNotified(index,
				       OBSERVED_ATTRIBUTE_ERROR_NOTIFIED);
		    msg = ref_ex.getMessage();
		}
	    }

	    if (msg == null) {
                // Check that the observed attribute is either of type
                // "Integer" or "Float".
                //
                if (scan_gauge instanceof Integer) {
                    type[index] = INTEGER;
                } else if (scan_gauge instanceof Byte) {
                    type[index] = BYTE;
                } else if (scan_gauge instanceof Short) {
                    type[index] = SHORT;
                } else if (scan_gauge instanceof Long) {
                    type[index] = LONG;
                } else if (scan_gauge instanceof Float) {
                    type[index] = FLOAT;
                } else if (scan_gauge instanceof Double) {
                    type[index] = DOUBLE;
                } else {
                    if (alreadyNotified(index,
					OBSERVED_ATTRIBUTE_TYPE_ERROR_NOTIFIED))
                        return;
                    else {
                        notif_type =
			    MonitorNotification.OBSERVED_ATTRIBUTE_TYPE_ERROR;
                        setAlreadyNotified(index,
					   OBSERVED_ATTRIBUTE_TYPE_ERROR_NOTIFIED);
                        msg =
			    "The observed attribute type must be an " +
			    "integer type or a floating-point type.";
                    }
                }
	    }

	    if (msg == null) {
                // Check that threshold high and threshold low are of
                // the same type as the gauge.
                //
                if (!isThresholdTypeValid(index)) {
                    if (alreadyNotified(index, THRESHOLD_ERROR_NOTIFIED))
                        return;
                    else {
                        notif_type = MonitorNotification.THRESHOLD_ERROR;
                        setAlreadyNotified(index, THRESHOLD_ERROR_NOTIFIED);
                        msg =
			    "The threshold high and threshold low must be " +
			    "of the same type as the gauge.";
                    }
                }
	    }

	    if (msg == null) {

		// Clear all already notified flags.
		//
		resetAllAlreadyNotified(index);

		// Update the derived gauge attributes and check
		// the validity of the new value.  The derived
		// gauge value is invalid when the differenceMode
		// flag is set to true and it is the first
		// notification (so we haven't 2 consecutive
		// values to update the derived gauge).
		//
		boolean is_derived_gauge_valid =
		    updateDerivedGauge(scan_gauge, index);

		// Notify the listeners if the updated derived
		// gauge value is valid.
		//
		if (is_derived_gauge_valid)
		    updateNotifications(index);

	    } else {

		// msg != null, will send an error notification

		timeStamp = derivedGaugeTimestamp[index];
		derGauge = derivedGauge[index];

		// Reset values.
		//
		status[index] = RISING_OR_FALLING;
		previousScanGauge[index] = null;
	    }
	}

	if (msg != null) {
	    sendNotification(notif_type,
			     timeStamp,
			     msg,
			     derGauge,
			     null,
			     index);
	}
    }

    /**
     * This method is called when adding a new observed object in the vector.
     * It updates all the gauge specific arrays.
     * @param index The index of the observed object.
     */
    synchronized void insertSpecificElementAt(int index) {
        // Update derivedGauge, derivedGaugeTimestamp,  previousScanGauge,
        // status and type values.

	if (index != elementCount)
	    throw new Error("Internal error: index != elementCount");

	if (elementCount >= derivedGauge.length) {
	    derivedGauge = expandArray(derivedGauge);
	    previousScanGauge = expandArray(previousScanGauge);
	    derivedGaugeTimestamp = expandArray(derivedGaugeTimestamp);
	    status = expandArray(status);
	    type = expandArray(type);
	}

	derivedGauge[index] = INTEGER_ZERO;
	previousScanGauge[index] = null;
	derivedGaugeTimestamp[index] = System.currentTimeMillis();
	status[index] = RISING_OR_FALLING;
	type[index] = INTEGER;
    }

    /**
     * This method is called when removing an observed object from the vector.
     * It updates all the gauge specific arrays.
     * @param index The index of the observed object.
     */
    synchronized void removeSpecificElementAt(int index) {
	if (index < 0 || index >= elementCount)
	    return;

        // Update derivedGauge, derivedGaugeTimestamp,  previousScanGauge,
        // status and type arrays.
        //
        removeElementAt(derivedGauge, index);
        removeElementAt(previousScanGauge, index);
        removeElementAt(derivedGaugeTimestamp, index);
        removeElementAt(status, index);
        removeElementAt(type, index);
    }


    /**
     * GaugeAlarmClock inner class:
     * This class provides a simple implementation of an alarm clock MBean.
     * The aim of this MBean is to set up an alarm which wakes up the
     * gauge monitor every granularity period.
     */

    private static class GaugeAlarmClock extends TimerTask {

	GaugeMonitor listener = null;

	/*
	 * ------------------------------------------
	 *  CONSTRUCTORS
	 * ------------------------------------------
	 */

	public GaugeAlarmClock(GaugeMonitor listener) {
	    this.listener = listener;
	}

	/*
	 * ------------------------------------------
	 *  PUBLIC METHODS
	 * ------------------------------------------
	 */

	/**
	 * This method is called by the GaugeAlarmClock thread when it
	 * is started.
	 */
	public void run() {
	    if (listener.isActive()) {
		for (int i = 0; i < listener.elementCount; i++) {
		    listener.notifyAlarmClock(i);
		}
	    }
	}
    }
}
