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