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