001package jmri.jmrit.operations.rollingstock.cars; 002 003import java.util.*; 004 005import javax.swing.JComboBox; 006 007import org.jdom2.Attribute; 008import org.jdom2.Element; 009import org.slf4j.Logger; 010import org.slf4j.LoggerFactory; 011 012import jmri.InstanceManager; 013import jmri.InstanceManagerAutoDefault; 014import jmri.jmrit.operations.OperationsPanel; 015import jmri.jmrit.operations.rollingstock.RollingStockAttribute; 016import jmri.jmrit.operations.trains.TrainCommon; 017import jmri.jmrit.operations.trains.TrainManifestHeaderText; 018 019/** 020 * Represents the loads that cars can have. 021 * 022 * @author Daniel Boudreau Copyright (C) 2008, 2014 023 */ 024public class CarLoads extends RollingStockAttribute implements InstanceManagerAutoDefault { 025 026 protected Hashtable<String, List<CarLoad>> listCarLoads = new Hashtable<>(); 027 protected String _emptyName = Bundle.getMessage("EmptyCar"); 028 protected String _loadName = Bundle.getMessage("LoadedCar"); 029 030 public static final String NONE = ""; // NOI18N 031 032 // for property change 033 public static final String LOAD_CHANGED_PROPERTY = "CarLoads_Load"; // NOI18N 034 public static final String LOAD_TYPE_CHANGED_PROPERTY = "CarLoads_Load_Type"; // NOI18N 035 public static final String LOAD_PRIORITY_CHANGED_PROPERTY = "CarLoads_Load_Priority"; // NOI18N 036 public static final String LOAD_NAME_CHANGED_PROPERTY = "CarLoads_Name"; // NOI18N 037 public static final String LOAD_COMMENT_CHANGED_PROPERTY = "CarLoads_Load_Comment"; // NOI18N 038 public static final String LOAD_HAZARDOUS_CHANGED_PROPERTY = "CarLoads_Load_Hazardous"; // NOI18N 039 040 public CarLoads() { 041 } 042 043 /** 044 * Add a car type with specific loads 045 * 046 * @param type car type 047 */ 048 public void addType(String type) { 049 listCarLoads.put(type, new ArrayList<>()); 050 } 051 052 /** 053 * Replace a car type. Transfers load type, priority, isHardous, drop and 054 * load comments. 055 * 056 * @param oldType old car type 057 * @param newType new car type 058 */ 059 public void replaceType(String oldType, String newType) { 060 List<String> names = getNames(oldType); 061 addType(newType); 062 for (String name : names) { 063 addName(newType, name); 064 setLoadType(newType, name, getLoadType(oldType, name)); 065 setPriority(newType, name, getPriority(oldType, name)); 066 setHazardous(newType, name, isHazardous(oldType, name)); 067 setDropComment(newType, name, getDropComment(oldType, name)); 068 setPickupComment(newType, name, getPickupComment(oldType, name)); 069 } 070 listCarLoads.remove(oldType); 071 } 072 073 /** 074 * Gets the appropriate car loads for the car's type. 075 * 076 * @param type Car type 077 * 078 * @return JComboBox with car loads starting with empty string. 079 */ 080 public JComboBox<String> getSelectComboBox(String type) { 081 JComboBox<String> box = new JComboBox<>(); 082 box.addItem(NONE); 083 for (String load : getNames(type)) { 084 box.addItem(load); 085 } 086 return box; 087 } 088 089 /** 090 * Gets the appropriate car loads for the car's type. 091 * 092 * @param type Car type 093 * 094 * @return JComboBox with car loads. 095 */ 096 public JComboBox<String> getComboBox(String type) { 097 JComboBox<String> box = new JComboBox<>(); 098 updateComboBox(type, box); 099 return box; 100 101 } 102 103 /** 104 * Gets a ComboBox with the available priorities 105 * 106 * @return JComboBox with car priorities. 107 */ 108 public JComboBox<String> getPriorityComboBox() { 109 JComboBox<String> box = new JComboBox<>(); 110 box.addItem(CarLoad.PRIORITY_LOW); 111 box.addItem(CarLoad.PRIORITY_MEDIUM); 112 box.addItem(CarLoad.PRIORITY_HIGH); 113 return box; 114 } 115 116 public JComboBox<String> getHazardousComboBox() { 117 JComboBox<String> box = new JComboBox<>(); 118 box.addItem(Bundle.getMessage("ButtonNo")); 119 box.addItem(Bundle.getMessage("ButtonYes")); 120 return box; 121 } 122 123 /** 124 * Gets a ComboBox with the available load types: empty and load 125 * 126 * @return JComboBox with load types: LOAD_TYPE_EMPTY and LOAD_TYPE_LOAD 127 */ 128 public JComboBox<String> getLoadTypesComboBox() { 129 JComboBox<String> box = new JComboBox<>(); 130 box.addItem(CarLoad.LOAD_TYPE_EMPTY); 131 box.addItem(CarLoad.LOAD_TYPE_LOAD); 132 return box; 133 } 134 135 /** 136 * Gets a sorted list of load names for a given car type 137 * 138 * @param type car type 139 * @return list of load names 140 */ 141 public List<String> getNames(String type) { 142 List<String> names = new ArrayList<>(); 143 if (type == null) { 144 names.add(getDefaultEmptyName()); 145 names.add(getDefaultLoadName()); 146 return names; 147 } 148 List<CarLoad> loads = listCarLoads.get(type); 149 if (loads == null) { 150 addType(type); 151 loads = listCarLoads.get(type); 152 } 153 if (loads.isEmpty()) { 154 loads.add(new CarLoad(getDefaultEmptyName())); 155 loads.add(new CarLoad(getDefaultLoadName())); 156 } 157 for (CarLoad carLoad : loads) { 158 names.add(carLoad.getName()); 159 } 160 java.util.Collections.sort(names); 161 return names; 162 } 163 164 /** 165 * Add a load name for the car type. 166 * 167 * @param type car type. 168 * @param name load name. 169 */ 170 public void addName(String type, String name) { 171 // don't add if name already exists 172 if (containsName(type, name)) { 173 return; 174 } 175 List<CarLoad> loads = listCarLoads.get(type); 176 if (loads == null) { 177 log.debug("car type ({}) does not exist", type); 178 return; 179 } 180 loads.add(new CarLoad(name)); 181 maxNameLength = 0; // reset maximum name length 182 setDirtyAndFirePropertyChange(LOAD_CHANGED_PROPERTY, null, name); 183 } 184 185 public void deleteName(String type, String name) { 186 List<CarLoad> loads = listCarLoads.get(type); 187 if (loads == null) { 188 log.debug("car type ({}) does not exist", type); 189 return; 190 } 191 for (CarLoad cl : loads) { 192 if (cl.getName().equals(name)) { 193 loads.remove(cl); 194 break; 195 } 196 } 197 maxNameLength = 0; // reset maximum name length 198 setDirtyAndFirePropertyChange(LOAD_CHANGED_PROPERTY, name, null); 199 } 200 201 /** 202 * Determines if a car type can have a specific load name. 203 * 204 * @param type car type. 205 * @param name load name. 206 * @return true if car can have this load name. 207 */ 208 public boolean containsName(String type, String name) { 209 List<String> names = getNames(type); 210 return names.contains(name); 211 } 212 213 public void updateComboBox(String type, JComboBox<String> box) { 214 box.removeAllItems(); 215 List<String> names = getNames(type); 216 for (String name : names) { 217 box.addItem(name); 218 } 219 OperationsPanel.padComboBox(box, getMaxNameLength() + 1); 220 } 221 222 /** 223 * Update a JComboBox with all load names for every type of car. 224 * 225 * @param box the combo box to update 226 */ 227 @Override 228 public void updateComboBox(JComboBox<String> box) { 229 box.removeAllItems(); 230 List<String> names = new ArrayList<>(); 231 for (String type : InstanceManager.getDefault(CarTypes.class).getNames()) { 232 for (String load : getNames(type)) { 233 if (!names.contains(load)) { 234 names.add(load); 235 } 236 } 237 } 238 java.util.Collections.sort(names); 239 for (String load : names) { 240 box.addItem(load); 241 } 242 } 243 244 public void updateRweComboBox(String type, JComboBox<String> box) { 245 box.removeAllItems(); 246 List<String> loads = getNames(type); 247 for (String name : loads) { 248 if (getLoadType(type, name).equals(CarLoad.LOAD_TYPE_EMPTY)) { 249 box.addItem(name); 250 } 251 } 252 } 253 254 public void updateRwlComboBox(String type, JComboBox<String> box) { 255 box.removeAllItems(); 256 List<String> loads = getNames(type); 257 for (String name : loads) { 258 if (getLoadType(type, name).equals(CarLoad.LOAD_TYPE_LOAD)) { 259 box.addItem(name); 260 } 261 } 262 } 263 264 public void replaceName(String type, String oldName, String newName) { 265 addName(type, newName); 266 deleteName(type, oldName); 267 setDirtyAndFirePropertyChange(LOAD_NAME_CHANGED_PROPERTY, oldName, newName); 268 } 269 270 public String getDefaultLoadName() { 271 return _loadName; 272 } 273 274 public void setDefaultLoadName(String name) { 275 String old = _loadName; 276 _loadName = name; 277 setDirtyAndFirePropertyChange(LOAD_NAME_CHANGED_PROPERTY, old, name); 278 } 279 280 public String getDefaultEmptyName() { 281 return _emptyName; 282 } 283 284 public void setDefaultEmptyName(String name) { 285 String old = _emptyName; 286 _emptyName = name; 287 setDirtyAndFirePropertyChange(LOAD_NAME_CHANGED_PROPERTY, old, name); 288 } 289 290 /** 291 * Sets the load type, empty or load. 292 * 293 * @param type car type. 294 * @param name load name. 295 * @param loadType load type: LOAD_TYPE_EMPTY or LOAD_TYPE_LOAD. 296 */ 297 public void setLoadType(String type, String name, String loadType) { 298 List<CarLoad> loads = listCarLoads.get(type); 299 for (CarLoad cl : loads) { 300 if (cl.getName().equals(name)) { 301 String oldType = cl.getLoadType(); 302 cl.setLoadType(loadType); 303 if (!oldType.equals(loadType)) { 304 setDirtyAndFirePropertyChange(LOAD_TYPE_CHANGED_PROPERTY, oldType, loadType); 305 } 306 } 307 } 308 } 309 310 /** 311 * Get the load type, empty or load. 312 * 313 * @param type car type. 314 * @param name load name. 315 * @return load type, LOAD_TYPE_EMPTY or LOAD_TYPE_LOAD. 316 */ 317 public String getLoadType(String type, String name) { 318 if (!containsName(type, name)) { 319 if (name != null && name.equals(getDefaultEmptyName())) { 320 return CarLoad.LOAD_TYPE_EMPTY; 321 } 322 return CarLoad.LOAD_TYPE_LOAD; 323 } 324 List<CarLoad> loads = listCarLoads.get(type); 325 for (CarLoad cl : loads) { 326 if (cl.getName().equals(name)) { 327 return cl.getLoadType(); 328 } 329 } 330 return "error"; // NOI18N 331 } 332 333 /** 334 * Sets a loads priority. 335 * 336 * @param type car type. 337 * @param name load name. 338 * @param priority load priority, PRIORITY_LOW, PRIORITY_MEDIUM or PRIORITY_HIGH. 339 */ 340 public void setPriority(String type, String name, String priority) { 341 List<CarLoad> loads = listCarLoads.get(type); 342 for (CarLoad cl : loads) { 343 if (cl.getName().equals(name)) { 344 String oldPriority = cl.getPriority(); 345 cl.setPriority(priority); 346 if (!oldPriority.equals(priority)) { 347 setDirtyAndFirePropertyChange(LOAD_PRIORITY_CHANGED_PROPERTY, oldPriority, priority); 348 } 349 } 350 } 351 } 352 353 /** 354 * Get's a load's priority. 355 * 356 * @param type car type. 357 * @param name load name. 358 * @return load priority, PRIORITY_LOW, PRIORITY_MEDIUM or PRIORITY_HIGH. 359 */ 360 public String getPriority(String type, String name) { 361 if (!containsName(type, name)) { 362 return CarLoad.PRIORITY_LOW; 363 } 364 List<CarLoad> loads = listCarLoads.get(type); 365 for (CarLoad cl : loads) { 366 if (cl.getName().equals(name)) { 367 return cl.getPriority(); 368 } 369 } 370 return "error"; // NOI18N 371 } 372 373 public void setHazardous(String type, String name, boolean isHazardous) { 374 List<CarLoad> loads = listCarLoads.get(type); 375 for (CarLoad cl : loads) { 376 if (cl.getName().equals(name)) { 377 boolean oldIsHazardous = cl.isHazardous(); 378 cl.setHazardous(isHazardous); 379 if (oldIsHazardous != isHazardous) { 380 setDirtyAndFirePropertyChange(LOAD_HAZARDOUS_CHANGED_PROPERTY, oldIsHazardous, isHazardous); 381 } 382 } 383 } 384 } 385 386 public boolean isHazardous(String type, String name) { 387 if (!containsName(type, name)) { 388 return false; 389 } 390 List<CarLoad> loads = listCarLoads.get(type); 391 for (CarLoad cl : loads) { 392 if (cl.getName().equals(name)) { 393 return cl.isHazardous(); 394 } 395 } 396 return false; 397 } 398 399 /** 400 * Sets the comment for a car type's load 401 * @param type the car type 402 * @param name the load name 403 * @param comment the comment 404 */ 405 public void setPickupComment(String type, String name, String comment) { 406 if (!containsName(type, name)) { 407 return; 408 } 409 List<CarLoad> loads = listCarLoads.get(type); 410 for (CarLoad cl : loads) { 411 if (cl.getName().equals(name)) { 412 String oldComment = cl.getPickupComment(); 413 cl.setPickupComment(comment); 414 if (!oldComment.equals(comment)) { 415 maxCommentLength = 0; 416 setDirtyAndFirePropertyChange(LOAD_COMMENT_CHANGED_PROPERTY, oldComment, comment); 417 } 418 } 419 } 420 } 421 422 public String getPickupComment(String type, String name) { 423 if (!containsName(type, name)) { 424 return NONE; 425 } 426 List<CarLoad> loads = listCarLoads.get(type); 427 for (CarLoad cl : loads) { 428 if (cl.getName().equals(name)) { 429 return cl.getPickupComment(); 430 } 431 } 432 return NONE; 433 } 434 435 public void setDropComment(String type, String name, String comment) { 436 if (!containsName(type, name)) { 437 return; 438 } 439 List<CarLoad> loads = listCarLoads.get(type); 440 for (CarLoad cl : loads) { 441 if (cl.getName().equals(name)) { 442 String oldComment = cl.getDropComment(); 443 cl.setDropComment(comment); 444 if (!oldComment.equals(comment)) { 445 maxCommentLength = 0; 446 setDirtyAndFirePropertyChange(LOAD_COMMENT_CHANGED_PROPERTY, oldComment, comment); 447 } 448 } 449 } 450 } 451 452 public String getDropComment(String type, String name) { 453 if (!containsName(type, name)) { 454 return NONE; 455 } 456 List<CarLoad> loads = listCarLoads.get(type); 457 for (CarLoad cl : loads) { 458 if (cl.getName().equals(name)) { 459 return cl.getDropComment(); 460 } 461 } 462 return NONE; 463 } 464 465 @edu.umd.cs.findbugs.annotations.SuppressFBWarnings( value="SLF4J_FORMAT_SHOULD_BE_CONST", 466 justification="I18N of Info Message") 467 @Override 468 public int getMaxNameLength() { 469 if (maxNameLength == 0) { 470 maxName = ""; 471 maxNameLength = MIN_NAME_LENGTH; 472 String carTypeName = ""; 473 Enumeration<String> en = listCarLoads.keys(); 474 while (en.hasMoreElements()) { 475 String cartype = en.nextElement(); 476 List<CarLoad> loads = listCarLoads.get(cartype); 477 for (CarLoad load : loads) { 478 if (load.getName().split(TrainCommon.HYPHEN)[0].length() > maxNameLength) { 479 maxName = load.getName().split(TrainCommon.HYPHEN)[0]; 480 maxNameLength = load.getName().split(TrainCommon.HYPHEN)[0].length(); 481 carTypeName = cartype; 482 } 483 } 484 } 485 log.info(Bundle.getMessage("InfoMaxLoad", maxName, maxNameLength, carTypeName)); 486 } 487 return maxNameLength; 488 } 489 490 int maxCommentLength = 0; 491 492 @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "SLF4J_FORMAT_SHOULD_BE_CONST", 493 justification = "I18N of Info Message") 494 public int getMaxLoadCommentLength() { 495 if (maxCommentLength == 0) { 496 String maxComment = ""; 497 String carTypeName = ""; 498 String carLoadName = ""; 499 Enumeration<String> en = listCarLoads.keys(); 500 while (en.hasMoreElements()) { 501 String carType = en.nextElement(); 502 List<CarLoad> loads = listCarLoads.get(carType); 503 for (CarLoad load : loads) { 504 if (load.getDropComment().length() > maxCommentLength) { 505 maxComment = load.getDropComment(); 506 maxCommentLength = load.getDropComment().length(); 507 carTypeName = carType; 508 carLoadName = load.getName(); 509 } 510 if (load.getPickupComment().length() > maxCommentLength) { 511 maxComment = load.getPickupComment(); 512 maxCommentLength = load.getPickupComment().length(); 513 carTypeName = carType; 514 carLoadName = load.getName(); 515 } 516 } 517 } 518 if (maxCommentLength < TrainManifestHeaderText.getStringHeader_Drop_Comment().length()) { 519 maxCommentLength = TrainManifestHeaderText.getStringHeader_Drop_Comment().length(); 520 } 521 if (maxCommentLength < TrainManifestHeaderText.getStringHeader_Pickup_Comment().length()) { 522 maxCommentLength = TrainManifestHeaderText.getStringHeader_Pickup_Comment().length(); 523 } 524 log.info(Bundle.getMessage("InfoMaxLoadMessage", maxComment, maxCommentLength, 525 carTypeName, carLoadName)); 526 } 527 return maxCommentLength; 528 } 529 530 private List<CarLoad> getSortedList(String type) { 531 List<CarLoad> loads = listCarLoads.get(type); 532 List<String> names = getNames(type); 533 List<CarLoad> out = new ArrayList<>(); 534 535 // return a list sorted by load name 536 for (String name : names) { 537 for (CarLoad carLoad : loads) { 538 if (name.equals(carLoad.getName())) { 539 out.add(carLoad); 540 break; 541 } 542 } 543 } 544 return out; 545 } 546 547 @SuppressWarnings("unchecked") 548 public Hashtable<String, List<CarLoad>> getList() { 549 return (Hashtable<String, List<CarLoad>>) listCarLoads.clone(); 550 } 551 552 @Override 553 public void dispose() { 554 listCarLoads.clear(); 555 setDefaultEmptyName(Bundle.getMessage("EmptyCar")); 556 setDefaultLoadName(Bundle.getMessage("LoadedCar")); 557 super.dispose(); 558 } 559 560 /** 561 * Create an XML element to represent this Entry. This member has to remain 562 * synchronized with the detailed DTD in operations-cars.dtd. 563 * 564 * @param root The common Element for operations-cars.dtd. 565 * 566 */ 567 public void store(Element root) { 568 Element values = new Element(Xml.LOADS); 569 // store default load and empty 570 Element defaults = new Element(Xml.DEFAULTS); 571 defaults.setAttribute(Xml.EMPTY, getDefaultEmptyName()); 572 defaults.setAttribute(Xml.LOAD, getDefaultLoadName()); 573 values.addContent(defaults); 574 // store loads based on car types 575 Enumeration<String> en = listCarLoads.keys(); 576 while (en.hasMoreElements()) { 577 String carType = en.nextElement(); 578 // check to see if car type still exists 579 if (!InstanceManager.getDefault(CarTypes.class).containsName(carType)) { 580 continue; 581 } 582 List<CarLoad> loads = getSortedList(carType); 583 Element xmlLoad = new Element(Xml.LOAD); 584 xmlLoad.setAttribute(Xml.TYPE, carType); 585 boolean mustStore = false; // only store loads that aren't the defaults 586 for (CarLoad load : loads) { 587 // don't store the defaults / low priority / not hazardous / no comment 588 if ((load.getName().equals(getDefaultEmptyName()) || load.getName().equals(getDefaultLoadName())) 589 && load.getPriority().equals(CarLoad.PRIORITY_LOW) 590 && !load.isHazardous() 591 && load.getPickupComment().equals(CarLoad.NONE) 592 && load.getDropComment().equals(CarLoad.NONE)) { 593 continue; 594 } 595 Element xmlCarLoad = new Element(Xml.CAR_LOAD); 596 xmlCarLoad.setAttribute(Xml.NAME, load.getName()); 597 if (!load.getPriority().equals(CarLoad.PRIORITY_LOW)) { 598 xmlCarLoad.setAttribute(Xml.PRIORITY, load.getPriority()); 599 mustStore = true; // must store 600 } 601 if (load.isHazardous()) { 602 xmlCarLoad.setAttribute(Xml.HAZARDOUS, load.isHazardous() ? Xml.TRUE : Xml.FALSE); 603 mustStore = true; // must store 604 } 605 if (!load.getPickupComment().equals(CarLoad.NONE)) { 606 xmlCarLoad.setAttribute(Xml.PICKUP_COMMENT, load.getPickupComment()); 607 mustStore = true; // must store 608 } 609 if (!load.getDropComment().equals(CarLoad.NONE)) { 610 xmlCarLoad.setAttribute(Xml.DROP_COMMENT, load.getDropComment()); 611 mustStore = true; // must store 612 } 613 xmlCarLoad.setAttribute(Xml.LOAD_TYPE, load.getLoadType()); 614 xmlLoad.addContent(xmlCarLoad); 615 } 616 if (loads.size() > 2 || mustStore) { 617 values.addContent(xmlLoad); 618 } 619 } 620 root.addContent(values); 621 } 622 623 public void load(Element e) { 624 if (e.getChild(Xml.LOADS) == null) { 625 return; 626 } 627 Attribute a; 628 Element defaults = e.getChild(Xml.LOADS).getChild(Xml.DEFAULTS); 629 if (defaults != null) { 630 if ((a = defaults.getAttribute(Xml.LOAD)) != null) { 631 _loadName = a.getValue(); 632 } 633 if ((a = defaults.getAttribute(Xml.EMPTY)) != null) { 634 _emptyName = a.getValue(); 635 } 636 } 637 List<Element> eLoads = e.getChild(Xml.LOADS).getChildren(Xml.LOAD); 638 log.debug("readFile sees {} car loads", eLoads.size()); 639 for (Element eLoad : eLoads) { 640 if ((a = eLoad.getAttribute(Xml.TYPE)) != null) { 641 String type = a.getValue(); 642 addType(type); 643 // old style had a list of names 644 if ((a = eLoad.getAttribute(Xml.NAMES)) != null) { 645 String names = a.getValue(); 646 String[] loadNames = names.split("%%");// NOI18N 647 Arrays.sort(loadNames); 648 log.debug("Car load type: {} loads: {}", type, names); 649 // addName puts new items at the start, so reverse load 650 for (int j = loadNames.length; j > 0;) { 651 addName(type, loadNames[--j]); 652 } 653 } 654 // new style load and comments 655 List<Element> eCarLoads = eLoad.getChildren(Xml.CAR_LOAD); 656 log.debug("{} car loads for type: {}", eCarLoads.size(), type); 657 for (Element eCarLoad : eCarLoads) { 658 if ((a = eCarLoad.getAttribute(Xml.NAME)) != null) { 659 String name = a.getValue(); 660 addName(type, name); 661 if ((a = eCarLoad.getAttribute(Xml.PRIORITY)) != null) { 662 setPriority(type, name, a.getValue()); 663 } 664 if ((a = eCarLoad.getAttribute(Xml.HAZARDOUS)) != null) { 665 setHazardous(type, name, a.getValue().equals(Xml.TRUE)); 666 } 667 if ((a = eCarLoad.getAttribute(Xml.PICKUP_COMMENT)) != null) { 668 setPickupComment(type, name, a.getValue()); 669 } 670 if ((a = eCarLoad.getAttribute(Xml.DROP_COMMENT)) != null) { 671 setDropComment(type, name, a.getValue()); 672 } 673 if ((a = eCarLoad.getAttribute(Xml.LOAD_TYPE)) != null) { 674 setLoadType(type, name, a.getValue()); 675 } 676 } 677 } 678 } 679 } 680 } 681 682 protected void setDirtyAndFirePropertyChange(String p, Object old, Object n) { 683 // Set dirty 684 InstanceManager.getDefault(CarManagerXml.class).setDirty(true); 685 super.firePropertyChange(p, old, n); 686 } 687 688 private final static Logger log = LoggerFactory.getLogger(CarLoads.class); 689 690}