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}