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