001package jmri.jmrit.operations.rollingstock; 002 003import java.beans.PropertyChangeEvent; 004import java.beans.PropertyChangeListener; 005import java.util.*; 006 007import javax.annotation.OverridingMethodsMustInvokeSuper; 008 009import org.slf4j.Logger; 010import org.slf4j.LoggerFactory; 011 012import jmri.beans.PropertyChangeSupport; 013import jmri.jmrit.operations.locations.Location; 014import jmri.jmrit.operations.locations.Track; 015import jmri.jmrit.operations.trains.Train; 016import jmri.jmrit.operations.trains.TrainCommon; 017 018/** 019 * Base class for rolling stock managers car and engine. 020 * 021 * @author Daniel Boudreau Copyright (C) 2010, 2011 022 * @param <T> the type of RollingStock managed by this manager 023 */ 024public abstract class RollingStockManager<T extends RollingStock> extends PropertyChangeSupport implements PropertyChangeListener { 025 026 public static final String NONE = ""; 027 028 // RollingStock 029 protected Hashtable<String, T> _hashTable = new Hashtable<>(); 030 031 public static final String LISTLENGTH_CHANGED_PROPERTY = "RollingStockListLength"; // NOI18N 032 033 abstract public RollingStock newRS(String road, String number); 034 035 public RollingStockManager() { 036 } 037 038 /** 039 * Get the number of items in the roster 040 * 041 * @return Number of rolling stock in the Roster 042 */ 043 public int getNumEntries() { 044 return _hashTable.size(); 045 } 046 047 public void dispose() { 048 deleteAll(); 049 } 050 051 /** 052 * Get rolling stock by id 053 * 054 * @param id The string id. 055 * 056 * @return requested RollingStock object or null if none exists 057 */ 058 public T getById(String id) { 059 return _hashTable.get(id); 060 } 061 062 /** 063 * Get rolling stock by road and number 064 * 065 * @param road RollingStock road 066 * @param number RollingStock number 067 * @return requested RollingStock object or null if none exists 068 */ 069 public T getByRoadAndNumber(String road, String number) { 070 String id = RollingStock.createId(road, number); 071 return getById(id); 072 } 073 074 /** 075 * Get a rolling stock by type and road. Used to test that rolling stock 076 * with a specific type and road exists. 077 * 078 * @param type RollingStock type. 079 * @param road RollingStock road. 080 * @return the first RollingStock found with the specified type and road. 081 */ 082 public T getByTypeAndRoad(String type, String road) { 083 Enumeration<String> en = _hashTable.keys(); 084 while (en.hasMoreElements()) { 085 T rs = getById(en.nextElement()); 086 if (rs.getTypeName().equals(type) && rs.getRoadName().equals(road)) { 087 return rs; 088 } 089 } 090 return null; 091 } 092 093 /** 094 * Get a rolling stock by Radio Frequency Identification (RFID) 095 * 096 * @param rfid RollingStock's RFID. 097 * @return the RollingStock with the specific RFID, or null if not found 098 */ 099 public T getByRfid(String rfid) { 100 Enumeration<String> en = _hashTable.keys(); 101 while (en.hasMoreElements()) { 102 T rs = getById(en.nextElement()); 103 if (rs.getRfid().equals(rfid)) { 104 return rs; 105 } 106 } 107 return null; 108 } 109 110 /** 111 * Load RollingStock. 112 * 113 * @param rs The RollingStock to load. 114 */ 115 public void register(T rs) { 116 if (!_hashTable.contains(rs)) { 117 int oldSize = _hashTable.size(); 118 rs.addPropertyChangeListener(this); 119 _hashTable.put(rs.getId(), rs); 120 firePropertyChange(LISTLENGTH_CHANGED_PROPERTY, oldSize, _hashTable.size()); 121 } 122 } 123 124 /** 125 * Unload RollingStock. 126 * 127 * @param rs The RollingStock to delete. 128 */ 129 public void deregister(T rs) { 130 rs.removePropertyChangeListener(this); 131 rs.dispose(); 132 int oldSize = _hashTable.size(); 133 _hashTable.remove(rs.getId()); 134 firePropertyChange(LISTLENGTH_CHANGED_PROPERTY, oldSize, _hashTable.size()); 135 } 136 137 /** 138 * Remove all RollingStock from roster 139 */ 140 public void deleteAll() { 141 int oldSize = _hashTable.size(); 142 Enumeration<String> en = _hashTable.keys(); 143 while (en.hasMoreElements()) { 144 T rs = getById(en.nextElement()); 145 rs.dispose(); 146 _hashTable.remove(rs.getId()); 147 } 148 firePropertyChange(LISTLENGTH_CHANGED_PROPERTY, oldSize, _hashTable.size()); 149 } 150 151 public void resetMoves() { 152 resetMoves(getList()); 153 } 154 155 public void resetMoves(List<T> list) { 156 for (RollingStock rs : list) { 157 rs.setMoves(0); 158 } 159 } 160 161 /** 162 * Returns a list (no order) of RollingStock. 163 * 164 * @return list of RollingStock 165 */ 166 public List<T> getList() { 167 return new ArrayList<>(_hashTable.values()); 168 } 169 170 /** 171 * Sort by rolling stock id 172 * 173 * @return list of RollingStock ordered by id 174 */ 175 public List<T> getByIdList() { 176 Enumeration<String> en = _hashTable.keys(); 177 String[] arr = new String[_hashTable.size()]; 178 List<T> out = new ArrayList<>(); 179 int i = 0; 180 while (en.hasMoreElements()) { 181 arr[i++] = en.nextElement(); 182 } 183 Arrays.sort(arr); 184 for (i = 0; i < arr.length; i++) { 185 out.add(getById(arr[i])); 186 } 187 return out; 188 } 189 190 /** 191 * Sort by rolling stock road name 192 * 193 * @return list of RollingStock ordered by road name 194 */ 195 public List<T> getByRoadNameList() { 196 return getByList(getByIdList(), BY_ROAD); 197 } 198 199 private static final int PAGE_SIZE = 64; 200 private static final int NOT_INTEGER = -999999999; // flag when RS number isn't an Integer 201 202 /** 203 * Sort by rolling stock number, number can be alphanumeric. RollingStock 204 * number can also be in the format of nnnn-N, where the "-N" allows the 205 * user to enter RollingStock with similar numbers. 206 * 207 * @return list of RollingStock ordered by number 208 */ 209 public List<T> getByNumberList() { 210 // first get by road list 211 List<T> sortIn = getByRoadNameList(); 212 // now re-sort 213 List<T> out = new ArrayList<>(); 214 int rsNumber = 0; 215 int outRsNumber = 0; 216 217 for (T rs : sortIn) { 218 boolean rsAdded = false; 219 try { 220 rsNumber = Integer.parseInt(rs.getNumber()); 221 rs.number = rsNumber; 222 } catch (NumberFormatException e) { 223 // maybe rolling stock number in the format nnnn-N 224 try { 225 String[] number = rs.getNumber().split(TrainCommon.HYPHEN); 226 rsNumber = Integer.parseInt(number[0]); 227 rs.number = rsNumber; 228 } catch (NumberFormatException e2) { 229 rs.number = NOT_INTEGER; 230 // sort alphanumeric numbers at the end of the out list 231 String numberIn = rs.getNumber(); 232 // log.debug("rolling stock in road number ("+numberIn+") isn't a number"); 233 for (int k = (out.size() - 1); k >= 0; k--) { 234 String numberOut = out.get(k).getNumber(); 235 try { 236 Integer.parseInt(numberOut); 237 // done, place rolling stock with alphanumeric 238 // number after rolling stocks with real numbers. 239 out.add(k + 1, rs); 240 rsAdded = true; 241 break; 242 } catch (NumberFormatException e3) { 243 if (numberIn.compareToIgnoreCase(numberOut) >= 0) { 244 out.add(k + 1, rs); 245 rsAdded = true; 246 break; 247 } 248 } 249 } 250 if (!rsAdded) { 251 out.add(0, rs); 252 } 253 continue; 254 } 255 } 256 257 int start = 0; 258 // page to improve sort performance. 259 int divisor = out.size() / PAGE_SIZE; 260 for (int k = divisor; k > 0; k--) { 261 outRsNumber = out.get((out.size() - 1) * k / divisor).number; 262 if (outRsNumber == NOT_INTEGER) { 263 continue; 264 } 265 if (rsNumber >= outRsNumber) { 266 start = (out.size() - 1) * k / divisor; 267 break; 268 } 269 } 270 for (int j = start; j < out.size(); j++) { 271 outRsNumber = out.get(j).number; 272 if (outRsNumber == NOT_INTEGER) { 273 try { 274 outRsNumber = Integer.parseInt(out.get(j).getNumber()); 275 } catch (NumberFormatException e) { 276 try { 277 String[] number = out.get(j).getNumber().split(TrainCommon.HYPHEN); 278 outRsNumber = Integer.parseInt(number[0]); 279 } catch (NumberFormatException e2) { 280 // force add 281 outRsNumber = rsNumber + 1; 282 } 283 } 284 } 285 if (rsNumber < outRsNumber) { 286 out.add(j, rs); 287 rsAdded = true; 288 break; 289 } 290 } 291 if (!rsAdded) { 292 out.add(rs); 293 } 294 } 295 // log.debug("end rolling stock sort by number list"); 296 return out; 297 } 298 299 /** 300 * Sort by rolling stock type names 301 * 302 * @return list of RollingStock ordered by RollingStock type 303 */ 304 public List<T> getByTypeList() { 305 return getByList(getByRoadNameList(), BY_TYPE); 306 } 307 308 /** 309 * Return rolling stock of a specific type 310 * 311 * @param type type of rolling stock 312 * @return list of RollingStock that are specific type 313 */ 314 public List<T> getByTypeList(String type) { 315 List<T> typeList = getByTypeList(); 316 List<T> out = new ArrayList<>(); 317 for (T rs : typeList) { 318 if (rs.getTypeName().equals(type)) { 319 out.add(rs); 320 } 321 } 322 return out; 323 } 324 325 /** 326 * Sort by rolling stock color names 327 * 328 * @return list of RollingStock ordered by RollingStock color 329 */ 330 public List<T> getByColorList() { 331 return getByList(getByTypeList(), BY_COLOR); 332 } 333 334 /** 335 * Sort by rolling stock location 336 * 337 * @return list of RollingStock ordered by RollingStock location 338 */ 339 public List<T> getByLocationList() { 340 return getByList(getByNumberList(), BY_LOCATION); 341 } 342 343 /** 344 * Sort by rolling stock destination 345 * 346 * @return list of RollingStock ordered by RollingStock destination 347 */ 348 public List<T> getByDestinationList() { 349 return getByList(getByLocationList(), BY_DESTINATION); 350 } 351 352 /** 353 * Sort by rolling stocks in trains 354 * 355 * @return list of RollingStock ordered by trains 356 */ 357 public List<T> getByTrainList() { 358 List<T> byDest = getByList(getByIdList(), BY_DESTINATION); 359 List<T> byLoc = getByList(byDest, BY_LOCATION); 360 return getByList(byLoc, BY_TRAIN); 361 } 362 363 /** 364 * Sort by rolling stock moves 365 * 366 * @return list of RollingStock ordered by RollingStock moves 367 */ 368 public List<T> getByMovesList() { 369 return getByList(getList(), BY_MOVES); 370 } 371 372 /** 373 * Sort by when rolling stock was built 374 * 375 * @return list of RollingStock ordered by RollingStock built date 376 */ 377 public List<T> getByBuiltList() { 378 return getByList(getByIdList(), BY_BUILT); 379 } 380 381 /** 382 * Sort by rolling stock owner 383 * 384 * @return list of RollingStock ordered by RollingStock owner 385 */ 386 public List<T> getByOwnerList() { 387 return getByList(getByIdList(), BY_OWNER); 388 } 389 390 /** 391 * Sort by rolling stock value 392 * 393 * @return list of RollingStock ordered by value 394 */ 395 public List<T> getByValueList() { 396 return getByList(getByIdList(), BY_VALUE); 397 } 398 399 /** 400 * Sort by rolling stock RFID 401 * 402 * @return list of RollingStock ordered by RFIDs 403 */ 404 public List<T> getByRfidList() { 405 return getByList(getByIdList(), BY_RFID); 406 } 407 408 /** 409 * Get a list of all rolling stock sorted last date used 410 * 411 * @return list of RollingStock ordered by last date 412 */ 413 public List<T> getByLastDateList() { 414 return getByList(getByIdList(), BY_LAST); 415 } 416 417 public List<T> getByCommentList() { 418 return getByList(getByIdList(), BY_COMMENT); 419 } 420 421 /** 422 * Sort a specific list of rolling stock last date used 423 * 424 * @param inList list of rolling stock to sort. 425 * @return list of RollingStock ordered by last date 426 */ 427 public List<T> getByLastDateList(List<T> inList) { 428 return getByList(inList, BY_LAST); 429 } 430 431 protected List<T> getByList(List<T> sortIn, int attribute) { 432 List<T> out = new ArrayList<>(sortIn); 433 out.sort(getComparator(attribute)); 434 return out; 435 } 436 437 // The various sort options for RollingStock 438 // see CarManager and EngineManger for other values 439 protected static final int BY_NUMBER = 0; 440 protected static final int BY_ROAD = 1; 441 protected static final int BY_TYPE = 2; 442 protected static final int BY_COLOR = 3; 443 protected static final int BY_LOCATION = 4; 444 protected static final int BY_DESTINATION = 5; 445 protected static final int BY_TRAIN = 6; 446 protected static final int BY_MOVES = 7; 447 protected static final int BY_BUILT = 8; 448 protected static final int BY_OWNER = 9; 449 protected static final int BY_RFID = 10; 450 protected static final int BY_VALUE = 11; 451 protected static final int BY_LAST = 12; 452 protected static final int BY_BLOCKING = 13; 453 protected static final int BY_COMMENT = 14; 454 455 protected java.util.Comparator<T> getComparator(int attribute) { 456 switch (attribute) { 457 case BY_NUMBER: 458 return (r1, r2) -> (r1.getNumber().compareToIgnoreCase(r2.getNumber())); 459 case BY_ROAD: 460 return (r1, r2) -> (r1.getRoadName().compareToIgnoreCase(r2.getRoadName())); 461 case BY_TYPE: 462 return (r1, r2) -> (r1.getTypeName().compareToIgnoreCase(r2.getTypeName())); 463 case BY_COLOR: 464 return (r1, r2) -> (r1.getColor().compareToIgnoreCase(r2.getColor())); 465 case BY_LOCATION: 466 return (r1, r2) -> (r1.getStatus() + r1.getLocationName() + r1.getTrackName()) 467 .compareToIgnoreCase(r2.getStatus() + r2.getLocationName() + r2.getTrackName()); 468 case BY_DESTINATION: 469 return (r1, r2) -> (r1.getDestinationName() + r1.getDestinationTrackName()) 470 .compareToIgnoreCase(r2.getDestinationName() + r2.getDestinationTrackName()); 471 case BY_TRAIN: 472 return (r1, r2) -> (r1.getTrainName().compareToIgnoreCase(r2.getTrainName())); 473 case BY_MOVES: 474 return (r1, r2) -> (r1.getMoves() - r2.getMoves()); 475 case BY_BUILT: 476 return (r1, 477 r2) -> (convertBuildDate(r1.getBuilt()).compareToIgnoreCase(convertBuildDate(r2.getBuilt()))); 478 case BY_OWNER: 479 return (r1, r2) -> (r1.getOwnerName().compareToIgnoreCase(r2.getOwnerName())); 480 case BY_RFID: 481 return (r1, r2) -> (r1.getRfid().compareToIgnoreCase(r2.getRfid())); 482 case BY_VALUE: 483 return (r1, r2) -> (r1.getValue().compareToIgnoreCase(r2.getValue())); 484 case BY_LAST: 485 return (r1, r2) -> (r1.getLastMoveDate().compareTo(r2.getLastMoveDate())); 486 case BY_BLOCKING: 487 return (r1, r2) -> (r1.getBlocking() - r2.getBlocking()); 488 case BY_COMMENT: 489 return (r1, r2) -> (r1.getComment().compareToIgnoreCase(r2.getComment())); 490 default: 491 return (r1, r2) -> ((r1.getRoadName() + r1.getNumber()) 492 .compareToIgnoreCase(r2.getRoadName() + r2.getNumber())); 493 } 494 } 495 496 /* 497 * Converts build date into consistent String. Three build date formats; Two 498 * digits YY becomes 19YY. MM-YY becomes 19YY. MM-YYYY becomes YYYY. 499 */ 500 public static String convertBuildDate(String date) { 501 String[] built = date.split("-"); 502 if (built.length == 2) { 503 try { 504 int d = Integer.parseInt(built[1]); 505 if (d < 100) { 506 d = d + 1900; 507 } 508 return Integer.toString(d); 509 } catch (NumberFormatException e) { 510 log.debug("Unable to parse built date ({})", date); 511 } 512 } else { 513 try { 514 int d = Integer.parseInt(date); 515 if (d < 100) { 516 d = d + 1900; 517 } 518 return Integer.toString(d); 519 } catch (NumberFormatException e) { 520 log.debug("Unable to parse built date ({})", date); 521 } 522 } 523 return date; 524 } 525 526 /** 527 * Get a list of rolling stocks assigned to a train ordered by location 528 * 529 * @param train The Train. 530 * 531 * @return List of RollingStock assigned to the train ordered by location 532 */ 533 public List<T> getByTrainList(Train train) { 534 return getByList(getList(train), BY_LOCATION); 535 } 536 537 /** 538 * Returns a list (no order) of RollingStock in a train. 539 * 540 * @param train The Train. 541 * 542 * @return list of RollingStock 543 */ 544 public List<T> getList(Train train) { 545 List<T> out = new ArrayList<>(); 546 _hashTable.values().stream().filter((rs) -> { 547 return rs.getTrain() == train; 548 }).forEachOrdered((rs) -> { 549 out.add(rs); 550 }); 551 return out; 552 } 553 554 /** 555 * Returns a list (no order) of RollingStock at a location. 556 * 557 * @param location location to search for. 558 * @return list of RollingStock 559 */ 560 public List<T> getList(Location location) { 561 List<T> out = new ArrayList<>(); 562 _hashTable.values().stream().filter((rs) -> { 563 return rs.getLocation() == location; 564 }).forEachOrdered((rs) -> { 565 out.add(rs); 566 }); 567 return out; 568 } 569 570 /** 571 * Returns a list (no order) of RollingStock on a track. 572 * 573 * @param track Track to search for. 574 * @return list of RollingStock 575 */ 576 public List<T> getList(Track track) { 577 List<T> out = new ArrayList<>(); 578 _hashTable.values().stream().filter((rs) -> { 579 return rs.getTrack() == track; 580 }).forEachOrdered((rs) -> { 581 out.add(rs); 582 }); 583 return out; 584 } 585 586 @Override 587 @OverridingMethodsMustInvokeSuper 588 public void propertyChange(PropertyChangeEvent evt) { 589 if (evt.getPropertyName().equals(Xml.ID)) { 590 @SuppressWarnings("unchecked") 591 T rs = (T) evt.getSource(); // unchecked cast to T 592 _hashTable.remove(evt.getOldValue()); 593 _hashTable.put(rs.getId(), rs); 594 // fire so listeners that rebuild internal lists get signal of change in id, even without change in size 595 firePropertyChange(LISTLENGTH_CHANGED_PROPERTY, _hashTable.size(), _hashTable.size()); 596 } 597 } 598 599 private final static Logger log = LoggerFactory.getLogger(RollingStockManager.class); 600 601}