001package jmri.jmrit.ctc.editor.code; 002 003import java.lang.reflect.Field; 004import java.util.ArrayList; 005import javax.swing.table.DefaultTableModel; 006import jmri.BlockManager; 007import jmri.InstanceManager; 008import jmri.SignalHeadManager; 009import jmri.SignalMastManager; 010import jmri.jmrit.ctc.ctcserialdata.ProjectsCommonSubs; 011 012/** 013 * 014 * @author Gregory J. Bedlek Copyright (C) 2018, 2019 015 * 016 * The purpose of this object is to take a passed class and by using reflection 017 * see if any of the public class Strings in the class has specific patterns in 018 * their name(s). 019 * 020 * If so, that variable's contents then is passed to the JMRIConnection object 021 * to see if it is valid. 022 * 023 */ 024public class CheckJMRIObject { 025 026// Putting these strings ANYWHERE in a string variable definition (with EXACT case!) 027// will cause this routine to try to validate it against JMRI Simple Server: 028 public static final String EXTERNAL_TURNOUT = "ExternalTurnout"; // NOI18N 029 public static final String EXTERNAL_SENSOR = "ExternalSensor"; // NOI18N 030 public static final String EXTERNAL_BLOCK = "ExternalBlock"; // NOI18N 031 public static final String EXTERNAL_SIGNAL = "ExternalSignal"; // NOI18N 032 033 public static enum OBJECT_TYPE { SENSOR, TURNOUT, SIGNAL, BLOCK } 034 035 public static class VerifyClassReturnValue { 036 public final String _mFieldContents; // The contents 037 public final OBJECT_TYPE _mObjectType; // What it is. 038 039 public VerifyClassReturnValue(String fieldContents, OBJECT_TYPE objectType) { 040 _mFieldContents = fieldContents; 041 _mObjectType = objectType; 042 } 043 044 @Override 045 public String toString() { 046 switch(_mObjectType) { 047 case SENSOR: 048 return Bundle.getMessage("CJMRIO_Sensor") + " " + _mFieldContents + " " + Bundle.getMessage("CJMRIO_DoesntExist"); // NOI18N 049 case TURNOUT: 050 return Bundle.getMessage("CJMRIO_Turnout") + " " + _mFieldContents + " " + Bundle.getMessage("CJMRIO_DoesntExist"); // NOI18N 051 case SIGNAL: 052 return Bundle.getMessage("CJMRIO_Signal") + " " + _mFieldContents + " " + Bundle.getMessage("CJMRIO_DoesntExist"); // NOI18N 053 case BLOCK: 054 return Bundle.getMessage("CJMRIO_Block") + " " + _mFieldContents + " " + Bundle.getMessage("CJMRIO_DoesntExist"); // NOI18N 055 default: 056 break; 057 } 058 return ""; 059 } 060 } 061 062// Quick and dirty routine for signals only: 063 public boolean checkSignal(String signalName) { 064 return lowLevelCheck(OBJECT_TYPE.SIGNAL, signalName); 065 } 066 067// NOTE below on function prefix naming conventions: 068// "valid" just returns boolean if the entire object is valid (true) or not (false). It stops scanning on first error. 069// "verify" returns a "VerifyClassReturnValue" (invalid) or null (valid) against the entire object. It stops scanning on first error. 070// "analyze" adds entry(s) to the end of a passed errors array for ALL invalid entries. No return value. 071// All of these work with String fields ONLY. If the field is blank or null, it is ignored, it is up to other code 072// to determine whether that is valid or not. 073 074 public boolean validClass(Object object) { 075 return verifyClassCommon("", object) == null; 076 } 077 078 public boolean validClassWithPrefix(String prefix, Object object) { 079 return verifyClassCommon(prefix, object) == null; 080 } 081 082 public VerifyClassReturnValue verifyClass(Object object) { 083 return verifyClassCommon("", object); 084 } 085 086 private VerifyClassReturnValue verifyClassCommon(String prefix, Object object) { 087 String fieldName; 088 Field[] objFields = object.getClass().getDeclaredFields(); 089 for (Field field : objFields) { // For all fields in the class 090 if (field.getType() == String.class) { // Strings only: need to check variable name: 091 if ((fieldName = field.getName()).startsWith(prefix)) { 092 String fieldContent; 093 try { 094 fieldContent = (String)field.get(object); 095 if (ProjectsCommonSubs.isNullOrEmptyString(fieldContent)) continue; // Skip blank fields 096 } catch (IllegalAccessException e) { continue; } // Should never happen, if it does, just skip this field. 097 VerifyClassReturnValue verifyClassReturnValue = processField(fieldName, fieldContent); 098 if (verifyClassReturnValue != null) return verifyClassReturnValue; // Error, stop and return error! 099 } 100 } 101 } 102 return null; // All fields pass. 103 } 104 105// Function similar to the above, EXCEPT that it is used for form processing. 106// Only JTextField's and JTable's are checked. 107// A LIST of errors is returned, i.e. it checks ALL fields. 108 @edu.umd.cs.findbugs.annotations.SuppressFBWarnings("DP_DO_INSIDE_DO_PRIVILEGED") 109// Gotcha: All JTextField's in a dialog are declared "private" by the IDE, ergo the need for "field.setAccessible(true);" 110 public void analyzeForm(String prefix, javax.swing.JFrame dialog, ArrayList<String> errors) { 111 Field[] objFields = dialog.getClass().getDeclaredFields(); 112 for (Field field : objFields) { // For all fields in the class 113 Class<?> fieldType = field.getType(); 114 if (fieldType == javax.swing.JTextField.class) { // JTextField: need to check variable name: 115 String fieldName; 116 if ((fieldName = field.getName()).startsWith(prefix)) { 117 String fieldContent; 118 try { 119 field.setAccessible(true); 120 fieldContent = ((javax.swing.JTextField)field.get(dialog)).getText(); 121 if (ProjectsCommonSubs.isNullOrEmptyString(fieldContent)) continue; // Skip blank fields 122 } catch (IllegalAccessException e) { continue; } // Should never happen, if it does, just skip this field. 123 VerifyClassReturnValue verifyClassReturnValue = processField(fieldName, fieldContent); 124 if (verifyClassReturnValue != null) { // Error: 125 errors.add(verifyClassReturnValue.toString()); 126 } 127 } 128 } 129 else if (fieldType == javax.swing.JTable.class) { // JTable: need to check variable name: 130 String fieldName; 131 if ((fieldName = field.getName()).startsWith(prefix)) { 132 OBJECT_TYPE objectType; 133 if (fieldName.contains(EXTERNAL_TURNOUT)) objectType = OBJECT_TYPE.TURNOUT; 134 else if (fieldName.contains(EXTERNAL_SENSOR)) objectType = OBJECT_TYPE.SENSOR; 135 else if (fieldName.contains(EXTERNAL_BLOCK)) objectType = OBJECT_TYPE.BLOCK; 136 else if (fieldName.contains(EXTERNAL_SIGNAL)) objectType = OBJECT_TYPE.SIGNAL; 137 else continue; // Nothing to check in this field, skip it. 138 DefaultTableModel defaultTableModel; 139 try { 140 field.setAccessible(true); 141 defaultTableModel = (DefaultTableModel)((javax.swing.JTable)field.get(dialog)).getModel(); 142 } catch (IllegalAccessException e) { continue; } // Should never happen, if it does, just skip this field. 143 for (int sourceIndex = 0; sourceIndex < defaultTableModel.getRowCount(); sourceIndex++) { 144 Object object = defaultTableModel.getValueAt(sourceIndex, 0); 145 if (object != null) { 146 if (ProjectsCommonSubs.isNullOrEmptyString(object.toString())) continue; // Skip blank fields 147 String jmriObjectName = object.toString().trim(); 148 if (!lowLevelCheck(objectType, jmriObjectName)) { // Invalid: 149 errors.add(new VerifyClassReturnValue(jmriObjectName, objectType).toString()); 150 } 151 } 152 } 153 } 154 } 155 } 156 } 157 158 private VerifyClassReturnValue processField(String fieldName, String fieldContent) { 159 OBJECT_TYPE objectType; 160 if (fieldName.contains(EXTERNAL_TURNOUT)) objectType = OBJECT_TYPE.TURNOUT; 161 else if (fieldName.contains(EXTERNAL_SENSOR)) objectType = OBJECT_TYPE.SENSOR; 162 else if (fieldName.contains(EXTERNAL_BLOCK)) objectType = OBJECT_TYPE.BLOCK; 163 else if (fieldName.contains(EXTERNAL_SIGNAL)) objectType = OBJECT_TYPE.SIGNAL; 164 else return null; // Nothing to check in this field, OK. 165 if (lowLevelCheck(objectType, fieldContent)) return null; // Valid, OK. 166// OOPPSS, JMRI don't know about it (at this time): 167 return new VerifyClassReturnValue(fieldContent, objectType); 168 } 169 170 private boolean lowLevelCheck(OBJECT_TYPE objectType, String JMRIObjectName) { 171 switch(objectType) { 172 case SENSOR: 173 if (InstanceManager.sensorManagerInstance().getSensor(JMRIObjectName) != null) return true; 174 break; 175 case TURNOUT: 176 if (InstanceManager.turnoutManagerInstance().getTurnout(JMRIObjectName) != null) return true; 177 break; 178 case SIGNAL: 179 if (InstanceManager.getDefault(SignalHeadManager.class).getSignalHead(JMRIObjectName) != null) return true; // Try BOTH: 180 if (InstanceManager.getDefault(SignalMastManager.class).getSignalMast(JMRIObjectName) != null) return true; 181 break; 182 case BLOCK: 183 if (InstanceManager.getDefault(BlockManager.class).getBlock(JMRIObjectName) != null) return true; 184 break; 185 default: 186 break; 187 } 188 return false; // Either bad objectType or object doesn't exist in JMRI 189 } 190}