001package jmri.jmrit.symbolicprog;
002
003import java.awt.event.ActionEvent;
004import java.io.UnsupportedEncodingException;
005import java.util.HashMap;
006
007import javax.swing.JFrame;
008import javax.swing.JLabel;
009
010import jmri.util.swing.JmriJOptionPane;
011
012/**
013 * Like {@link SplitVariableValue}, except that the string representation is
014 * text.
015 * <br><br>
016 * Most attributes of {@link SplitVariableValue} are inherited.
017 * <br><br>
018 * Specific attributes for this class are:
019 * <ul>
020 * <li>
021 * A {@code match} attribute (which must be a {@code regular expression}) can be
022 * used to impose constraints on entered text.
023 * </li>
024 * <li>
025 * A {@code termByteStr} attribute can be used to change the default string
026 * terminator byte value. Valid values are 0-255 or "" to specify no terminator
027 * byte. The default is "0" (a null byte).
028 * </li>
029 * <li>
030 * A {@code padByteStr} attribute can be used to change the default string
031 * padding byte value. Valid values are 0-255 or "" to specify no pad byte. The
032 * default is "0" (a null byte).
033 * </li>
034 * <li>
035 * A {@code charSet} attribute can be used to change the character set used to
036 * encode or decode the text string. Valid values are any Java-supported
037 * {@link java.nio.charset.Charset} name. If not specified, the default
038 * character set of this Java virtual machine is used.
039 * </li>
040 * </ul>
041 *
042 * @author Bob Jacobsen Copyright (C) 2001, 2002, 2003, 2004, 2013, 2014
043 * @author Dave Heap Copyright (C) 2016
044 */
045public class SplitTextVariableValue extends SplitVariableValue {
046
047    public static final String NO_TERM_BYTE = "";
048    public static final String NO_PAD_BYTE = "";
049
050    String matchRegex;
051    String termByteStr;
052    String padByteStr;
053    String charSet;
054    Byte termByteVal;
055    Byte padByteVal;
056    int atest;
057
058    public SplitTextVariableValue(String name, String comment, String cvName,
059            boolean readOnly, boolean infoOnly, boolean writeOnly, boolean opsOnly,
060            String cvNum, String mask, int minVal, int maxVal,
061            HashMap<String, CvValue> v, JLabel status, String stdname,
062            String pSecondCV, int pFactor, int pOffset, String uppermask, String extra1, String extra2, String extra3, String extra4) {
063        super(name, comment, cvName, readOnly, infoOnly, writeOnly, opsOnly, cvNum, mask, minVal, maxVal, v, status, stdname, pSecondCV, pFactor, pOffset, uppermask, extra1, extra2, extra3, extra4);
064    }
065
066    @Override
067    public void stepOneActions(String name, String comment, String cvName,
068            boolean readOnly, boolean infoOnly, boolean writeOnly, boolean opsOnly,
069            String cvNum, String mask, int minVal, int maxVal,
070            HashMap<String, CvValue> v, JLabel status, String stdname,
071            String pSecondCV, int pFactor, int pOffset, String uppermask, String extra1, String extra2, String extra3, String extra4) {
072        atest = 77;
073        matchRegex = extra1;
074        termByteStr = extra2;
075        padByteStr = extra3;
076        charSet = extra4;
077        if (!termByteStr.equals(NO_TERM_BYTE)) {
078            termByteVal = (byte) Integer.parseUnsignedInt(termByteStr);
079        }
080        if (!padByteStr.equals(NO_PAD_BYTE)) {
081            padByteVal = (byte) Integer.parseUnsignedInt(padByteStr);
082        }
083        log.debug("stepOneActions");
084        log.debug("atest={}", atest);
085        log.debug("termByteStr=\"{}\",padByteStr=\"{}\"", termByteStr, padByteStr);
086        log.debug("termByteVal={},padByteVal={}", termByteVal, padByteVal);
087    }
088
089    @Override
090    public void stepTwoActions() {
091        log.debug("stepTwoActions");
092        log.debug("atest={}", atest);
093        log.debug("termByteStr=\"{}\",padByteStr=\"{}\"", termByteStr, padByteStr);
094        log.debug("termByteVal={},padByteVal={}", termByteVal, padByteVal);
095        _columns = cvCount + 2; //update column width now we have a better idea
096    }
097
098    boolean isMatched(String s) {
099        if (matchRegex != null && !matchRegex.equals("")) {
100            return s.matches(matchRegex);
101        } else {
102            return true;
103        }
104    }
105
106    byte[] getBytesFromText(String s) {
107        byte[] ret = {};
108//        log.debug("defaultCharset()=" + defaultCharset().name());
109//        log.debug("displayName()=" + defaultCharset().displayName());
110//        log.debug("aliases()=" + defaultCharset().aliases());
111        try {
112            ret = s.getBytes(charSet);
113        } catch (UnsupportedEncodingException ex) {
114            unsupportedCharset();
115        }
116        return ret;
117    }
118
119    String getTextFromBytes(byte[] v) {
120        String ret = "";
121        int textBytesLength = v.length;
122        for (int i = 0; i < v.length; i++) {
123            if (!termByteStr.equals(NO_TERM_BYTE) && (v[i] == termByteVal)) {
124                textBytesLength = i;
125                break;
126            }
127        }
128        if (textBytesLength > 0) {
129            byte[] textBytes = new byte[textBytesLength];
130            System.arraycopy(v, 0, textBytes, 0, textBytesLength);
131            try {
132                ret = new String(textBytes, charSet);
133            } catch (UnsupportedEncodingException ex) {
134                unsupportedCharset();
135            }
136        }
137        return ret; //fall through
138    }
139
140    @edu.umd.cs.findbugs.annotations.SuppressFBWarnings( value="SLF4J_FORMAT_SHOULD_BE_CONST",
141        justification="I18N of Error Message")
142    void unsupportedCharset() {
143        synchronized (this) {
144            JmriJOptionPane.showMessageDialog(new JFrame(), Bundle.getMessage("UnsupportedCharset", charSet, _name),
145                    Bundle.getMessage("DecoderDefError"), JmriJOptionPane.ERROR_MESSAGE); // NOI18N
146        }
147        log.error(Bundle.getMessage("UnsupportedCharset", charSet, _name));
148    }
149
150    @Override
151    int[] getCvValsFromTextField() {
152//        log.debug("getCvValsFromTextField");
153//        log.debug("atest=" + atest);
154//        log.debug("termByteStr=\"" + termByteStr + "\",padByteStr=\"" + padByteStr + "\"");
155//        log.debug("termByteVal=" + termByteVal + ",padByteVal=" + padByteVal);
156        // get new bytes from string
157        byte[] newEntries = getBytesFromText(_textField.getText());
158
159        log.debug("getCvValsFromTextField>newEntries.length={}", newEntries.length);
160        int[] retVals = new int[cvCount];
161
162        // convert to UnsignedInt in retVals
163        // string may be shorter, so pad to length
164        for (int i = 0; i < cvCount; i++) {
165            if (i < newEntries.length) {
166                retVals[i] = Byte.toUnsignedInt(newEntries[i]);
167            } else if ((i == newEntries.length) && !termByteStr.equals(NO_TERM_BYTE)) {
168//                log.debug("terminating with " + termByteVal);
169                retVals[i] = termByteVal;
170            } else if (!padByteStr.equals(NO_PAD_BYTE)) {
171//                log.debug("padding with " + padByteVal);
172                retVals[i] = padByteVal;
173            }
174//            log.debug("retVals[" + i + "] set to " + retVals[i]);
175        }
176        return retVals;
177    }
178
179    /**
180     * Contains byte-value specific code.
181     * <br>
182     * Calculates new value for _textField and invokes
183     * {@link #setValue(String) setValue(newVal)} to make and notify the change
184     *
185     * @param intVals array of new CV values
186     */
187    @Override
188    void updateVariableValue(int[] intVals) {
189
190        byte[] byteVals = new byte[intVals.length];
191
192        for (int i = 0; i < intVals.length; i++) {
193            byteVals[i] = (byte) intVals[i];
194        }
195        String newVal = getTextFromBytes(byteVals);
196        log.debug("Variable={}; set value to '{}';length = {}", _name, newVal, newVal.length());
197        if (log.isDebugEnabled()) {
198            log.debug("Variable={}; set value to {}", _name, newVal);
199        }
200        log.debug("setValue(newVal)to {}", newVal);
201        setValue(newVal);  // check for duplicate is done inside setValue
202        log.debug("done setValue(newVal)to {}", newVal);
203        if (log.isDebugEnabled()) {
204            log.debug("Variable={}; in property change after setValueFromString call", _name);
205        }
206    }
207
208    /**
209     * Contains byte-value specific code.
210     * <br>
211     * firePropertyChange for "Value" with new and old contents of _textField
212     */
213    @Override
214    void exitField() {
215        // there may be a lost focus event left in the queue when disposed so protect
216        String oldVal = oldContents;
217        String newVal = _textField.getText();
218        if (!isMatched(newVal)) {        // check for match to regex if applicable
219            _textField.setText(oldVal); // if mismatch, restore old value
220            return;                     // & return without triggering property change
221        }
222        if (!oldVal.equals(newVal)) {
223            log.debug("Value changed from '{}' to '{}", oldVal, newVal);
224            // special care needed if _textField is shrinking
225            _fieldShrink = (newVal.length() < oldVal.length());
226            log.debug("_fieldShrink={}", _fieldShrink);
227            updatedTextField();
228            prop.firePropertyChange("Value", oldVal, newVal);
229        }
230    }
231
232    /**
233     * Contains byte-value specific code.
234     * <br>
235     * invokes {@link #exitField exitField()} to process text and
236     * firePropertyChange for "Value" with new contents of _textField
237     *
238     * @param e the action event
239     */
240    @Override
241    public void actionPerformed(ActionEvent e) {
242        log.debug("Variable={}; actionPerformed", _name);
243        exitField();
244    }
245
246    @Override
247    public int getIntValue() {
248        log.error("getValue doesn't make sense for a split text value");
249        return 0;
250    }
251
252    @Override
253    public long getLongValue() {
254        log.error("getLongValue doesn't make sense for a split text value");
255        return 0;
256    }
257
258    @Override
259    public void setValue(String value) {
260        log.debug("Variable={}; enter setValue {}", _name, value);
261        String oldVal = _textField.getText();
262        log.debug("Variable={}; setValue with new value {} old value {}", _name, value, oldVal);
263        _textField.setText(value);
264//        if (!oldVal.equals(value) || getState() == VariableValue.UNKNOWN) {
265//            actionPerformed(null);
266//        }
267        prop.firePropertyChange("Value", oldVal, value);
268        log.debug("Variable={}; exit setValue {}", _name, value);
269    }
270
271    @Override
272    public void setIntValue(int i) {
273        log.warn("setIntValue doesn't make sense for a split text value: {}", i);
274    }
275
276    @Override
277    public void setLongValue(long i) {
278        log.warn("setLongValue doesn't make sense for a split text value: {}", i);
279    }
280
281    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(SplitTextVariableValue.class);
282
283}