001package jmri.jmrit.ctc;
002
003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
004
005import java.beans.PropertyChangeListener;
006import java.util.ArrayList;
007
008import jmri.InstanceManager;
009import jmri.JmriException;
010import jmri.NamedBeanHandle;
011import jmri.NamedBeanHandleManager;
012import jmri.Sensor;
013import jmri.SensorManager;
014import jmri.jmrit.ctc.ctcserialdata.ProjectsCommonSubs;
015
016/**
017 * This object attempts to "extend" (implements) Sensor functionality that
018 * is needed by the CTC system.
019 * <p>
020 * Goals:
021 * <ol>
022 * <li> Catches and thereby prevents exceptions from propagating upward.  No need
023 *     for this everywhere in your code:
024 *     try { sensor.getBean().setKnownState(Sensor.ACTIVE); } catch (JmriException ex) {}
025 *     We ASSUME here that you are ALWAYS passing a valid "newState".  If not,
026 *     then this call is effectively a no-op (do nothing).
027 * <li> Use ONLY named beans internally for proper JMRI support of renaming objects.
028 * <li> Allow any CTC Editor routines to mindlessly change the underlying sensor
029 *     name used WITHOUT affecting any existing run time (CTCRunTime) code.
030 *     For example: User changes existing IS:XYZ to IS:ABC, which is a different
031 *     sensor altogether.  Everything happens under the covers, and the higher
032 *     level code that uses these objects do not know the change happened, and
033 *     they SHOULD continue to function without affect.
034 * <li> Prevents "null" access to improperly constructed internal objects.  For example:
035 *     If the caller passes invalid parameter(s) to the constructor(s), then this object's
036 *     internal Sensor will be set to null by the constructor(s).  If the USER then
037 *     attempts to use the CTC panel and the underlying code winds up calling method(s)
038 *     in here that reference that null sensor, the JMRI system would crash.
039 * <li> If the internal Sensor is null and you call a function that returns a value,
040 *     that function will return a "sane" value.  See the constants below for
041 *     general return values in this situation and individual functions for specific info.
042 * <li> Support for required sensors.  If the sensor name doesn't exist or is invalid in ANY way,
043 *     then it is considered an error, and this object logs it as such.  We do
044 *     NOT create the JMRI object automatically in this situation.
045 * <li> Support for optional sensors.  In my system, a sensor may be optional.
046 *     If specified, it MUST be valid and exist in the system already.
047 * <li> In each of the two above situations, ANY error situation will leave
048 *     this object properly protected.
049 * <li> My CTC system that uses this object can "mindlessly" call methods on this
050 *     object at any time, and return "sane" values in ANY error situation and
051 *     rely on these values being consistent.  This prevents the need of calling
052 *     routines to CONSTANTLY check the internal status of this object, and can
053 *     rely on those "sane" return values.  I'm a lazy programmer, and want to
054 *     prevent the following at the call site:
055 *     int blah;
056 *     if (NBHSensor.valid()) blah = NBHSensor.getKnownState();
057 *     or
058 *     try { blah = NBHSensor.getKnownState()} catch() {}
059 *     it becomes just:
060 *     blah = NBHSensor.getKnownState();
061 *     You may (and I do) have specific circumstances whereby you need to know
062 *     the internal state of this object is valid or not, and act on
063 *     it differently than the default sane values.  There is a function "valid()"
064 *     for that situation.
065 * </ol>
066 * @author Gregory J. Bedlek Copyright (C) 2018, 2019, 2020
067 */
068
069// Prefix NBH = Named Bean Handler....
070
071public class NBHSensor {
072//  Special case sane return values:
073    public static final int DEFAULT_SENSOR_STATE_RV = Sensor.INACTIVE;
074//  Standard sane return values for the types indicated:
075//  public static final Object DEFAULT_OBJECT_RV = null;       // For any function that returns something derived from Java's Object.
076//  public static final boolean DEFAULT_BOOLEAN_RV = false;    // For any function that returns boolean.
077//  public static final int DEFAULT_INT_RV = 0;                // For any function that returns int.
078//  public static final long DEFAULT_LONG_RV = 0;              // For any function that returns long.
079//  public static final String DEFAULT_STRING_RV = "UNKNOWN";  // NOI18N  For any function that returns String.
080//  Functions that don't return any of the above have specific implementations.  Ex: PropertyChangeListener[] or ArrayList<>
081
082//  The "thing" we're protecting:
083    private NamedBeanHandle<Sensor> _mNamedBeanHandleSensor;
084    private final String _mUserIdentifier;
085    private final String _mParameter;
086    private final boolean _mOptional;
087    private final ArrayList<PropertyChangeListener> _mArrayListOfPropertyChangeListeners = new ArrayList<>();
088    public Sensor getBean() {
089        if (valid()) return _mNamedBeanHandleSensor.getBean();
090        return null;
091    }
092
093    public NamedBeanHandle<?> getBeanHandle() {
094        if (valid()) return _mNamedBeanHandleSensor;
095        return null;
096    }
097
098    public boolean matchSensor(Sensor sensor) {
099        if (valid()) return _mNamedBeanHandleSensor.getBean() == sensor;
100        return false;
101    }
102
103    public NBHSensor(String module, String userIdentifier, String parameter, String sensor, boolean optional) {
104        _mUserIdentifier = userIdentifier;
105        _mParameter = parameter;
106        _mOptional = optional;
107        Sensor tempSensor = _mOptional ? getSafeOptionalJMRISensor(module, userIdentifier, _mParameter, sensor) : getSafeExistingJMRISensor(module, userIdentifier, _mParameter, sensor);
108        if (tempSensor != null) {
109            _mNamedBeanHandleSensor = InstanceManager.getDefault(NamedBeanHandleManager.class).getNamedBeanHandle(sensor, tempSensor);
110        } else {
111            _mNamedBeanHandleSensor = null;
112        }
113        registerSensor(sensor);
114    }
115
116    public NBHSensor(String module, String userIdentifier, String parameter, String sensorName) {
117        _mUserIdentifier = userIdentifier;
118        _mParameter = parameter;
119        _mOptional = false;
120
121        Sensor tempSensor = getSafeInternalSensor(module, userIdentifier, parameter, sensorName);
122        if (tempSensor != null) {
123            _mNamedBeanHandleSensor = InstanceManager.getDefault(NamedBeanHandleManager.class).getNamedBeanHandle(sensorName, tempSensor);
124        } else {
125            _mNamedBeanHandleSensor = null;
126        }
127        registerSensor(sensorName);
128    }
129
130    void registerSensor(String sensorName) {
131        if (valid()) InstanceManager.getDefault(CtcManager.class).putNBHSensor(sensorName, this);
132    }
133
134//  Used by CallOn to create a temporary NBHSensor for the current sensor for a block.
135    public NBHSensor(NamedBeanHandle<Sensor> namedBeanHandleSensor) {
136        _mUserIdentifier = Bundle.getMessage("Unknown");    // NOI18N
137        _mParameter = _mUserIdentifier;
138        _mOptional = true;              // We CAN'T know, but this is safe.
139        _mNamedBeanHandleSensor = namedBeanHandleSensor;
140    }
141
142    public boolean valid() { return _mNamedBeanHandleSensor != null; }  // For those that want to know the internal state.
143
144    private static Sensor getSafeExistingJMRISensor(String module, String userIdentifier, String parameter, String sensor) {
145        try { return getExistingJMRISensor(module, userIdentifier, parameter, sensor); } catch (CTCException e) { e.logError(); }
146        return null;
147    }
148
149    private static Sensor getSafeOptionalJMRISensor(String module, String userIdentifier, String parameter, String sensor) {
150        try { return getOptionalJMRISensor(module, userIdentifier, parameter, sensor); } catch (CTCException e) { e.logError(); }
151        return null;
152    }
153
154    private static Sensor getSafeInternalSensor(String module, String userIdentifier, String parameter, String sensor) {
155        try { return getInternalSensor(module, userIdentifier, parameter, sensor); } catch (CTCException e) { e.logError(); }
156        return null;
157    }
158
159//  sensor is NOT optional and cannot be null.  Raises Exception in ALL error cases.
160    private static Sensor getExistingJMRISensor(String module, String userIdentifier, String parameter, String sensor) throws CTCException {
161        if (!ProjectsCommonSubs.isNullOrEmptyString(sensor)) {
162            // Cannot use a constant Instance manager reference due to the dynamic nature of tests.
163            Sensor returnValue = InstanceManager.getDefault(SensorManager.class).getSensor(sensor);
164            if (returnValue == null) { throw new CTCException(module, userIdentifier, parameter, Bundle.getMessage("NBHSensorDoesNotExist") + " " + sensor); }  // NOI18N
165            return returnValue;
166        } else { throw new CTCException(module, userIdentifier, parameter, Bundle.getMessage("NBHSensorRequiredSensorMissing")); }  // NOI18N
167    }
168
169//  sensor is optional, but must exist if given.  Raises Exception in ALL error cases.
170    private static Sensor getOptionalJMRISensor(String module, String userIdentifier, String parameter, String sensor) throws CTCException {
171        if (!ProjectsCommonSubs.isNullOrEmptyString(sensor)) {
172            // Cannot use a constant Instance manager reference due to the dynamic nature of tests.
173            Sensor returnValue = InstanceManager.getDefault(SensorManager.class).getSensor(sensor);
174            if (returnValue == null) { throw new CTCException(module, userIdentifier, parameter, Bundle.getMessage("NBHSensorDoesNotExist") + " " + sensor); }  // NOI18N
175            return returnValue;
176        } else { return null; }
177    }
178
179// Special case for CTC internal sensors.  These are not always predefined so the provide() method is used.
180    private static Sensor getInternalSensor(String module, String userIdentifier, String parameter, String sensor) throws CTCException {
181        if (!ProjectsCommonSubs.isNullOrEmptyString(sensor)) {
182            // Cannot use a constant Instance manager reference due to the dynamic nature of tests.
183            return InstanceManager.getDefault(SensorManager.class).provide(sensor);
184        } else { return null; }
185    }
186
187    public int getKnownState() {
188        if (_mNamedBeanHandleSensor == null) return DEFAULT_SENSOR_STATE_RV;
189        return _mNamedBeanHandleSensor.getBean().getKnownState();
190    }
191
192    @SuppressFBWarnings(value = "DE_MIGHT_IGNORE", justification = "Let it not do anything if it fails.")
193    public void setKnownState(int newState) {
194        if (_mNamedBeanHandleSensor == null) return;
195        try { _mNamedBeanHandleSensor.getBean().setKnownState(newState); } catch (JmriException ex) {}
196    }
197
198
199    public void addPropertyChangeListener(PropertyChangeListener propertyChangeListener) {
200        if (_mNamedBeanHandleSensor == null) return;
201        _mNamedBeanHandleSensor.getBean().addPropertyChangeListener(propertyChangeListener);
202        _mArrayListOfPropertyChangeListeners.add(propertyChangeListener);
203    }
204
205    public void removePropertyChangeListener(PropertyChangeListener propertyChangeListener) {
206        if (_mNamedBeanHandleSensor == null) return;
207        _mNamedBeanHandleSensor.getBean().removePropertyChangeListener(propertyChangeListener);
208        _mArrayListOfPropertyChangeListeners.remove(propertyChangeListener);
209    }
210
211//  Support for "Grand Unification" (Editor support):
212
213
214    /**
215     * @return The sensor's handle name.
216     */
217    public String getHandleName() {
218        return valid() ? _mNamedBeanHandleSensor.getName() : "";
219    }
220
221    /**
222     * For Unit testing only.
223     * @return Returns the present number of property change listeners registered with us so far.
224     */
225    public int testingGetCountOfPropertyChangeListenersRegistered() {
226        return _mArrayListOfPropertyChangeListeners.size();
227    }
228
229//     private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(NBHSensor.class);
230}