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}