001package jmri.jmrit.symbolicprog; 002 003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 004 005import static java.nio.charset.Charset.defaultCharset; 006import static java.nio.charset.Charset.isSupported; 007 008import java.awt.event.ActionEvent; 009import java.awt.event.ActionListener; 010import java.beans.PropertyChangeEvent; 011import java.beans.PropertyChangeListener; 012import java.util.Arrays; 013import java.util.List; 014import java.util.Objects; 015import java.util.Vector; 016import javax.swing.JButton; 017import javax.swing.JFrame; 018import javax.swing.JLabel; 019import javax.swing.JTextField; 020import javax.swing.table.AbstractTableModel; 021 022import jmri.AddressedProgrammer; 023import jmri.jmrit.decoderdefn.DecoderFile; 024import jmri.util.CvUtil; 025import jmri.util.jdom.LocaleSelector; 026import jmri.util.swing.JmriJOptionPane; 027 028import org.jdom2.Attribute; 029import org.jdom2.Content; 030import org.jdom2.Element; 031import org.jdom2.util.IteratorIterable; 032 033/** 034 * Table data model for display of variables in symbolic programmer. Also 035 * responsible for loading from the XML file. 036 * 037 * @author Bob Jacobsen Copyright (C) 2001, 2006, 2010 038 * @author Howard G. Penny Copyright (C) 2005 039 * @author Daniel Boudreau Copyright (C) 2007 040 * @author Dave Heap Copyright (C) 2012 Added support for Marklin mfx style 041 * speed table 042 */ 043public class VariableTableModel extends AbstractTableModel implements ActionListener, PropertyChangeListener { 044 045 private String[] headers; 046 047 private Vector<VariableValue> rowVector = new Vector<>(); // vector of Variable items 048 private CvTableModel _cvModel; // reference to external table model 049 private Vector<JButton> _writeButtons = new Vector<>(); 050 private Vector<JButton> _readButtons = new Vector<>(); 051 private JLabel _status; 052 protected transient volatile DecoderFile _df = null; 053 054 /** 055 * Define the columns. 056 * <p> 057 * Values understood are: "Name", "Value", "Range", "Read", "Write", 058 * "Comment", "CV", "Mask", "State". 059 * <p> 060 * For each, a property key in SymbolicProgBundle by the same name allows 061 * i18n. 062 * 063 * @param status variable status. 064 * @param h values headers array. 065 * @param cvModel cv table model to use. 066 */ 067 public VariableTableModel(JLabel status, String[] h, CvTableModel cvModel) { 068 super(); 069 _status = status; 070 _cvModel = cvModel; 071 headers = Arrays.copyOf(h, h.length); 072 } 073 074 // basic methods for AbstractTableModel implementation 075 @Override 076 public int getRowCount() { 077 return rowVector.size(); 078 } 079 080 @Override 081 public int getColumnCount() { 082 return headers.length; 083 } 084 085 @Override 086 public String getColumnName(int col) { 087 log.debug("getColumnName {}", col); 088 return Bundle.getMessage(headers[col]); // I18N 089 } 090 091 @Override 092 public Class<?> getColumnClass(int col) { 093 // log.debug("getColumnClass {}", col; 094 switch (headers[col]) { 095 case "Value": 096 return JTextField.class; 097 case "Read": 098 case "Write": 099 return JButton.class; 100 default: 101 return String.class; 102 } 103 } 104 105 @Override 106 public boolean isCellEditable(int row, int col) { 107 log.debug("isCellEditable {}", col); 108 if (headers[col].equals("Value")) { 109 return true; 110 } else if (headers[col].equals("Read")) { 111 return true; 112 } else 113 return headers[col].equals("Write") && !((rowVector.elementAt(row))).getReadOnly(); 114 } 115 116 public VariableValue getVariable(int row) { 117 return (rowVector.elementAt(row)); 118 } 119 120 public String getLabel(int row) { 121 return (rowVector.elementAt(row)).label(); 122 } 123 124 public String getItem(int row) { 125 return (rowVector.elementAt(row)).item(); 126 } 127 128 public String getCvName(int row) { 129 return (rowVector.elementAt(row)).cvName(); 130 } 131 132 public String getValString(int row) { 133 return (rowVector.elementAt(row)).getValueString(); 134 } 135 136 public void setIntValue(int row, int val) { 137 (rowVector.elementAt(row)).setIntValue(val); 138 } 139 140 public void setState(int row, AbstractValue.ValueState val) { 141 log.debug("setState row: {} val: {}", row, val); 142 (rowVector.elementAt(row)).setState(val); 143 } 144 145 public AbstractValue.ValueState getState(int row) { 146 return (rowVector.elementAt(row)).getState(); 147 } 148 149 /* 150 * Request a "unique representation", e.g. something we can show 151 * for the row-th variable. 152 */ 153 public Object getRep(int row, String format) { 154 VariableValue v = rowVector.elementAt(row); 155 return v.getNewRep(format); 156 } 157 158 @Override 159 public Object getValueAt(int row, int col) { 160 // log.debug("getValueAt {} {}", row, col; 161 if (row >= rowVector.size()) { 162 log.debug("row index greater than row vector size"); 163 return "Error"; 164 } 165 VariableValue v = rowVector.elementAt(row); 166 if (v == null) { 167 log.debug("v is null!"); 168 return "Error value"; 169 } 170 switch (headers[col]) { 171 case "Value": 172 return v.getCommonRep(); 173 case "Read": 174 // NOI18N 175 return _readButtons.elementAt(row); 176 case "Write": 177 // NOI18N 178 return _writeButtons.elementAt(row); 179 case "CV": 180 // NOI18N 181 return "" + v.getCvNum(); 182 case "Name": 183 // NOI18N 184 return "" + v.label(); 185 case "Comment": 186 // NOI18N 187 return v.getComment(); 188 case "Mask": 189 // NOI18N 190 return v.getMask(); 191 case "State": 192 // NOI18N 193 AbstractValue.ValueState state = v.getState(); 194 switch (state) { 195 case UNKNOWN: 196 return "Unknown"; 197 case READ: 198 return "Read"; 199 case EDITED: 200 return "Edited"; 201 case STORED: 202 return "Stored"; 203 case FROMFILE: 204 return "From file"; 205 default: 206 return "inconsistent"; 207 } 208 case "Range": 209 return v.rangeVal(); 210 default: 211 return "Later, dude"; 212 } 213 } 214 215 @Override 216 public void setValueAt(Object value, int row, int col) { 217 log.debug("setvalueAt {} {} {}", row, col, value); 218 setFileDirty(true); 219 } 220 221 /** 222 * Load one row in the VariableTableModel, by reading in the Element 223 * containing its definition. 224 * <p> 225 * Note that this method does not pass a reference to a {@link DecoderFile} 226 * instance, hence include/exclude processing at the sub-variable level is 227 * not possible and will be ignored. 228 * <p> 229 * Use of {@link #setRow(int row, Element e, DecoderFile df)} is preferred. 230 * 231 * @param row number of row to fill 232 * @param e Element of type "variable" 233 */ 234 public void setRow(int row, Element e) { 235 this.setRow(row, e, null); 236 } 237 238 /** 239 * Load one row in the VariableTableModel, by reading in the Element 240 * containing its definition. 241 * <p> 242 * Invoked from {@link DecoderFile} 243 * 244 * @param row number of row to fill 245 * @param e Element of type "variable" 246 * @param df the source {@link DecoderFile} instance (needed for 247 * include/exclude processing at the sub-variable level) 248 */ 249 public void setRow(int row, Element e, DecoderFile df) { 250 // get the values for the VariableValue ctor 251 _df = df; 252 String name = LocaleSelector.getAttribute(e, "label"); // Note the name variable is actually the label attribute 253 log.debug("Starting to setRow \"{}\"", name); 254 String item = (e.getAttribute("item") != null 255 ? e.getAttribute("item").getValue() 256 : null); 257 // as a special case, if no item, use label 258 if (item == null) { 259 item = name; 260 log.debug("no item attribute, used label \"{}\"", name); 261 } 262 // as a special case, if no label, use item 263 if (name == null) { 264 name = item; 265 log.debug("no label attribute, used item attribute \"{}\"", item); 266 } 267 268 String comment = LocaleSelector.getAttribute(e, "comment"); 269 270 String CV = ""; 271 if (e.getAttribute("CV") != null) { 272 CV = e.getAttribute("CV").getValue(); 273 } 274 String mask; 275 if (e.getAttribute("mask") != null) { 276 mask = e.getAttribute("mask").getValue(); 277 } else { 278 mask = "VVVVVVVV"; // default mask is 8 bits 279 // for some VariableValue types this is replaced in #processDecVal() by larger mask if maxVal>256 280 } 281 282 boolean readOnly = e.getAttribute("readOnly") != null && e.getAttribute("readOnly").getValue().equals("yes"); 283 boolean infoOnly = e.getAttribute("infoOnly") != null && e.getAttribute("infoOnly").getValue().equals("yes"); 284 boolean writeOnly = e.getAttribute("writeOnly") != null && e.getAttribute("writeOnly").getValue().equals("yes"); 285 boolean opsOnly = e.getAttribute("opsOnly") != null && e.getAttribute("opsOnly").getValue().equals("yes"); 286 287 // Handle special case of opsOnly mode & specific programmer type 288 if (_cvModel.getProgrammer() != null) { 289 if (opsOnly && !AddressedProgrammer.class.isAssignableFrom(_cvModel.getProgrammer().getClass())) { 290 // opsOnly but not Ops mode, so adjust 291 readOnly = false; 292 writeOnly = false; 293 infoOnly = true; 294 } 295 } 296 297 JButton bw = new JButton(Bundle.getMessage("ButtonWrite")); 298 _writeButtons.addElement(bw); 299 JButton br = new JButton(Bundle.getMessage("ButtonRead")); 300 _readButtons.addElement(br); 301 setButtonsReadWrite(readOnly, infoOnly, writeOnly, bw, br, row); 302 303 if (_cvModel == null) { 304 log.error("CvModel reference is null; cannot add variables"); 305 return; 306 } 307 if (!CV.equals("")) { // some variables have no CV per se 308 List<String> cvList = CvUtil.expandCvList(CV); 309 if (cvList.isEmpty()) { 310 _cvModel.addCV(CV, readOnly, infoOnly, writeOnly); 311 } else { // or require expansion 312 for (String s : cvList) { 313 _cvModel.addCV(s, readOnly, infoOnly, writeOnly); 314 } 315 } 316 } 317 318 // decode and handle specific types 319 Element child; 320 VariableValue v; 321 if ((child = e.getChild("decVal")) != null) { 322 v = processDecVal(child, name, comment, readOnly, infoOnly, writeOnly, opsOnly, CV, mask, item); 323 324 } else if ((child = e.getChild("hexVal")) != null) { 325 v = processHexVal(child, name, comment, readOnly, infoOnly, writeOnly, opsOnly, CV, mask, item); 326 327 } else if ((child = e.getChild("enumVal")) != null) { 328 v = processEnumVal(child, name, comment, readOnly, infoOnly, writeOnly, opsOnly, CV, mask, item); 329 330 } else if ((child = e.getChild("compositeVal")) != null) { 331 // loop over the choices 332 v = processCompositeVal(child, name, comment, readOnly, infoOnly, writeOnly, opsOnly, CV, mask, item); 333 334 } else if ((child = e.getChild("speedTableVal")) != null) { 335 v = processSpeedTableVal(child, CV, readOnly, infoOnly, writeOnly, name, comment, opsOnly, mask, item); 336 337 } else if ((child = e.getChild("longAddressVal")) != null) { 338 v = processLongAddressVal(CV, readOnly, infoOnly, writeOnly, name, comment, opsOnly, mask, item); 339 340 } else if ((child = e.getChild("shortAddressVal")) != null) { 341 v = processShortAddressVal(name, comment, readOnly, infoOnly, writeOnly, opsOnly, CV, mask, item, child); 342 343 } else if ((child = e.getChild("splitVal")) != null) { 344 v = processSplitVal(child, CV, readOnly, infoOnly, writeOnly, name, comment, opsOnly, mask, item); 345 346 } else if ((child = e.getChild("splitHexVal")) != null) { 347 v = processSplitHexVal(child, CV, readOnly, infoOnly, writeOnly, name, comment, opsOnly, mask, item); 348 349 } else if ((child = e.getChild("splitHundredsVal")) != null) { 350 v = processSplitHundredsVal(child, CV, readOnly, infoOnly, writeOnly, name, comment, opsOnly, mask, item); 351 352 } else if ((child = e.getChild("splitTextVal")) != null) { 353 v = processSplitTextVal(child, CV, readOnly, infoOnly, writeOnly, name, comment, opsOnly, mask, item); 354 355 } else if ((child = e.getChild("splitDateTimeVal")) != null) { 356 v = processSplitDateTimeVal(child, CV, readOnly, infoOnly, writeOnly, name, comment, opsOnly, mask, item); 357 358 } else if ((child = e.getChild("splitEnumVal")) != null) { 359 v = processSplitEnumVal(child, CV, readOnly, infoOnly, writeOnly, name, comment, opsOnly, mask, item); 360 361 } else { 362 reportBogus(e); 363 return; 364 } 365 366 processModifierElements(e, v); 367 368 setToolTip(e, v); 369 370 // record new variable, update state, hook up listeners 371 rowVector.addElement(v); 372 v.setState(AbstractValue.ValueState.FROMFILE); 373 v.addPropertyChangeListener(this); 374 375 // set to default value if specified (CV load may later override this) 376 if (setDefaultValue(e, v)) { 377 // need to correct state of associated CV(s) & handle a possible CV List 378 List<String> cvList = CvUtil.expandCvList(CV); // see if CV is in list format 379 if (cvList.isEmpty()) { 380 cvList.add(CV); // it's an ordinary CV so add it as such 381 } 382 for (String theCV : cvList) { 383 log.debug("Setting CV={} of '{}'to {}", theCV, CV, AbstractValue.ValueState.FROMFILE.getName()); 384 _cvModel.getCvByNumber(theCV).setState(AbstractValue.ValueState.FROMFILE); // correct for transition to "edited" 385 } 386 } 387 } 388 389 /** 390 * If there are any modifier elements, process them by e.g. setting 391 * attributes on the VariableValue. 392 * 393 * @param e Element that's source of info 394 * @param variable Variable to load 395 */ 396 protected void processModifierElements(final Element e, final VariableValue variable) { 397 QualifierAdder qa = new QualifierAdder() { 398 @Override 399 protected Qualifier createQualifier(VariableValue variable2, String relation, String value) { 400 return new ValueQualifier(variable, variable2, Integer.parseInt(value), relation); 401 } 402 403 @Override 404 protected void addListener(java.beans.PropertyChangeListener qc) { 405 variable.addPropertyChangeListener(qc); 406 } 407 }; 408 409 qa.processModifierElements(e, this); 410 } 411 412 /** 413 * If there's a "default" attribute, or matching defaultItem element, set 414 * that value to start. 415 * 416 * @param e Element that's source of info 417 * @param variable Variable to load 418 * @return true if the value was set 419 */ 420 boolean setDefaultValue(Element e, VariableValue variable) { 421 Attribute a; 422 boolean set = false; 423 if ((a = e.getAttribute("default")) != null) { 424 String val = a.getValue(); 425 variable.setValue(val); 426 set = true; 427 } 428 // check for matching child 429 List<Element> elements = e.getChildren("defaultItem"); 430 for (Element defaultItem : elements) { 431 if (_df != null && DecoderFile.isIncluded(defaultItem, _df.getProductID(), _df.getModel(), _df.getFamily(), "", "")) { 432 log.debug("element included by productID={} model={} family={}", _df.getProductID(), _df.getModel(), _df.getFamily()); 433 variable.setValue(defaultItem.getAttribute("default").getValue()); 434 return true; 435 } 436 } 437 return set; 438 } 439 440 protected VariableValue processCompositeVal(Element child, String name, String comment, boolean readOnly, boolean infoOnly, boolean writeOnly, boolean opsOnly, String CV, String mask, String item) { 441 int count = 0; 442 IteratorIterable<Content> iterator = child.getDescendants(); 443 while (iterator.hasNext()) { 444 Object ex = iterator.next(); 445 if (ex instanceof Element) { 446 if (((Element) ex).getName().equals("compositeChoice")) { 447 count++; 448 } 449 } 450 } 451 452 VariableValue v; 453 CompositeVariableValue v1 = new CompositeVariableValue(name, comment, "", readOnly, infoOnly, writeOnly, opsOnly, CV, mask, 0, count, _cvModel.allCvMap(), _status, item); 454 v = v1; // v1 is of CompositeVariableType, so doesn't need casts 455 456 v1.nItems(count); 457 handleCompositeValChildren(child, v1); 458 v1.lastItem(); 459 return v; 460 } 461 462 /** 463 * Recursively walk the child compositeChoice elements, working through the 464 * compositeChoiceGroup elements as needed. 465 * <p> 466 * Adapted from handleEnumValChildren for use in LocoIO Legacy tool. 467 * 468 * @param e Element that's source of info 469 * @param var Variable to load 470 */ 471 protected void handleCompositeValChildren(Element e, CompositeVariableValue var) { 472 List<Element> local = e.getChildren(); 473 for (Element el : local) { 474 log.debug("processing element='{}' name='{}' choice='{}' value='{}'", el.getName(), LocaleSelector.getAttribute(el, "name"), LocaleSelector.getAttribute(el, "choice"), el.getAttribute("value")); 475 if (_df != null && !DecoderFile.isIncluded(el, _df.getProductID(), _df.getModel(), _df.getFamily(), "", "")) { 476 log.debug("element excluded by productID={} model={} family={}", _df.getProductID(), _df.getModel(), _df.getFamily()); 477 continue; 478 } 479 if (el.getName().equals("compositeChoice")) { 480 // Create the choice 481 String choice = LocaleSelector.getAttribute(el, "choice"); 482 var.addChoice(choice); 483 // for each choice, capture the settings 484 List<Element> lSetting = el.getChildren("compositeSetting"); 485 for (Element settingElement : lSetting) { 486 String varName = LocaleSelector.getAttribute(settingElement, "label"); 487 String value = settingElement.getAttribute("value").getValue(); 488 var.addSetting(choice, varName, findVar(varName), value); 489 } 490 } else if (el.getName().equals("compositeChoiceGroup")) { 491 // no tree to manage as in enumGroup 492 handleCompositeValChildren(el, var); 493 } 494 log.debug("element processed"); 495 } 496 } 497 498 protected VariableValue processDecVal(Element child, String name, String comment, boolean readOnly, boolean infoOnly, 499 boolean writeOnly, boolean opsOnly, String CV, String mask, String item) 500 throws NumberFormatException { 501 VariableValue v; 502 Attribute a; 503 int minVal = 0; 504 int maxVal = 255; 505 if ((a = child.getAttribute("min")) != null) { 506 minVal = Integer.parseInt(a.getValue()); 507 } 508 if ((a = child.getAttribute("max")) != null) { 509 maxVal = Integer.parseInt(a.getValue()); 510 } 511 int factor = 1; 512 if ((a = child.getAttribute("factor")) != null) { 513 factor = Integer.parseInt(a.getValue()); 514 } 515 int offset = 0; 516 if ((a = child.getAttribute("offset")) != null) { 517 offset = Integer.parseInt(a.getValue()); 518 } 519 if (maxVal > 255 && Objects.equals(mask, "VVVVVVVV")) { 520 mask = VariableValue.getMaxMask(maxVal); // replaces the default 8 bit mask when no mask is provided in xml 521 log.debug("Created mask {} for DecVar CV {}", mask, name); 522 } 523 v = new DecVariableValue(name, comment, "", readOnly, infoOnly, writeOnly, opsOnly, 524 CV, mask, minVal, maxVal, _cvModel.allCvMap(), _status, item, offset, factor); 525 _cvModel.registerCvToVariableMapping(CV, name); 526 return v; 527 } 528 529 protected VariableValue processEnumVal(Element child, String name, String comment, boolean readOnly, boolean infoOnly, 530 boolean writeOnly, boolean opsOnly, String CV, String mask, String item) 531 throws NumberFormatException { 532 VariableValue v; 533 int count = 0; 534 IteratorIterable<Content> iterator = child.getDescendants(); 535 while (iterator.hasNext()) { 536 Object ex = iterator.next(); 537 if (ex instanceof Element) { 538 if (((Element) ex).getName().equals("enumChoice")) { 539 count++; 540 } 541 } 542 } 543 Attribute a; 544 int maxVal = 255; 545 if ((a = child.getAttribute("max")) != null) { 546 // requires explicit max attribute for Radix mask if not all options are filled by enum 547 maxVal = Integer.parseInt(a.getValue()); 548 } else { 549 maxVal = count; 550 } 551 EnumVariableValue v1 = new EnumVariableValue(name, comment, "", readOnly, infoOnly, writeOnly, opsOnly, 552 CV, mask, 0, maxVal, _cvModel.allCvMap(), _status, item); 553 v = v1; // v1 is of EnumVariableValue type, so doesn't need casts 554 _cvModel.registerCvToVariableMapping(CV, name); 555 556 v1.nItems(count); 557 handleEnumValChildren(child, v1); 558 v1.lastItem(); 559 return v; 560 } 561 562 protected VariableValue processSplitEnumVal(Element child, String CV, boolean readOnly, boolean infoOnly, boolean writeOnly, String name, String comment, boolean opsOnly, String mask, String item) throws NumberFormatException { 563 VariableValue v; 564 Attribute a; 565 int minVal = 0; 566 int maxVal = 255; 567 String highCV = null; 568 569 int count = 0; 570 IteratorIterable<Content> iterator = child.getDescendants(); 571 while (iterator.hasNext()) { 572 Object ex = iterator.next(); 573 if (ex instanceof Element) { 574 if (((Element) ex).getName().equals("enumChoice")) { 575 count++; 576 } 577 } 578 } 579 580 if ((a = child.getAttribute("highCV")) != null) { 581 highCV = a.getValue(); 582 _cvModel.addCV("" + (highCV), readOnly, infoOnly, writeOnly); // ensure 2nd CV exists 583 _cvModel.registerCvToVariableMapping(highCV, name); 584 } 585 int factor = 1; 586 if ((a = child.getAttribute("factor")) != null) { 587 factor = Integer.parseInt(a.getValue()); 588 } 589 int offset = 0; 590 if ((a = child.getAttribute("offset")) != null) { 591 offset = Integer.parseInt(a.getValue()); 592 } 593 String uppermask = "VVVVVVVV"; 594 if ((a = child.getAttribute("upperMask")) != null) { 595 uppermask = a.getValue(); 596 } 597 String extra3 = "0"; 598 if ((a = child.getAttribute("min")) != null) { 599 extra3 = a.getValue(); 600 } 601 String extra4 = Long.toUnsignedString(~0); 602 if ((a = child.getAttribute("max")) != null) { 603 extra4 = a.getValue(); 604 } 605 606 SplitEnumVariableValue v1 = new SplitEnumVariableValue(name, comment, "", readOnly, infoOnly, writeOnly, opsOnly, CV, mask, minVal, maxVal, _cvModel.allCvMap(), _status, item, highCV, factor, offset, uppermask, null, null, extra3, extra4); 607 v = v1; // v1 is of EnunVariableValue type, so doesn't need casts 608 _cvModel.registerCvToVariableMapping(CV, name); 609 610 v1.nItems(count); 611 612 handleSplitEnumValChildren(child, v1); 613 v1.lastItem(); 614 return v; 615 } 616 617 /** 618 * Recursively walk the child enumChoice elements, working through the 619 * enumChoiceGroup elements as needed. 620 * 621 * @param e Element that's source of info 622 * @param var Variable to load 623 */ 624 protected void handleSplitEnumValChildren(Element e, SplitEnumVariableValue var) { 625 List<Element> local = e.getChildren(); 626 627 for (Element el : local) { 628 log.debug("processing element='{}' name='{}' choice='{}' value='{}'", el.getName(), LocaleSelector.getAttribute(el, "name"), LocaleSelector.getAttribute(el, "choice"), el.getAttribute("value")); 629 if (_df != null && !DecoderFile.isIncluded(el, _df.getProductID(), _df.getModel(), _df.getFamily(), "", "")) { 630 log.debug("element excluded by productID={} model={} family={}", _df.getProductID(), _df.getModel(), _df.getFamily()); 631 continue; 632 } 633 if (el.getName().equals("enumChoice")) { 634 Attribute valAttr = el.getAttribute("value"); 635 if (valAttr == null) { 636 var.addItem(LocaleSelector.getAttribute(el, "choice")); 637 } else { 638 var.addItem(LocaleSelector.getAttribute(el, "choice"), 639 Integer.parseInt(valAttr.getValue())); 640 } 641 } else if (el.getName().equals("enumChoiceGroup")) { 642 var.startGroup(LocaleSelector.getAttribute(el, "name")); 643 handleSplitEnumValChildren(el, var); 644 var.endGroup(); 645 } 646 log.debug("element processed"); 647 } 648 } 649 650 /** 651 * Recursively walk the child enumChoice elements, working through the 652 * enumChoiceGroup elements as needed. 653 * 654 * @param e Element that's source of info 655 * @param var Variable to load 656 */ 657 protected void handleEnumValChildren(Element e, EnumVariableValue var) { 658 List<Element> local = e.getChildren(); 659 for (Element el : local) { 660 log.debug("processing element='{}' name='{}' choice='{}' value='{}'", el.getName(), LocaleSelector.getAttribute(el, "name"), LocaleSelector.getAttribute(el, "choice"), el.getAttribute("value")); 661 if (_df != null && !DecoderFile.isIncluded(el, _df.getProductID(), _df.getModel(), _df.getFamily(), "", "")) { 662 log.debug("element excluded by productID={} model={} family={}", _df.getProductID(), _df.getModel(), _df.getFamily()); 663 continue; 664 } 665 if (el.getName().equals("enumChoice")) { 666 Attribute valAttr = el.getAttribute("value"); 667 if (valAttr == null) { 668 var.addItem(LocaleSelector.getAttribute(el, "choice")); 669 } else { 670 var.addItem(LocaleSelector.getAttribute(el, "choice"), 671 Integer.parseInt(valAttr.getValue())); 672 } 673 } else if (el.getName().equals("enumChoiceGroup")) { 674 var.startGroup(LocaleSelector.getAttribute(el, "name")); 675 handleEnumValChildren(el, var); 676 var.endGroup(); 677 } 678 log.debug("element processed"); 679 } 680 } 681 682 protected VariableValue processHexVal(Element child, String name, String comment, boolean readOnly, boolean infoOnly, boolean writeOnly, boolean opsOnly, String CV, String mask, String item) throws NumberFormatException { 683 VariableValue v; 684 Attribute a; 685 int minVal = 0; 686 int maxVal = 255; 687 if ((a = child.getAttribute("min")) != null) { 688 minVal = Integer.valueOf(a.getValue(), 16); 689 } 690 if ((a = child.getAttribute("max")) != null) { 691 maxVal = Integer.valueOf(a.getValue(), 16); 692 } 693 if (maxVal > 255 && Objects.equals(mask, "VVVVVVVV")) { 694 mask = VariableValue.getMaxMask(maxVal); // replaces the default 8 bit mask when no mask is provided in xml 695 log.debug("Created mask {} for Hex CV {}", mask, name); 696 } 697 v = new HexVariableValue(name, comment, "", readOnly, infoOnly, writeOnly, opsOnly, CV, mask, minVal, maxVal, _cvModel.allCvMap(), _status, item); 698 _cvModel.registerCvToVariableMapping(CV, name); 699 return v; 700 } 701 702 protected VariableValue processLongAddressVal(String CV, boolean readOnly, boolean infoOnly, boolean writeOnly, String name, String comment, boolean opsOnly, String mask, String item) { 703 VariableValue v; 704 int minVal = 0; 705 int maxVal = 255; 706 _cvModel.addCV("18", readOnly, infoOnly, writeOnly); // ensure 2nd CV exists 707 v = new LongAddrVariableValue(name, comment, "", readOnly, infoOnly, writeOnly, opsOnly, CV, mask, minVal, maxVal, _cvModel.allCvMap(), _status, item, _cvModel.allCvMap().get("18")); 708 _cvModel.registerCvToVariableMapping(CV, name); 709 _cvModel.registerCvToVariableMapping("18", name); // see fixed value two lines up 710 return v; 711 } 712 713 protected VariableValue processShortAddressVal(String name, String comment, boolean readOnly, boolean infoOnly, boolean writeOnly, boolean opsOnly, String CV, String mask, String item, Element child) { 714 VariableValue v; 715 ShortAddrVariableValue v1 = new ShortAddrVariableValue(name, comment, "", readOnly, infoOnly, writeOnly, opsOnly, CV, mask, _cvModel.allCvMap(), _status, item); 716 v = v1; 717 // get specifics if any 718 List<Element> l = child.getChildren("shortAddressChanges"); 719 for (Element element : l) { 720 v1.setModifiedCV(element.getAttribute("cv").getValue()); 721 } 722 _cvModel.registerCvToVariableMapping(CV, name); 723 return v; 724 } 725 726 protected VariableValue processSpeedTableVal(Element child, String CV, boolean readOnly, boolean infoOnly, boolean writeOnly, String name, String comment, boolean opsOnly, String mask, String item) throws NumberFormatException { 727 VariableValue v; 728 Attribute a; 729 int minVal = 0; 730 int maxVal = 255; 731 if ((a = child.getAttribute("min")) != null) { 732 minVal = Integer.parseInt(a.getValue()); 733 } 734 if ((a = child.getAttribute("max")) != null) { 735 maxVal = Integer.parseInt(a.getValue()); 736 } 737 Attribute entriesAttr = child.getAttribute("entries"); 738 int entries = 28; 739 try { 740 if (entriesAttr != null) { 741 entries = entriesAttr.getIntValue(); 742 } 743 } catch (org.jdom2.DataConversionException ignored) { 744 } 745 Attribute ESUAttr = child.getAttribute("mfx"); 746 boolean mfxFlag = false; 747 try { 748 if (ESUAttr != null) { 749 mfxFlag = ESUAttr.getBooleanValue(); 750 } 751 } catch (org.jdom2.DataConversionException ignored) { 752 } 753 // ensure all CVs exist 754 for (int i = 0; i < entries; i++) { 755 _cvModel.addCV(Integer.toString(Integer.parseInt(CV) + i), readOnly, infoOnly, writeOnly); 756 _cvModel.registerCvToVariableMapping(Integer.toString(Integer.parseInt(CV) + i), name); 757 758 } 759 if (mfxFlag) { 760 _cvModel.addCV("2", readOnly, infoOnly, writeOnly); 761 _cvModel.registerCvToVariableMapping("2", name); 762 763 _cvModel.addCV("5", readOnly, infoOnly, writeOnly); 764 _cvModel.registerCvToVariableMapping("5", name); 765 766 } 767 v = new SpeedTableVarValue(name, comment, "", readOnly, infoOnly, writeOnly, opsOnly, CV, mask, minVal, maxVal, _cvModel.allCvMap(), _status, item, entries, mfxFlag); 768 return v; 769 } 770 771 protected VariableValue processSplitVal(Element child, String CV, boolean readOnly, boolean infoOnly, boolean writeOnly, String name, String comment, boolean opsOnly, String mask, String item) throws NumberFormatException { 772 VariableValue v; 773 Attribute a; 774 int minVal = 0; 775 int maxVal = 255; 776 String highCV = null; 777 778 if ((a = child.getAttribute("highCV")) != null) { 779 highCV = a.getValue(); 780 _cvModel.addCV("" + (highCV), readOnly, infoOnly, writeOnly); // ensure 2nd CV exists 781 _cvModel.registerCvToVariableMapping("" + (highCV), name); 782 783 } 784 int factor = 1; 785 if ((a = child.getAttribute("factor")) != null) { 786 factor = Integer.parseInt(a.getValue()); 787 } 788 int offset = 0; 789 if ((a = child.getAttribute("offset")) != null) { 790 offset = Integer.parseInt(a.getValue()); 791 } 792 String uppermask = "VVVVVVVV"; 793 if ((a = child.getAttribute("upperMask")) != null) { 794 uppermask = a.getValue(); 795 } 796 String extra3 = "0"; 797 if ((a = child.getAttribute("min")) != null) { 798 extra3 = a.getValue(); 799 } 800 String extra4 = Long.toUnsignedString(~0); 801 if ((a = child.getAttribute("max")) != null) { 802 extra4 = a.getValue(); 803 } 804 v = new SplitVariableValue(name, comment, "", readOnly, infoOnly, writeOnly, opsOnly, CV, mask, minVal, maxVal, _cvModel.allCvMap(), _status, item, highCV, factor, offset, uppermask, null, null, extra3, extra4); 805 _cvModel.registerCvToVariableMapping(CV, name); 806 return v; 807 } 808 809 protected VariableValue processSplitHexVal(Element child, String CV, boolean readOnly, boolean infoOnly, boolean writeOnly, String name, String comment, boolean opsOnly, String mask, String item) throws NumberFormatException { 810 VariableValue v; 811 Attribute a; 812 int minVal = 0; 813 int maxVal = 255; 814 String highCV = null; 815 816 if ((a = child.getAttribute("highCV")) != null) { 817 highCV = a.getValue(); 818 _cvModel.addCV("" + (highCV), readOnly, infoOnly, writeOnly); // ensure 2nd CV exists 819 _cvModel.registerCvToVariableMapping("" + (highCV), name); 820 } 821 int factor = 1; 822 if ((a = child.getAttribute("factor")) != null) { 823 factor = Integer.parseInt(a.getValue()); 824 } 825 int offset = 0; 826 if ((a = child.getAttribute("offset")) != null) { 827 offset = Integer.parseInt(a.getValue()); 828 } 829 String uppermask = "VVVVVVVV"; 830 if ((a = child.getAttribute("upperMask")) != null) { 831 uppermask = a.getValue(); 832 } 833 String extra1 = "default"; 834 if ((a = child.getAttribute("case")) != null) { 835 extra1 = a.getValue(); 836 } 837 String extra3 = "0"; 838 if ((a = child.getAttribute("min")) != null) { 839 extra3 = a.getValue(); 840 } 841 String extra4 = Long.toUnsignedString(~0, 16); 842 if ((a = child.getAttribute("max")) != null) { 843 extra4 = a.getValue(); 844 } 845 v = new SplitHexVariableValue(name, comment, "", readOnly, infoOnly, writeOnly, opsOnly, CV, mask, minVal, maxVal, _cvModel.allCvMap(), _status, item, highCV, factor, offset, uppermask, extra1, null, extra3, extra4); 846 _cvModel.registerCvToVariableMapping(CV, name); 847 return v; 848 } 849 850 protected VariableValue processSplitHundredsVal(Element child, String CV, boolean readOnly, boolean infoOnly, boolean writeOnly, String name, String comment, boolean opsOnly, String mask, String item) throws NumberFormatException { 851 VariableValue v; 852 Attribute a; 853 int minVal = 0; 854 int maxVal = 255; 855 String highCV = null; 856 857 if ((a = child.getAttribute("highCV")) != null) { 858 highCV = a.getValue(); 859 _cvModel.addCV("" + (highCV), readOnly, infoOnly, writeOnly); // ensure 2nd CV exists 860 _cvModel.registerCvToVariableMapping("" + (highCV), name); 861 } 862 int factor = 1; 863 if ((a = child.getAttribute("factor")) != null) { 864 factor = Integer.parseInt(a.getValue()); 865 } 866 int offset = 0; 867 if ((a = child.getAttribute("offset")) != null) { 868 offset = Integer.parseInt(a.getValue()); 869 } 870 String uppermask = "VVVVVVVV"; 871 if ((a = child.getAttribute("upperMask")) != null) { 872 uppermask = a.getValue(); 873 } 874 String extra3 = "0"; 875 if ((a = child.getAttribute("min")) != null) { 876 extra3 = a.getValue(); 877 } 878 String extra4 = Long.toUnsignedString(~0); 879 if ((a = child.getAttribute("max")) != null) { 880 extra4 = a.getValue(); 881 } 882 v = new SplitHundredsVariableValue(name, comment, "", readOnly, infoOnly, writeOnly, opsOnly, CV, mask, minVal, maxVal, _cvModel.allCvMap(), _status, item, highCV, factor, offset, uppermask, null, null, extra3, extra4); 883 _cvModel.registerCvToVariableMapping(CV, name); 884 return v; 885 } 886 887 @edu.umd.cs.findbugs.annotations.SuppressFBWarnings( value="SLF4J_FORMAT_SHOULD_BE_CONST", 888 justification="I18N of Error Message") 889 protected VariableValue processSplitTextVal(Element child, String CV, boolean readOnly, boolean infoOnly, boolean writeOnly, String name, String comment, boolean opsOnly, String mask, String item) throws NumberFormatException { 890 VariableValue v; 891 Attribute a; 892 int minVal = 0; 893 int maxVal = 255; 894 String highCV = null; 895 896 if ((a = child.getAttribute("min")) != null) { 897 minVal = Integer.parseInt(a.getValue()); 898 } 899 if ((a = child.getAttribute("max")) != null) { 900 maxVal = Integer.parseInt(a.getValue()); 901 } 902 if ((a = child.getAttribute("highCV")) != null) { 903 highCV = a.getValue(); 904 _cvModel.addCV("" + (highCV), readOnly, infoOnly, writeOnly); // ensure 2nd CV exists 905 _cvModel.registerCvToVariableMapping("" + (highCV), name); 906 } 907 int factor = 1; 908 if ((a = child.getAttribute("factor")) != null) { 909 factor = Integer.parseInt(a.getValue()); 910 } 911 int offset = 0; 912 if ((a = child.getAttribute("offset")) != null) { 913 offset = Integer.parseInt(a.getValue()); 914 } 915 String uppermask = "VVVVVVVV"; 916 if ((a = child.getAttribute("upperMask")) != null) { 917 uppermask = a.getValue(); 918 } 919 String match = null; 920 if ((a = child.getAttribute("match")) != null) { 921 match = a.getValue(); 922 } 923 String termByte = "0"; 924 if ((a = child.getAttribute("termByte")) != null) { 925 termByte = a.getValue(); 926 } 927 String padByte = "0"; 928 if ((a = child.getAttribute("padByte")) != null) { 929 padByte = a.getValue(); 930 } 931 String charSet = defaultCharset().name(); 932 if ((a = child.getAttribute("charSet")) != null) { 933 charSet = a.getValue(); 934 } 935 boolean ok; 936 try { 937 ok = isSupported(charSet); 938 } catch (IllegalArgumentException ex) { 939 ok = false; 940 } 941 if (!ok) { 942 synchronized (this) { 943 JmriJOptionPane.showMessageDialog(new JFrame(), Bundle.getMessage("UnsupportedCharset", charSet, name), 944 Bundle.getMessage("DecoderDefError"), JmriJOptionPane.ERROR_MESSAGE); // NOI18N 945 } 946 log.error(Bundle.getMessage("UnsupportedCharset", charSet, name)); 947 } 948 v = new SplitTextVariableValue(name, comment, "", readOnly, infoOnly, writeOnly, opsOnly, CV, mask, minVal, maxVal, _cvModel.allCvMap(), _status, item, highCV, factor, offset, uppermask, match, termByte, padByte, charSet); 949 _cvModel.registerCvToVariableMapping(CV, name); 950 return v; 951 } 952 953 protected VariableValue processSplitDateTimeVal(Element child, String CV, boolean readOnly, boolean infoOnly, boolean writeOnly, String name, String comment, boolean opsOnly, String mask, String item) throws NumberFormatException { 954 VariableValue v; 955 Attribute a; 956 int minVal = 0; 957 int maxVal = 255; 958 boolean varRreadOnly = true; // unable to parse text dates accurately enough so force variable (but not CVs) to be read only 959 String highCV = null; 960 961 if ((a = child.getAttribute("min")) != null) { 962 minVal = Integer.parseInt(a.getValue()); 963 } 964 if ((a = child.getAttribute("max")) != null) { 965 maxVal = Integer.parseInt(a.getValue()); 966 } 967 if ((a = child.getAttribute("highCV")) != null) { 968 highCV = a.getValue(); 969 _cvModel.addCV("" + (highCV), readOnly, infoOnly, writeOnly); // ensure 2nd CV exists 970 _cvModel.registerCvToVariableMapping("" + (highCV), name); 971 } 972 int factor = 1; 973 int offset = 0; 974 975 String uppermask = "VVVVVVVV"; 976 if ((a = child.getAttribute("upperMask")) != null) { 977 uppermask = a.getValue(); 978 } 979 String extra1 = "2000-01-01T00:00:00"; // The S9.3.2 RailCom epoch 980 // Java epoch is "1970-01-01T00:00:00" 981 if ((a = child.getAttribute("base")) != null) { 982 extra1 = a.getValue(); 983 } 984 String extra2 = "1"; 985 if ((a = child.getAttribute("factor")) != null) { 986 extra2 = a.getValue(); 987 } 988 String extra3 = "Seconds"; 989 if ((a = child.getAttribute("unit")) != null) { 990 extra3 = a.getValue(); 991 } 992 String extra4 = "default"; 993 if ((a = child.getAttribute("display")) != null) { 994 extra4 = a.getValue(); 995 } 996 v = new SplitDateTimeVariableValue(name, comment, "", varRreadOnly, infoOnly, writeOnly, opsOnly, CV, mask, minVal, maxVal, _cvModel.allCvMap(), _status, item, highCV, factor, offset, uppermask, extra1, extra2, extra3, extra4); 997 _cvModel.registerCvToVariableMapping(CV, name); 998 return v; 999 } 1000 1001 protected void setButtonsReadWrite(boolean readOnly, boolean infoOnly, boolean writeOnly, JButton bw, JButton br, int row) { 1002 if (readOnly || infoOnly) { 1003 // readOnly or infoOnly, config write, read buttons 1004 if (writeOnly) { 1005 bw.setEnabled(true); 1006 bw.setActionCommand("W" + row); 1007 bw.addActionListener(this); 1008 } else { 1009 bw.setEnabled(false); 1010 } 1011 if (infoOnly) { 1012 br.setEnabled(false); 1013 } else { 1014 br.setActionCommand("R" + row); 1015 br.addActionListener(this); 1016 } 1017 } else { 1018 // not readOnly or infoOnly, config write, read buttons 1019 bw.setActionCommand("W" + row); 1020 bw.addActionListener(this); 1021 if (writeOnly) { 1022 br.setEnabled(false); 1023 } else { 1024 br.setActionCommand("R" + row); 1025 br.addActionListener(this); 1026 } 1027 } 1028 } 1029 1030 public void setButtonModeFromProgrammer() { 1031 if (_cvModel.getProgrammer() == null || !_cvModel.getProgrammer().getCanRead()) { 1032 for (JButton b : _readButtons) { 1033 b.setEnabled(false); 1034 } 1035 } 1036 } 1037 1038 protected void setToolTip(Element e, VariableValue v) { 1039 // back to general processing 1040 // add tooltip text if present 1041 { 1042 String t; 1043 if ((t = LocaleSelector.getAttribute(e, "tooltip")) != null) { 1044 v.setToolTipText(t); 1045 } 1046 } 1047 } 1048 1049 void reportBogus(Element elem) { 1050 log.error("Did not find a valid type in {}", elem.getChildren()); 1051 for (Attribute a : elem.getAttributes()) log.error(" attribute: {}",a); 1052 } 1053 1054 /** 1055 * Configure from a constant. This is like setRow (which processes a 1056 * variable Element). 1057 * 1058 * @param e element to set. 1059 */ 1060 @SuppressFBWarnings(value = "NP_LOAD_OF_KNOWN_NULL_VALUE", 1061 justification = "null mask parameter to ConstantValue constructor expected.") 1062 public void setConstant(Element e) { 1063 // get the values for the VariableValue ctor 1064 String stdname = e.getAttribute("item").getValue(); 1065 log.debug("Starting to setConstant \"{}\"", stdname); 1066 1067 String name = LocaleSelector.getAttribute(e, "label"); 1068 if (name == null || name.equals("")) { 1069 name = stdname; 1070 } 1071 1072 String comment = LocaleSelector.getAttribute(e, "comment"); 1073 1074 String mask = null; 1075 1076 // intrinsically readOnly, so use just that branch 1077 JButton bw = new JButton(); 1078 _writeButtons.addElement(bw); 1079 1080 // config read button as a dummy - there's really nothing to read 1081 JButton br = new JButton("Read"); // NOI18N 1082 _readButtons.addElement(br); 1083 1084 // no CV references are added here 1085 // have to handle various value types, see "snippet" 1086 Attribute a; 1087 1088 // set to default value if specified (CV load will later override this) 1089 int defaultVal = 0; 1090 if ((a = e.getAttribute("default")) != null) { 1091 String val = a.getValue(); 1092 log.debug("Found default value: {} for {}", val, stdname); 1093 defaultVal = Integer.parseInt(val); 1094 } 1095 1096 // create the specific object 1097 ConstantValue v = new ConstantValue(name, comment, "", true, true, false, false, 1098 "", mask, defaultVal, defaultVal, 1099 _cvModel.allCvMap(), _status, stdname); 1100 1101 // record new variable, update state, hook up listeners 1102 rowVector.addElement(v); 1103 v.setState(AbstractValue.ValueState.FROMFILE); 1104 v.addPropertyChangeListener(this); 1105 1106 // set to default value if specified (CV load will later override this) 1107 if ((a = e.getAttribute("default")) != null) { 1108 String val = a.getValue(); 1109 log.debug("Found default value: {} for {}", val, name); 1110 v.setIntValue(defaultVal); 1111 } 1112 } 1113 1114 /** 1115 * Programmatically create a new DecVariableValue from parameters. 1116 * 1117 * @param name variable name. 1118 * @param CV CV string. 1119 * @param comment variable comment. 1120 * @param mask CV mask. 1121 * @param readOnly true if read only, else false. 1122 * @param infoOnly true if information only, else false. 1123 * @param writeOnly true if write only, else false. 1124 * @param opsOnly true if ops only, else false. 1125 */ 1126 public void newDecVariableValue(String name, String CV, String comment, String mask, 1127 boolean readOnly, boolean infoOnly, boolean writeOnly, boolean opsOnly) { 1128 setFileDirty(true); 1129 1130 int minVal = 0; 1131 int maxVal = 255; 1132 _cvModel.addCV("" + CV, readOnly, infoOnly, writeOnly); 1133 1134 int row = getRowCount(); 1135 1136 // config write button 1137 JButton bw = new JButton(Bundle.getMessage("ButtonWrite")); 1138 bw.setActionCommand("W" + row); 1139 bw.addActionListener(this); 1140 _writeButtons.addElement(bw); 1141 1142 // config read button 1143 JButton br = new JButton(Bundle.getMessage("ButtonRead")); 1144 br.setActionCommand("R" + row); 1145 br.addActionListener(this); 1146 _readButtons.addElement(br); 1147 1148 VariableValue v = new DecVariableValue(name, comment, "", readOnly, infoOnly, writeOnly, opsOnly, 1149 CV, mask, minVal, maxVal, _cvModel.allCvMap(), _status, null); 1150 rowVector.addElement(v); 1151 v.addPropertyChangeListener(this); 1152 } 1153 1154 @Override 1155 public void actionPerformed(ActionEvent e) { 1156 if (log.isDebugEnabled()) { 1157 log.debug("action performed, command: {}", e.getActionCommand()); 1158 } 1159 setFileDirty(true); 1160 char b = e.getActionCommand().charAt(0); 1161 int row = Integer.parseInt(e.getActionCommand().substring(1)); 1162 log.debug("event on {} row {}", b, row); 1163 if (b == 'R') { 1164 // read command 1165 read(row); 1166 } else { 1167 // write command 1168 write(row); 1169 } 1170 } 1171 1172 /** 1173 * Command reading of a particular variable. 1174 * 1175 * @param i row number 1176 */ 1177 public void read(int i) { 1178 VariableValue v = rowVector.elementAt(i); 1179 v.readAll(); 1180 } 1181 1182 /** 1183 * Command writing of a particular variable. 1184 * 1185 * @param i row number 1186 */ 1187 public void write(int i) { 1188 VariableValue v = rowVector.elementAt(i); 1189 v.writeAll(); 1190 } 1191 1192 @Override 1193 public void propertyChange(PropertyChangeEvent e) { 1194 if (log.isDebugEnabled()) { 1195 log.debug("prop changed {} new value: {}{} Source {}", e.getPropertyName(), e.getNewValue(), e.getPropertyName().equals("State") ? (" (" + ((AbstractValue.ValueState) e.getNewValue()).getName() + ") ") : " ", e.getSource()); 1196 } 1197 if (e.getNewValue() == null) { 1198 log.error("new value of {} should not be null!", e.getPropertyName(), new Exception()); 1199 } 1200 // set dirty only if edited or read 1201 if (e.getPropertyName().equals("State") 1202 && ((AbstractValue.ValueState) e.getNewValue()) == AbstractValue.ValueState.READ 1203 || e.getPropertyName().equals("State") 1204 && ((AbstractValue.ValueState) e.getNewValue()) == AbstractValue.ValueState.EDITED) { 1205 setFileDirty(true); 1206 1207 } 1208 fireTableDataChanged(); 1209 } 1210 1211 public void configDone() { 1212 fireTableDataChanged(); 1213 } 1214 1215 /** 1216 * Represents any change to values, etc, hence rewriting the file is 1217 * desirable. 1218 * 1219 * @return true if dirty, else false. 1220 */ 1221 public boolean fileDirty() { 1222 return _fileDirty; 1223 } 1224 1225 public void setFileDirty(boolean b) { 1226 _fileDirty = b; 1227 } 1228 private boolean _fileDirty; 1229 1230 /** 1231 * Check for change to values, etc, hence rewriting the decoder is 1232 * desirable. 1233 * 1234 * @return true if dirty, else false. 1235 */ 1236 public boolean decoderDirty() { 1237 int len = rowVector.size(); 1238 for (int i = 0; i < len; i++) { 1239 if (((rowVector.elementAt(i))).getState() == AbstractValue.ValueState.EDITED) { 1240 return true; 1241 } 1242 } 1243 return false; 1244 } 1245 1246 /** 1247 * Returns the (first) variable that matches a given name string. 1248 * <p> 1249 * Searches first for "item", the true name, but if none found will attempt 1250 * to find a matching "label". In that case, only the default language is 1251 * checked. 1252 * 1253 * @param name search string. 1254 * @return first matching variable found. 1255 */ 1256 public VariableValue findVar(String name) { 1257 for (int i = 0; i < getRowCount(); i++) { 1258 if (name.equals(getItem(i))) { 1259 log.trace("findVar matched '{}' by Item", name); 1260 return getVariable(i); 1261 } 1262 } 1263 for (int i = 0; i < getRowCount(); i++) { 1264 if (name.equals(getLabel(i))) { 1265 log.trace("findVar matched '{}' by Label rather than Item", name); 1266 return getVariable(i); 1267 } 1268 } 1269 log.debug("findVar did not match {}, returns null", name); 1270 return null; 1271 } 1272 1273 /** 1274 * Returns the index of the first variable that matches a given name string. 1275 * <p> 1276 * Checks the search string against every variable's "item", the true name, 1277 * then against their "label" (default language only) and finally the 1278 * CV name before moving on to the next variable if none of those match. 1279 * 1280 * @param name search string. 1281 * @return index of the first matching variable found. 1282 */ 1283 public int findVarIndex(String name) { 1284 return findVarIndex(name, false); 1285 } 1286 1287 /** 1288 * Returns the index of a variable that matches a given name string. 1289 * <p> 1290 * Checks the search string against every variable's "item", the true name, 1291 * then against their "label" (default language only) and finally the 1292 * CV name before moving on to the next variable if none of those match. 1293 * 1294 * Depending on the second parameter, it will return the index of the first 1295 * or last variable in our internal rowVector that matches the given string. 1296 * 1297 * @param name search string. 1298 * @param searchFromEnd If true, will start searching from the end. 1299 * @return index of the first matching variable found. 1300 */ 1301 public int findVarIndex(String name, boolean searchFromEnd) { 1302 if(searchFromEnd) { 1303 for (int i = getRowCount() - 1; i >= 0; i--) { 1304 if (name.equals(getItem(i))) { 1305 return i; 1306 } 1307 if (name.equals(getLabel(i))) { 1308 return i; 1309 } 1310 if (name.equals("CV" + getCvName(i))) { 1311 return i; 1312 } 1313 } 1314 } else { 1315 for (int i = 0; i < getRowCount(); i++) { 1316 if (name.equals(getItem(i))) { 1317 return i; 1318 } 1319 if (name.equals(getLabel(i))) { 1320 return i; 1321 } 1322 if (name.equals("CV" + getCvName(i))) { 1323 return i; 1324 } 1325 } 1326 } 1327 return -1; 1328 } 1329 1330 public void dispose() { 1331 log.debug("dispose"); 1332 1333 // remove buttons 1334 for (int i = 0; i < _writeButtons.size(); i++) { 1335 _writeButtons.elementAt(i).removeActionListener(this); 1336 } 1337 for (int i = 0; i < _readButtons.size(); i++) { 1338 _readButtons.elementAt(i).removeActionListener(this); 1339 } 1340 1341 // remove variables listeners 1342 for (int i = 0; i < rowVector.size(); i++) { 1343 VariableValue v = rowVector.elementAt(i); 1344 v.removePropertyChangeListener(this); 1345 v.dispose(); 1346 } 1347 1348 headers = null; 1349 1350 rowVector.removeAllElements(); 1351 rowVector = null; 1352 1353 _cvModel = null; 1354 1355 _writeButtons.removeAllElements(); 1356 _writeButtons = null; 1357 1358 _readButtons.removeAllElements(); 1359 _readButtons = null; 1360 1361 _status = null; 1362 } 1363 1364 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(VariableTableModel.class); 1365 1366}