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