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