/*******************************************************************************
* ALMA - Atacama Large Millimiter Array
* (c) European Southern Observatory, 2002
* Copyright by ESO (in the framework of the ALMA collaboration)
* and Cosylab 2002, All rights reserved
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package alma.ACS.impl;
import java.util.concurrent.atomic.AtomicLong;
import org.omg.CORBA.BooleanHolder;
import alma.ACS.CBDescIn;
import alma.ACS.Callback;
import alma.ACS.jbaci.BACIFramework;
import alma.ACS.jbaci.BACIPriority;
import alma.ACS.jbaci.BACITimer;
import alma.ACS.jbaci.CompletionUtil;
import alma.ACS.jbaci.DataAccess;
import alma.ACS.jbaci.PrioritizedRunnable;
import alma.ACSErr.Completion;
import alma.ACSErr.CompletionHolder;
/**
* Implementation of common compareable (notifies on change) monitor.
* @author <a href="mailto:matej.sekoranjaATcosylab.com">Matej Sekoranja</a>
* @version $id$
*/
public class CommonComparableMonitorImpl
extends CommonMonitorImpl
implements DataAccess.ValueChangeListener {
/**
* Pool timer implementation.
*/
protected class PoolTimer implements BACITimer.TimerRunnable, PrioritizedRunnable
{
/**
* Key time to process, 0 means none.
*/
protected AtomicLong queuedKeyPoolTime = new AtomicLong(0);
/**
* @see alma.ACS.jbaci.BACITimer.TimerRunnable#timeout(long)
*/
public void timeout(long timeToRun) {
// if none is queued, initiate executor otherwise override old value
if (queuedKeyPoolTime.getAndSet(timeToRun) == 0)
{
property.getParentComponent().execute(this);
}
}
/**
* @see alma.ACS.jbaci.PrioritizedRunnable#getPriority()
*/
public BACIPriority getPriority() {
return CommonComparableMonitorImpl.this.getPriority();
}
/**
* @see java.lang.Runnable#run()
*/
public void run() {
// TODO should be better to ACK (set to 0) at the end?
long keyTime = queuedKeyPoolTime.getAndSet(0);
// create new holder (done expeditiously)
CompletionHolder completionHolder = CompletionUtil.createCompletionHolder();
// TODO completion....
// retrieve value
Object value = property.mnemonicValue(keyTime, completionHolder);
valueChanged(property.getDataAccess(), oldValue, value /*, completion */);
}
}
/**
* Current required delta.
*/
protected Object deltaValue;
/**
* Flag if notification has to be done on every value change.
* NOTE: this mode is explicitly used for non-comparable properties,
* i.e. properties is implementing <code>CommonComparablePropertyImpl</code>.
*/
protected boolean onEveryChange;
/**
* Cache - casted <code>property</code> to <code>CommonComparablePropertyImpl</code>.
*/
protected CommonComparablePropertyImpl comparableProperty;
/**
* Last pooled (notified) value.
*/
protected Object oldValue;
/**
* Enabled status (might be true even if disabled (if suspended)) - user status.
*/
protected boolean enabled;
/**
* Monitor timer task.
*/
protected Object poolTimerTask;
/**
* Default pool time (if on-change notification are not supported) in ms.
*/
public static final long DEFAULT_POOL_TIME = 1000;
/**
* Constructor with immediate monitor notification (synchronized monitors supported).
* @param property property to be monitored, non-<code>null</code>.
* @param callback callback, non-<code>null</code>.
* @param descIn callback in-descriptor.
*/
public CommonComparableMonitorImpl(CommonPropertyImpl property, Callback callback, CBDescIn descIn) {
this(property, callback, descIn, 0);
}
/**
* Constructor.
* @param property property to be monitored, non-<code>null</code>.
* @param callback callback, non-<code>null</code>.
* @param descIn callback in-descriptor.
* @param startTime startTime (OMG time), values less or equal to current time mean immediately,
* value 0 means that start time should be controlled automatically (synchronized monitors).
*/
public CommonComparableMonitorImpl(
CommonPropertyImpl property,
Callback callback,
CBDescIn descIn,
long startTime) {
super(property, callback, descIn, startTime);
enabled = false;
if (property instanceof CommonComparablePropertyImpl)
comparableProperty = (CommonComparablePropertyImpl)property;
}
/**
* Enable on-change monitoring/pooling.
*/
protected synchronized void enable()
{
// if suspended, do not really enable
if (isSuspended)
{
enabled = true;
return;
}
// disable first (on-change support might be added, recommendedPoolTime can be changed)
disable();
try
{
property.getDataAccess().addValueChangeListener(this);
}
catch (DataAccess.OnChangeNotSupportedException ex)
{
// get pool time
long poolTime = ex.recommendedPoolTime();
if (poolTime <= 0 || poolTime < property.minTimerTrigger)
poolTime = DEFAULT_POOL_TIME;
// get current value to have a value to compare with...
oldValue = property.mnemonicValue(System.currentTimeMillis(),
CompletionUtil.createCompletionHolder());
// determine start time to align with sync monitors - might improve performance
long startTime = (System.currentTimeMillis()/poolTime + 1) * poolTime;
// pool
poolTimerTask = BACIFramework.INSTANCE.getTimer().executePeriodically(poolTime, new PoolTimer(), startTime);
}
enabled = true;
}
/**
* Disable on-change monitoring/pooling.
*/
protected synchronized void disable()
{
// cancel first...
if (poolTimerTask != null)
{
// canceling is thread-safe (and can be done multiple times)
BACITimer.cancel(poolTimerTask);
poolTimerTask = null;
}
else
// remove on-change listener
property.getDataAccess().removeValueChangeListener(this);
enabled = false;
}
/**
* @see alma.ACS.jbaci.DataAccess.ValueChangeListener#valueChanged(alma.ACS.jbaci.DataAccess, java.lang.Object, java.lang.Object)
*/
public void valueChanged(
DataAccess source,
Object oldValue,
Object newValue) {
if (onEveryChange)
{
// if equals, return
if (oldValue.equals(newValue))
return;
}
// dispatch, if equal or more than delta
// this code should not never be reached for non-comparable properties
else if (comparableProperty.lessThanDelta(oldValue, newValue, deltaValue))
return;
// set new 'oldValue'
this.oldValue = newValue;
// TODO completion
Completion completion = CompletionUtil.generateNoErrorCompletion();
// TODO
// set monitor type and on-change code, if non-error type
//completion.code = ACSErr.ACSErrMonitorOnChange;
// ... and dispatch
dispatchAction.dispatchWorkingRequest(completion, newValue);
// TODO remove
//System.out.println("Dispatched (on-change) monitor: " + newValue);
}
/**
* @see alma.ACS.SubscriptionOperations#resume()
*/
public synchronized void resume() {
boolean suspended = isSuspended;
super.resume();
// revive monitoring/pooling, if necessary
if (suspended && enabled)
enable();
}
/**
* @see alma.ACS.SubscriptionOperations#suspend()
*/
public synchronized void suspend() {
boolean suspended = isSuspended;
super.suspend();
// store current (user) status
boolean enabledStatus = enabled;
if (!suspended)
disable();
// restore current (user) status
enabled = enabledStatus;
}
/*********************** [ Monitor<type> helpers ] ***********************/
/**
* Enable on-change monitoring.
* @param enable switch to enable/disable
* @see alma.ACS.Monitor<type>Operations#set_value_trigger(<type>, boolean)
*/
public synchronized void setValueTrigger(boolean enable) {
setValueTrigger(null, enable);
}
/**
* Enable on-delta-change monitoring, requires to operatie on <code>CommonComparableProperty</code>.
* @param delta delta value, non-<code>null</code>.
* @param enable switch to enable/disable
* @see alma.ACS.Monitor<type>Operations#set_value_trigger(<type>, boolean)
*/
public synchronized void setValueTrigger(Object delta, boolean enable) {
// disable, if changing values...
disable();
if (delta == null)
onEveryChange = true;
else
{
if (comparableProperty != null)
{
// Value 0 assigned to delta trigger means that a notification should be sent
// on every change of the monitored value.
onEveryChange = comparableProperty.noDelta(delta);
this.deltaValue = delta;
}
else
{
// TODO issue warning
onEveryChange = true;
}
}
if (enable)
{
enable();
}
else
{
disable();
}
}
/**
* @param enableHolder holder to be set current 'enable' status of the monitor.
* @return deltaValue current delta value, can be <code>null</code>.
* @see alma.ACS.Monitor<type>Operations#get_value_trigger(org.omg.CORBA.<type>Holder, org.omg.CORBA.BooleanHolder)
*/
public Object getValueTrigger(BooleanHolder enableHolder) {
// not perfectly thread safe...
enableHolder.value = enabled;
return deltaValue;
}
}