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}