001// Warnings objectInputStream here about changes to this structure and how it will affect old/new programs: 002//https://howtodoinjava.com/java/serialization/a-mini-guide-for-implementing-serializable-interface-objectInputStream-java/ 003 004package jmri.jmrit.ctc.configurexml; 005 006import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 007import java.io.BufferedReader; 008import java.io.BufferedWriter; 009import java.io.File; 010import java.io.FileReader; 011import java.io.FileWriter; 012import java.io.IOException; 013import java.io.Serializable; 014import java.util.HashMap; 015import jmri.jmrit.ctc.CTCFiles; 016 017/** 018 * 019 * @author Gregory J. Bedlek Copyright (C) 2018, 2019 020 */ 021public class ImportCodeButtonHandlerData implements Serializable { 022 private final static int FILE_VERSION = 6; 023 public static final int SWITCH_NOT_SLAVED = -1; 024 025 public enum LOCK_IMPLEMENTATION { 026// The values in paren's are the RadioGroup values set by "CommonSubs.numberButtonGroup", 027// gotten by calling "CommonSubs.getButtonSelectedInt". 028 GREGS(0), OTHER(1); 029 private final int _mRadioGroupValue; 030 private final static HashMap<Integer, LOCK_IMPLEMENTATION> map = new HashMap<>(); 031 private LOCK_IMPLEMENTATION (int radioGroupValue) { _mRadioGroupValue = radioGroupValue; } 032 static { for (LOCK_IMPLEMENTATION value : LOCK_IMPLEMENTATION.values()) { map.put(value._mRadioGroupValue, value); }} 033 } 034 035 public enum TURNOUT_TYPE { 036// The values in paren's are the RadioGroup values set by "CommonSubs.numberButtonGroup", 037// gotten by calling "CommonSubs.getButtonSelectedInt". 038 TURNOUT(0), CROSSOVER(1), DOUBLE_CROSSOVER(2); 039 private final int _mRadioGroupValue; 040 private final static HashMap<Integer, TURNOUT_TYPE> map = new HashMap<>(); 041 private TURNOUT_TYPE (int radioGroupValue) { _mRadioGroupValue = radioGroupValue; } 042 static { for (TURNOUT_TYPE value : TURNOUT_TYPE.values()) { map.put(value._mRadioGroupValue, value); }} 043 } 044 045 public ImportCodeButtonHandlerData() { 046 _mOSSectionSwitchSlavedToUniqueID = SWITCH_NOT_SLAVED; 047 _mSWDI_GUITurnoutType = ImportCodeButtonHandlerData.TURNOUT_TYPE.TURNOUT; 048 _mTUL_LockImplementation = LOCK_IMPLEMENTATION.GREGS; 049 } 050 private static final long serialVersionUID = 1L; 051 052// This number NEVER changes, and is how this object is uniquely identified: 053 public int _mUniqueID = -1; // FORCE serialization to write out the FIRST unique number 0 into the XML file (to make me happy!) 054// Used by the Editor only: 055 public int _mSwitchNumber; // Switch Indicators and lever # 056 public int _mSignalEtcNumber; // Signal Indicators, lever, locktoggle, callon and code button number 057// PRESENTLY (as of 10/18/18) these are ONLY used by the edit routines to TEMPORARILY get a copy. The 058// data is NEVER stored anywhere. I say this because "_mUniqueID" MUST have another unique number if it is EVER 059// stored anywhere! For example: take the source # and add 5,000,000 to it each time. Even copies of copies would 060// get unique numbers! If the user ever creates 5,000,000 objects, they must be GOD! 061 062// Version of this file for supporting upgrade paths from prior versions: 063 public int _mFileVersion; 064// Data used by the runtime (JMRI) and Editor systems: 065 public String _mCodeButtonInternalSensor; 066 public String _mOSSectionOccupiedExternalSensor; // Required 067 public String _mOSSectionOccupiedExternalSensor2; // Optional 068 public int _mOSSectionSwitchSlavedToUniqueID; 069 public int _mGUIColumnNumber; 070 public boolean _mGUIGeneratedAtLeastOnceAlready; 071 public int _mCodeButtonDelayTime; 072// Signal Direction Indicators: 073 public boolean _mSIDI_Enabled; 074 public String _mSIDI_LeftInternalSensor; 075 public String _mSIDI_NormalInternalSensor; 076 public String _mSIDI_RightInternalSensor; 077 public int _mSIDI_CodingTimeInMilliseconds; 078 public int _mSIDI_TimeLockingTimeInMilliseconds; 079 public String _mSIDI_LeftRightTrafficSignalsCSVList; 080 public String _mSIDI_RightLeftTrafficSignalsCSVList; 081// Signal Direction Lever: 082 public boolean _mSIDL_Enabled; 083 public String _mSIDL_LeftInternalSensor; 084 public String _mSIDL_NormalInternalSensor; 085 public String _mSIDL_RightInternalSensor; 086// Switch Direction Indicators: 087 public boolean _mSWDI_Enabled; 088 public String _mSWDI_NormalInternalSensor; 089 public String _mSWDI_ReversedInternalSensor; 090 public String _mSWDI_ExternalTurnout; 091 public int _mSWDI_CodingTimeInMilliseconds; 092 public boolean _mSWDI_FeedbackDifferent; 093 public TURNOUT_TYPE _mSWDI_GUITurnoutType; 094 public boolean _mSWDI_GUITurnoutLeftHand; 095 public boolean _mSWDI_GUICrossoverLeftHand; 096// Switch Direction Lever: 097 public boolean _mSWDL_Enabled; 098 public String _mSWDL_InternalSensor; 099// Call On: 100 public boolean _mCO_Enabled; 101 public String _mCO_CallOnToggleInternalSensor; 102 public String _mCO_GroupingsListString; 103// Traffic Locking: 104 public boolean _mTRL_Enabled; 105 public String _mTRL_LeftTrafficLockingRulesSSVList; 106 public String _mTRL_RightTrafficLockingRulesSSVList; 107// Turnout Locking: 108 public boolean _mTUL_Enabled; 109 public String _mTUL_DispatcherInternalSensorLockToggle; 110 public String _mTUL_ExternalTurnout; 111 public boolean _mTUL_ExternalTurnoutFeedbackDifferent; 112 public String _mTUL_DispatcherInternalSensorUnlockedIndicator; 113 public boolean _mTUL_NoDispatcherControlOfSwitch; 114 public boolean _mTUL_ndcos_WhenLockedSwitchStateIsClosed; 115 public LOCK_IMPLEMENTATION _mTUL_LockImplementation; 116 public String _mTUL_AdditionalExternalTurnout1; 117 public boolean _mTUL_AdditionalExternalTurnout1FeedbackDifferent; 118 public String _mTUL_AdditionalExternalTurnout2; 119 public boolean _mTUL_AdditionalExternalTurnout2FeedbackDifferent; 120 public String _mTUL_AdditionalExternalTurnout3; 121 public boolean _mTUL_AdditionalExternalTurnout3FeedbackDifferent; 122// Indication Locking (Signals): 123 public boolean _mIL_Enabled; 124 public String _mIL_ListOfCSVSignalNames; 125 126 public void upgradeSelf() { 127 for (int oldVersion = _mFileVersion; oldVersion < FILE_VERSION; oldVersion++) { 128 switch(oldVersion) { 129 case 0: // 0->1: Get rid of ALL Traffic locking stuff. Incompatible with prior version. 130 case 1: // 1->2: Get rid of ALL Traffic locking stuff. Incompatible with prior version. 131 case 2: // 2->3: Get rid of ALL Traffic locking stuff. Incompatible with prior version. 132 case 3: // 3->4: Get rid of ALL Traffic locking stuff. Incompatible with prior version. 133 _mTRL_Enabled = false; 134 _mTRL_LeftTrafficLockingRulesSSVList = ""; 135 _mTRL_RightTrafficLockingRulesSSVList = ""; 136 break; 137 default: // 4->5, 5->6: Do NOTHING! 138 break; 139 } 140 } 141 _mFileVersion = FILE_VERSION; // Now at this version 142 } 143 144/* When I change a variable name but want to keep the contents, I need to 145 pre-process the file BEFORE I turn it over to serialization. That is done 146 here: 147 NOTE: 148 This is ALWAYS done BEFORE the normal "upgradeSelf"! And if it matches a version to change, 149 it ALWAYS increments the file version by one! Therefore "upgradeSelf" will see one greater! 150 So if you want to do BOTH, then you need to increase file version by 2, and insure that the 151 first increment is processed by this: 152*/ 153 private final static String FILE_VERSION_STRING = "<string>_mFileVersion</string>"; // NOI18N 154 private final static String LESS_THAN_SIGN = "<"; // NOI18N 155 private static final String TEMPORARY_EXTENSION = ".xmlTMP"; // NOI18N 156 157// Regarding "@SuppressFBWarnings": My attitude is that if the input file is screwed up, do nothing!: 158 @SuppressFBWarnings(value = "NP_IMMEDIATE_DEREFERENCE_OF_READLINE", justification = "I'm already catching 'NullPointerException', it's ok!") 159 static public void preprocessingUpgradeSelf(String filename) { 160// First, get the existing _mFileVersion from the file to see if we need to work on it: 161 int fileVersion = -1; // Indicate none found. 162 try (BufferedReader bufferedReader = new BufferedReader(new FileReader(filename))) { 163 String aLine; 164 while (!(aLine = bufferedReader.readLine()).contains(FILE_VERSION_STRING)) {} // Skip to the line IF it exists. 165 bufferedReader.readLine(); // Ignore <void method="set"> 166 bufferedReader.readLine(); // Ignore <object idref="CodeButtonHandlerData18"/> 167 aLine = bufferedReader.readLine().trim(); // Get something like <int>4</int> 168 if (aLine.startsWith(INT_START_STRING)) { 169 aLine = aLine.substring(5); // Get rid of it. 170 fileVersion = Integer.parseInt(aLine.substring(0, aLine.indexOf(LESS_THAN_SIGN))); 171 } 172 } catch (IOException | NumberFormatException | NullPointerException e) {} 173 if (fileVersion < 0) return; // Safety: Nothing found, ignore it (though we should have found and parsed it!) 174 switch (fileVersion) { 175 case 4: 176 upgradeVersion4FileTo5(filename); 177 break; 178 case 5: 179 upgradeVersion5FileTo6(filename); 180 break; 181 default: 182 break; 183 } 184 } 185 186 @SuppressFBWarnings(value = "RV_RETURN_VALUE_IGNORED_BAD_PRACTICE", justification = "Any problems, I don't care, it's too late by this point") 187 static private void upgradeVersion4FileTo5(String filename) { 188 String temporaryFilename = CTCFiles.changeExtensionTo(filename, TEMPORARY_EXTENSION); 189 (new File(temporaryFilename)).delete(); // Just delete it for safety before we start: 190 boolean hadAChange = false; 191 try (BufferedReader bufferedReader = new BufferedReader(new FileReader(filename)); 192 BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(temporaryFilename))) { 193 String aLine = null; 194 while ((aLine = bufferedReader.readLine()) != null) { // Not EOF: 195 if ((aLine = checkFileVersion(bufferedReader, bufferedWriter, aLine, "4", "5")) == null) { hadAChange = true; continue; } // Was processed. 196 if ((aLine = checkForRefactor(bufferedWriter, aLine, "_mSWDI_ActualTurnout", "_mSWDI_ExternalTurnout")) == null) { hadAChange = true; continue; } // NOI18N Was processed. 197 if ((aLine = checkForRefactor(bufferedWriter, aLine, "_mTUL_ActualTurnout", "_mTUL_ExternalTurnout")) == null) { hadAChange = true; continue; } // NOI18N Was processed. 198 if ((aLine = checkForRefactor(bufferedWriter, aLine, "_mTUL_ActualTurnoutFeedbackDifferent", "_mTUL_ExternalTurnoutFeedbackDifferent")) == null) { hadAChange = true; continue; } // NOI18N Was processed. 199 if ((aLine = checkForRefactor(bufferedWriter, aLine, "_mTUL_AdditionalTurnout1", "_mTUL_AdditionalExternalTurnout1")) == null) { hadAChange = true; continue; } // NOI18N Was processed. 200 if ((aLine = checkForRefactor(bufferedWriter, aLine, "_mTUL_AdditionalTurnout1FeedbackDifferent", "_mTUL_AdditionalExternalTurnout1FeedbackDifferent")) == null) { hadAChange = true; continue; } // NOI18N Was processed. 201 if ((aLine = checkForRefactor(bufferedWriter, aLine, "_mTUL_AdditionalTurnout2", "_mTUL_AdditionalExternalTurnout2")) == null) { hadAChange = true; continue; } // NOI18N Was processed. 202 if ((aLine = checkForRefactor(bufferedWriter, aLine, "_mTUL_AdditionalTurnout2FeedbackDifferent", "_mTUL_AdditionalExternalTurnout2FeedbackDifferent")) == null) { hadAChange = true; continue; } // NOI18N Was processed. 203 if ((aLine = checkForRefactor(bufferedWriter, aLine, "_mTUL_AdditionalTurnout3", "_mTUL_AdditionalExternalTurnout3")) == null) { hadAChange = true; continue; } // NOI18N Was processed. 204 if ((aLine = checkForRefactor(bufferedWriter, aLine, "_mTUL_AdditionalTurnout3FeedbackDifferent", "_mTUL_AdditionalExternalTurnout3FeedbackDifferent")) == null) { hadAChange = true; continue; } // NOI18N Was processed. 205 writeLine(bufferedWriter, aLine); 206 } 207// Regarding commented out code (due to SpotBugs): 208// I'm a safety "nut". I will do such things in case other code is someday inserted 209// between the above "check for != null" and here. But to satisfy SpotBugs: 210 if (/*aLine == null && */hadAChange) { // Do the two step: 211 bufferedReader.close(); 212 bufferedWriter.close(); 213 File oldFile = new File(filename); 214 oldFile.delete(); // Delete existing old file. 215 (new File(temporaryFilename)).renameTo(oldFile); // Rename temporary filename to proper final file. 216 } 217 } catch (IOException e) {} // Any other error(s) just cleans up: 218 (new File(temporaryFilename)).delete(); // If we get here, just clean up. 219 } 220 221 /** 222 * This routine was written because CSVPrinter at some point began putting "\r\n" at the 223 * end of lines returned by "toString()" based upon the version of the Java Library that 224 * was present. This corrupted my data internally, and a user out in the field got caught 225 * by this change in the Java library. The result was lines like: 226 * Hurricane X-over Track 1 West,LEFTTRAFFIC,Flashing Red,,Hurricane Track 1,IS5:SWNI,IS3:SWNI,IS1:SWNI,,,;;;;Hurricane X-over Track 1 West,LEFTTRAFFIC,Flashing Red,,Hurricane Track 2,IS5:SWNI,IS3:SWNI,IS1:SWRI,,,;;;;Hurricane X-over Track 1 East,RIGHTTRAFFIC,Flashing Red,,East Hurricane Track 1,IS1:SWNI,IS3:SWNI,IS5:SWNI,,,;;;;Hurricane X-over Track 1 East,RIGHTTRAFFIC,Flashing Red,,East Hurricane Track 2,IS1:SWNI,IS3:SWNI,IS5:SWRI,IS7:SWNI,, 227 * You'll notice here (and other places in the line) that there are multiple ;;;; in a row ^^^^ ...-> also 228 * caused by this. What I will do here is fix any multiple ;;;; to a single ;: 229 * @param filename The .xml file to convert from version 5 format to version 6 format. 230 */ 231 @SuppressFBWarnings(value = "RV_RETURN_VALUE_IGNORED_BAD_PRACTICE", justification = "Any problems, I don't care, it's too late by this point") 232 static private void upgradeVersion5FileTo6(String filename) { 233 String temporaryFilename = CTCFiles.changeExtensionTo(filename, TEMPORARY_EXTENSION); 234 (new File(temporaryFilename)).delete(); // Just delete it for safety before we start: 235 boolean hadAChange = false; 236 try (BufferedReader bufferedReader = new BufferedReader(new FileReader(filename)); BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(temporaryFilename))) { 237 String aLine = null; 238 while ((aLine = bufferedReader.readLine()) != null) { // Not EOF: 239 if ((aLine = checkFileVersion(bufferedReader, bufferedWriter, aLine, "5", "6")) == null) { hadAChange = true; continue; } // Was processed. 240 if ((aLine = checkForMultipleSemiColons(bufferedWriter, aLine)) == null) { hadAChange = true; continue; } // NOI18N Was processed. 241 writeLine(bufferedWriter, aLine); 242 } 243// Regarding commented out code (due to SpotBugs): 244// I'm a safety "nut". I will do such things in case other code is someday inserted 245// between the above "check for != null" and here. But to satisfy SpotBugs: 246 if (/*aLine == null && */hadAChange) { // Do the two step: 247 bufferedReader.close(); 248 bufferedWriter.close(); 249 File oldFile = new File(filename); 250 oldFile.delete(); // Delete existing old file. 251 (new File(temporaryFilename)).renameTo(oldFile); // Rename temporary filename to proper final file. 252 } 253 } catch (IOException e) {} // Any other error(s) just cleans up: 254 (new File(temporaryFilename)).delete(); // If we get here, just clean up. 255 } 256 257 258/* 259 Returns: null if we processed it or it was the wrong format, and in either case WROTE the line(s) out indicating that we handled it. 260 or 261 The original aLine passed and NOTHING written, so that other(s) can check it further. 262*/ 263 private final static String INT_START_STRING = "<int>"; // NOI18N 264 private final static String INT_END_STRING = "</int>"; // NOI18N 265 static private String checkFileVersion(BufferedReader bufferedReader, BufferedWriter bufferedWriter, String aLine, String oldVersion, String newVersion) throws IOException { 266 if (aLine.contains(FILE_VERSION_STRING)) { 267 writeLine(bufferedWriter, aLine); 268 writeLine(bufferedWriter, bufferedReader.readLine()); // Ignore <void method="set"> 269 writeLine(bufferedWriter, bufferedReader.readLine()); // Ignore <object idref="CodeButtonHandlerData18"/> 270 aLine = bufferedReader.readLine(); // Get something like <int>4</int> 271 if (aLine != null) { 272 int intStart = aLine.indexOf(INT_START_STRING + oldVersion + INT_END_STRING); 273 if (intStart >= 0) { // Found, replace: 274 writeLine(bufferedWriter, aLine.substring(0, intStart) + INT_START_STRING + newVersion + INT_END_STRING); 275 } else { 276 writeLine(bufferedWriter, aLine); 277 } 278 } 279 return null; 280 } 281 return aLine; // Line wasn't for us! 282 } 283 284 private final static String STRING_START_STRING = "<string>"; // NOI18N 285 private final static String STRING_END_STRING = "</string>"; // NOI18N 286 static private String checkForRefactor(BufferedWriter bufferedWriter, String aLine, String oldName, String newName) throws IOException { 287 int intStart = aLine.indexOf(STRING_START_STRING + oldName + STRING_END_STRING); 288 if (intStart >= 0) { // Found, replace: 289 writeLine(bufferedWriter, aLine.substring(0, intStart) + STRING_START_STRING + newName + STRING_END_STRING); 290 return null; 291 } 292 return aLine; 293 } 294 295 static private String checkForMultipleSemiColons(BufferedWriter bufferedWriter, String aLine) throws IOException { 296 int intStart = aLine.indexOf(STRING_START_STRING); 297 int intEnd = aLine.indexOf(STRING_END_STRING); 298 if (intStart >=0 && intEnd >=0 && intStart < intEnd) { // Insure a line we might look at: 299 while (aLine.contains(";;")) { 300 aLine= aLine.replace(";;", ";"); 301 } 302 } 303 if (intStart >= 0) { // Found, replace: 304 writeLine(bufferedWriter, aLine); 305 return null; 306 } 307 return aLine; 308 } 309 310 311 static private void writeLine(BufferedWriter bufferedWriter, String aLine) throws IOException { 312 bufferedWriter.write(aLine); bufferedWriter.newLine(); 313 } 314}