001package jmri.jmrit.vsdecoder;
002
003import java.io.File;
004import java.util.ArrayList;
005import java.util.List;
006import java.util.Iterator;
007import jmri.jmrit.XmlFile;
008import jmri.Scale;
009import jmri.Reporter;
010import jmri.util.FileUtil;
011import jmri.util.PhysicalLocation;
012import org.jdom2.Element;
013import org.slf4j.Logger;
014import org.slf4j.LoggerFactory;
015
016/**
017 * Load parameter from XML for the Advanced Location Following.
018 *
019 * <hr>
020 * This file is part of JMRI.
021 * <p>
022 * JMRI is free software; you can redistribute it and/or modify it under 
023 * the terms of version 2 of the GNU General Public License as published 
024 * by the Free Software Foundation. See the "COPYING" file for a copy
025 * of this license.
026 * <p>
027 * JMRI is distributed in the hope that it will be useful, but WITHOUT 
028 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
029 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License 
030 * for more details.
031 *
032 * @author Klaus Killinger Copyright (C) 2018-2020
033 */
034public class VSDGeoFile extends XmlFile {
035
036    static final String VSDGeoDataFileName = "VSDGeoData.xml"; // NOI18N
037    protected Element root;
038    private float blockParameter[][][];
039    private List<List<PhysicalLocation>> blockPositionlists; // Two-dimensional ArrayList
040    private List<List<Integer>> reporterlists; // Two-dimensional ArrayList
041    private List<Boolean> circlelist;
042    private int setup_index;
043    private int num_issues;
044    boolean geofile_ok;
045    int num_setups;
046    Scale _layout_scale;
047    float layout_scale;
048    int check_time; // Time interval in ms for track following updates
049
050    /**
051     * Looking for additional parameter for train tracking
052     */
053    @SuppressWarnings("unchecked") // ArrayList[n] is not detected as the coded generics
054    public VSDGeoFile() {
055
056        // Setup lists for Reporters and Positions
057        reporterlists = new ArrayList<>();
058        List<Integer>[] reporterlist = new ArrayList[VSDecoderManager.max_decoder]; // Limit number of supported VSDecoders
059        blockPositionlists = new ArrayList<>();
060        List<PhysicalLocation>[] blockPositionlist = new ArrayList[VSDecoderManager.max_decoder];
061        for (int i = 0; i < VSDecoderManager.max_decoder; i++) {
062            reporterlist[i] = new ArrayList<>();
063            blockPositionlist[i] = new ArrayList<>();
064        }
065
066        // Another list to provide a flag for circling or non-circling routes
067        circlelist = new ArrayList<>();
068
069        File file = new File(FileUtil.getUserFilesPath() + VSDGeoDataFileName);
070
071        Element c, c0, c1;
072        String n, np;
073        num_issues = 0;
074
075        // Try to load data from the file
076        try {
077            root = rootFromFile(file);
078        } catch (java.io.FileNotFoundException e1) {
079            log.debug("{} for train tracking is not available", VSDGeoDataFileName);
080            root = null;
081        } catch (Exception e2) {
082            log.error("Exception while loading file {}:", VSDGeoDataFileName, e2);
083            root = null;
084        }
085        if (root == null) {
086            geofile_ok = false;
087            return;
088        }
089
090        // Get some layout parameters and route geometric data
091        n = root.getChildText("layout-scale");
092        if (n != null) {
093            _layout_scale = jmri.ScaleManager.getScale(n);
094            if (_layout_scale == null) {
095                _layout_scale = jmri.ScaleManager.getScale("N"); // default
096                log.info("{}: Element layout-scale '{}' unknown, defaulting to N", VSDGeoDataFileName, n);
097            }
098        } else {
099            _layout_scale = jmri.ScaleManager.getScale("N"); // default
100            log.info("{}: Element layout-scale missing, defaulting to N", VSDGeoDataFileName);
101        }
102        layout_scale = (float) _layout_scale.getScaleRatio(); // Take this for further calculations
103        log.debug("layout-scale: {}, used for further calculations: {}", _layout_scale.toString(), layout_scale);
104
105        n = root.getChildText("check-time");
106        if (n != null) {
107            check_time = Integer.parseInt(n);
108            // Process some limitations; values in milliseconds
109            if (check_time < 500 || check_time > 5000) {
110                check_time = 2000; // default
111                log.info("{}: Element check-time not in range, defaulting to {} ms", VSDGeoDataFileName, check_time);
112            }
113        } else {
114            check_time = 2000; // default
115            log.info("{}: Element check-time missing, defaulting to {} ms", VSDGeoDataFileName, check_time);
116        }
117        log.debug("check-time: {} ms", check_time);
118
119        // Detect number of "setup" tags and maximal number of "geodataset" tags
120        num_setups = 0; // # setup
121        int num_geodatasets = 0; // # geodataset
122        int max_geodatasets = 0; // helper
123        Iterator<Element> ix = root.getChildren("setup").iterator(); // NOI18N
124        while (ix.hasNext()) {
125            c = ix.next();
126            num_geodatasets = c.getChildren("geodataset").size();
127            log.debug("setup {} has {} geodataset(s)", num_setups + 1, num_geodatasets);
128            if (num_geodatasets > max_geodatasets) {
129                max_geodatasets = num_geodatasets; // # geodatasets can vary; take highest value
130            }
131            num_setups++;
132        }
133        log.debug("counting setups: {}, maximum geodatasets: {}", num_setups, max_geodatasets);
134        // Limitation check is done by the schema validation, but a XML schema is not yet in place
135        if (num_setups == 0 || num_geodatasets == 0 || num_setups > VSDecoderManager.max_decoder) {
136            log.warn("{}: Invalid number of setups or geodatasets", VSDGeoDataFileName);
137            geofile_ok = false;
138            return;
139        }
140
141        // Setup array to save the block parameters
142        blockParameter = new float[num_setups][max_geodatasets][5];
143
144        // Go through all setups and their geodatasets 
145        //  - get the PhysicalLocation (position) from the parameter file
146        //  - make checks which are not covered by the schema validation
147        //  - make some basic checks for not validated VSDGeoData.xml files (avoid NPEs)
148        setup_index = 0;
149        Iterator<Element> i0 = root.getChildren("setup").iterator(); // NOI18N
150        while (i0.hasNext()) {
151            c0 = i0.next();
152            log.debug("--- SETUP: {}", setup_index + 1);
153
154            boolean is_end_position_set = false; // Need one end-position per setup
155            int j = 0;
156            Iterator<Element> i1 = c0.getChildren("geodataset").iterator(); // NOI18N
157            while (i1.hasNext()) {
158                c1 = i1.next();
159                Reporter rep = null;
160                int rep_int = 0;
161                np = c1.getChildText("reporter-systemname");
162                // An element "reporter-systemname" is required and a XML schema and a XML schema is not yet in place
163                if (np != null) {
164                    rep = jmri.InstanceManager.getDefault(jmri.ReporterManager.class).getBySystemName(np);
165                    if (rep != null) {
166                        rep_int = Integer.parseInt(np.substring(2));
167                        reporterlist[setup_index].add(rep_int);
168                        n = c1.getChildText("position");
169                        // An element "position" is required and a XML schema and a XML schema is not yet in place
170                        if (n != null) {
171                            PhysicalLocation pl = PhysicalLocation.parse(n);
172                            blockPositionlist[setup_index].add(pl);
173                            // Establish relationship Reporter-PhysicalLocation (see window Manage VSD Locations)
174                            PhysicalLocation.setBeanPhysicalLocation(pl, rep);
175                            log.debug("Reporter: {}, position set to: {}", rep, pl);
176                        } else {
177                            log.warn("{}: Element position not found", VSDGeoDataFileName);
178                            num_issues++;
179                        }
180                        n = c1.getChildText("radius");
181                        if (n != null) {
182                            blockParameter[setup_index][j][0] = Float.parseFloat(n);
183                            log.debug(" radius: {}", n); 
184                        } else {
185                            log.warn("{}: Element radius not found", VSDGeoDataFileName);
186                            num_issues++;
187                        }
188                        n = c1.getChildText("slope");
189                        if (n != null) {
190                            blockParameter[setup_index][j][1] = Float.parseFloat(n);
191                            log.debug(" slope: {}", n); 
192                        } else {
193                            // If a radius is not defined (radius = 0), slope must exist!
194                            if (blockParameter[setup_index][j][0] == 0.0f) {
195                                log.warn("{}: Element slope not found", VSDGeoDataFileName);
196                                num_issues++;
197                            }
198                        }
199                        n = c1.getChildText("rotate-xpos");
200                        if (n != null) {
201                            blockParameter[setup_index][j][2] = Float.parseFloat(n);
202                            log.debug(" rotate-xpos: {}", n); 
203                        } else {
204                            // If a radius is defined (radius > 0), rotate-xpos must exist!
205                            if (blockParameter[setup_index][j][0] > 0.0f) {
206                                log.warn("{}: Element rotate-xpos not found", VSDGeoDataFileName);
207                                num_issues++;
208                            }
209                        }
210                        n = c1.getChildText("rotate-ypos");
211                        if (n != null) {
212                            blockParameter[setup_index][j][3] = Float.parseFloat(n);
213                            log.debug(" rotate-ypos: {}", n); 
214                        } else {
215                            // If a radius is defined (radius > 0), rotate-ypos must exist!
216                            if (blockParameter[setup_index][j][0] > 0.0f) {
217                                log.warn("{}: Element rotate-ypos not found", VSDGeoDataFileName);
218                                num_issues++;
219                            }
220                        }
221                        n = c1.getChildText("length");
222                        if (n != null) {
223                            blockParameter[setup_index][j][4] = Float.parseFloat(n);
224                            log.debug(" length: {}", n); 
225                        } else {
226                            log.warn("{}: Element length not found", VSDGeoDataFileName);
227                            num_issues++;
228                        }
229                        n = c1.getChildText("end-position");
230                        if (n != null) {
231                            if (!is_end_position_set) {
232                                blockPositionlist[setup_index].add(PhysicalLocation.parse(n));
233                                is_end_position_set = true;
234                                log.debug("end-position for location {} set to {}", j,
235                                        blockPositionlist[setup_index].get(blockPositionlist[setup_index].size() - 1));
236                            } else {
237                                log.warn("{}: Only the last geodataset should have an end-position", VSDGeoDataFileName);
238                                num_issues++;
239                            }
240                        }
241                    } else {
242                        log.warn("{}: No Reporter available for system name = {}", VSDGeoDataFileName, np);
243                        num_issues++;
244                    }
245                } else {
246                    log.warn("{}: Reporter system name missing", VSDGeoDataFileName);
247                    num_issues++;
248                }
249                j++;
250            }
251
252            if (!is_end_position_set) {
253                log.warn("{}: End-position missing for setup {}", VSDGeoDataFileName, setup_index + 1);
254                num_issues++;
255            }
256
257            if (num_issues == 0) {
258                // Add lists to their array
259                reporterlists.add(reporterlist[setup_index]);
260                blockPositionlists.add(blockPositionlist[setup_index]);
261
262                // Prove, if the setup has a circling route and add the result to a list
263                //  compare first and last blockPosition without the tunnel attribute
264                //  needed for the Reporter validation check in VSDecoderManager
265                int last_index = blockPositionlist[setup_index].size() - 1;
266                log.debug("first setup position: {}, last setup position: {}", blockPositionlist[setup_index].get(0),
267                        blockPositionlist[setup_index].get(last_index));
268                if (blockPositionlist[setup_index].get(0).x == blockPositionlist[setup_index].get(last_index).x
269                        && blockPositionlist[setup_index].get(0).y == blockPositionlist[setup_index].get(last_index).y
270                        && blockPositionlist[setup_index].get(0).z == blockPositionlist[setup_index].get(last_index).z) {
271                    circlelist.add(true);
272                } else {
273                    circlelist.add(false);
274                }
275                log.debug("circling: {}", circlelist.get(setup_index));
276            }
277
278            setup_index++;
279        }
280
281        // Some Debug infos
282        if (log.isDebugEnabled()) {
283            log.debug("--- LISTS");
284            log.debug("number of Reporter lists: {}", reporterlists.size());
285            log.debug("Reporter lists with their Reporters (digit only): {}", reporterlists);
286            //log.debug("TEST reporter get 0 list size: {}", reporterlists.get(0).size());
287            //log.debug("TEST reporter [0] list size: {}", reporterlist[0].size());
288            log.debug("number of Position lists: {}", blockPositionlists.size());
289            log.debug("Position lists: {}", blockPositionlists);
290            log.debug("--- COUNTERS");
291            log.debug("number of setups: {}", num_setups);            
292            log.debug("number of issues: {}", num_issues);
293        }
294
295        if (num_issues > 0) {
296            geofile_ok = false;
297        } else {
298            geofile_ok = true;
299        }
300    }
301
302    // Number of setups
303    public int getNumberOfSetups() {
304        return num_setups;
305    }
306
307    // Reporter lists
308    public List<List<Integer>> getReporterList() {
309        return reporterlists;
310    }
311
312    // Reporter Parameter
313    public float[][][] getBlockParameter() {
314        return blockParameter;
315    }
316
317    // Reporter (Block) Position lists
318    public List<List<PhysicalLocation>> getBlockPosition() {
319        return blockPositionlists;
320    }
321
322    // Circling list
323    public List<Boolean> getCirclingList() {
324        return circlelist;
325    }
326
327    private static final Logger log = LoggerFactory.getLogger(VSDGeoFile.class);
328
329}