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