001package jmri.jmrit.operations.trains.schedules; 002 003import java.beans.PropertyChangeListener; 004import java.util.*; 005 006import javax.swing.JComboBox; 007 008import org.jdom2.Attribute; 009import org.jdom2.Element; 010import org.slf4j.Logger; 011import org.slf4j.LoggerFactory; 012 013import jmri.*; 014import jmri.beans.PropertyChangeSupport; 015import jmri.jmrit.operations.OperationsPanel; 016import jmri.jmrit.operations.locations.Location; 017import jmri.jmrit.operations.locations.LocationManager; 018import jmri.jmrit.operations.setup.Setup; 019import jmri.jmrit.operations.trains.*; 020import jmri.jmrit.operations.trains.csv.TrainCsvSwitchLists; 021 022/** 023 * Manages train schedules. The default is the days of the week, but can be 024 * anything the user wants when defining when trains will run. 025 * 026 * @author Bob Jacobsen Copyright (C) 2003 027 * @author Daniel Boudreau Copyright (C) 2010 028 */ 029public class TrainScheduleManager extends PropertyChangeSupport implements InstanceManagerAutoDefault, InstanceManagerAutoInitialize, PropertyChangeListener { 030 031 public TrainScheduleManager() { 032 } 033 034 public static final String NONE = ""; 035 private String _trainScheduleActiveId = NONE; 036 private int _id = 0; 037 038 public static final String LISTLENGTH_CHANGED_PROPERTY = "trainScheduleListLength"; // NOI18N 039 public static final String SCHEDULE_ID_CHANGED_PROPERTY = "ActiveTrainScheduleId"; // NOI18N 040 041 public void dispose() { 042 _scheduleHashTable.clear(); 043 } 044 045 // stores known TrainSchedule instances by id 046 protected Hashtable<String, TrainSchedule> _scheduleHashTable = new Hashtable<>(); 047 048 /** 049 * @return Number of schedules 050 */ 051 public int numEntries() { 052 return _scheduleHashTable.size(); 053 } 054 055 /** 056 * Sets the selected schedule id 057 * 058 * @param id Selected schedule id 059 */ 060 public void setTrainScheduleActiveId(String id) { 061 String old = _trainScheduleActiveId; 062 _trainScheduleActiveId = id; 063 if (!old.equals(id)) { 064 setDirtyAndFirePropertyChange(SCHEDULE_ID_CHANGED_PROPERTY, old, id); 065 } 066 } 067 068 public String getTrainScheduleActiveId() { 069 return _trainScheduleActiveId; 070 } 071 072 public TrainSchedule getActiveSchedule() { 073 return getScheduleById(getTrainScheduleActiveId()); 074 } 075 076 /** 077 * @param name The schedule string name to search for. 078 * @return requested TrainSchedule object or null if none exists 079 */ 080 public TrainSchedule getScheduleByName(String name) { 081 TrainSchedule s; 082 Enumeration<TrainSchedule> en = _scheduleHashTable.elements(); 083 while (en.hasMoreElements()) { 084 s = en.nextElement(); 085 if (s.getName().equals(name)) { 086 return s; 087 } 088 } 089 return null; 090 } 091 092 public TrainSchedule getScheduleById(String id) { 093 return _scheduleHashTable.get(id); 094 } 095 096 /** 097 * Finds an existing schedule or creates a new schedule if needed requires 098 * schedule's name creates a unique id for this schedule 099 * 100 * @param name The string name of the schedule. 101 * 102 * 103 * @return new TrainSchedule or existing TrainSchedule 104 */ 105 public TrainSchedule newSchedule(String name) { 106 TrainSchedule schedule = getScheduleByName(name); 107 if (schedule == null) { 108 _id++; 109 schedule = new TrainSchedule(Integer.toString(_id), name); 110 Integer oldSize = Integer.valueOf(_scheduleHashTable.size()); 111 _scheduleHashTable.put(schedule.getId(), schedule); 112 setDirtyAndFirePropertyChange(LISTLENGTH_CHANGED_PROPERTY, oldSize, 113 Integer.valueOf(_scheduleHashTable.size())); 114 } 115 return schedule; 116 } 117 118 /** 119 * Remember a NamedBean Object created outside the manager. 120 * 121 * @param schedule The TrainSchedule to add. 122 */ 123 public void register(TrainSchedule schedule) { 124 Integer oldSize = Integer.valueOf(_scheduleHashTable.size()); 125 _scheduleHashTable.put(schedule.getId(), schedule); 126 // find last id created 127 int id = Integer.parseInt(schedule.getId()); 128 if (id > _id) { 129 _id = id; 130 } 131 setDirtyAndFirePropertyChange(LISTLENGTH_CHANGED_PROPERTY, oldSize, Integer.valueOf(_scheduleHashTable.size())); 132 } 133 134 /** 135 * Forget a NamedBean Object created outside the manager. 136 * 137 * @param schedule The TrainSchedule to delete. 138 */ 139 public void deregister(TrainSchedule schedule) { 140 if (schedule == null) { 141 return; 142 } 143 Integer oldSize = Integer.valueOf(_scheduleHashTable.size()); 144 _scheduleHashTable.remove(schedule.getId()); 145 setDirtyAndFirePropertyChange(LISTLENGTH_CHANGED_PROPERTY, oldSize, Integer.valueOf(_scheduleHashTable.size())); 146 } 147 148 /** 149 * Sort by train schedule name 150 * 151 * @return list of train schedules ordered by name 152 */ 153 public List<TrainSchedule> getSchedulesByNameList() { 154 List<TrainSchedule> sortList = getList(); 155 // now re-sort 156 List<TrainSchedule> out = new ArrayList<>(); 157 for (int i = 0; i < sortList.size(); i++) { 158 for (int j = 0; j < out.size(); j++) { 159 if (sortList.get(i).getName().compareToIgnoreCase(out.get(j).getName()) < 0) { 160 out.add(j, sortList.get(i)); 161 break; 162 } 163 } 164 if (!out.contains(sortList.get(i))) { 165 out.add(sortList.get(i)); 166 } 167 } 168 return out; 169 } 170 171 /** 172 * Sort by train schedule id numbers 173 * 174 * @return list of train schedules ordered by id numbers 175 */ 176 public List<TrainSchedule> getSchedulesByIdList() { 177 List<TrainSchedule> sortList = getList(); 178 // now re-sort 179 List<TrainSchedule> out = new ArrayList<>(); 180 for (int i = 0; i < sortList.size(); i++) { 181 for (int j = 0; j < out.size(); j++) { 182 try { 183 if (Integer.parseInt(sortList.get(i).getId()) < Integer.parseInt(out.get(j).getId())) { 184 out.add(j, sortList.get(i)); 185 break; 186 } 187 } catch (NumberFormatException e) { 188 log.debug("list id number isn't a number"); 189 } 190 } 191 if (!out.contains(sortList.get(i))) { 192 out.add(sortList.get(i)); 193 } 194 } 195 return out; 196 } 197 198 private List<TrainSchedule> getList() { 199 // no schedules? then load defaults 200 if (numEntries() == 0) { 201 createDefaultSchedules(); 202 } 203 List<TrainSchedule> out = new ArrayList<>(); 204 Enumeration<TrainSchedule> en = _scheduleHashTable.elements(); 205 while (en.hasMoreElements()) { 206 out.add(en.nextElement()); 207 } 208 return out; 209 } 210 211 /** 212 * Gets a JComboBox loaded with schedules starting with null. 213 * 214 * @return JComboBox with a list of schedules. 215 */ 216 public JComboBox<TrainSchedule> getComboBox() { 217 JComboBox<TrainSchedule> box = new JComboBox<>(); 218 updateComboBox(box); 219 OperationsPanel.padComboBox(box); 220 return box; 221 } 222 223 /** 224 * Gets a JComboBox loaded with schedules starting with null. 225 * 226 * @return JComboBox with a list of schedules starting with null. 227 */ 228 public JComboBox<TrainSchedule> getSelectComboBox() { 229 JComboBox<TrainSchedule> box = new JComboBox<>(); 230 box.addItem(null); 231 for (TrainSchedule sch : getSchedulesByIdList()) { 232 box.addItem(sch); 233 } 234 OperationsPanel.padComboBox(box); 235 return box; 236 } 237 238 /** 239 * Update a JComboBox with the latest schedules. 240 * 241 * @param box the JComboBox needing an update. 242 * @throws IllegalArgumentException if box is null 243 */ 244 public void updateComboBox(JComboBox<TrainSchedule> box) { 245 if (box == null) { 246 throw new IllegalArgumentException("Attempt to update non-existant comboBox"); 247 } 248 box.removeAllItems(); 249 for (TrainSchedule sch : getSchedulesByNameList()) { 250 box.addItem(sch); 251 } 252 } 253 254 public void buildSwitchLists() { 255 TrainSwitchLists trainSwitchLists = new TrainSwitchLists(); 256 TrainCsvSwitchLists trainCsvSwitchLists = new TrainCsvSwitchLists(); 257 String locationName = ""; // only create switch lists once for locations with similar names 258 for (Location location : InstanceManager.getDefault(LocationManager.class).getLocationsByNameList()) { 259 if (location.isSwitchListEnabled() && !locationName.equals(location.getSplitName())) { 260 trainCsvSwitchLists.buildSwitchList(location); 261 trainSwitchLists.buildSwitchList(location); 262 locationName = location.getSplitName(); 263 // print switch lists for locations that have changes 264 if (Setup.isSwitchListRealTime() && location.getStatus().equals(Location.UPDATED)) { 265 trainSwitchLists.printSwitchList(location, InstanceManager.getDefault(TrainManager.class).isPrintPreviewEnabled()); 266 } 267 } 268 } 269 // set trains switch lists printed 270 InstanceManager.getDefault(TrainManager.class).setTrainsSwitchListStatus(Train.PRINTED); 271 } 272 273 /** 274 * Create an XML element to represent this Entry. This member has to remain 275 * synchronized with the detailed DTD in operations-trains.dtd. 276 * 277 * @param root The common Element for operations-trains.dtd. 278 * 279 */ 280 public void store(Element root) { 281 Element e = new Element(Xml.TRAIN_SCHEDULE_OPTIONS); 282 e.setAttribute(Xml.ACTIVE_ID, InstanceManager.getDefault(TrainScheduleManager.class).getTrainScheduleActiveId()); 283 root.addContent(e); 284 Element values = new Element(Xml.SCHEDULES); 285 // add entries 286 List<TrainSchedule> schedules = getSchedulesByIdList(); 287 for (TrainSchedule schedule : schedules) { 288 values.addContent(schedule.store()); 289 } 290 root.addContent(values); 291 } 292 293 public void load(Element root) { 294 Element e = root.getChild(Xml.TRAIN_SCHEDULE_OPTIONS); 295 Attribute a; 296 if (e != null) { 297 if ((a = e.getAttribute(Xml.ACTIVE_ID)) != null) { 298 setTrainScheduleActiveId(a.getValue()); 299 } 300 } 301 302 e = root.getChild(Xml.SCHEDULES); 303 if (e != null) { 304 List<Element> eSchedules = root.getChild(Xml.SCHEDULES).getChildren(Xml.SCHEDULE); 305 log.debug("TrainScheduleManager sees {} train schedules", eSchedules.size()); 306 for (Element eSchedule : eSchedules) { 307 register(new TrainSchedule(eSchedule)); 308 } 309 } 310 } 311 312 public void createDefaultSchedules() { 313 log.debug("creating default schedules"); 314 newSchedule(Bundle.getMessage("Sunday")); 315 newSchedule(Bundle.getMessage("Monday")); 316 newSchedule(Bundle.getMessage("Tuesday")); 317 newSchedule(Bundle.getMessage("Wednesday")); 318 newSchedule(Bundle.getMessage("Thursday")); 319 newSchedule(Bundle.getMessage("Friday")); 320 newSchedule(Bundle.getMessage("Saturday")); 321 } 322 323 @Override 324 public void propertyChange(java.beans.PropertyChangeEvent e) { 325 log.debug("ScheduleManager sees property change: ({}) old: ({}) new ({})", 326 e.getPropertyName(), e.getOldValue(), e.getNewValue()); 327 } 328 329 protected void setDirtyAndFirePropertyChange(String p, Object old, Object n) { 330 InstanceManager.getDefault(TrainManagerXml.class).setDirty(true); 331 firePropertyChange(p, old, n); 332 } 333 334 private final static Logger log = LoggerFactory.getLogger(TrainScheduleManager.class); 335 336 @Override 337 public void initialize() { 338 InstanceManager.getDefault(TrainManagerXml.class); // load trains 339 } 340 341}