001package jmri.jmrit.operations.locations.schedules; 002 003import java.beans.PropertyChangeListener; 004import java.util.*; 005 006import javax.swing.JComboBox; 007 008import org.jdom2.Element; 009import org.slf4j.Logger; 010import org.slf4j.LoggerFactory; 011 012import jmri.*; 013import jmri.beans.PropertyChangeSupport; 014import jmri.jmrit.operations.OperationsPanel; 015import jmri.jmrit.operations.locations.*; 016import jmri.jmrit.operations.rollingstock.cars.CarRoads; 017import jmri.jmrit.operations.rollingstock.cars.CarTypes; 018import jmri.jmrit.operations.setup.Control; 019 020/** 021 * Manages schedules. 022 * 023 * @author Bob Jacobsen Copyright (C) 2003 024 * @author Daniel Boudreau Copyright (C) 2008, 2013 025 */ 026public class ScheduleManager extends PropertyChangeSupport implements InstanceManagerAutoDefault, InstanceManagerAutoInitialize, PropertyChangeListener { 027 028 public static final String LISTLENGTH_CHANGED_PROPERTY = "scheduleListLength"; // NOI18N 029 030 public ScheduleManager() { 031 } 032 033 private int _id = 0; 034 035 public void dispose() { 036 _scheduleHashTable.clear(); 037 } 038 039 // stores known Schedule instances by id 040 protected Hashtable<String, Schedule> _scheduleHashTable = new Hashtable<String, Schedule>(); 041 042 /** 043 * @return Number of schedules 044 */ 045 public int numEntries() { 046 return _scheduleHashTable.size(); 047 } 048 049 /** 050 * @param name The string name for the schedule 051 * @return requested Schedule object or null if none exists 052 */ 053 public Schedule getScheduleByName(String name) { 054 Schedule s; 055 Enumeration<Schedule> en = _scheduleHashTable.elements(); 056 while (en.hasMoreElements()) { 057 s = en.nextElement(); 058 if (s.getName().equals(name)) { 059 return s; 060 } 061 } 062 return null; 063 } 064 065 public Schedule getScheduleById(String id) { 066 return _scheduleHashTable.get(id); 067 } 068 069 /** 070 * Finds an existing schedule or creates a new schedule if needed requires 071 * schedule's name creates a unique id for this schedule 072 * 073 * @param name The string name for this schedule 074 * 075 * 076 * @return new schedule or existing schedule 077 */ 078 public Schedule newSchedule(String name) { 079 Schedule schedule = getScheduleByName(name); 080 if (schedule == null && !name.isBlank()) { 081 _id++; 082 schedule = new Schedule(Integer.toString(_id), name); 083 int oldSize = _scheduleHashTable.size(); 084 _scheduleHashTable.put(schedule.getId(), schedule); 085 setDirtyAndFirePropertyChange(LISTLENGTH_CHANGED_PROPERTY, oldSize, _scheduleHashTable.size()); 086 } 087 return schedule; 088 } 089 090 /** 091 * Remember a NamedBean Object created outside the manager. 092 * 093 * @param schedule The Schedule to add. 094 */ 095 public void register(Schedule schedule) { 096 int oldSize = _scheduleHashTable.size(); 097 _scheduleHashTable.put(schedule.getId(), schedule); 098 // find last id created 099 int id = Integer.parseInt(schedule.getId()); 100 if (id > _id) { 101 _id = id; 102 } 103 setDirtyAndFirePropertyChange(LISTLENGTH_CHANGED_PROPERTY, oldSize, _scheduleHashTable.size()); 104 } 105 106 /** 107 * Forget a NamedBean Object created outside the manager. 108 * 109 * @param schedule The Schedule to delete. 110 */ 111 public void deregister(Schedule schedule) { 112 if (schedule == null) { 113 return; 114 } 115 schedule.dispose(); 116 int oldSize = _scheduleHashTable.size(); 117 _scheduleHashTable.remove(schedule.getId()); 118 setDirtyAndFirePropertyChange(LISTLENGTH_CHANGED_PROPERTY, oldSize, _scheduleHashTable.size()); 119 } 120 121 /** 122 * Sort by schedule name 123 * 124 * @return list of schedules ordered by name 125 */ 126 public List<Schedule> getSchedulesByNameList() { 127 List<Schedule> sortList = getList(); 128 // now re-sort 129 List<Schedule> out = new ArrayList<Schedule>(); 130 for (Schedule sch : sortList) { 131 for (int j = 0; j < out.size(); j++) { 132 if (sch.getName().compareToIgnoreCase(out.get(j).getName()) < 0) { 133 out.add(j, sch); 134 break; 135 } 136 } 137 if (!out.contains(sch)) { 138 out.add(sch); 139 } 140 } 141 return out; 142 143 } 144 145 /** 146 * Sort by schedule id number 147 * 148 * @return list of schedules ordered by id number 149 */ 150 public List<Schedule> getSchedulesByIdList() { 151 List<Schedule> sortList = getList(); 152 // now re-sort 153 List<Schedule> out = new ArrayList<Schedule>(); 154 for (Schedule sch : sortList) { 155 for (int j = 0; j < out.size(); j++) { 156 try { 157 if (Integer.parseInt(sch.getId()) < Integer.parseInt(out.get(j).getId())) { 158 out.add(j, sch); 159 break; 160 } 161 } catch (NumberFormatException e) { 162 log.debug("list id number isn't a number"); 163 } 164 } 165 if (!out.contains(sch)) { 166 out.add(sch); 167 } 168 } 169 return out; 170 } 171 172 private List<Schedule> getList() { 173 List<Schedule> out = new ArrayList<Schedule>(); 174 Enumeration<Schedule> en = _scheduleHashTable.elements(); 175 while (en.hasMoreElements()) { 176 out.add(en.nextElement()); 177 } 178 return out; 179 } 180 181 public Schedule copySchedule(Schedule schedule, String newScheduleName) { 182 Schedule newSchedule = newSchedule(newScheduleName); 183 for (ScheduleItem si : schedule.getItemsBySequenceList()) { 184 ScheduleItem newSi = newSchedule.addItem(si.getTypeName()); 185 newSi.copyScheduleItem(si); 186 } 187 return newSchedule; 188 } 189 190 public void resetHitCounts() { 191 for (Schedule schedule : getList()) { 192 schedule.resetHitCounts(); 193 } 194 } 195 196 /** 197 * Gets a JComboBox loaded with schedules. 198 * 199 * @return JComboBox with a list of schedules. 200 */ 201 public JComboBox<Schedule> getComboBox() { 202 JComboBox<Schedule> box = new JComboBox<>(); 203 OperationsPanel.padComboBox(box, Control.max_len_string_location_name); 204 updateComboBox(box); 205 return box; 206 } 207 208 /** 209 * Update a JComboBox with the latest schedules. 210 * 211 * @param box the JComboBox needing an update. 212 */ 213 public void updateComboBox(JComboBox<Schedule> box) { 214 box.removeAllItems(); 215 box.addItem(null); 216 for (Schedule schedule : getSchedulesByNameList()) { 217 box.addItem(schedule); 218 } 219 } 220 221 /** 222 * Replaces car type in all schedules. 223 * 224 * @param oldType car type to be replaced. 225 * @param newType replacement car type. 226 */ 227 public void replaceType(String oldType, String newType) { 228 for (Schedule sch : getSchedulesByIdList()) { 229 for (ScheduleItem si : sch.getItemsBySequenceList()) { 230 if (si.getTypeName().equals(oldType)) { 231 si.setTypeName(newType); 232 } 233 } 234 } 235 } 236 237 /** 238 * Replaces car roads in all schedules. 239 * 240 * @param oldRoad car road to be replaced. 241 * @param newRoad replacement car road. 242 */ 243 public void replaceRoad(String oldRoad, String newRoad) { 244 if (newRoad == null) { 245 return; 246 } 247 for (Schedule sch : getSchedulesByIdList()) { 248 for (ScheduleItem si : sch.getItemsBySequenceList()) { 249 if (si.getRoadName().equals(oldRoad)) { 250 si.setRoadName(newRoad); 251 } 252 } 253 } 254 } 255 256 /** 257 * Replaces car loads in all schedules with specific car type. 258 * 259 * @param type car type. 260 * @param oldLoad car load to be replaced. 261 * @param newLoad replacement car load. 262 */ 263 public void replaceLoad(String type, String oldLoad, String newLoad) { 264 for (Schedule sch : getSchedulesByIdList()) { 265 for (ScheduleItem si : sch.getItemsBySequenceList()) { 266 if (si.getTypeName().equals(type) && si.getReceiveLoadName().equals(oldLoad)) { 267 if (newLoad != null) { 268 si.setReceiveLoadName(newLoad); 269 } else { 270 si.setReceiveLoadName(ScheduleItem.NONE); 271 } 272 } 273 if (si.getTypeName().equals(type) && si.getShipLoadName().equals(oldLoad)) { 274 if (newLoad != null) { 275 si.setShipLoadName(newLoad); 276 } else { 277 si.setShipLoadName(ScheduleItem.NONE); 278 } 279 } 280 } 281 } 282 } 283 284 public void replaceTrack(Track oldTrack, Track newTrack) { 285 for (Schedule sch : getSchedulesByIdList()) { 286 for (ScheduleItem si : sch.getItemsBySequenceList()) { 287 if (si.getDestinationTrack() == oldTrack) { 288 si.setDestination(newTrack.getLocation()); 289 si.setDestinationTrack(newTrack); 290 } 291 } 292 } 293 } 294 295 /** 296 * Gets a JComboBox with a list of spurs that use this schedule. 297 * 298 * @param schedule The schedule for this JComboBox. 299 * @return JComboBox with a list of spurs using schedule. 300 */ 301 public JComboBox<LocationTrackPair> getSpursByScheduleComboBox(Schedule schedule) { 302 JComboBox<LocationTrackPair> box = new JComboBox<>(); 303 // search all spurs for that use schedule 304 for (Track spur : getListSpurs(schedule)) { 305 if (spur.getScheduleId().equals(schedule.getId())) { 306 LocationTrackPair ltp = new LocationTrackPair(spur); 307 box.addItem(ltp); 308 } 309 } 310 return box; 311 } 312 313 public List<Track> getListSpurs(Schedule schedule) { 314 List<Track> spurs = new ArrayList<Track>(); 315 for (Location location : InstanceManager.getDefault(LocationManager.class).getLocationsByNameList()) { 316 for (Track spur : location.getTracksByNameList(Track.SPUR)) { 317 if (spur.getScheduleId().equals(schedule.getId())) { 318 spurs.add(spur); 319 } 320 } 321 } 322 return spurs; 323 } 324 325 public void load(Element root) { 326 if (root.getChild(Xml.SCHEDULES) != null) { 327 List<Element> eSchedules = root.getChild(Xml.SCHEDULES).getChildren(Xml.SCHEDULE); 328 log.debug("readFile sees {} schedules", eSchedules.size()); 329 for (Element eSchedule : eSchedules) { 330 register(new Schedule(eSchedule)); 331 } 332 } 333 } 334 335 public void store(Element root) { 336 Element values; 337 root.addContent(values = new Element(Xml.SCHEDULES)); 338 // add entries 339 for (Schedule schedule : getSchedulesByIdList()) { 340 values.addContent(schedule.store()); 341 } 342 } 343 344 /** 345 * Check for car type and road name changes. 346 */ 347 @Override 348 public void propertyChange(java.beans.PropertyChangeEvent e) { 349 if (Control.SHOW_PROPERTY) { 350 log.debug("Property change: ({}) old: ({}) new: ({})", e.getPropertyName(), e.getOldValue(), e 351 .getNewValue()); 352 } 353 if (e.getPropertyName().equals(CarTypes.CARTYPES_NAME_CHANGED_PROPERTY)) { 354 replaceType((String) e.getOldValue(), (String) e.getNewValue()); 355 } 356 if (e.getPropertyName().equals(CarRoads.CARROADS_NAME_CHANGED_PROPERTY)) { 357 replaceRoad((String) e.getOldValue(), (String) e.getNewValue()); 358 } 359 } 360 361 protected void setDirtyAndFirePropertyChange(String p, Object old, Object n) { 362 // set dirty 363 InstanceManager.getDefault(LocationManagerXml.class).setDirty(true); 364 firePropertyChange(p, old, n); 365 } 366 367 private static final Logger log = LoggerFactory.getLogger(ScheduleManager.class); 368 369 @Override 370 public void initialize() { 371 InstanceManager.getDefault(CarTypes.class).addPropertyChangeListener(this); 372 InstanceManager.getDefault(CarRoads.class).addPropertyChangeListener(this); 373 } 374 375}