package org.csstudio.sds.eventhandling; import org.csstudio.sds.SdsPlugin; import org.csstudio.sds.model.AbstractWidgetModel; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IConfigurationElement; import org.csstudio.dal.context.ConnectionState; import org.csstudio.dal.simple.AnyData; import org.csstudio.dal.simple.AnyDataChannel; import org.csstudio.dal.simple.ChannelListener; import org.csstudio.dal.simple.MetaData; /** * Base class for behaviors. A behavior encapsulates code that influences a * widgets appearance when it is connected to a control system. Behavior * implementations have to be stateless - at runtime a single behavior instance * is used to update all widgets that use this behavior. * * Behaviors are registered as an extension to * {@link SdsPlugin#EXTPOINT_BEHAVIORS}. * * @author Kai Meyer, Sven Wende * * @param <W> * the type of widget which is controlled by this behavior */ public abstract class AbstractBehavior<W extends AbstractWidgetModel> { private static final String PROP_DESCRIPTION = "description"; private static final String PROP_WIDGET_TYPE_ID = "widgetTypeId"; private static final String PROP_ID = "id"; private String _behaviorId; private String _widgetTypeId; private String _description; public final void setInitializationData(final IConfigurationElement config, final String propertyName, final Object data) throws CoreException { _behaviorId = config.getAttribute(PROP_ID); _widgetTypeId = config.getAttribute(PROP_WIDGET_TYPE_ID); _description = config.getAttribute(PROP_DESCRIPTION); assert _behaviorId != null : "behaviorId != null"; assert _behaviorId.trim().length() > 0 : "behaviorId.trim().length() > 0"; assert _widgetTypeId != null : "widgetTypeId != null"; assert _widgetTypeId.trim().length() > 0 : "widgetTypeId.trim().length() > 0"; assert _description != null : "description != null"; assert _description.trim().length() > 0 : "description.trim().length() > 0"; } public final String getBehaviorId() { return _behaviorId; } public final String getDescription() { return _description; } /** * Returns the widget type, this behavior is registered for. * * @return the widget type, this behavior is registered for */ public final String getWidgetTypeId() { return _widgetTypeId; } /** * Initializes the widget independently of any connections. Will be called * before the widget is connected to the control system. * * * @param widget * the widget */ public final void initializeWidget(final W widget) { assert widget != null; doInitialize(widget); } /** * Handles a change of the {@link AnyData} object which provides some * metadata for the current connection. * * @param model * the widget * @param anyData * the {@link AnyData} object */ /** * Processes DAL events received via * {@link ChannelListener#channelDataUpdate(AnyDataChannel)}. By default * this method delegates event handling to different template methods * {@link #doProcessMetaDataChange(AbstractWidgetModel, MetaData)} , * {@link #doProcessValueChange(AbstractWidgetModel, AnyData)} and * {@link #doProcessConnectionStateChange(AbstractWidgetModel, AnyDataChannel)} * hiding some of the flexibility that comes with {@link AnyDataChannel}. * * Subclasses that need to access all features offered by * {@link AnyDataChannel} may override this method. * * @param model * the widget model that needs to be changed * @param channel * the {@link AnyDataChannel} */ public final void processChannelDataUpdate(final W model, final AnyDataChannel channel) { AnyData data = channel.getData(); doProcessMetaDataChange(model, data.getMetaData()); doProcessValueChange(model, data); doProcessConnectionStateChange(model, channel); } /** * Processes DAL events received via * {@link ChannelListener#channelDataUpdate(AnyDataChannel)}. By default * this method delegates event handling to * {@link #doProcessConnectionStateChange(AbstractWidgetModel, AnyDataChannel)} * hiding some of the flexibility that comes with {@link AnyDataChannel}. * * Subclasses that need to access all features offered by * {@link AnyDataChannel} may override this method. * * @param model * the widget model that needs to be changed * @param channel * the {@link AnyDataChannel} */ public final void processChannelStateUpdate(final W model, final AnyDataChannel channel) { doProcessConnectionStateChange(model, channel); } /** * Handles changes of widget property manual values. * @param widgetModel * the model of the current widget * @param propertyId * the property id * @param value * the new manual value */ public final Object convertOutgoingValue(final W widgetModel, final String propertyId, final Object value) { assert propertyId != null; return doConvertOutgoingValue(widgetModel, propertyId, value); } /** * Returns ids of properties that will become invisible when this behavior * is active. * * @return ids of properties that will become invisible when this behavior * is active */ public final String[] getInvisiblePropertyIds() { String[] result = doGetInvisiblePropertyIds(); if (result == null) return new String[0]; return result; } /** * Returns ids of properties that are used to trigger write access to the * control system. * * @return ids of properties that are used to trigger write access */ public final String[] getSettablePropertyIds() { String[] result = doGetSettablePropertyIds(); if (result == null) result = new String[0]; return result; } /** * Template method which is called when a manual value of a widget property * changes. Subclasses should apply type conversion if necessary. By default * the current value is returned without any conversion. * @param widgetModel * the model of the current widget * @param propertyId * the property id * @param value * the new manual value for that property */ protected Object doConvertOutgoingValue(final W widgetModel, final String propertyId, final Object value) { return value; } /** * Template method which should return the ids of properties that are used * for writing values to the control system. Returns an empty set by * default. May be overridden by subclasses. * * @return ids of writable properties or null */ protected String[] doGetSettablePropertyIds() { return new String[0]; } /** * Template method which is called before the widget is connected to the * control systems. Subclasses should initialize the widgets initial look * and feel. * * @param widget * the widget */ protected abstract void doInitialize(W widget); /** * Template method which is called when the connection state of the * underlying channel changes. Subclasses may implement a widgets look and * feel depending on the current connection state, e.g. configure a red * border in case of a {@link ConnectionState#CONNECTION_LOST} state. * * @param widget * the widget * @param anyDataChannel * the current connection state */ protected abstract void doProcessConnectionStateChange(W widget, AnyDataChannel anyDataChannel); /** * Template method which is called when the value of the underlying channel * changes. Subclasses may implement a widgets look and feel depending on * the current value. * * @param widget * the widget * @param connectionState * the current connection state */ protected abstract void doProcessValueChange(W model, AnyData anyData); /** * Template method which is called when the {@link AnyData} of the * underlying channel changes. Subclasses may implement a widgets look and * feel depending on the current {@link AnyData} and its meta data. * * @param widget * the widget * @param metaData * the {@link MetaData} */ protected abstract void doProcessMetaDataChange(W widget, MetaData metaData); /** * Subclasses should return identifiers of all properties handled by this * behavior. Those properties will not appear in the property view when this * behavior is active. * * @return identifiers of all properties handled by this behavior */ protected abstract String[] doGetInvisiblePropertyIds(); /** *{@inheritDoc} */ @Override public String toString() { StringBuffer buffer = new StringBuffer("Behavior (id: "); buffer.append(_behaviorId); buffer.append(", widgetTypeId: "); buffer.append(_widgetTypeId); buffer.append(", description: "); buffer.append(_description); return buffer.toString(); } /** *{@inheritDoc} */ @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ( (_behaviorId == null) ? 0 : _behaviorId.hashCode()); result = prime * result + ( (_widgetTypeId == null) ? 0 : _widgetTypeId.hashCode()); return result; } /** *{@inheritDoc} */ @SuppressWarnings("unchecked") @Override public boolean equals(final Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; AbstractBehavior<W> other = (AbstractBehavior<W>) obj; if (_behaviorId == null) { if (other._behaviorId != null) return false; } else if (!_behaviorId.equals(other._behaviorId)) { return false; } if (_widgetTypeId == null) { if (other._widgetTypeId != null) return false; } else if (!_widgetTypeId.equals(other._widgetTypeId)) return false; return true; } }