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.rollingstock.cars.CarLoad; 015import jmri.jmrit.operations.setup.OperationsSetupXml; 016 017/** 018 * Manages locations. 019 * 020 * @author Bob Jacobsen Copyright (C) 2003 021 * @author Daniel Boudreau Copyright (C) 2008, 2009, 2013, 2014 022 */ 023public class LocationManager extends PropertyChangeSupport implements InstanceManagerAutoDefault, InstanceManagerAutoInitialize, PropertyChangeListener { 024 025 public static final String LISTLENGTH_CHANGED_PROPERTY = "locationsListLength"; // NOI18N 026 027 protected boolean _showId = false; // when true show location ids 028 029 public LocationManager() { 030 } 031 032 private int _id = 0; 033 034 public void dispose() { 035 _locationHashTable.clear(); 036 _id = 0; 037 } 038 039 protected Hashtable<String, Location> _locationHashTable = new Hashtable<String, Location>(); 040 041 /** 042 * @return Number of locations 043 */ 044 public int getNumberOfLocations() { 045 return _locationHashTable.size(); 046 } 047 048 /** 049 * @param name The string name of the Location to get. 050 * @return requested Location object or null if none exists 051 */ 052 public Location getLocationByName(String name) { 053 Location location; 054 Enumeration<Location> en = _locationHashTable.elements(); 055 while (en.hasMoreElements()) { 056 location = en.nextElement(); 057 if (location.getName().equals(name)) { 058 return location; 059 } 060 } 061 return null; 062 } 063 064 public Location getLocationById(String id) { 065 return _locationHashTable.get(id); 066 } 067 068 /** 069 * Used to determine if a division name has been assigned to a location 070 * @return true if a location has a division name 071 */ 072 public boolean hasDivisions() { 073 for (Location location : getList()) { 074 if (location.getDivision() != null) { 075 return true; 076 } 077 } 078 return false; 079 } 080 081 public boolean hasWork() { 082 for (Location location : getList()) { 083 if (location.hasWork()) { 084 return true; 085 } 086 } 087 return false; 088 } 089 090 /** 091 * Used to determine if a reporter has been assigned to a location 092 * @return true if a location has a RFID reporter 093 */ 094 public boolean hasReporters() { 095 for (Location location : getList()) { 096 if (location.getReporter() != null) { 097 return true; 098 } 099 } 100 return false; 101 } 102 103 public void setShowIdEnabled(boolean showId) { 104 _showId = showId; 105 } 106 107 public boolean isShowIdEnabled() { 108 return _showId; 109 } 110 111 /** 112 * Request a location associated with a given reporter. 113 * 114 * @param r Reporter object associated with desired location. 115 * @return requested Location object or null if none exists 116 */ 117 public Location getLocationByReporter(Reporter r) { 118 for (Location location : _locationHashTable.values()) { 119 if (location.getReporter() != null) { 120 if (location.getReporter().equals(r)) { 121 return location; 122 } 123 } 124 } 125 return null; 126 } 127 128 /** 129 * Request a track associated with a given reporter. 130 * 131 * @param r Reporter object associated with desired location. 132 * @return requested Location object or null if none exists 133 */ 134 public Track getTrackByReporter(Reporter r) { 135 for (Track track : getTracks(null)) { 136 if (track.getReporter() != null) { 137 if (track.getReporter().equals(r)) { 138 return track; 139 } 140 } 141 } 142 return null; 143 } 144 145 /** 146 * Finds an existing location or creates a new location if needed requires 147 * location's name creates a unique id for this location 148 * 149 * @param name The string name for a new Location. 150 * 151 * 152 * @return new location or existing location 153 */ 154 public Location newLocation(String name) { 155 Location location = getLocationByName(name); 156 if (location == null) { 157 _id++; 158 location = new Location(Integer.toString(_id), name); 159 Integer oldSize = Integer.valueOf(_locationHashTable.size()); 160 _locationHashTable.put(location.getId(), location); 161 resetNameLengths(); 162 setDirtyAndFirePropertyChange(LISTLENGTH_CHANGED_PROPERTY, oldSize, 163 Integer.valueOf(_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 Integer oldSize = Integer.valueOf(_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, Integer.valueOf(_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 Integer oldSize = Integer.valueOf(_locationHashTable.size()); 195 _locationHashTable.remove(location.getId()); 196 setDirtyAndFirePropertyChange(LISTLENGTH_CHANGED_PROPERTY, oldSize, Integer.valueOf(_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 316 * are not 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 * @return locations for this railroad 359 */ 360 public JComboBox<Location> getComboBox() { 361 JComboBox<Location> box = new JComboBox<>(); 362 updateComboBox(box); 363 return box; 364 } 365 366 /** 367 * Updates JComboBox alphabetically with a list of locations. 368 * @param box The JComboBox to update. 369 */ 370 public void updateComboBox(JComboBox<Location> box) { 371 box.removeAllItems(); 372 box.addItem(null); 373 for (Location loc : getLocationsByNameList()) { 374 box.addItem(loc); 375 } 376 } 377 378 /** 379 * Replace all track car load names for a given type of car 380 * 381 * @param type type of car 382 * @param oldLoadName load name to replace 383 * @param newLoadName new load name 384 */ 385 public void replaceLoad(String type, String oldLoadName, String newLoadName) { 386 List<Location> locs = getList(); 387 for (Location loc : locs) { 388 // now adjust tracks 389 List<Track> tracks = loc.getTracksList(); 390 for (Track track : tracks) { 391 for (String loadName : track.getLoadNames()) { 392 if (loadName.equals(oldLoadName)) { 393 track.deleteLoadName(oldLoadName); 394 if (newLoadName != null) { 395 track.addLoadName(newLoadName); 396 } 397 } 398 // adjust combination car type and load name 399 String[] splitLoad = loadName.split(CarLoad.SPLIT_CHAR); 400 if (splitLoad.length > 1) { 401 if (splitLoad[0].equals(type) && splitLoad[1].equals(oldLoadName)) { 402 track.deleteLoadName(loadName); 403 if (newLoadName != null) { 404 track.addLoadName(type + CarLoad.SPLIT_CHAR + newLoadName); 405 } 406 } 407 } 408 } 409 // now adjust ship load names 410 for (String loadName : track.getShipLoadNames()) { 411 if (loadName.equals(oldLoadName)) { 412 track.deleteShipLoadName(oldLoadName); 413 if (newLoadName != null) { 414 track.addShipLoadName(newLoadName); 415 } 416 } 417 // adjust combination car type and load name 418 String[] splitLoad = loadName.split(CarLoad.SPLIT_CHAR); 419 if (splitLoad.length > 1) { 420 if (splitLoad[0].equals(type) && splitLoad[1].equals(oldLoadName)) { 421 track.deleteShipLoadName(loadName); 422 if (newLoadName != null) { 423 track.addShipLoadName(type + CarLoad.SPLIT_CHAR + newLoadName); 424 } 425 } 426 } 427 } 428 } 429 } 430 } 431 432 protected int _maxLocationNameLength = 0; 433 protected int _maxTrackNameLength = 0; 434 protected int _maxLocationAndTrackNameLength = 0; 435 436 public void resetNameLengths() { 437 _maxLocationNameLength = 0; 438 _maxTrackNameLength = 0; 439 _maxLocationAndTrackNameLength = 0; 440 } 441 442 public int getMaxLocationNameLength() { 443 calculateMaxNameLengths(); 444 return _maxLocationNameLength; 445 } 446 447 public int getMaxTrackNameLength() { 448 calculateMaxNameLengths(); 449 return _maxTrackNameLength; 450 } 451 452 public int getMaxLocationAndTrackNameLength() { 453 calculateMaxNameLengths(); 454 return _maxLocationAndTrackNameLength; 455 } 456 457 @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "SLF4J_FORMAT_SHOULD_BE_CONST", 458 justification = "I18N of Info Message") 459 private void calculateMaxNameLengths() { 460 if (_maxLocationNameLength != 0) // only do this once 461 { 462 return; 463 } 464 String maxTrackName = ""; 465 String maxLocNameForTrack = ""; 466 String maxLocationName = ""; 467 String maxLocationAndTrackName = ""; 468 for (Track track : getTracks(null)) { 469 if (track.getSplitName().length() > _maxTrackNameLength) { 470 maxTrackName = track.getName(); 471 maxLocNameForTrack = track.getLocation().getName(); 472 _maxTrackNameLength = track.getSplitName().length(); 473 } 474 if (track.getLocation().getSplitName().length() > _maxLocationNameLength) { 475 maxLocationName = track.getLocation().getName(); 476 _maxLocationNameLength = track.getLocation().getSplitName().length(); 477 } 478 if (track.getLocation().getSplitName().length() 479 + track.getSplitName().length() > _maxLocationAndTrackNameLength) { 480 maxLocationAndTrackName = track.getLocation().getName() + ", " + track.getName(); 481 _maxLocationAndTrackNameLength = track.getLocation().getSplitName().length() 482 + track.getSplitName().length(); 483 } 484 } 485 log.info(Bundle.getMessage("InfoMaxTrackName", maxTrackName, _maxTrackNameLength, maxLocNameForTrack)); 486 log.info(Bundle.getMessage("InfoMaxLocationName", maxLocationName, _maxLocationNameLength)); 487 log.info(Bundle.getMessage("InfoMaxLocAndTrackName", maxLocationAndTrackName, _maxLocationAndTrackNameLength)); 488 } 489 490 /** 491 * Load the locations from a xml file. 492 * @param root xml file 493 */ 494 public void load(Element root) { 495 if (root.getChild(Xml.LOCATIONS) != null) { 496 List<Element> locs = root.getChild(Xml.LOCATIONS).getChildren(Xml.LOCATION); 497 log.debug("readFile sees {} locations", locs.size()); 498 for (Element loc : locs) { 499 register(new Location(loc)); 500 } 501 } 502 } 503 504 public void store(Element root) { 505 Element values; 506 root.addContent(values = new Element(Xml.LOCATIONS)); 507 // add entries 508 List<Location> locationList = getLocationsByIdList(); 509 for (Location loc : locationList) { 510 values.addContent(loc.store()); 511 } 512 } 513 514 /** 515 * There aren't any current property changes being monitored. 516 */ 517 @Override 518 public void propertyChange(java.beans.PropertyChangeEvent e) { 519 log.debug("LocationManager sees property change: ({}) old: ({}) new: ({})", e.getPropertyName(), e 520 .getOldValue(), e.getNewValue()); // NOI18N 521 } 522 523 protected void setDirtyAndFirePropertyChange(String p, Object old, Object n) { 524 // set dirty 525 InstanceManager.getDefault(LocationManagerXml.class).setDirty(true); 526 firePropertyChange(p, old, n); 527 } 528 529 private final static Logger log = LoggerFactory.getLogger(LocationManager.class); 530 531 @Override 532 public void initialize() { 533 InstanceManager.getDefault(OperationsSetupXml.class); // load setup 534 InstanceManager.getDefault(LocationManagerXml.class); // load locations 535 } 536}