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