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}