001package jmri.implementation;
002
003import javax.annotation.CheckForNull;
004import javax.annotation.Nonnull;
005import javax.annotation.CheckReturnValue;
006import jmri.JmriException;
007import jmri.NamedBean;
008import jmri.AnalogIO;
009
010/**
011 * Base implementation of the AnalogIO interface.
012 *
013 * @author Daniel Bergqvist Copyright (c) 2018
014 */
015public abstract class AbstractAnalogIO extends AbstractNamedBean implements AnalogIO {
016
017    private final boolean _commandedValueSetsKnownValue;
018    private double _commandedValue = 0.0;
019    private double _knownValue = 0.0;
020
021    /**
022     * Abstract constructor for new AnalogIO with system name
023     *
024     * @param systemName AnalogIO system name
025     * @param commandedValueSetsKnownValue true if setCommandedValue() also sets
026     * known value, false othervise
027     */
028    public AbstractAnalogIO(@Nonnull String systemName, boolean commandedValueSetsKnownValue) {
029        super(systemName);
030        this._commandedValueSetsKnownValue = commandedValueSetsKnownValue;
031    }
032
033    /**
034     * Abstract constructor for new AnalogIO with system name and user name
035     *
036     * @param systemName AnalogIO system name
037     * @param userName   AnalogIO user name
038     * @param commandedValueSetsKnownValue true if setCommandedValue() also sets
039     * known value, false othervise
040     */
041    public AbstractAnalogIO(@Nonnull String systemName, @CheckForNull String userName, boolean commandedValueSetsKnownValue) {
042        super(systemName, userName);
043        this._commandedValueSetsKnownValue = commandedValueSetsKnownValue;
044    }
045
046    /**
047     * Sends the string to the layout.
048     * The string [u]must not[/u] be longer than the value of getMaximumLength()
049     * unless that value is zero. Some microcomputers have little memory and
050     * it's very important that this method is never called with too long strings.
051     *
052     * @param value the desired string value
053     * @throws jmri.JmriException general error when setting the value fails
054     */
055    abstract protected void sendValueToLayout(double value) throws JmriException;
056
057    /**
058     * Set the value of this AnalogIO. Called from the implementation class
059     * when the layout updates this AnalogIO.
060     * 
061     * @param newValue the new value
062     */
063    protected void setValue(double newValue) {
064        Object _old = this._knownValue;
065        this._knownValue = newValue;
066        firePropertyChange(PROPERTY_STATE, _old, _knownValue); //NOI18N
067    }
068
069    /** {@inheritDoc} */
070    @Override
071    public void setCommandedAnalogValue(double value) throws JmriException {
072        if (value == Double.NEGATIVE_INFINITY) {
073            throw new IllegalArgumentException("value is negative infinity");
074        }
075        if (value == Double.POSITIVE_INFINITY) {
076            throw new IllegalArgumentException("value is positive infinity");
077        }
078        if (Double.isNaN(value)) {
079            throw new IllegalArgumentException("value is not-a-number");
080        }
081        
082        double min = getMin();
083        double max = getMax();
084        
085        if (value < min) {
086            if (cutOutOfBoundsValues()) value = min;
087            else throw new JmriException("value out of bounds");
088        }
089        if (value > max) {
090            if (cutOutOfBoundsValues()) value = max;
091            else throw new JmriException("value out of bounds");
092        }
093        _commandedValue = value;
094        
095        if (_commandedValueSetsKnownValue) {
096            setValue(_commandedValue);
097        }
098        sendValueToLayout(_commandedValue);
099    }
100
101    /** {@inheritDoc} */
102    @Override
103    public double getCommandedAnalogValue() {
104        return _commandedValue;
105    }
106
107    /** {@inheritDoc} */
108    @Override
109    public double getKnownAnalogValue() {
110        return _knownValue;
111    }
112
113    /**
114     * Cut out of bounds values instead of throwing an exception?
115     * For example, if the AnalogIO is a display, it could be desired to
116     * accept too long strings.
117     * On the other hand, if the AnalogIO is used to send a command, a too
118     * long string is an error.
119     *
120     * @return true if long strings should be cut
121     */
122    abstract protected boolean cutOutOfBoundsValues();
123
124    /** {@inheritDoc} */
125    @Override
126    public double getState(double v) {
127        return getCommandedAnalogValue();
128    }
129
130    /** {@inheritDoc} */
131    @Override
132    public void setState(double value) throws JmriException {
133        setCommandedAnalogValue(value);
134    }
135
136    /** {@inheritDoc} */
137    @Override
138    @Nonnull
139    public String getBeanType() {
140        return Bundle.getMessage("BeanNameAnalogIO");
141    }
142
143    /**
144     * {@inheritDoc} 
145     * 
146     * Do a string comparison.
147     */
148    @CheckReturnValue
149    @Override
150    public int compareSystemNameSuffix(@Nonnull String suffix1, @Nonnull String suffix2, @Nonnull NamedBean n) {
151        return suffix1.compareTo(suffix2);
152    }
153
154}