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}