001package jmri.managers;
002
003import java.util.Enumeration;
004import java.util.Objects;
005import javax.annotation.Nonnull;
006import jmri.JmriException;
007import jmri.Manager;
008import jmri.Sensor;
009import jmri.SensorManager;
010import jmri.SystemConnectionMemo;
011
012/**
013 * Abstract base implementation of the SensorManager interface.
014 *
015 * @author Bob Jacobsen Copyright (C) 2001, 2003
016 */
017public abstract class AbstractSensorManager extends AbstractManager<Sensor> implements SensorManager {
018
019    /**
020     * Create a new SensorManager instance.
021     *
022     * @param memo the system connection
023     */
024    public AbstractSensorManager(SystemConnectionMemo memo) {
025        super(memo);
026    }
027
028    /** {@inheritDoc} */
029    @Override
030    public int getXMLOrder() {
031        return Manager.SENSORS;
032    }
033
034    /** {@inheritDoc} */
035    @Override
036    public char typeLetter() {
037        return 'S';
038    }
039
040    /** {@inheritDoc} */
041    @Override
042    @Nonnull
043    public Sensor provideSensor(@Nonnull String name) {
044        Sensor t = getSensor(name);
045        if (t == null) {
046            t = newSensor(makeSystemName(name), null);
047        }
048        return t;
049    }
050
051    /** {@inheritDoc} */
052    @Override
053    public Sensor getSensor(@Nonnull String name) {
054        Sensor t = getByUserName(name);
055        if (t != null) {
056            return t;
057        }
058        return getBySystemName(name);
059    }
060
061    static final java.util.regex.Matcher numberMatcher = java.util.regex.Pattern.compile("\\d++").matcher("");
062
063    boolean isNumber(@Nonnull String s) {
064        synchronized (numberMatcher) {
065            return numberMatcher.reset(s).matches();
066        }
067    }
068
069    /** {@inheritDoc}
070     * Special handling for numeric argument, which is treated as the suffix of a new system name
071    */
072    @Override
073
074    public Sensor getBySystemName(@Nonnull String key) {
075        if (isNumber(key)) {
076            key = makeSystemName(key);
077        }
078        return _tsys.get(key);
079    }
080
081    /**
082     * Create a New Sensor.
083     * {@inheritDoc}
084     */
085    @Override
086    @Nonnull
087    public Sensor newSensor(@Nonnull String systemName, String userName) throws IllegalArgumentException {
088        log.debug(" newSensor(\"{}\", \"{}\")", systemName, (userName == null ? "null" : userName));
089        Objects.requireNonNull(systemName, "SystemName cannot be null. UserName was "
090                + (userName == null ? "null" : userName));  // NOI18N
091        systemName = validateSystemNameFormat(systemName);
092        // return existing if there is one
093        Sensor s;
094        if (userName != null) {
095            s = getByUserName(userName);
096            if (s != null) {
097                if (getBySystemName(systemName) != s) {
098                    log.error("inconsistent user ({}) and system name ({}) results; userName related to ({})", userName, systemName, s.getSystemName());
099                }
100                return s;
101            }
102        }
103        s = getBySystemName(systemName);
104        if (s != null) {
105            if ((s.getUserName() == null) && (userName != null)) {
106                s.setUserName(userName);
107            } else if (userName != null) {
108                log.warn("Found sensor via system name ({}) with non-null user name ({}). Sensor \"{}({})\" cannot be used.",
109                        systemName, s.getUserName(), systemName, userName);
110            }
111            return s;
112        }
113        // doesn't exist, make a new one
114        s = createNewSensor(systemName, userName);
115        // save in the maps
116        register(s);
117
118        return s;
119    }
120
121    /** {@inheritDoc} */
122    @Override
123    @Nonnull
124    public String getBeanTypeHandled(boolean plural) {
125        return Bundle.getMessage(plural ? "BeanNameSensors" : "BeanNameSensor");
126    }
127
128    /**
129     * {@inheritDoc}
130     */
131    @Override
132    public Class<Sensor> getNamedBeanClass() {
133        return Sensor.class;
134    }
135
136    /**
137     * Internal method to invoke the factory and create a new Sensor.
138     *
139     * Called after all the logic for returning an existing Sensor
140     * has been invoked.
141     * An existing SystemName is not found, existing UserName not found.
142     *
143     * Implementing classes should base Sensor on the system name, then add user name.
144     *
145     * @param systemName the system name to use for the new Sensor
146     * @param userName   the optional user name to use for the new Sensor
147     * @return the new Sensor
148     * @throws IllegalArgumentException if unsuccessful with reason for fail.
149     */
150    @Nonnull
151    abstract protected Sensor createNewSensor(@Nonnull String systemName, String userName) throws IllegalArgumentException;
152
153    /**
154     * {@inheritDoc}
155     * Delay between requesting individual Sensor status is determined by the
156     * Connection Output Interval Setting.
157     */
158    @Override
159    public void updateAll() {
160        int i = 0;
161        for ( Sensor nb : getNamedBeanSet() ) {
162            jmri.util.ThreadingUtil.runOnLayoutDelayed( nb::requestUpdateFromLayout,
163                i * getMemo().getOutputInterval() );
164            i++;
165        }
166    }
167
168    /**
169     * Default Sensor ensures a numeric only system name.
170     * {@inheritDoc}
171     */
172    @Nonnull
173    @Override
174    public String createSystemName(@Nonnull String curAddress, @Nonnull String prefix) throws JmriException {
175        return prefix + typeLetter() + checkNumeric(curAddress);
176    }
177
178    protected long sensorDebounceGoingActive = 0L;
179    protected long sensorDebounceGoingInActive = 0L;
180
181    /** {@inheritDoc} */
182    @Override
183    public long getDefaultSensorDebounceGoingActive() {
184        return sensorDebounceGoingActive;
185    }
186
187    /** {@inheritDoc} */
188    @Override
189    public long getDefaultSensorDebounceGoingInActive() {
190        return sensorDebounceGoingInActive;
191    }
192
193    /** {@inheritDoc} */
194    @Override
195    public void setDefaultSensorDebounceGoingActive(long time) {
196        if (time == sensorDebounceGoingActive) {
197            return;
198        }
199        sensorDebounceGoingActive = time;
200        Enumeration<String> en = _tsys.keys();
201        while (en.hasMoreElements()) {
202            Sensor sen = _tsys.get(en.nextElement());
203            if (sen.getUseDefaultTimerSettings()) {
204                sen.setSensorDebounceGoingActiveTimer(time);
205            }
206        }
207    }
208
209    /** {@inheritDoc} */
210    @Override
211    public void setDefaultSensorDebounceGoingInActive(long time) {
212        if (time == sensorDebounceGoingInActive) {
213            return;
214        }
215        sensorDebounceGoingInActive = time;
216        Enumeration<String> en = _tsys.keys();
217        while (en.hasMoreElements()) {
218            Sensor sen = _tsys.get(en.nextElement());
219            if (sen.getUseDefaultTimerSettings()) {
220                sen.setSensorDebounceGoingInActiveTimer(time);
221            }
222        }
223    }
224
225    /**
226     * {@inheritDoc}
227     * This default implementation always returns false.
228     *
229     * @return true if pull up/pull down configuration is supported.
230     */
231    @Override
232    public boolean isPullResistanceConfigurable(){
233       return false;
234    }
235
236    /** {@inheritDoc} */
237    @Override
238    public String getEntryToolTip() {
239        return Bundle.getMessage("EnterNumber1to9999ToolTip");
240    }
241
242    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(AbstractSensorManager.class);
243
244}