001package jmri.jmrit.operations.locations; 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.rollingstock.cars.CarLoad; 016import jmri.jmrit.operations.setup.OperationsSetupXml; 017 018/** 019 * Manages locations. 020 * 021 * @author Bob Jacobsen Copyright (C) 2003 022 * @author Daniel Boudreau Copyright (C) 2008, 2009, 2013, 2014 023 */ 024public class LocationManager extends PropertyChangeSupport implements InstanceManagerAutoDefault, InstanceManagerAutoInitialize, PropertyChangeListener { 025 026 public static final String LISTLENGTH_CHANGED_PROPERTY = "locationsListLength"; // NOI18N 027 028 protected boolean _showId = false; // when true show location ids 029 030 public LocationManager() { 031 } 032 033 private int _id = 0; 034 035 public void dispose() { 036 _locationHashTable.clear(); 037 _id = 0; 038 } 039 040 protected Hashtable<String, Location> _locationHashTable = new Hashtable<String, Location>(); 041 042 /** 043 * @return Number of locations 044 */ 045 public int getNumberOfLocations() { 046 return _locationHashTable.size(); 047 } 048 049 /** 050 * @param name The string name of the Location to get. 051 * @return requested Location object or null if none exists 052 */ 053 public Location getLocationByName(String name) { 054 Location location; 055 Enumeration<Location> en = _locationHashTable.elements(); 056 while (en.hasMoreElements()) { 057 location = en.nextElement(); 058 if (location.getName().equals(name)) { 059 return location; 060 } 061 } 062 return null; 063 } 064 065 public Location getLocationById(String id) { 066 return _locationHashTable.get(id); 067 } 068 069 /** 070 * Used to determine if a division name has been assigned to a location 071 * 072 * @return true if a location has a division name 073 */ 074 public boolean hasDivisions() { 075 for (Location location : getList()) { 076 if (location.getDivision() != null) { 077 return true; 078 } 079 } 080 return false; 081 } 082 083 public boolean hasWork() { 084 for (Location location : getList()) { 085 if (location.hasWork()) { 086 return true; 087 } 088 } 089 return false; 090 } 091 092 /** 093 * Used to determine if a reporter has been assigned to a location 094 * 095 * @return true if a location has a RFID reporter 096 */ 097 public boolean hasReporters() { 098 for (Location location : getList()) { 099 if (location.getReporter() != null) { 100 return true; 101 } 102 } 103 return false; 104 } 105 106 public void setShowIdEnabled(boolean showId) { 107 _showId = showId; 108 } 109 110 public boolean isShowIdEnabled() { 111 return _showId; 112 } 113 114 /** 115 * Request a location associated with a given reporter. 116 * 117 * @param r Reporter object associated with desired location. 118 * @return requested Location object or null if none exists 119 */ 120 public Location getLocationByReporter(Reporter r) { 121 for (Location location : _locationHashTable.values()) { 122 if (location.getReporter() != null) { 123 if (location.getReporter().equals(r)) { 124 return location; 125 } 126 } 127 } 128 return null; 129 } 130 131 /** 132 * Request a track associated with a given reporter. 133 * 134 * @param r Reporter object associated with desired location. 135 * @return requested Location object or null if none exists 136 */ 137 public Track getTrackByReporter(Reporter r) { 138 for (Track track : getTracks(null)) { 139 if (track.getReporter() != null) { 140 if (track.getReporter().equals(r)) { 141 return track; 142 } 143 } 144 } 145 return null; 146 } 147 148 /** 149 * Finds an existing location or creates a new location if needed requires 150 * location's name creates a unique id for this location 151 * 152 * @param name The string name for a new Location. 153 * @return new location or existing location 154 */ 155 public Location newLocation(String name) { 156 Location location = getLocationByName(name); 157 if (location == null) { 158 _id++; 159 location = new Location(Integer.toString(_id), name); 160 int oldSize = _locationHashTable.size(); 161 _locationHashTable.put(location.getId(), location); 162 resetNameLengths(); 163 setDirtyAndFirePropertyChange(LISTLENGTH_CHANGED_PROPERTY, oldSize, _locationHashTable.size()); 164 } 165 return location; 166 } 167 168 /** 169 * Remember a NamedBean Object created outside the manager. 170 * 171 * @param location The Location to add. 172 */ 173 public void register(Location location) { 174 int oldSize = _locationHashTable.size(); 175 _locationHashTable.put(location.getId(), location); 176 // find last id created 177 int id = Integer.parseInt(location.getId()); 178 if (id > _id) { 179 _id = id; 180 } 181 setDirtyAndFirePropertyChange(LISTLENGTH_CHANGED_PROPERTY, oldSize, _locationHashTable.size()); 182 } 183 184 /** 185 * Forget a NamedBean Object created outside the manager. 186 * 187 * @param location The Location to delete. 188 */ 189 public void deregister(Location location) { 190 if (location == null) { 191 return; 192 } 193 location.dispose(); 194 int oldSize = _locationHashTable.size(); 195 _locationHashTable.remove(location.getId()); 196 setDirtyAndFirePropertyChange(LISTLENGTH_CHANGED_PROPERTY, oldSize, _locationHashTable.size()); 197 } 198 199 /** 200 * Sort by location name 201 * 202 * @return list of locations ordered by name 203 */ 204 public List<Location> getLocationsByNameList() { 205 // first get id list 206 List<Location> sortList = getList(); 207 // now re-sort 208 List<Location> out = new ArrayList<Location>(); 209 for (Location location : sortList) { 210 for (int j = 0; j < out.size(); j++) { 211 if (location.getName().compareToIgnoreCase(out.get(j).getName()) < 0) { 212 out.add(j, location); 213 break; 214 } 215 } 216 if (!out.contains(location)) { 217 out.add(location); 218 } 219 } 220 return out; 221 } 222 223 /** 224 * Get unique locations list by location name. 225 * 226 * @return list of locations ordered by name. Locations with "similar" names 227 * to the primary location are not returned. Also checks and updates 228 * the primary location for any changes to the other "similar" 229 * locations. 230 */ 231 public List<Location> getUniqueLocationsByNameList() { 232 List<Location> locations = getLocationsByNameList(); 233 List<Location> out = new ArrayList<Location>(); 234 Location mainLocation = null; 235 236 // also update the primary location for locations with similar names 237 for (Location location : locations) { 238 String name = location.getSplitName(); 239 if (mainLocation != null && mainLocation.getSplitName().equals(name)) { 240 location.setSwitchListEnabled(mainLocation.isSwitchListEnabled()); 241 if (mainLocation.isSwitchListEnabled() && location.getStatus().equals(Location.MODIFIED)) { 242 mainLocation.setStatus(Location.MODIFIED); // we need to update the primary location 243 location.setStatus(Location.UPDATED); // and clear the secondaries 244 } 245 continue; 246 } 247 mainLocation = location; 248 out.add(location); 249 } 250 return out; 251 } 252 253 /** 254 * Sort by location number, number can alpha numeric 255 * 256 * @return list of locations ordered by id numbers 257 */ 258 public List<Location> getLocationsByIdList() { 259 List<Location> sortList = getList(); 260 // now re-sort 261 List<Location> out = new ArrayList<Location>(); 262 for (Location location : sortList) { 263 for (int j = 0; j < out.size(); j++) { 264 try { 265 if (Integer.parseInt(location.getId()) < Integer.parseInt(out.get(j).getId())) { 266 out.add(j, location); 267 break; 268 } 269 } catch (NumberFormatException e) { 270 log.debug("list id number isn't a number"); 271 } 272 } 273 if (!out.contains(location)) { 274 out.add(location); 275 } 276 } 277 return out; 278 } 279 280 /** 281 * Gets an unsorted list of all locations. 282 * 283 * @return All locations. 284 */ 285 public List<Location> getList() { 286 List<Location> out = new ArrayList<Location>(); 287 Enumeration<Location> en = _locationHashTable.elements(); 288 while (en.hasMoreElements()) { 289 out.add(en.nextElement()); 290 } 291 return out; 292 } 293 294 /** 295 * Returns all tracks of type 296 * 297 * @param type Spur (Track.SPUR), Yard (Track.YARD), Interchange 298 * (Track.INTERCHANGE), Staging (Track.STAGING), or null 299 * (returns all track types) 300 * @return List of tracks 301 */ 302 public List<Track> getTracks(String type) { 303 List<Location> sortList = getList(); 304 List<Track> trackList = new ArrayList<Track>(); 305 for (Location location : sortList) { 306 List<Track> tracks = location.getTracksByNameList(type); 307 for (Track track : tracks) { 308 trackList.add(track); 309 } 310 } 311 return trackList; 312 } 313 314 /** 315 * Returns all tracks of type sorted by use. Alternate tracks are not 316 * included. 317 * 318 * @param type Spur (Track.SPUR), Yard (Track.YARD), Interchange 319 * (Track.INTERCHANGE), Staging (Track.STAGING), or null 320 * (returns all track types) 321 * @return List of tracks ordered by use 322 */ 323 public List<Track> getTracksByMoves(String type) { 324 List<Track> trackList = getTracks(type); 325 // now re-sort 326 List<Track> moveList = new ArrayList<Track>(); 327 for (Track track : trackList) { 328 boolean locAdded = false; 329 if (track.isAlternate()) { 330 continue; 331 } 332 for (int j = 0; j < moveList.size(); j++) { 333 if (track.getMoves() < moveList.get(j).getMoves()) { 334 moveList.add(j, track); 335 locAdded = true; 336 break; 337 } 338 } 339 if (!locAdded) { 340 moveList.add(track); 341 } 342 } 343 return moveList; 344 } 345 346 /** 347 * Sets move count to 0 for all tracks 348 */ 349 public void resetMoves() { 350 List<Location> locations = getList(); 351 for (Location loc : locations) { 352 loc.resetMoves(); 353 } 354 } 355 356 /** 357 * Returns a JComboBox with locations sorted alphabetically. 358 * 359 * @return locations for this railroad 360 */ 361 public JComboBox<Location> getComboBox() { 362 JComboBox<Location> box = new JComboBox<>(); 363 updateComboBox(box); 364 OperationsPanel.padComboBox(box, getMaxLocationNameLength()); 365 return box; 366 } 367 368 /** 369 * Updates JComboBox alphabetically with a list of locations. 370 * 371 * @param box The JComboBox to update. 372 */ 373 public void updateComboBox(JComboBox<Location> box) { 374 box.removeAllItems(); 375 box.addItem(null); 376 for (Location loc : getLocationsByNameList()) { 377 box.addItem(loc); 378 } 379 } 380 381 /** 382 * Replace all track car load names for a given type of car 383 * 384 * @param type type of car 385 * @param oldLoadName load name to replace 386 * @param newLoadName new load name 387 */ 388 public void replaceLoad(String type, String oldLoadName, String newLoadName) { 389 List<Location> locs = getList(); 390 for (Location loc : locs) { 391 // now adjust tracks 392 List<Track> tracks = loc.getTracksList(); 393 for (Track track : tracks) { 394 for (String loadName : track.getLoadNames()) { 395 if (loadName.equals(oldLoadName)) { 396 track.deleteLoadName(oldLoadName); 397 if (newLoadName != null) { 398 track.addLoadName(newLoadName); 399 } 400 } 401 // adjust combination car type and load name 402 String[] splitLoad = loadName.split(CarLoad.SPLIT_CHAR); 403 if (splitLoad.length > 1) { 404 if (splitLoad[0].equals(type) && splitLoad[1].equals(oldLoadName)) { 405 track.deleteLoadName(loadName); 406 if (newLoadName != null) { 407 track.addLoadName(type + CarLoad.SPLIT_CHAR + newLoadName); 408 } 409 } 410 } 411 } 412 // now adjust ship load names 413 for (String loadName : track.getShipLoadNames()) { 414 if (loadName.equals(oldLoadName)) { 415 track.deleteShipLoadName(oldLoadName); 416 if (newLoadName != null) { 417 track.addShipLoadName(newLoadName); 418 } 419 } 420 // adjust combination car type and load name 421 String[] splitLoad = loadName.split(CarLoad.SPLIT_CHAR); 422 if (splitLoad.length > 1) { 423 if (splitLoad[0].equals(type) && splitLoad[1].equals(oldLoadName)) { 424 track.deleteShipLoadName(loadName); 425 if (newLoadName != null) { 426 track.addShipLoadName(type + CarLoad.SPLIT_CHAR + newLoadName); 427 } 428 } 429 } 430 } 431 } 432 } 433 } 434 435 protected int _maxLocationNameLength = 0; 436 protected int _maxTrackNameLength = 0; 437 protected int _maxLocationAndTrackNameLength = 0; 438 439 public void resetNameLengths() { 440 _maxLocationNameLength = 0; 441 _maxTrackNameLength = 0; 442 _maxLocationAndTrackNameLength = 0; 443 } 444 445 public int getMaxLocationNameLength() { 446 calculateMaxNameLengths(); 447 return _maxLocationNameLength; 448 } 449 450 public int getMaxTrackNameLength() { 451 calculateMaxNameLengths(); 452 return _maxTrackNameLength; 453 } 454 455 public int getMaxLocationAndTrackNameLength() { 456 calculateMaxNameLengths(); 457 return _maxLocationAndTrackNameLength; 458 } 459 460 @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "SLF4J_FORMAT_SHOULD_BE_CONST", 461 justification = "I18N of Info Message") 462 private void calculateMaxNameLengths() { 463 if (_maxLocationNameLength != 0) // only do this once 464 { 465 return; 466 } 467 String maxTrackName = ""; 468 String maxLocNameForTrack = ""; 469 String maxLocationName = ""; 470 String maxLocationAndTrackName = ""; 471 for (Track track : getTracks(null)) { 472 if (track.getSplitName().length() > _maxTrackNameLength) { 473 maxTrackName = track.getName(); 474 maxLocNameForTrack = track.getLocation().getName(); 475 _maxTrackNameLength = track.getSplitName().length(); 476 } 477 if (track.getLocation().getSplitName().length() > _maxLocationNameLength) { 478 maxLocationName = track.getLocation().getName(); 479 _maxLocationNameLength = track.getLocation().getSplitName().length(); 480 } 481 if (track.getLocation().getSplitName().length() + 482 track.getSplitName().length() > _maxLocationAndTrackNameLength) { 483 maxLocationAndTrackName = track.getLocation().getName() + ", " + track.getName(); 484 _maxLocationAndTrackNameLength = 485 track.getLocation().getSplitName().length() + track.getSplitName().length(); 486 } 487 } 488 log.info(Bundle.getMessage("InfoMaxTrackName", maxTrackName, _maxTrackNameLength, maxLocNameForTrack)); 489 log.info(Bundle.getMessage("InfoMaxLocationName", maxLocationName, _maxLocationNameLength)); 490 log.info(Bundle.getMessage("InfoMaxLocAndTrackName", maxLocationAndTrackName, _maxLocationAndTrackNameLength)); 491 } 492 493 /** 494 * Load the locations from a xml file. 495 * 496 * @param root xml file 497 */ 498 public void load(Element root) { 499 if (root.getChild(Xml.LOCATIONS) != null) { 500 List<Element> locs = root.getChild(Xml.LOCATIONS).getChildren(Xml.LOCATION); 501 log.debug("readFile sees {} locations", locs.size()); 502 for (Element loc : locs) { 503 register(new Location(loc)); 504 } 505 } 506 } 507 508 public void store(Element root) { 509 Element values; 510 root.addContent(values = new Element(Xml.LOCATIONS)); 511 // add entries 512 List<Location> locationList = getLocationsByIdList(); 513 for (Location loc : locationList) { 514 values.addContent(loc.store()); 515 } 516 } 517 518 /** 519 * There aren't any current property changes being monitored. 520 */ 521 @Override 522 public void propertyChange(java.beans.PropertyChangeEvent e) { 523 log.debug("LocationManager sees property change: ({}) old: ({}) new: ({})", e.getPropertyName(), e 524 .getOldValue(), e.getNewValue()); // NOI18N 525 } 526 527 protected void setDirtyAndFirePropertyChange(String p, Object old, Object n) { 528 // set dirty 529 InstanceManager.getDefault(LocationManagerXml.class).setDirty(true); 530 firePropertyChange(p, old, n); 531 } 532 533 private static final Logger log = LoggerFactory.getLogger(LocationManager.class); 534 535 @Override 536 public void initialize() { 537 InstanceManager.getDefault(OperationsSetupXml.class); // load setup 538 InstanceManager.getDefault(LocationManagerXml.class); // load locations 539 } 540}