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    // Sensors 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    protected Sensor createNewSensor(@Nonnull String systemName, String userName) throws IllegalArgumentException {
092        String addr = systemName.substring(getSystemNamePrefix().length());
093        // first, check validity
094        try {
095            validateSystemNameFormat(systemName,Locale.getDefault());
096        } catch (jmri.NamedBean.BadSystemNameException e) {
097            log.error("Exception: {}", e.getMessage());
098            throw e;
099        }
100        // OK, make
101        OlcbSensor s = new OlcbSensor(getSystemPrefix(), addr, (CanSystemConnectionMemo) memo);
102        s.setUserName(userName);
103
104        synchronized (pendingSensors) {
105            if (isLoading) {
106                pendingSensors.add(s);
107            } else {
108                s.finishLoad();
109            }
110        }
111        return s;
112    }
113
114    /**
115     * This function is invoked before an XML load is started. We defer initialization of the
116     * newly created Sensors until finishLoad because the feedback type might be changing as we
117     * are parsing the XML.
118     */
119    public void startLoad() {
120        log.debug("Sensor manager : start load");
121        synchronized (pendingSensors) {
122            isLoading = true;
123        }
124    }
125
126    /**
127     * This function is invoked after the XML load is complete and all Sensors are instantiated
128     * and their feedback type is read in. We use this hook to finalize the construction of the
129     * OpenLCB objects whose instantiation was deferred until the feedback type was known.
130     */
131    public void finishLoad() {
132        log.debug("Sensor manager : finish load");
133        synchronized (pendingSensors) {
134            pendingSensors.forEach(OlcbSensor::finishLoad);
135            pendingSensors.clear();
136            isLoading = false;
137        }
138    }
139
140    @Override
141    public boolean allowMultipleAdditions(@Nonnull String systemName) {
142        return false;
143    }
144
145    @Override
146    @Nonnull
147    public String createSystemName(@Nonnull String curAddress, @Nonnull String prefix) throws JmriException {
148        String tmpPrefix = prefix + typeLetter();
149        String tmpSName  = tmpPrefix + curAddress;
150        try {
151            OlcbAddress.validateSystemNameFormat(tmpSName,Locale.getDefault(),tmpPrefix, (CanSystemConnectionMemo) memo);
152        }
153        catch ( jmri.NamedBean.BadSystemNameException ex ){
154            throw new JmriException(ex.getMessage());
155        }
156        return prefix + typeLetter() + curAddress;
157    }
158
159    @Override
160    @javax.annotation.Nonnull
161    @javax.annotation.CheckReturnValue
162    public String getNextValidSystemName(@Nonnull NamedBean currentBean) throws JmriException {
163        throw new jmri.JmriException("getNextValidSystemName should not have been called");
164    }
165
166    /**
167     * {@inheritDoc}
168     */
169    @Override
170    public String getEntryToolTip() {
171        return Bundle.getMessage("AddSensorEntryToolTip");
172    }
173
174    // listen for sensors, creating them as needed
175    @Override
176    public void reply(CanReply l) {
177        // doesn't do anything, because for now
178        // we want you to create manually
179    }
180
181    @Override
182    public void message(CanMessage l) {
183        // doesn't do anything, because
184        // messages come from us
185    }
186
187    /**
188     * Validates to OpenLCB format.
189     * {@inheritDoc}
190     */
191    @Override
192    @Nonnull
193    public String validateSystemNameFormat(@Nonnull String name, @Nonnull java.util.Locale locale) throws jmri.NamedBean.BadSystemNameException {
194        name = super.validateSystemNameFormat(name,locale);
195        name = OlcbAddress.validateSystemNameFormat(name,locale,getSystemNamePrefix(), (CanSystemConnectionMemo) memo);
196        return name;
197    }
198
199    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(OlcbSensorManager.class);
200
201}
202
203