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