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