001package jmri.jmrit.operations.rollingstock.cars; 002 003import java.text.NumberFormat; 004import java.util.*; 005 006import org.jdom2.Element; 007import org.slf4j.Logger; 008import org.slf4j.LoggerFactory; 009 010import jmri.*; 011import jmri.jmrit.operations.rollingstock.RollingStockManager; 012import jmri.jmrit.operations.routes.Route; 013import jmri.jmrit.operations.routes.RouteLocation; 014import jmri.jmrit.operations.setup.OperationsSetupXml; 015import jmri.jmrit.operations.setup.Setup; 016import jmri.jmrit.operations.trains.Train; 017 018/** 019 * Manages the cars. 020 * 021 * @author Daniel Boudreau Copyright (C) 2008 022 */ 023public class CarManager extends RollingStockManager<Car> 024 implements InstanceManagerAutoDefault, InstanceManagerAutoInitialize { 025 026 public CarManager() { 027 } 028 029 /** 030 * Finds an existing Car or creates a new Car if needed requires car's road and 031 * number 032 * 033 * @param road car road 034 * @param number car number 035 * @return new car or existing Car 036 */ 037 @Override 038 public Car newRS(String road, String number) { 039 Car car = getByRoadAndNumber(road, number); 040 if (car == null) { 041 car = new Car(road, number); 042 register(car); 043 } 044 return car; 045 } 046 047 @Override 048 public void deregister(Car car) { 049 super.deregister(car); 050 InstanceManager.getDefault(CarManagerXml.class).setDirty(true); 051 } 052 053 /** 054 * Sort by rolling stock location 055 * 056 * @return list of cars ordered by the Car's location 057 */ 058 @Override 059 public List<Car> getByLocationList() { 060 return getByList(getByKernelList(), BY_LOCATION); 061 } 062 063 /** 064 * Sort by car kernel names 065 * 066 * @return list of cars ordered by car kernel 067 */ 068 public List<Car> getByKernelList() { 069 return getByList(getByList(getByNumberList(), BY_BLOCKING), BY_KERNEL); 070 } 071 072 /** 073 * Sort by car loads 074 * 075 * @return list of cars ordered by car loads 076 */ 077 public List<Car> getByLoadList() { 078 return getByList(getByLocationList(), BY_LOAD); 079 } 080 081 /** 082 * Sort by car return when empty location and track 083 * 084 * @return list of cars ordered by car return when empty 085 */ 086 public List<Car> getByRweList() { 087 return getByList(getByLocationList(), BY_RWE); 088 } 089 090 public List<Car> getByRwlList() { 091 return getByList(getByLocationList(), BY_RWL); 092 } 093 094 public List<Car> getByDivisionList() { 095 return getByList(getByLocationList(), BY_DIVISION); 096 } 097 098 public List<Car> getByFinalDestinationList() { 099 return getByList(getByDestinationList(), BY_FINAL_DEST); 100 } 101 102 /** 103 * Sort by car wait count 104 * 105 * @return list of cars ordered by wait count 106 */ 107 public List<Car> getByWaitList() { 108 return getByList(getByIdList(), BY_WAIT); 109 } 110 111 public List<Car> getByPickupList() { 112 return getByList(getByIdList(), BY_PICKUP); 113 } 114 115 // The special sort options for cars 116 private static final int BY_LOAD = 4; 117 private static final int BY_KERNEL = 5; 118 private static final int BY_RWE = 13; // Return When Empty 119 private static final int BY_FINAL_DEST = 14; 120 private static final int BY_WAIT = 16; 121 private static final int BY_PICKUP = 19; 122 private static final int BY_HAZARD = 21; 123 private static final int BY_RWL = 22; // Return When loaded 124 private static final int BY_DIVISION = 23; 125 126 // add car options to sort comparator 127 @Override 128 protected java.util.Comparator<Car> getComparator(int attribute) { 129 switch (attribute) { 130 case BY_LOAD: 131 return (c1, c2) -> (c1.getLoadName().compareToIgnoreCase(c2.getLoadName())); 132 case BY_KERNEL: 133 return (c1, c2) -> (c1.getKernelName().compareToIgnoreCase(c2.getKernelName())); 134 case BY_RWE: 135 return (c1, 136 c2) -> (c1.getReturnWhenEmptyDestName().compareToIgnoreCase(c2.getReturnWhenEmptyDestName())); 137 case BY_RWL: 138 return (c1, 139 c2) -> (c1.getReturnWhenLoadedDestName().compareToIgnoreCase(c2.getReturnWhenLoadedDestName())); 140 case BY_FINAL_DEST: 141 return (c1, c2) -> (c1.getFinalDestinationName().compareToIgnoreCase(c2.getFinalDestinationName())); 142 case BY_DIVISION: 143 return (c1, c2) -> (c1.getDivisionName().compareToIgnoreCase(c2.getDivisionName())); 144 case BY_WAIT: 145 return (c1, c2) -> (c1.getWait() - c2.getWait()); 146 case BY_PICKUP: 147 return (c1, c2) -> (c1.getPickupScheduleName().compareToIgnoreCase(c2.getPickupScheduleName())); 148 case BY_HAZARD: 149 return (c1, c2) -> ((c1.isHazardous() ? 1 : 0) - (c2.isHazardous() ? 1 : 0)); 150 default: 151 return super.getComparator(attribute); 152 } 153 } 154 155 /** 156 * Return a list available cars (no assigned train or car already assigned to 157 * this train) on a route, cars are ordered least recently moved to most 158 * recently moved. 159 * 160 * @param train The Train to use. 161 * 162 * @return List of cars with no assigned train on a route 163 */ 164 public List<Car> getAvailableTrainList(Train train) { 165 List<Car> out = new ArrayList<>(); 166 Route route = train.getRoute(); 167 if (route == null) { 168 return out; 169 } 170 // get a list of locations served by this route 171 List<RouteLocation> routeList = route.getLocationsBySequenceList(); 172 // don't include Car at route destination 173 RouteLocation destination = null; 174 if (routeList.size() > 1) { 175 destination = routeList.get(routeList.size() - 1); 176 // However, if the destination is visited more than once, must 177 // include all cars 178 for (int i = 0; i < routeList.size() - 1; i++) { 179 if (destination.getName().equals(routeList.get(i).getName())) { 180 destination = null; // include cars at destination 181 break; 182 } 183 } 184 // pickup allowed at destination? Don't include cars in staging 185 if (destination != null && 186 destination.isPickUpAllowed() && 187 destination.getLocation() != null && 188 !destination.getLocation().isStaging()) { 189 destination = null; // include cars at destination 190 } 191 } 192 // get rolling stock by priority and then by moves 193 List<Car> sortByPriority = sortByPriority(getByMovesList()); 194 // now build list of available Car for this route 195 for (Car car : sortByPriority) { 196 // only use Car with a location 197 if (car.getLocation() == null) { 198 continue; 199 } 200 RouteLocation rl = route.getLastLocationByName(car.getLocationName()); 201 // get Car that don't have an assigned train, or the 202 // assigned train is this one 203 if (rl != null && rl != destination && (car.getTrain() == null || train.equals(car.getTrain()))) { 204 out.add(car); 205 } 206 } 207 return out; 208 } 209 210 // sorts the high priority cars to the start of the list 211 protected List<Car> sortByPriority(List<Car> list) { 212 List<Car> out = new ArrayList<>(); 213 // move high priority cars to the start 214 for (Car car : list) { 215 if (car.getLoadPriority().equals(CarLoad.PRIORITY_HIGH)) { 216 out.add(car); 217 } 218 } 219 for (Car car : list) { 220 if (car.getLoadPriority().equals(CarLoad.PRIORITY_MEDIUM)) { 221 out.add(car); 222 } 223 } 224 // now load all of the remaining low priority cars 225 for (Car car : list) { 226 if (!out.contains(car)) { 227 out.add(car); 228 } 229 } 230 return out; 231 } 232 233 /** 234 * Provides a very sorted list of cars assigned to the train. Note that this 235 * isn't the final sort as the cars must be sorted by each location the 236 * train visits. 237 * <p> 238 * The sort priority is as follows: 239 * <ol> 240 * <li>Caboose or car with FRED to the end of the list, unless passenger. 241 * <li>Passenger cars have blocking numbers which places them relative to 242 * each other. Passenger cars with positive blocking numbers to the end of 243 * the list, but before cabooses or car with FRED. Passenger cars with 244 * negative blocking numbers are placed at the front of the train. 245 * <li>Car's destination (alphabetical by location and track name or by 246 * track blocking order) 247 * <li>Car is hazardous (hazardous placed after a non-hazardous car) 248 * <li>Car's current location (alphabetical by location and track name) 249 * <li>Car's final destination (alphabetical by location and track name) 250 * </ol> 251 * <p> 252 * Cars in a kernel are placed together by their kernel blocking numbers, 253 * except if they are type passenger. The kernel's position in the list is 254 * based on the lead car in the kernel. 255 * <p> 256 * If the train is to be blocked by track blocking order, all of the tracks 257 * at that location need a blocking number greater than 0. 258 * 259 * @param train The selected Train. 260 * @return Ordered list of cars assigned to the train 261 */ 262 public List<Car> getByTrainDestinationList(Train train) { 263 List<Car> byFinal = getByList(getList(train), BY_FINAL_DEST); 264 List<Car> byLocation = getByList(byFinal, BY_LOCATION); 265 List<Car> byHazard = getByList(byLocation, BY_HAZARD); 266 List<Car> byDestination = getByList(byHazard, BY_DESTINATION); 267 // now place cabooses, cars with FRED, and passenger cars at the rear of the 268 // train 269 List<Car> out = new ArrayList<>(); 270 int lastCarsIndex = 0; // incremented each time a car is added to the end of the list 271 for (Car car : byDestination) { 272 if (car.getKernel() != null && !car.isLead() && !car.isPassenger()) { 273 continue; // not the lead car, skip for now. 274 } 275 if (!car.isCaboose() && !car.hasFred() && !car.isPassenger()) { 276 // sort order based on train direction when serving track, low to high if West 277 // or North bound trains 278 if (car.getDestinationTrack() != null && car.getDestinationTrack().getBlockingOrder() > 0) { 279 for (int j = 0; j < out.size(); j++) { 280 if (out.get(j).getDestinationTrack() == null) { 281 continue; 282 } 283 if (car.getRouteDestination() != null && 284 (car.getRouteDestination().getTrainDirectionString().equals(RouteLocation.WEST_DIR) || 285 car.getRouteDestination().getTrainDirectionString() 286 .equals(RouteLocation.NORTH_DIR))) { 287 if (car.getDestinationTrack().getBlockingOrder() < out.get(j).getDestinationTrack() 288 .getBlockingOrder()) { 289 out.add(j, car); 290 break; 291 } 292 // Train is traveling East or South when setting out the car 293 } else { 294 if (car.getDestinationTrack().getBlockingOrder() > out.get(j).getDestinationTrack() 295 .getBlockingOrder()) { 296 out.add(j, car); 297 break; 298 } 299 } 300 } 301 } 302 if (!out.contains(car)) { 303 out.add(out.size() - lastCarsIndex, car); 304 } 305 } else if (car.isPassenger()) { 306 if (car.getBlocking() < 0) { 307 // block passenger cars with negative blocking numbers at 308 // front of train 309 int index; 310 for (index = 0; index < out.size(); index++) { 311 Car carTest = out.get(index); 312 if (!carTest.isPassenger() || carTest.getBlocking() > car.getBlocking()) { 313 break; 314 } 315 } 316 out.add(index, car); 317 } else { 318 // block passenger cars at end of list, but before cabooses 319 // or car with FRED 320 int index; 321 for (index = 0; index < lastCarsIndex; index++) { 322 Car carTest = out.get(out.size() - 1 - index); 323 log.debug("Car ({}) has blocking number: {}", carTest.toString(), carTest.getBlocking()); 324 if (carTest.isPassenger() && 325 !carTest.isCaboose() && 326 !carTest.hasFred() && 327 carTest.getBlocking() < car.getBlocking()) { 328 break; 329 } 330 } 331 out.add(out.size() - index, car); 332 lastCarsIndex++; 333 } 334 } else if (car.isCaboose() || car.hasFred()) { 335 out.add(car); // place at end of list 336 lastCarsIndex++; 337 } 338 // group the cars in the kernel together, except passenger 339 if (car.isLead()) { 340 int index = out.indexOf(car); 341 int numberOfCars = 1; // already added the lead car to the list 342 for (Car kcar : car.getKernel().getCars()) { 343 if (car != kcar && !kcar.isPassenger()) { 344 // Block cars in kernel 345 for (int j = 0; j < numberOfCars; j++) { 346 if (kcar.getBlocking() < out.get(index + j).getBlocking()) { 347 out.add(index + j, kcar); 348 break; 349 } 350 } 351 if (!out.contains(kcar)) { 352 out.add(index + numberOfCars, kcar); 353 } 354 numberOfCars++; 355 if (car.hasFred() || car.isCaboose() || car.isPassenger() && car.getBlocking() > 0) { 356 lastCarsIndex++; // place entire kernel at the end of list 357 } 358 } 359 } 360 } 361 } 362 return out; 363 } 364 365 /** 366 * Get a list of car road names where the car was flagged as a caboose. 367 * 368 * @return List of caboose road names. 369 */ 370 public List<String> getCabooseRoadNames() { 371 List<String> names = new ArrayList<>(); 372 Enumeration<String> en = _hashTable.keys(); 373 while (en.hasMoreElements()) { 374 Car car = getById(en.nextElement()); 375 if (car.isCaboose() && !names.contains(car.getRoadName())) { 376 names.add(car.getRoadName()); 377 } 378 } 379 java.util.Collections.sort(names); 380 return names; 381 } 382 383 /** 384 * Get a list of car road names where the car was flagged with FRED 385 * 386 * @return List of road names of cars with FREDs 387 */ 388 public List<String> getFredRoadNames() { 389 List<String> names = new ArrayList<>(); 390 Enumeration<String> en = _hashTable.keys(); 391 while (en.hasMoreElements()) { 392 Car car = getById(en.nextElement()); 393 if (car.hasFred() && !names.contains(car.getRoadName())) { 394 names.add(car.getRoadName()); 395 } 396 } 397 java.util.Collections.sort(names); 398 return names; 399 } 400 401 /** 402 * Replace car loads 403 * 404 * @param type type of car 405 * @param oldLoadName old load name 406 * @param newLoadName new load name 407 */ 408 public void replaceLoad(String type, String oldLoadName, String newLoadName) { 409 List<Car> cars = getList(); 410 for (Car car : cars) { 411 if (car.getTypeName().equals(type) && car.getLoadName().equals(oldLoadName)) { 412 if (newLoadName != null) { 413 car.setLoadName(newLoadName); 414 } else { 415 car.setLoadName(InstanceManager.getDefault(CarLoads.class).getDefaultEmptyName()); 416 } 417 } 418 if (car.getTypeName().equals(type) && car.getReturnWhenEmptyLoadName().equals(oldLoadName)) { 419 if (newLoadName != null) { 420 car.setReturnWhenEmptyLoadName(newLoadName); 421 } else { 422 car.setReturnWhenEmptyLoadName(InstanceManager.getDefault(CarLoads.class).getDefaultEmptyName()); 423 } 424 } 425 if (car.getTypeName().equals(type) && car.getReturnWhenLoadedLoadName().equals(oldLoadName)) { 426 if (newLoadName != null) { 427 car.setReturnWhenLoadedLoadName(newLoadName); 428 } else { 429 car.setReturnWhenLoadedLoadName(InstanceManager.getDefault(CarLoads.class).getDefaultLoadName()); 430 } 431 } 432 } 433 } 434 435 public List<Car> getCarsLocationUnknown() { 436 List<Car> mias = new ArrayList<>(); 437 List<Car> cars = getByIdList(); 438 for (Car rs : cars) { 439 Car car = rs; 440 if (car.isLocationUnknown()) { 441 mias.add(car); // return unknown location car 442 } 443 } 444 return mias; 445 } 446 447 /** 448 * Determines a car's weight in ounces based on car's scale length 449 * 450 * @param carLength Car's scale length 451 * @return car's weight in ounces 452 * @throws NumberFormatException if length isn't a number 453 */ 454 public static String calculateCarWeight(String carLength) throws NumberFormatException { 455 double doubleCarLength = Double.parseDouble(carLength) * 12 / Setup.getScaleRatio(); 456 double doubleCarWeight = (Setup.getInitalWeight() + doubleCarLength * Setup.getAddWeight()) / 1000; 457 NumberFormat nf = NumberFormat.getNumberInstance(); 458 nf.setMaximumFractionDigits(1); 459 return nf.format(doubleCarWeight); // car weight in ounces. 460 } 461 462 public void load(Element root) { 463 if (root.getChild(Xml.CARS) != null) { 464 List<Element> eCars = root.getChild(Xml.CARS).getChildren(Xml.CAR); 465 log.debug("readFile sees {} cars", eCars.size()); 466 for (Element eCar : eCars) { 467 register(new Car(eCar)); 468 } 469 } 470 } 471 472 /** 473 * Create an XML element to represent this Entry. This member has to remain 474 * synchronized with the detailed DTD in operations-cars.dtd. 475 * 476 * @param root The common Element for operations-cars.dtd. 477 */ 478 public void store(Element root) { 479 // nothing to save under options 480 root.addContent(new Element(Xml.OPTIONS)); 481 482 Element values; 483 root.addContent(values = new Element(Xml.CARS)); 484 // add entries 485 List<Car> carList = getByIdList(); 486 for (Car rs : carList) { 487 Car car = rs; 488 values.addContent(car.store()); 489 } 490 } 491 492 protected void setDirtyAndFirePropertyChange(String p, Object old, Object n) { 493 // Set dirty 494 InstanceManager.getDefault(CarManagerXml.class).setDirty(true); 495 super.firePropertyChange(p, old, n); 496 } 497 498 private final static Logger log = LoggerFactory.getLogger(CarManager.class); 499 500 @Override 501 public void initialize() { 502 InstanceManager.getDefault(OperationsSetupXml.class); // load setup 503 // create manager to load cars and their attributes 504 InstanceManager.getDefault(CarManagerXml.class); 505 } 506 507}