001package jmri.jmrit.symbolicprog; 002 003import java.awt.Component; 004import java.util.HashMap; 005import javax.swing.JComponent; 006import javax.swing.JLabel; 007import org.slf4j.Logger; 008import org.slf4j.LoggerFactory; 009 010/** 011 * Represents a single Variable value; abstract base class. 012 * <p> 013 * The "changed" parameter (non-bound, accessed via isChanged) indicates whether 014 * a "write changes" or "read changes" operation should handle this object. 015 * <br><br> 016 * The mask shown below comes in two forms: 017 * <ul> 018 * <li> A character-by-character bit mask of 8 or 16 binary digits, e.g. 019 * "XXVVVVXXX" 020 * <br> 021 * In this case, the "V" bits denote a continuous bit field that contains the 022 * datum. For use in SplitVariableValue this mask can also be entered a a list of 023 * multiple bit masks, separated by spaces. 024 * <li>A small decimal value, i.e. "9" 025 * <br> 026 * In this case, aka Radix mask, it forms the multiplier (N) which combines with the 027 * maximum value (maxVal, defined in a subclass) to break the CV into three 028 * parts: 029 * <ul> 030 * <li>lowest part, stored as 1 times a value 0-(N-1) 031 * <li>datum stored as datum*N (datum is limited to maxVal) 032 * <li>highest part, which stored as N*(maxVal+1) times the value 033 * </ul> 034 * As an example, consider storing two decimal digits as a decimal value. You 035 * can't use a bit mask changing the 2nd digit from 1 to 7, for example with a 036 * total value of 14 to 74, changes bits that are also used by the first digit. 037 * Instead, code this as 038 * <ul> 039 * <li> mask="1" maxVal="9" 040 * <li> mask="10" maxVal="9" 041 * </ul> 042 * and you'll get the desired effect. (This requires Schema 043 * <a href="http://jmri.org/xml/schema/decoder-4-15-2.xsd">xml/schema/decoder-4-15-2.xsd</a> 044 * for validation) 045 * </ul> 046 * 047 * @author Bob Jacobsen Copyright (C) 2001, 2002, 2003, 2004, 2005, 2013 048 * @author Howard G. Penny Copyright (C) 2005 049 */ 050public abstract class VariableValue extends AbstractValue implements java.beans.PropertyChangeListener { 051 052 private String _label; 053 private String _item; 054 private String _cvName; 055 056 /** 057 * A vector of CV objects used to look up CVs. 058 */ 059 protected HashMap<String, CvValue> _cvMap; 060 061 /** 062 * Field holds the current status. 063 */ 064 protected JLabel _status = null; 065 066 /** 067 * Field holds the current tool tip text. 068 */ 069 protected String _tooltipText = null; 070 // and thus can be called without limit 071 // and thus should be called a limited number of times 072 073 /** 074 * Get a display representation {@code Object} of this variable. 075 * <br><br> 076 * The actual stored value of a variable is not the most interesting thing. 077 * Instead, you usually get an {@code Object} representation for display in 078 * a table, etc. Modification of the state of that object then gets 079 * reflected back, causing the underlying CV objects to change. 080 * 081 * @return the {@code Object} representation for display purposes 082 */ 083 public abstract Component getCommonRep(); // and thus should be called a limited number of times 084 085 /** 086 * Creates a new {@code Object} representation for display purposes, using 087 * the specified format. 088 * 089 * @param format a name representing 090 * @return an {@code Object} representation for display purposes 091 */ 092 public abstract Component getNewRep(String format); // this one is returning a new object 093 094 /** 095 * @return String that can (usually) be interpreted as an integer 096 */ 097 public abstract String getValueString(); 098 099 /** 100 * @return Value as a native-form Object 101 */ 102 public abstract Object getValueObject(); 103 104 /** 105 * @return User-desired value, which may or may not be an integer 106 */ 107 public String getTextValue() { 108 return getValueString(); 109 } 110 111 /** 112 * Provide a user-readable description of the CVs accessed by this variable. 113 * <p> 114 * Default is a single CV number 115 * 116 * @return a user-readable description 117 */ 118 public String getCvDescription() { 119 return "CV" + _cvNum; 120 } 121 122 /** 123 * Set the value from a single number. 124 * <p> 125 * In some cases, e.g. speed tables, this will result in complex behavior, 126 * where setIntValue(getIntValue()) results in something unexpected. 127 * 128 * @param i the integer value to set 129 */ 130 public abstract void setIntValue(int i); 131 132 /** 133 * Set value from a String value. 134 * <p> 135 * The current implementation is a stand-in. Note that e.g. Speed Tables 136 * don't use a single Int. The solution to that is to override this in 137 * complicated variable types. 138 * <p> 139 * Since variable values can now be non-integer (text, long, hex etc.) we 140 * need a universally-usable method for setting values, such as default 141 * values in decoder definitions. 142 * <p> 143 * In the long term we don't want to have this method failing silently. 144 * Subclasses that need silent failure should override this method. 145 * 146 * @param value the String value to set 147 */ 148 public void setValue(String value) { 149 try { 150 int val = Integer.parseInt(value); 151 setIntValue(val); 152 } catch (NumberFormatException e) { 153 log.warn("skipping set of non-integer value \"{}\"", value); 154 } 155 } 156 157 /** 158 * Get the value as a single integer. 159 * <p> 160 * In some cases, e.g. speed tables, this will result in complex behavior, 161 * where setIntValue(getIntValue()) results in something unexpected. 162 * 163 * @return the value as an integer 164 */ 165 public abstract int getIntValue(); 166 167 /** 168 * Get the value as an Unsigned Long. 169 * <p> 170 * Some subclasses (e.g. {@link SplitVariableValue}) store the value as a 171 * {@code long} rather than an {@code integer}. This method should be used 172 * in cases where such a class may be queried (e.g. by 173 * {@link ArithmeticQualifier}). 174 * <p> 175 * If not overridden by a subclass, it will return an 176 * {@link Integer#toUnsignedLong UnsignedLong} conversion of the value 177 * returned by {@link #getIntValue getIntValue()}. 178 * 179 * @return the value as a long 180 */ 181 public long getLongValue() { 182 return Integer.toUnsignedLong(getIntValue()); 183 } 184 185 /** 186 * This should be overridden by any implementation. 187 */ 188 void updatedTextField() { 189 log.error("unexpected use of updatedTextField()", new Exception("traceback")); 190 } 191 192 /** 193 * Always read the contents of this Variable. 194 */ 195 public abstract void readAll(); 196 197 /** 198 * Always write the contents of this Variable. 199 */ 200 public abstract void writeAll(); 201 202 /** 203 * Confirm the contents of this Variable. 204 */ 205 public void confirmAll() { 206 log.error("should never execute this"); 207 } 208 209 /** 210 * Read the contents of this Variable if it's in a state that indicates it 211 * was "changed". 212 * 213 * @see #isChanged 214 */ 215 public abstract void readChanges(); 216 217 /** 218 * Write the contents of this Variable if it's in a state that indicates it 219 * was "changed". 220 * 221 * @see #isChanged 222 */ 223 public abstract void writeChanges(); 224 225 /** 226 * Determine whether this Variable is "changed", so that "read changes" and 227 * "write changes" will act on it. 228 * 229 * @see #considerChanged 230 * @return true if Variable is "changed" 231 */ 232 public abstract boolean isChanged(); 233 234 /** 235 * Default implementation for subclasses to tell if a CV meets a common 236 * definition of "changed". This implementation will only consider a 237 * variable to be changed if the underlying CV(s) state is EDITED, e.g. if 238 * the CV(s) has been manually edited. 239 * 240 * @param c CV to be examined 241 * @return true if to be considered changed 242 */ 243 public static boolean considerChanged(CvValue c) { 244 if (c == null) { 245 return false; // if no CV was assigned to a decoder variable 246 } 247 ValueState state = c.getState(); 248 return (state == ValueState.EDITED || state == ValueState.UNKNOWN); 249 } 250 251 // handle incoming parameter notification 252 @Override 253 public abstract void propertyChange(java.beans.PropertyChangeEvent e); 254 255 /** 256 * Dispose of the object. 257 */ 258 public abstract void dispose(); 259 260 /** 261 * Gets a (usually text) description of the variable type and range. 262 * 263 * @return description of the variable type and range 264 */ 265 public abstract Object rangeVal(); 266 267 // methods implemented here: 268 /** 269 * 270 * @param label the displayed label for the Variable 271 * @param comment for information only, generally not displayed 272 * @param cvName the name for the CV. Seems to be generally ignored and 273 * set to "". 274 * @param readOnly true if the variable is to be readable-only 275 * @param infoOnly true if the variable is to be for information only (a 276 * fixed value that is neither readable or writable) 277 * @param writeOnly true if the variable is to be writable-only 278 * @param opsOnly true if the variable is to be programmable in ops mode 279 * only 280 * @param cvNum the CV number 281 * @param mask a bit mask like XXXVVVXX (converts to a value like 282 * 0b00011100) or a series of masks separated by spaces 283 * @param v a vector of CV objects used to look up CVs 284 * @param status a field that holds the current status 285 * @param item the unique name for this Variable 286 * @see VariableValue 287 */ 288 public VariableValue(String label, String comment, String cvName, 289 boolean readOnly, boolean infoOnly, boolean writeOnly, boolean opsOnly, 290 String cvNum, String mask, HashMap<String, CvValue> v, JLabel status, String item) { 291 _label = label; 292 _comment = comment; 293 _cvName = cvName; 294 _readOnly = readOnly; 295 _infoOnly = infoOnly; 296 _writeOnly = writeOnly; 297 _opsOnly = opsOnly; 298 _cvNum = cvNum; 299 _mask = mask; // normally a single 8 bit mask but could be a space separated list of masks 300 _cvMap = v; 301 _status = status; 302 _item = item; 303 } 304 305 /** 306 * Create a null object. Normally only used for tests and to pre-load 307 * classes. 308 */ 309 protected VariableValue() { 310 } 311 312 // common information - none of these are bound 313 /** 314 * Gets the displayed label for the Variable. 315 * 316 * @return the displayed label for the Variable 317 */ 318 public String label() { 319 return _label; 320 } 321 322 /** 323 * Gets the unique name for this Variable. 324 * 325 * @return the unique name for this Variable 326 */ 327 public String item() { 328 return _item; 329 } 330 331 /** 332 * Get the CV name. 333 * 334 * @return the name for the CV 335 */ 336 public String cvName() { 337 return _cvName; 338 } 339 340 /** 341 * Set tooltip text to be used by both the "value" and representations of 342 * this Variable. 343 * <p> 344 * This is expected to be overridden in subclasses to change their internal 345 * info. 346 * 347 * @param t the tooltip text to be used 348 * @see #updateRepresentation 349 */ 350 public void setToolTipText(String t) { 351 _tooltipText = t; 352 } 353 354 /** 355 * Add the proper tooltip text to a graphical rep before returning it, sets 356 * the visibility. 357 * 358 * @param c the current graphical representation 359 * @return the updated graphical representation 360 */ 361 protected JComponent updateRepresentation(JComponent c) { 362 c.setToolTipText(_tooltipText); 363 c.setVisible(getAvailable()); 364 return c; 365 } 366 367 /** 368 * 369 * @return the comment 370 */ 371 public String getComment() { 372 return _comment; 373 } 374 private String _comment; 375 376 /** 377 * 378 * @return the value of the readOnly attribute 379 */ 380 public boolean getReadOnly() { 381 return _readOnly; 382 } 383 private boolean _readOnly; 384 385 /** 386 * 387 * @return the value of the infoOnly attribute 388 */ 389 public boolean getInfoOnly() { 390 return _infoOnly; 391 } 392 private boolean _infoOnly; 393 394 /** 395 * 396 * @return the value of the writeOnly attribute 397 */ 398 public boolean getWriteOnly() { 399 return _writeOnly; 400 } 401 private boolean _writeOnly; 402 403 /** 404 * 405 * @return the value of the opsOnly attribute 406 */ 407 public boolean getOpsOnly() { 408 return _opsOnly; 409 } 410 private boolean _opsOnly; 411 412 /** 413 * 414 * @return the CV number 415 */ 416 public String getCvNum() { 417 return _cvNum; 418 } 419 private String _cvNum; 420 421 /** 422 * 423 * @return the CV name 424 */ 425 public String getCvName() { 426 return _cvName; 427 } 428 429 /** 430 * Extending classes should override to return a single mask in case a list 431 * of masks was provided and the class only uses one. 432 * 433 * @return the CV bitmask in the form XXXVVVXX 434 */ 435 public String getMask() { 436 return _mask; 437 } 438 private String _mask; 439 440 /** 441 * 442 * @return the current state of the Variable 443 */ 444 public ValueState getState() { 445 return _state; 446 } 447 448 /** 449 * Sets the current state of the variable. 450 * 451 * @param state the desired state as per definitions in AbstractValue 452 * @see AbstractValue 453 */ 454 public void setState(ValueState state) { 455 setColor(state.getColor()); 456 if (_state != state || _state == ValueState.UNKNOWN) { 457 prop.firePropertyChange("State", _state, state); 458 } 459 _state = state; 460 } 461 private ValueState _state = ValueState.UNKNOWN; 462 463 /** 464 * {@inheritDoc} 465 * <p> 466 * Simple implementation for the case of a single CV. Intended to be 467 * sufficient for many subclasses. 468 */ 469 @Override 470 public void setToRead(boolean state) { 471 boolean newState = state; 472 473 // if this variable is disabled, then don't read, unless 474 // some other variable has already set that 475 if (!getAvailable() && !state) { // do want to set when state is true 476 log.debug("Variable not available, skipping setToRead(false) to leave as is"); 477 return; 478 } 479 480 // if read not available, don't force read 481 if (getInfoOnly() || getWriteOnly()) { 482 newState = false; 483 } 484 485 if (log.isDebugEnabled()) { 486 // avoid method calls unless debugging 487 log.debug("setToRead({}) with overrides {},{},{} sets {}", state, getInfoOnly(), getWriteOnly(), !getAvailable(), newState); 488 } 489 if (getCvNum() == null || getCvNum().equals("")) { 490 log.debug("no CV defined for value {}. setToRead skipped", _item); 491 return; 492 } 493 _cvMap.get(getCvNum()).setToRead(newState); 494 } 495 496 /** 497 * {@inheritDoc} 498 * <p> 499 * Simple implementation for the case of a single CV. Intended to be 500 * sufficient for many subclasses. 501 */ 502 @Override 503 public boolean isToRead() { 504 if (_cvMap.get(getCvNum()) != null) { // skip displayed variables without a CV 505 return _cvMap.get(getCvNum()).isToRead(); 506 } 507 return false; 508 } 509 510 /** 511 * {@inheritDoc} 512 * <p> 513 * Simple implementation for the case of a single CV. Intended to be 514 * sufficient for many subclasses. 515 */ 516 @Override 517 public void setToWrite(boolean state) { 518 boolean newState = state; 519 520 // if this variable is disabled, then don't write, unless 521 // some other variable has already set that 522 if (!getAvailable() && !state) { // do want to set when state is true 523 log.debug("Variable not available, skipping setToRead(false) to leave as is"); 524 return; 525 } 526 527 // if read not available, don't force read 528 if (getInfoOnly() || getReadOnly()) { 529 newState = false; 530 } 531 532 if (log.isDebugEnabled()) { 533 // avoid method calls unless debugging 534 log.debug("setToRead({}) with overrides {},{},{} sets {}", 535 state, getInfoOnly(), getWriteOnly(), !getAvailable(), newState); 536 } 537 CvValue cvVal; // null check in case decoder variable has no CV defined (yet) 538 try { 539 cvVal = _cvMap.get(getCvNum()); 540 } catch (NullPointerException e) { 541 log.error("no CV defined for value {}. setToWrite skipped. Verify variable was defined", _item); 542 return; 543 } 544 if (cvVal != null) { 545 cvVal.setToWrite(newState); 546 } 547 } 548 549 /** 550 * {@inheritDoc} 551 * <p> 552 * Simple implementation for the case of a single CV. Intended to be 553 * sufficient for many subclasses. 554 */ 555 @Override 556 public boolean isToWrite() { 557 if (_cvMap.get(getCvNum()) != null) { // skip displayed variables without a CV 558 return _cvMap.get(getCvNum()).isToWrite(); 559 } 560 return false; 561 } 562 563 /** 564 * Propagate a state change here to the CVs that are related, which will in 565 * turn propagate back to here. 566 * 567 * @param state the new state to set 568 */ 569 public abstract void setCvState(ValueState state); 570 571 /** 572 * Check if a variable is busy (during read, write operations). 573 * 574 * @return {@code true} if busy 575 */ 576 public boolean isBusy() { 577 return _busy; 578 } 579 580 /** 581 * 582 * @param newBusy the desired state 583 */ 584 protected void setBusy(boolean newBusy) { 585 boolean oldBusy = _busy; 586 _busy = newBusy; 587 if (newBusy != oldBusy) { 588 prop.firePropertyChange("Busy", Boolean.valueOf(oldBusy), Boolean.valueOf(newBusy)); 589 } 590 } 591 private boolean _busy = false; 592 593 /** 594 * In case a set of masks was provided, at end of Ctor pick the first mask 595 * for implementing classes that use just one. Call not required if mask is 596 * ignored. 597 */ 598 protected void simplifyMask() { 599 if (_mask != null && _mask.contains(" ")) { 600 log.debug("Mask for var {} was:{}", getCvName(), _mask); 601 _mask = _mask.split(" ")[0]; 602 log.debug("Mask1 for var {} is:{}", getCvName(), _mask); 603 } 604 } 605 606 /** 607 * Create a "VVV" style mask matching the size of max value in bits. 608 * @param maxVal the maximum value to be stored in the cv as decimal 609 * @return a string of V's 610 */ 611 protected static String getMaxMask(int maxVal) { 612 int length = Integer.toBinaryString(maxVal).length(); 613 StringBuilder sb = new StringBuilder(); 614 while (sb.length() < length) { 615 sb.append('V'); 616 } 617 return sb.toString(); 618 } 619 620 /** 621 * Convert a String bit mask like XXXVVVXX to an int like 0b00011100. 622 * 623 * @param maskString the textual (XXXVVVXX style) mask 624 * @return the binary integer (0b00011100 style) mask 625 */ 626 protected int maskValAsInt(String maskString) { 627 // convert String mask to int 628 int mask = 0; 629 for (int i = 0; i < maskString.length(); i++) { 630 mask = mask << 1; 631 try { 632 if (maskString.charAt(i) == 'V') { 633 mask = mask | 1; 634 } 635 } catch (StringIndexOutOfBoundsException e) { 636 log.error("mask \"{}\" could not be handled for variable {}", maskString, label()); 637 } 638 } 639 return mask; 640 } 641 642 /** 643 * Is this a bit mask (such as XVVVXXXX form) vs. radix mask (small 644 * integer)? 645 * 646 * @param mask the bit mask to check 647 * @return {@code true} if XVVVXXXX form 648 */ 649 protected boolean isBitMask(String mask) { 650 return mask.isEmpty() || mask.startsWith("X") || mask.startsWith("V"); 651 } 652 653 /** 654 * Find number of places to shift a value left to align it with a mask. 655 * <p> 656 * For example, a mask of "XXVVVXXX" means that the value 5 needs to be 657 * shifted left 3 places before being masked and stored as XX101XXX 658 * 659 * @param maskString the (XXXVVVXX style) mask 660 * @return the number of places to shift left before masking 661 */ 662 protected int offsetVal(String maskString) { 663 // convert String mask to int 664 int offset = 0; 665 for (int i = 0; i < maskString.length(); i++) { 666 if (maskString.charAt(i) == 'V') { 667 offset = maskString.length() - 1 - i; // number of places to shift left 668 } 669 } 670 return offset; 671 } 672 673 /** 674 * Extract the current value from the CV, using the mask as needed. 675 * 676 * @param Cv the full CV value of interest. 677 * @param maskString the (XXXVVVXX style or small int) mask for extracting the Variable 678 * value from this CV 679 * @param maxVal the maximum possible value for this Variable position in the CV. 680 * Note it's 10 (0-9) in a single digit using a radix mask. 681 * @return the current value of the Variable. Optional factor and offset not yet applied. 682 */ 683 protected int getValueInCV(int Cv, String maskString, int maxVal) { 684 if (isBitMask(maskString)) { 685 return (Cv & maskValAsInt(maskString)) >>> offsetVal(maskString); 686 } else { 687 int radix = Integer.parseInt(maskString); 688 log.trace("get value {} radix {} returns {}", Cv, radix, Cv / radix); 689 return (Cv / radix) % (maxVal + 1); 690 } 691 } 692 693 /** 694 * Insert a value into a CV, using the mask as needed. 695 * 696 * @param oldCv Value of the CV before this update is applied 697 * @param newVal Value for this variable (e.g. not the CV value). Optional factor and offset already applied. 698 * @param maskString The (XXXVVVXX style or small int) mask for this variable in character form 699 * @param maxVal the maximum possible value for this Variable 700 * @return int new value for the full CV 701 */ 702 protected int setValueInCV(int oldCv, int newVal, String maskString, int maxVal) { 703 if (isBitMask(maskString)) { 704 int mask = maskValAsInt(maskString); 705 int offset = offsetVal(maskString); 706 return (oldCv & ~mask) + ((newVal << offset) & mask); 707 } else { 708 int radix = Integer.parseInt(maskString); 709 int lowPart = oldCv % radix; 710 int newPart = newVal % (maxVal + 1) * radix; 711 int highPart = (oldCv / (radix * (maxVal + 1))) * (radix * (maxVal + 1)); 712 int retval = highPart + newPart + lowPart; 713 log.trace("Set sees oldCv {} radix {}, lowPart {}, newVal {}, highPart {}, does {}", oldCv, radix, lowPart, newVal, highPart, retval); 714 return retval; 715 } 716 } 717 718 /** 719 * Provide access to CVs used by this Variable. 720 * 721 * @return an array of CVs used by this Variable 722 */ 723 public abstract CvValue[] usesCVs(); 724 725 // initialize logging 726 private final static Logger log = LoggerFactory.getLogger(VariableValue.class); 727 728}