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 int oldSize = _scheduleHashTable.size(); 111 _scheduleHashTable.put(schedule.getId(), schedule); 112 setDirtyAndFirePropertyChange(LISTLENGTH_CHANGED_PROPERTY, oldSize, _scheduleHashTable.size()); 113 } 114 return schedule; 115 } 116 117 /** 118 * Remember a NamedBean Object created outside the manager. 119 * 120 * @param schedule The TrainSchedule to add. 121 */ 122 public void register(TrainSchedule schedule) { 123 int oldSize = _scheduleHashTable.size(); 124 _scheduleHashTable.put(schedule.getId(), schedule); 125 // find last id created 126 int id = Integer.parseInt(schedule.getId()); 127 if (id > _id) { 128 _id = id; 129 } 130 setDirtyAndFirePropertyChange(LISTLENGTH_CHANGED_PROPERTY, oldSize, _scheduleHashTable.size()); 131 } 132 133 /** 134 * Forget a NamedBean Object created outside the manager. 135 * 136 * @param schedule The TrainSchedule to delete. 137 */ 138 public void deregister(TrainSchedule schedule) { 139 if (schedule == null) { 140 return; 141 } 142 int oldSize = _scheduleHashTable.size(); 143 _scheduleHashTable.remove(schedule.getId()); 144 setDirtyAndFirePropertyChange(LISTLENGTH_CHANGED_PROPERTY, oldSize, _scheduleHashTable.size()); 145 } 146 147 /** 148 * Sort by train schedule name 149 * 150 * @return list of train schedules ordered by name 151 */ 152 public List<TrainSchedule> getSchedulesByNameList() { 153 List<TrainSchedule> sortList = getList(); 154 // now re-sort 155 List<TrainSchedule> out = new ArrayList<>(); 156 for (int i = 0; i < sortList.size(); i++) { 157 for (int j = 0; j < out.size(); j++) { 158 if (sortList.get(i).getName().compareToIgnoreCase(out.get(j).getName()) < 0) { 159 out.add(j, sortList.get(i)); 160 break; 161 } 162 } 163 if (!out.contains(sortList.get(i))) { 164 out.add(sortList.get(i)); 165 } 166 } 167 return out; 168 } 169 170 /** 171 * Sort by train schedule id numbers 172 * 173 * @return list of train schedules ordered by id numbers 174 */ 175 public List<TrainSchedule> getSchedulesByIdList() { 176 List<TrainSchedule> sortList = getList(); 177 // now re-sort 178 List<TrainSchedule> out = new ArrayList<>(); 179 for (int i = 0; i < sortList.size(); i++) { 180 for (int j = 0; j < out.size(); j++) { 181 try { 182 if (Integer.parseInt(sortList.get(i).getId()) < Integer.parseInt(out.get(j).getId())) { 183 out.add(j, sortList.get(i)); 184 break; 185 } 186 } catch (NumberFormatException e) { 187 log.debug("list id number isn't a number"); 188 } 189 } 190 if (!out.contains(sortList.get(i))) { 191 out.add(sortList.get(i)); 192 } 193 } 194 return out; 195 } 196 197 private List<TrainSchedule> getList() { 198 // no schedules? then load defaults 199 if (numEntries() == 0) { 200 createDefaultSchedules(); 201 } 202 List<TrainSchedule> out = new ArrayList<>(); 203 Enumeration<TrainSchedule> en = _scheduleHashTable.elements(); 204 while (en.hasMoreElements()) { 205 out.add(en.nextElement()); 206 } 207 return out; 208 } 209 210 /** 211 * Gets a JComboBox loaded with schedules starting with null. 212 * 213 * @return JComboBox with a list of schedules. 214 */ 215 public JComboBox<TrainSchedule> getComboBox() { 216 JComboBox<TrainSchedule> box = new JComboBox<>(); 217 updateComboBox(box); 218 OperationsPanel.padComboBox(box); 219 return box; 220 } 221 222 /** 223 * Gets a JComboBox loaded with schedules starting with null. 224 * 225 * @return JComboBox with a list of schedules starting with null. 226 */ 227 public JComboBox<TrainSchedule> getSelectComboBox() { 228 JComboBox<TrainSchedule> box = new JComboBox<>(); 229 box.addItem(null); 230 for (TrainSchedule sch : getSchedulesByIdList()) { 231 box.addItem(sch); 232 } 233 OperationsPanel.padComboBox(box); 234 return box; 235 } 236 237 /** 238 * Update a JComboBox with the latest schedules. 239 * 240 * @param box the JComboBox needing an update. 241 * @throws IllegalArgumentException if box is null 242 */ 243 public void updateComboBox(JComboBox<TrainSchedule> box) { 244 if (box == null) { 245 throw new IllegalArgumentException("Attempt to update non-existant comboBox"); 246 } 247 box.removeAllItems(); 248 for (TrainSchedule sch : getSchedulesByNameList()) { 249 box.addItem(sch); 250 } 251 } 252 253 public void buildSwitchLists() { 254 TrainSwitchLists trainSwitchLists = new TrainSwitchLists(); 255 TrainCsvSwitchLists trainCsvSwitchLists = new TrainCsvSwitchLists(); 256 String locationName = ""; // only create switch lists once for locations with similar names 257 for (Location location : InstanceManager.getDefault(LocationManager.class).getLocationsByNameList()) { 258 if (location.isSwitchListEnabled() && !locationName.equals(location.getSplitName())) { 259 trainCsvSwitchLists.buildSwitchList(location); 260 trainSwitchLists.buildSwitchList(location); 261 locationName = location.getSplitName(); 262 // print switch lists for locations that have changes 263 if (Setup.isSwitchListRealTime() && location.getStatus().equals(Location.UPDATED)) { 264 trainSwitchLists.printSwitchList(location, InstanceManager.getDefault(TrainManager.class).isPrintPreviewEnabled()); 265 } 266 } 267 } 268 // set trains switch lists printed 269 InstanceManager.getDefault(TrainManager.class).setTrainsSwitchListStatus(Train.PRINTED); 270 } 271 272 /** 273 * Create an XML element to represent this Entry. This member has to remain 274 * synchronized with the detailed DTD in operations-trains.dtd. 275 * 276 * @param root The common Element for operations-trains.dtd. 277 * 278 */ 279 public void store(Element root) { 280 Element e = new Element(Xml.TRAIN_SCHEDULE_OPTIONS); 281 e.setAttribute(Xml.ACTIVE_ID, InstanceManager.getDefault(TrainScheduleManager.class).getTrainScheduleActiveId()); 282 root.addContent(e); 283 Element values = new Element(Xml.SCHEDULES); 284 // add entries 285 List<TrainSchedule> schedules = getSchedulesByIdList(); 286 for (TrainSchedule schedule : schedules) { 287 values.addContent(schedule.store()); 288 } 289 root.addContent(values); 290 } 291 292 public void load(Element root) { 293 Element e = root.getChild(Xml.TRAIN_SCHEDULE_OPTIONS); 294 Attribute a; 295 if (e != null) { 296 if ((a = e.getAttribute(Xml.ACTIVE_ID)) != null) { 297 setTrainScheduleActiveId(a.getValue()); 298 } 299 } 300 301 e = root.getChild(Xml.SCHEDULES); 302 if (e != null) { 303 List<Element> eSchedules = root.getChild(Xml.SCHEDULES).getChildren(Xml.SCHEDULE); 304 log.debug("TrainScheduleManager sees {} train schedules", eSchedules.size()); 305 for (Element eSchedule : eSchedules) { 306 register(new TrainSchedule(eSchedule)); 307 } 308 } 309 } 310 311 public void createDefaultSchedules() { 312 log.debug("creating default schedules"); 313 for (String s : getDaysOfWeek()) { 314 newSchedule(s); 315 } 316 } 317 318 public String[] getDaysOfWeek() { 319 String[] s = {Bundle.getMessage("Sunday"), Bundle.getMessage("Monday"), Bundle.getMessage("Tuesday"), 320 Bundle.getMessage("Wednesday"), Bundle.getMessage("Thursday"), Bundle.getMessage("Friday"), 321 Bundle.getMessage("Saturday")}; 322 return s; 323 } 324 325 @Override 326 public void propertyChange(java.beans.PropertyChangeEvent e) { 327 log.debug("ScheduleManager sees property change: ({}) old: ({}) new ({})", 328 e.getPropertyName(), e.getOldValue(), e.getNewValue()); 329 } 330 331 protected void setDirtyAndFirePropertyChange(String p, Object old, Object n) { 332 InstanceManager.getDefault(TrainManagerXml.class).setDirty(true); 333 firePropertyChange(p, old, n); 334 } 335 336 private static final Logger log = LoggerFactory.getLogger(TrainScheduleManager.class); 337 338 @Override 339 public void initialize() { 340 InstanceManager.getDefault(TrainManagerXml.class); // load trains 341 } 342 343}