001package jmri.jmrix.openlcb;
002
003import java.util.*;
004
005import javax.annotation.Nonnull;
006import jmri.BooleanPropertyDescriptor;
007import jmri.JmriException;
008import jmri.NamedBean;
009import jmri.NamedBeanPropertyDescriptor;
010import jmri.Sensor;
011import jmri.jmrix.can.CanListener;
012import jmri.jmrix.can.CanMessage;
013import jmri.jmrix.can.CanReply;
014import jmri.jmrix.can.CanSystemConnectionMemo;
015import org.openlcb.OlcbInterface;
016import org.slf4j.Logger;
017import org.slf4j.LoggerFactory;
018
019/**
020 * Manage the OpenLCB-specific Sensor implementation.
021 *
022 * System names are "MSnnn", where M is the user configurable system prefix,
023 * nnn is the sensor number without padding.
024 *
025 * @author Bob Jacobsen Copyright (C) 2008, 2010
026 */
027public class OlcbSensorManager extends jmri.managers.AbstractSensorManager implements CanListener {
028
029    // Whether we accumulate partially loaded objects in pendingSensors.
030    private boolean isLoading = false;
031    // Turnouts that are being loaded from XML.
032    private final ArrayList<OlcbSensor> pendingSensors = new ArrayList<>();
033
034    /**
035     * {@inheritDoc}
036     */
037    @Override
038    @Nonnull
039    public CanSystemConnectionMemo getMemo() {
040        return (CanSystemConnectionMemo) memo;
041    }
042
043    @Override
044    @Nonnull
045    public List<NamedBeanPropertyDescriptor<?>> getKnownBeanProperties() {
046        List<NamedBeanPropertyDescriptor<?>> l = new ArrayList<>();
047        l.add(new BooleanPropertyDescriptor(OlcbUtils.PROPERTY_IS_AUTHORITATIVE, OlcbTurnout
048                .DEFAULT_IS_AUTHORITATIVE) {
049            @Override
050            public String getColumnHeaderText() {
051                return Bundle.getMessage("OlcbStateAuthHeader");
052            }
053
054            @Override
055            public boolean isEditable(NamedBean bean) {
056                return OlcbUtils.isOlcbBean(bean);
057            }
058        });
059        l.add(new BooleanPropertyDescriptor(OlcbUtils.PROPERTY_LISTEN, OlcbTurnout
060                .DEFAULT_LISTEN) {
061            @Override
062            public String getColumnHeaderText() {
063                return Bundle.getMessage("OlcbStateListenHeader");
064            }
065
066            @Override
067            public boolean isEditable(NamedBean bean) {
068                return OlcbUtils.isOlcbBean(bean);
069            }
070        });
071        return l;
072    }
073
074    // to free resources when no longer used
075    @Override
076    public void dispose() {
077        getMemo().getTrafficController().removeCanListener(this);
078        super.dispose();
079    }
080
081    // Implemented ready for new system connection memo
082    public OlcbSensorManager(CanSystemConnectionMemo memo) {
083        super(memo);
084        memo.getTrafficController().addCanListener(this);
085    }
086
087    /**
088     * {@inheritDoc}
089     *
090     * @throws IllegalArgumentException when SystemName can't be converted
091     */
092    @Override
093    @Nonnull
094    @edu.umd.cs.findbugs.annotations.SuppressFBWarnings( value = "SLF4J_FORMAT_SHOULD_BE_CONST",
095        justification = "passing exception text")
096    protected Sensor createNewSensor(@Nonnull String systemName, String userName) throws IllegalArgumentException {
097        String addr = systemName.substring(getSystemNamePrefix().length());
098        // first, check validity
099        try {
100            validateSystemNameFormat(systemName,Locale.getDefault());
101        } catch (jmri.NamedBean.BadSystemNameException e) {
102            log.error(e.getMessage());
103            throw e;
104        }
105        // OK, make
106        OlcbSensor s = new OlcbSensor(getSystemPrefix(), addr, memo.get(OlcbInterface.class));
107        s.setUserName(userName);
108
109        synchronized (pendingSensors) {
110            if (isLoading) {
111                pendingSensors.add(s);
112            } else {
113                s.finishLoad();
114            }
115        }
116        return s;
117    }
118
119    /**
120     * This function is invoked before an XML load is started. We defer initialization of the
121     * newly created Sensors until finishLoad because the feedback type might be changing as we
122     * are parsing the XML.
123     */
124    public void startLoad() {
125        log.debug("Sensor manager : start load");
126        synchronized (pendingSensors) {
127            isLoading = true;
128        }
129    }
130
131    /**
132     * This function is invoked after the XML load is complete and all Sensors are instantiated
133     * and their feedback type is read in. We use this hook to finalize the construction of the
134     * OpenLCB objects whose instantiation was deferred until the feedback type was known.
135     */
136    public void finishLoad() {
137        log.debug("Sensor manager : finish load");
138        synchronized (pendingSensors) {
139            pendingSensors.forEach(OlcbSensor::finishLoad);
140            pendingSensors.clear();
141            isLoading = false;
142        }
143    }
144
145    @Override
146    public boolean allowMultipleAdditions(@Nonnull String systemName) {
147        return false;
148    }
149
150    @Override
151    @Nonnull
152    public String createSystemName(@Nonnull String curAddress, @Nonnull String prefix) throws JmriException {
153        String tmpPrefix = prefix + typeLetter();
154        String tmpSName  = tmpPrefix + curAddress;
155        try {
156            OlcbAddress.validateSystemNameFormat(tmpSName,Locale.getDefault(),tmpPrefix);
157        }
158        catch ( jmri.NamedBean.BadSystemNameException ex ){
159            throw new JmriException(ex.getMessage());
160        }
161        // don't check for integer; should check for validity here
162        return prefix + typeLetter() + curAddress;
163    }
164
165    @Override
166    @javax.annotation.Nonnull
167    @javax.annotation.CheckReturnValue
168    public String getNextValidSystemName(@Nonnull NamedBean currentBean) throws JmriException {
169        throw new jmri.JmriException("getNextValidSystemName should not have been called");
170    }
171
172    /**
173     * {@inheritDoc}
174     */
175    @Override
176    public String getEntryToolTip() {
177        return Bundle.getMessage("AddSensorEntryToolTip");
178    }
179
180    // listen for sensors, creating them as needed
181    @Override
182    public void reply(CanReply l) {
183        // doesn't do anything, because for now
184        // we want you to create manually
185    }
186
187    @Override
188    public void message(CanMessage l) {
189        // doesn't do anything, because
190        // messages come from us
191    }
192
193    /**
194     * Validates to OpenLCB format.
195     * {@inheritDoc}
196     */
197    @Override
198    @Nonnull
199    public String validateSystemNameFormat(@Nonnull String name, @Nonnull java.util.Locale locale) throws jmri.NamedBean.BadSystemNameException {
200        name = super.validateSystemNameFormat(name,locale);
201        name = OlcbAddress.validateSystemNameFormat(name,locale,getSystemNamePrefix());
202        return name;
203    }
204
205    private static final Logger log = LoggerFactory.getLogger(OlcbSensorManager.class);
206
207}
208
209