001package jmri.jmrit.operations.routes;
002
003import java.util.*;
004
005import javax.swing.JComboBox;
006
007import org.jdom2.Element;
008import org.slf4j.Logger;
009import org.slf4j.LoggerFactory;
010
011import jmri.*;
012import jmri.beans.PropertyChangeSupport;
013import jmri.jmrit.operations.locations.Location;
014import jmri.jmrit.operations.locations.LocationManager;
015import jmri.jmrit.operations.setup.OperationsSetupXml;
016
017/**
018 * Manages the routes
019 *
020 * @author Bob Jacobsen Copyright (C) 2003
021 * @author Daniel Boudreau Copyright (C) 2008, 2009, 2010
022 */
023public class RouteManager extends PropertyChangeSupport implements InstanceManagerAutoDefault, InstanceManagerAutoInitialize {
024
025    public static final String LISTLENGTH_CHANGED_PROPERTY = "routesListLengthChanged"; // NOI18N
026
027    public RouteManager() {
028    }
029
030    private int _id = 0;
031
032    public void dispose() {
033        _routeHashTable.clear();
034        _id = 0;
035    }
036
037    // stores known Route instances by id
038    protected Hashtable<String, Route> _routeHashTable = new Hashtable<>();
039
040    /**
041     * @param name The string name of the Route.
042     * @return requested Route object or null if none exists
043     */
044    public Route getRouteByName(String name) {
045        Route l;
046        Enumeration<Route> en = _routeHashTable.elements();
047        while (en.hasMoreElements()) {
048            l = en.nextElement();
049            if (l.getName().equals(name)) {
050                return l;
051            }
052        }
053        return null;
054    }
055
056    public Route getRouteById(String id) {
057        return _routeHashTable.get(id);
058    }
059
060    /**
061     * Finds an existing route or creates a new route if needed requires route's
062     * name creates a unique id for this route
063     *
064     * @param name The string name of the new Route.
065     *
066     *
067     * @return new route or existing route
068     */
069    public Route newRoute(String name) {
070        Route route = getRouteByName(name);
071        if (route == null) {
072            _id++;
073            route = new Route(Integer.toString(_id), name);
074            Integer oldSize = Integer.valueOf(_routeHashTable.size());
075            _routeHashTable.put(route.getId(), route);
076            setDirtyAndFirePropertyChange(LISTLENGTH_CHANGED_PROPERTY, oldSize,
077                    Integer.valueOf(_routeHashTable.size()));
078        }
079        return route;
080    }
081
082    /**
083     * Remember a NamedBean Object created outside the manager.
084     *
085     * @param route The Route to add.
086     */
087    public void register(Route route) {
088        Integer oldSize = Integer.valueOf(_routeHashTable.size());
089        _routeHashTable.put(route.getId(), route);
090        // find last id created
091        int id = Integer.parseInt(route.getId());
092        if (id > _id) {
093            _id = id;
094        }
095        setDirtyAndFirePropertyChange(LISTLENGTH_CHANGED_PROPERTY, oldSize, Integer.valueOf(_routeHashTable.size()));
096        // listen for name and state changes to forward
097    }
098
099    /**
100     * Forget a NamedBean Object created outside the manager.
101     *
102     * @param route The Route to delete.
103     */
104    public void deregister(Route route) {
105        if (route == null) {
106            return;
107        }
108        route.dispose();
109        Integer oldSize = Integer.valueOf(_routeHashTable.size());
110        _routeHashTable.remove(route.getId());
111        setDirtyAndFirePropertyChange(LISTLENGTH_CHANGED_PROPERTY, oldSize, Integer.valueOf(_routeHashTable.size()));
112    }
113
114    /**
115     * Sort by route name
116     *
117     * @return list of routes ordered by name
118     */
119    public List<Route> getRoutesByNameList() {
120        List<Route> sortList = getList();
121        // now re-sort
122        List<Route> out = new ArrayList<>();
123        for (Route route : sortList) {
124            for (int j = 0; j < out.size(); j++) {
125                if (route.getName().compareToIgnoreCase(out.get(j).getName()) < 0) {
126                    out.add(j, route);
127                    break;
128                }
129            }
130            if (!out.contains(route)) {
131                out.add(route);
132            }
133        }
134        return out;
135
136    }
137
138    /**
139     * Sort by route number, number can alpha numeric
140     *
141     * @return list of routes ordered by id numbers
142     */
143    public List<Route> getRoutesByIdList() {
144        List<Route> sortList = getList();
145        // now re-sort
146        List<Route> out = new ArrayList<>();
147        for (Route route : sortList) {
148            for (int j = 0; j < out.size(); j++) {
149                try {
150                    if (Integer.parseInt(route.getId()) < Integer.parseInt(out.get(j).getId())) {
151                        out.add(j, route);
152                        break;
153                    }
154                } catch (NumberFormatException e) {
155                    log.error("list id number isn't a number");
156                }
157            }
158            if (!out.contains(route)) {
159                out.add(route);
160            }
161        }
162        return out;
163    }
164
165    private List<Route> getList() {
166        List<Route> out = new ArrayList<>();
167        Enumeration<Route> en = _routeHashTable.elements();
168        while (en.hasMoreElements()) {
169            out.add(en.nextElement());
170        }
171        return out;
172    }
173    
174    /**
175     * Used to determine if a location is part of any route.
176     * 
177     * @param loc The location being checked.
178     * @return null if location isn't used, otherwise a route using the
179     *         location.
180     */
181    public Route isLocationInUse(Location loc) {
182        for (Route route : getList()) {
183            RouteLocation rl = route.getLastLocationByName(loc.getName());
184           if (rl != null) {
185               return route;
186           }
187        }
188        return null;
189    }
190
191    public JComboBox<Route> getComboBox() {
192        JComboBox<Route> box = new JComboBox<>();
193        box.addItem(null);
194        List<Route> routes = getRoutesByNameList();
195        for (Route route : routes) {
196            box.addItem(route);
197        }
198        return box;
199    }
200
201    public void updateComboBox(JComboBox<Route> box) {
202        box.removeAllItems();
203        box.addItem(null);
204        List<Route> routes = getRoutesByNameList();
205        for (Route route : routes) {
206            box.addItem(route);
207        }
208    }
209
210    /**
211     * Copy route, returns a new route named routeName. If invert is true the
212     * reverse of the route is returned.
213     *
214     * @param route     The route to be copied
215     * @param routeName The name of the new route
216     * @param invert    If true, return the inversion of route
217     * @return A copy of the route
218     */
219    public Route copyRoute(Route route, String routeName, boolean invert) {
220        Route newRoute = newRoute(routeName);
221        List<RouteLocation> routeList = route.getLocationsBySequenceList();
222        if (!invert) {
223            for (RouteLocation rl : routeList) {
224                copyRouteLocation(newRoute, rl, null, invert);
225            }
226            // invert route order
227        } else {
228            for (int i = routeList.size() - 1; i >= 0; i--) {
229                int y = i - 1;
230                if (y < 0) {
231                    y = 0;
232                }
233                copyRouteLocation(newRoute, routeList.get(i), routeList.get(y), invert);
234            }
235        }
236        newRoute.setComment(route.getComment());
237        return newRoute;
238    }
239
240    private void copyRouteLocation(Route newRoute, RouteLocation rl, RouteLocation rlNext, boolean invert) {
241        Location loc = InstanceManager.getDefault(LocationManager.class).getLocationByName(rl.getName());
242        RouteLocation rlNew = newRoute.addLocation(loc);
243        // now copy the route location objects we want
244        rlNew.setMaxCarMoves(rl.getMaxCarMoves());
245        rlNew.setRandomControl(rl.getRandomControl());
246        rlNew.setWait(rl.getWait());
247        rlNew.setDepartureTime(rl.getDepartureTime());
248        rlNew.setComment(rl.getComment());
249        rlNew.setCommentColor(rl.getCommentColor());
250        if (!invert) {
251            rlNew.setDropAllowed(rl.isDropAllowed());
252            rlNew.setPickUpAllowed(rl.isPickUpAllowed());
253            rlNew.setGrade(rl.getGrade());
254            rlNew.setTrainDirection(rl.getTrainDirection());
255            rlNew.setMaxTrainLength(rl.getMaxTrainLength());
256        } else {
257            // flip set outs and pick ups
258            rlNew.setDropAllowed(rl.isPickUpAllowed());
259            rlNew.setPickUpAllowed(rl.isDropAllowed());
260            // invert train directions
261            int oldDirection = rl.getTrainDirection();
262            if (oldDirection == RouteLocation.NORTH) {
263                rlNew.setTrainDirection(RouteLocation.SOUTH);
264            } else if (oldDirection == RouteLocation.SOUTH) {
265                rlNew.setTrainDirection(RouteLocation.NORTH);
266            } else if (oldDirection == RouteLocation.EAST) {
267                rlNew.setTrainDirection(RouteLocation.WEST);
268            } else if (oldDirection == RouteLocation.WEST) {
269                rlNew.setTrainDirection(RouteLocation.EAST);
270            }
271            // get the max length between location
272            if (rlNext == null) {
273                log.error("Can not copy route, rlNext is null!");
274                return;
275            }
276            rlNew.setMaxTrainLength(rlNext.getMaxTrainLength());
277        }
278        rlNew.setTrainIconX(rl.getTrainIconX());
279        rlNew.setTrainIconY(rl.getTrainIconY());
280    }
281
282    /**
283     * @return Number of routes
284     */
285    public int numEntries() {
286        return _routeHashTable.size();
287    }
288
289    public void load(Element root) {
290        // decode type, invoke proper processing routine if a decoder file
291        if (root.getChild(Xml.ROUTES) != null) {
292            List<Element> eRoutes = root.getChild(Xml.ROUTES).getChildren(Xml.ROUTE);
293            log.debug("readFile sees {} routes", eRoutes.size());
294            for (Element eRoute : eRoutes) {
295                register(new Route(eRoute));
296            }
297        }
298    }
299
300    public void store(Element root) {
301        Element values = new Element(Xml.ROUTES);
302        root.addContent(values);
303        for (Route route : getRoutesByIdList()) {
304            values.addContent(route.store());
305        }
306    }
307
308    protected void setDirtyAndFirePropertyChange(String p, Object old, Object n) {
309        InstanceManager.getDefault(RouteManagerXml.class).setDirty(true);
310        firePropertyChange(p, old, n);
311    }
312
313    private final static Logger log = LoggerFactory.getLogger(RouteManager.class);
314
315    @Override
316    public void initialize() {
317        InstanceManager.getDefault(OperationsSetupXml.class); // load setup
318        InstanceManager.getDefault(RouteManagerXml.class); // load routes
319    }
320
321}