001package jmri.jmris;
002
003import java.beans.PropertyChangeEvent;
004import java.beans.PropertyChangeListener;
005import java.io.IOException;
006import java.util.ArrayList;
007import java.util.HashMap;
008import java.util.List;
009import java.util.Map;
010import javax.management.Attribute;
011
012import jmri.JmriException;
013import jmri.jmrit.operations.locations.LocationManager;
014import jmri.jmrit.operations.rollingstock.engines.Engine;
015import jmri.jmrit.operations.trains.Train;
016import jmri.jmrit.operations.trains.TrainManager;
017
018import org.slf4j.Logger;
019import org.slf4j.LoggerFactory;
020
021/**
022 * Abstract interface between the JMRI operations and a network connection
023 *
024 * @author Paul Bender Copyright (C) 2010
025 * @author Dan Boudreau Copyright (C) 2012 (added checks for null train)
026 * @author Rodney Black Copyright (C) 2012
027 * @author Randall Wood Copyright (C) 2012, 2014
028 */
029abstract public class AbstractOperationsServer implements PropertyChangeListener {
030
031    protected final TrainManager tm;
032    protected final LocationManager lm;
033    protected final HashMap<String, TrainListener> trains;
034
035//    @SuppressWarnings("LeakingThisInConstructor")
036    public AbstractOperationsServer() {
037        tm = jmri.InstanceManager.getDefault(TrainManager.class);
038        tm.addPropertyChangeListener(this);
039        lm = jmri.InstanceManager.getDefault(LocationManager.class);
040        lm.addPropertyChangeListener(this);
041        addPropertyChangeListeners();
042        trains = new HashMap<>();
043    }
044
045    public abstract void sendTrainList();
046
047    public abstract void sendLocationList();
048
049    /**
050     * constructs a String containing the status of a train
051     *
052     * @param trainName is the name of the train. If not found in Operations, an
053     *                  error message is sent to the client.
054     * @return the train's status as known by Operations
055     * @throws IOException on failure to send an error message to the client
056     */
057    public String constructTrainStatus(String trainName) throws IOException {
058        Train train = tm.getTrainByName(trainName);
059        if (train != null) {
060            return train.getStatus();
061        }
062        sendErrorStatus("ERROR train name doesn't exist " + trainName);
063        return null;
064    }
065
066    /**
067     * constructs a String containing the location of a train
068     *
069     * @param trainName is the name of the desired train. If not found in
070     *                  Operations, an error message is sent to the client
071     * @return the train's location, as known by Operations
072     * @throws IOException on failure to send an error message to the client
073     */
074    public String constructTrainLocation(String trainName) throws IOException {
075        Train train = tm.getTrainByName(trainName);
076        if (train != null) {
077            return train.getCurrentLocationName();
078        }
079        sendErrorStatus("ERROR train name doesn't exist " + trainName);
080        return null;
081    }
082
083    /**
084     * constructs a String containing the location of a train
085     *
086     * @param trainName    is the name of the desired train. If not found in
087     *                     Operations, an error message is sent to the client
088     * @param locationName is the name of the desired location.
089     * @return the train's location, as known by Operations
090     * @throws IOException on failure to send an error message to the client
091     */
092    public String setTrainLocation(String trainName, String locationName)
093            throws IOException {
094        log.debug("Set train {} Location {}", trainName, locationName);
095        Train train = tm.getTrainByName(trainName);
096        if (train != null) {
097            if (!exactLocationName && train.move(locationName)
098                    || exactLocationName && train.moveToNextLocation(locationName)) {
099                return constructTrainLocation(trainName);
100            } else {
101                sendErrorStatus("WARNING move of " + trainName + " to location " + locationName
102                        + " failed. Train's current location " + train.getCurrentLocationName()
103                        + " next location " + train.getNextLocationName());
104            }
105        } else {
106            sendErrorStatus("ERROR train name doesn't exist " + trainName);
107        }
108        return null;
109    }
110
111    private static boolean exactLocationName = true;
112
113    public static void setExactLocationName(boolean enabled) {
114        exactLocationName = enabled;
115    }
116
117    public static boolean isExactLoationNameEnabled() {
118        return exactLocationName;
119    }
120
121    /**
122     * constructs a String containing the length of a train
123     *
124     * @param trainName is the name of the desired train. If not found in
125     *                  Operations, an error message is sent to the client
126     * @return the train's length, as known by Operations
127     * @throws IOException on failure to send an error message to the client
128     */
129    public String constructTrainLength(String trainName) throws IOException {
130        Train train = tm.getTrainByName(trainName);
131        if (train != null) {
132            return String.valueOf(train.getTrainLength());
133        }
134        sendErrorStatus("ERROR train name doesn't exist " + trainName);
135        return null;
136    }
137
138    /**
139     * constructs a String containing the tonnage of a train
140     *
141     * @param trainName is the name of the desired train. If not found in
142     *                  Operations, an error message is sent to the client
143     * @return the train's tonnage, as known by Operations
144     * @throws IOException on failure to send an error message to the client
145     */
146    public String constructTrainWeight(String trainName) throws IOException {
147        Train train = tm.getTrainByName(trainName);
148        if (train != null) {
149            return String.valueOf(train.getTrainWeight());
150        }
151        sendErrorStatus("ERROR train name doesn't exist " + trainName);
152        return null;
153    }
154
155    /**
156     * constructs a String containing the number of cars in a train
157     *
158     * @param trainName is the name of the desired train. If not found in
159     *                  Operations, an error message is sent to the client
160     * @return the number of cars in a train, as known by Operations
161     * @throws IOException on failure to send an error message to the client
162     */
163    public String constructTrainNumberOfCars(String trainName) throws IOException {
164        Train train = tm.getTrainByName(trainName);
165        if (train != null) {
166            return String.valueOf(train.getNumberCarsInTrain());
167        }
168        sendErrorStatus("ERROR train name doesn't exist " + trainName);
169        return null;
170    }
171
172    /**
173     * Constructs a String containing the road and number of lead loco, if
174     * there's one assigned to the train.
175     *
176     * @param trainName is the name of the desired train. If not found in
177     *                  Operations, an error message is sent to the client
178     * @return the lead loco
179     * @throws IOException on failure to send an error message to the client
180     */
181    public String constructTrainLeadLoco(String trainName) throws IOException {
182        Train train = tm.getTrainByName(trainName);
183        if (train != null) {
184            Engine leadEngine = train.getLeadEngine();
185            if (leadEngine != null) {
186                return leadEngine.toString();
187            }
188        } else { // train is null
189            sendErrorStatus("ERROR train name doesn't exist " + trainName);
190        }
191        return null;
192    }
193
194    /**
195     * constructs a String containing the caboose on a train
196     *
197     * @param trainName is the name of the desired train. If not found in
198     *                  Operations, an error message is sent to the client
199     * @return the caboose on a train, as known by Operations
200     * @throws IOException on failure to send an error message to the client
201     */
202    public String constructTrainCaboose(String trainName) throws IOException {
203        Train train = tm.getTrainByName(trainName);
204        if (train != null) {
205            return train.getCabooseRoadAndNumber();
206        }
207        sendErrorStatus("ERROR train name doesn't exist " + trainName);
208        return null;
209    }
210
211    /**
212     * tells Operations that a train has terminated. If not found in Operations,
213     * an error message is sent to the client
214     *
215     * @param trainName is the name of the train
216     * @return the termination String
217     * @throws IOException on failure to send an error message to the client
218     */
219    public String terminateTrain(String trainName) throws IOException {
220        Train train = tm.getTrainByName(trainName);
221        if (train != null) {
222            train.terminate();
223            return constructTrainStatus(trainName);
224        }
225        sendErrorStatus("ERROR train name doesn't exist " + trainName);
226        return null;
227    }
228
229    /**
230     * sends the full status for a train to a client
231     *
232     * @param train is the name of the desired train. If not found, an error
233     *                  is sent to the client
234     * @throws IOException on failure to send an error message
235     */
236     //public void sendFullStatus(String trainName) throws IOException {
237     //   Train train = tm.getTrainByName(trainName);
238     //       if (train != null) {
239     //           sendFullStatus(train);
240     //       } else {
241     //           sendErrorStatus("ERROR train name doesn't exist " + trainName);
242     //       }
243     //}
244
245    /**
246     * sends the full status for a train to a client
247     *
248     * @param train is the Train object we are sending information about.
249     * @throws IOException on failure to send an error message
250     */
251    public abstract void sendFullStatus(Train train) throws IOException;
252
253    private void addPropertyChangeListeners() {
254        List<Train> trainList = tm.getTrainsByNameList();
255        for (Train train : trainList) {
256            train.addPropertyChangeListener(this);
257        }
258    }
259
260    private void removePropertyChangeListeners() {
261        List<Train> trainList = tm.getTrainsByNameList();
262        for (Train train : trainList) {
263            train.removePropertyChangeListener(this);
264        }
265    }
266
267    @Override
268    public abstract void propertyChange(PropertyChangeEvent e);
269
270    synchronized protected void addTrainToList(String trainId) {
271        if (!trains.containsKey(trainId)) {
272            trains.put(trainId, new TrainListener(trainId));
273            jmri.InstanceManager.getDefault(TrainManager.class).getTrainById(trainId).addPropertyChangeListener(trains.get(trainId));
274        }
275    }
276
277    synchronized protected void removeTrainFromList(String trainId) {
278        if (trains.containsKey(trainId)) {
279            jmri.InstanceManager.getDefault(TrainManager.class).getTrainById(trainId).removePropertyChangeListener(trains.get(trainId));
280            trains.remove(trainId);
281        }
282    }
283
284    protected TrainListener getListener(String trainId) {
285        return new TrainListener(trainId);
286    }
287
288    public void dispose() {
289        if (tm != null) {
290            tm.removePropertyChangeListener(this);
291            removePropertyChangeListeners();
292        }
293        if (lm != null) {
294            lm.removePropertyChangeListener(this);
295        }
296        for (Map.Entry<String, TrainListener> train : this.trains.entrySet()) {
297            jmri.InstanceManager.getDefault(TrainManager.class).getTrainById(train.getKey()).removePropertyChangeListener(train.getValue());
298        }
299        this.trains.clear();
300    }
301
302    /*
303     * Protocol Specific Abstract Functions
304     */
305    abstract public void sendMessage(ArrayList<Attribute> contents) throws IOException;
306
307    abstract public void sendErrorStatus(String errorStatus) throws IOException;
308
309    abstract public void parseStatus(String statusString) throws JmriException, IOException;
310
311    private final static Logger log = LoggerFactory.getLogger(AbstractOperationsServer.class);
312
313    /*
314     * This isn't currently used for operations
315     */
316    protected class TrainListener implements PropertyChangeListener {
317
318        private final Train train;
319
320        protected TrainListener(String trainId) {
321            this.train = jmri.InstanceManager.getDefault(TrainManager.class).getTrainById(trainId);
322        }
323
324        @Override
325        public void propertyChange(PropertyChangeEvent e) {
326            try {
327                sendFullStatus(this.train);
328            } catch (IOException ie) {
329                log.debug("Error Sending Status");
330                // if we get an error, de-register
331                this.train.removePropertyChangeListener(this);
332                removeTrainFromList(this.train.getId());
333            }
334        }
335    }
336
337}