001package jmri.jmrix.nce.consist;
002
003import java.io.IOException;
004import java.io.Writer;
005import java.util.List;
006import java.util.StringTokenizer;
007import java.util.Vector;
008import jmri.InstanceManager;
009import jmri.util.davidflanagan.HardcopyWriter;
010import org.jdom2.Element;
011import org.slf4j.Logger;
012import org.slf4j.LoggerFactory;
013
014/**
015 * ConsistRosterEntry represents a single element in a consist roster.
016 * <p>
017 * The ConsistRosterEntry is the central place to find information about a
018 * consists configuration, including loco address, address type, loco's
019 * direction, and consist number. Up to six consist locos are currently tracked.
020 * ConsistRosterEntry handles persistency through the LocoFile class. Creating a
021 * ConsistRosterEntry does not necessarily read the corresponding file (which
022 * might not even exist), please see readFile(), writeFile() member functions.
023 * <p>
024 * All the data attributes have a content, not null.
025 * <p>
026 * When the filePath attribute is non-null, the user has decided to organize the
027 * roster into directories.
028 *
029 * @author Bob Jacobsen Copyright (C) 2001, 2002, 2004, 2005
030 * @author Dennis Miller Copyright 2004
031 * @author Daniel Boudreau (C) 2008
032 * @see NceConsistRoster
033 *
034 */
035public class NceConsistRosterEntry {
036
037    /**
038     * Construct a blank object.
039     *
040     */
041    public NceConsistRosterEntry() {
042    }
043
044    public NceConsistRosterEntry(NceConsistRosterEntry pEntry, String pID) {
045        this();
046        // The ID is different for this element
047        _id = pID;
048
049        // All other items are copied
050        _roadName = pEntry._roadName;
051        _roadNumber = pEntry._roadNumber;
052        _model = pEntry._model;
053        _consistNumber = pEntry._consistNumber;
054        _loco1DccAddress = pEntry._loco1DccAddress;
055        _isLoco1LongAddress = pEntry._isLoco1LongAddress;
056        _loco2DccAddress = pEntry._loco2DccAddress;
057        _isLoco2LongAddress = pEntry._isLoco2LongAddress;
058        _loco3DccAddress = pEntry._loco3DccAddress;
059        _isLoco3LongAddress = pEntry._isLoco3LongAddress;
060        _loco4DccAddress = pEntry._loco4DccAddress;
061        _isLoco4LongAddress = pEntry._isLoco4LongAddress;
062        _loco5DccAddress = pEntry._loco5DccAddress;
063        _isLoco5LongAddress = pEntry._isLoco5LongAddress;
064        _loco6DccAddress = pEntry._loco6DccAddress;
065        _isLoco6LongAddress = pEntry._isLoco6LongAddress;
066
067        _comment = pEntry._comment;
068    }
069
070    public void setId(String s) {
071        String oldID = _id;
072        _id = s;
073        if (!oldID.equals(s)) {
074            InstanceManager.getDefault(NceConsistRoster.class).entryIdChanged(this);
075        }
076    }
077
078    public String getId() {
079        return _id;
080    }
081
082    public void setConsistNumber(String s) {
083        _consistNumber = s;
084    }
085
086    public String getConsistNumber() {
087        return _consistNumber;
088    }
089
090    public void setRoadName(String s) {
091        _roadName = s;
092    }
093
094    public String getRoadName() {
095        return _roadName;
096    }
097
098    public void setRoadNumber(String s) {
099        _roadNumber = s;
100    }
101
102    public String getRoadNumber() {
103        return _roadNumber;
104    }
105
106    public void setModel(String s) {
107        _model = s;
108    }
109
110    public String getModel() {
111        return _model;
112    }
113
114    public void setLoco1DccAddress(String s) {
115        _loco1DccAddress = s;
116    }
117
118    public String getLoco1DccAddress() {
119        return _loco1DccAddress;
120    }
121
122    public void setLoco1LongAddress(boolean b) {
123        _isLoco1LongAddress = b;
124    }
125
126    public boolean isLoco1LongAddress() {
127        return _isLoco1LongAddress;
128    }
129
130    public void setLoco1Direction(String s) {
131        _loco1Direction = s;
132    }
133
134    public String getLoco1Direction() {
135        return _loco1Direction;
136    }
137
138    public void setLoco2DccAddress(String s) {
139        _loco2DccAddress = s;
140    }
141
142    public String getLoco2DccAddress() {
143        return _loco2DccAddress;
144    }
145
146    public void setLoco2LongAddress(boolean b) {
147        _isLoco2LongAddress = b;
148    }
149
150    public boolean isLoco2LongAddress() {
151        return _isLoco2LongAddress;
152    }
153
154    public void setLoco2Direction(String s) {
155        _loco2Direction = s;
156    }
157
158    public String getLoco2Direction() {
159        return _loco2Direction;
160    }
161
162    public void setLoco3DccAddress(String s) {
163        _loco3DccAddress = s;
164    }
165
166    public String getLoco3DccAddress() {
167        return _loco3DccAddress;
168    }
169
170    public void setLoco3LongAddress(boolean b) {
171        _isLoco3LongAddress = b;
172    }
173
174    public boolean isLoco3LongAddress() {
175        return _isLoco3LongAddress;
176    }
177
178    public void setLoco3Direction(String s) {
179        _loco3Direction = s;
180    }
181
182    public String getLoco3Direction() {
183        return _loco3Direction;
184    }
185
186    public void setLoco4DccAddress(String s) {
187        _loco4DccAddress = s;
188    }
189
190    public String getLoco4DccAddress() {
191        return _loco4DccAddress;
192    }
193
194    public void setLoco4LongAddress(boolean b) {
195        _isLoco4LongAddress = b;
196    }
197
198    public boolean isLoco4LongAddress() {
199        return _isLoco4LongAddress;
200    }
201
202    public void setLoco4Direction(String s) {
203        _loco4Direction = s;
204    }
205
206    public String getLoco4Direction() {
207        return _loco4Direction;
208    }
209
210    public void setLoco5DccAddress(String s) {
211        _loco5DccAddress = s;
212    }
213
214    public String getLoco5DccAddress() {
215        return _loco5DccAddress;
216    }
217
218    public void setLoco5LongAddress(boolean b) {
219        _isLoco5LongAddress = b;
220    }
221
222    public boolean isLoco5LongAddress() {
223        return _isLoco5LongAddress;
224    }
225
226    public void setLoco5Direction(String s) {
227        _loco5Direction = s;
228    }
229
230    public String getLoco5Direction() {
231        return _loco5Direction;
232    }
233
234    public void setLoco6DccAddress(String s) {
235        _loco6DccAddress = s;
236    }
237
238    public String getLoco6DccAddress() {
239        return _loco6DccAddress;
240    }
241
242    public void setLoco6LongAddress(boolean b) {
243        _isLoco6LongAddress = b;
244    }
245
246    public boolean isLoco6LongAddress() {
247        return _isLoco6LongAddress;
248    }
249
250    public void setLoco6Direction(String s) {
251        _loco6Direction = s;
252    }
253
254    public String getLoco6Direction() {
255        return _loco6Direction;
256    }
257
258    public void setComment(String s) {
259        _comment = s;
260    }
261
262    public String getComment() {
263        return _comment;
264    }
265
266    /**
267     * Construct this Entry from XML. This member has to remain synchronized
268     * with the detailed DTD in xml/DTD/consist-roster-config.dtd.
269     *
270     * @param e Consist XML element
271     */
272    public NceConsistRosterEntry(org.jdom2.Element e) {
273        if (log.isDebugEnabled()) {
274            log.debug("ctor from element {}", e);
275        }
276        org.jdom2.Attribute a;
277        if ((a = e.getAttribute("id")) != null) {
278            _id = a.getValue();
279        } else {
280            log.warn("no id attribute in consist element when reading ConsistRoster"); // NOI18N
281        }
282
283        if ((a = e.getAttribute("consistNumber")) != null) {
284            _consistNumber = a.getValue();
285        }
286        if ((a = e.getAttribute("roadName")) != null) {
287            _roadName = a.getValue();
288        }
289        if ((a = e.getAttribute("roadNumber")) != null) {
290            _roadNumber = a.getValue();
291        }
292        if ((a = e.getAttribute("model")) != null) {
293            _model = a.getValue();
294        }
295        if ((a = e.getAttribute("comment")) != null) {
296            _comment = a.getValue();
297        }
298
299        List<Element> elementList = e.getChildren("loco");
300
301        for (Element element : elementList) {
302            String locoName = "";
303            String locoMidNumber = "";
304            if ((a = element.getAttribute("locoName")) != null) {
305                locoName = a.getValue();
306            }
307            if ((a = element.getAttribute("locoMidNumber")) != null) {
308                locoMidNumber = a.getValue();
309            }
310
311            if (locoName.equals("lead")) {
312                if ((a = element.getAttribute("dccLocoAddress")) != null) {
313                    _loco1DccAddress = a.getValue();
314                }
315                if ((a = element.getAttribute("longAddress")) != null) {
316                    setLoco1LongAddress(a.getValue().equals("yes"));
317                }
318                if ((a = element.getAttribute("locoDir")) != null) {
319                    _loco1Direction = (a.getValue());
320                }
321            }
322            if (locoName.equals("rear")) {
323                if ((a = element.getAttribute("dccLocoAddress")) != null) {
324                    _loco2DccAddress = a.getValue();
325                }
326                if ((a = element.getAttribute("longAddress")) != null) {
327                    setLoco2LongAddress(a.getValue().equals("yes"));
328                }
329                if ((a = element.getAttribute("locoDir")) != null) {
330                    _loco2Direction = (a.getValue());
331                }
332            }
333            if (locoName.equals("mid") && locoMidNumber.equals("1")) {
334                if ((a = element.getAttribute("dccLocoAddress")) != null) {
335                    _loco3DccAddress = a.getValue();
336                }
337                if ((a = element.getAttribute("longAddress")) != null) {
338                    setLoco3LongAddress(a.getValue().equals("yes"));
339                }
340                if ((a = element.getAttribute("locoDir")) != null) {
341                    _loco3Direction = (a.getValue());
342                }
343            }
344            if (locoName.equals("mid") && locoMidNumber.equals("2")) {
345                if ((a = element.getAttribute("dccLocoAddress")) != null) {
346                    _loco4DccAddress = a.getValue();
347                }
348                if ((a = element.getAttribute("longAddress")) != null) {
349                    setLoco4LongAddress(a.getValue().equals("yes"));
350                }
351                if ((a = element.getAttribute("locoDir")) != null) {
352                    _loco4Direction = (a.getValue());
353                }
354            }
355            if (locoName.equals("mid") && locoMidNumber.equals("3")) {
356                if ((a = element.getAttribute("dccLocoAddress")) != null) {
357                    _loco5DccAddress = a.getValue();
358                }
359                if ((a = element.getAttribute("longAddress")) != null) {
360                    setLoco5LongAddress(a.getValue().equals("yes"));
361                }
362                if ((a = element.getAttribute("locoDir")) != null) {
363                    _loco5Direction = (a.getValue());
364                }
365            }
366            if (locoName.equals("mid") && locoMidNumber.equals("4")) {
367                if ((a = element.getAttribute("dccLocoAddress")) != null) {
368                    _loco6DccAddress = a.getValue();
369                }
370                if ((a = element.getAttribute("longAddress")) != null) {
371                    setLoco6LongAddress(a.getValue().equals("yes"));
372                }
373                if ((a = element.getAttribute("locoDir")) != null) {
374                    _loco6Direction = (a.getValue());
375                }
376            }
377        }
378        if (_loco1DccAddress.equals("")) {
379            log.warn("no lead loco attribute in consist element when reading ConsistRoster"); // NOI18N
380        }
381        if (_loco2DccAddress.equals("")) {
382            log.warn("no rear loco attribute in consist element when reading ConsistRoster"); // NOI18N
383        }
384    }
385
386    /**
387     * Create an XML element to represent this Entry. This member has to remain
388     * synchronized with the detailed DTD in xml/DTD/consist-roster-config.dtd.
389     *
390     * @return Contents in a JDOM Element
391     */
392    org.jdom2.Element store() {
393        org.jdom2.Element e = new org.jdom2.Element("consist");
394        e.setAttribute("id", getId());
395        e.setAttribute("consistNumber", getConsistNumber());
396        e.setAttribute("roadNumber", getRoadNumber());
397        e.setAttribute("roadName", getRoadName());
398        e.setAttribute("model", getModel());
399        e.setAttribute("comment", getComment());
400
401        org.jdom2.Element loco1 = new org.jdom2.Element("loco");
402        loco1.setAttribute("locoName", "lead");
403        loco1.setAttribute("dccLocoAddress", getLoco1DccAddress());
404        loco1.setAttribute("longAddress", isLoco1LongAddress() ? "yes" : "no");
405        loco1.setAttribute("locoDir", getLoco1Direction());
406        e.addContent(loco1);
407
408        org.jdom2.Element loco2 = new org.jdom2.Element("loco");
409        loco2.setAttribute("locoName", "rear");
410        loco2.setAttribute("dccLocoAddress", getLoco2DccAddress());
411        loco2.setAttribute("longAddress", isLoco2LongAddress() ? "yes" : "no");
412        loco2.setAttribute("locoDir", getLoco2Direction());
413        e.addContent(loco2);
414
415        if (!getLoco3DccAddress().isEmpty()) {
416            org.jdom2.Element loco3 = new org.jdom2.Element("loco");
417            loco3.setAttribute("locoName", "mid");
418            loco3.setAttribute("locoMidNumber", "1");
419            loco3.setAttribute("dccLocoAddress", getLoco3DccAddress());
420            loco3.setAttribute("longAddress", isLoco3LongAddress() ? "yes" : "no");
421            loco3.setAttribute("locoDir", getLoco3Direction());
422            e.addContent(loco3);
423        }
424
425        if (!getLoco4DccAddress().isEmpty()) {
426            org.jdom2.Element loco4 = new org.jdom2.Element("loco");
427            loco4.setAttribute("locoName", "mid");
428            loco4.setAttribute("locoMidNumber", "2");
429            loco4.setAttribute("dccLocoAddress", getLoco4DccAddress());
430            loco4.setAttribute("longAddress", isLoco4LongAddress() ? "yes" : "no");
431            loco4.setAttribute("locoDir", getLoco4Direction());
432            e.addContent(loco4);
433        }
434
435        if (!getLoco5DccAddress().isEmpty()) {
436            org.jdom2.Element loco5 = new org.jdom2.Element("loco");
437            loco5.setAttribute("locoName", "mid");
438            loco5.setAttribute("locoMidNumber", "3");
439            loco5.setAttribute("dccLocoAddress", getLoco5DccAddress());
440            loco5.setAttribute("longAddress", isLoco5LongAddress() ? "yes" : "no");
441            loco5.setAttribute("locoDir", getLoco5Direction());
442            e.addContent(loco5);
443        }
444
445        if (!getLoco6DccAddress().isEmpty()) {
446            org.jdom2.Element loco6 = new org.jdom2.Element("loco");
447            loco6.setAttribute("locoName", "mid");
448            loco6.setAttribute("locoMidNumber", "4");
449            loco6.setAttribute("dccLocoAddress", getLoco6DccAddress());
450            loco6.setAttribute("longAddress", isLoco6LongAddress() ? "yes" : "no");
451            loco6.setAttribute("locoDir", getLoco6Direction());
452            e.addContent(loco6);
453        }
454
455        return e;
456    }
457
458    public String titleString() {
459        return getId();
460    }
461
462    @Override
463    public String toString() {
464        String out = "[ConsistRosterEntry: "
465                + _id + " "
466                + " " + _consistNumber
467                + " " + _roadName
468                + " " + _roadNumber
469                + " " + _model
470                + " " + _loco1DccAddress
471                + " " + _loco2DccAddress
472                + " " + _loco3DccAddress
473                + " " + _loco4DccAddress
474                + " " + _loco5DccAddress
475                + " " + _loco6DccAddress
476                + " " + _comment
477                + "]";
478        return out;
479    }
480
481    /**
482     * Prints the roster information. Updated to allow for multiline comment
483     * field. Created separate write statements for text and line feeds to work
484     * around the HardcopyWriter bug that misplaces borders.
485     * @param w stream to printer
486     */
487    public void printEntry(Writer w) {
488        if (!(w instanceof HardcopyWriter)){
489            throw new IllegalArgumentException("Writer is not an instance of HardcopyWriter");
490        }
491        try {
492            String indent = "                      ";
493            int indentWidth = indent.length();
494            HardcopyWriter ww = (HardcopyWriter) w;
495            int textSpace = ww.getCharactersPerLine() - indentWidth - 1;
496            String newLine = "\n";
497
498            w.write(newLine, 0, 1);
499            String s = "   ID:                " + _id;
500            w.write(s, 0, s.length());
501
502            if (!(_consistNumber.isEmpty())) {
503                w.write(newLine, 0, 1);
504                s = "   Consist number:    " + _consistNumber;
505                w.write(s, 0, s.length());
506            }
507            if (!(_roadName.isEmpty())) {
508                w.write(newLine, 0, 1);
509                s = "   Road name:         " + _roadName;
510                w.write(s, 0, s.length());
511            }
512            if (!(_roadNumber.isEmpty())) {
513                w.write(newLine, 0, 1);
514                s = "   Road number:       " + _roadNumber;
515                w.write(s, 0, s.length());
516            }
517            if (!(_model.isEmpty())) {
518                w.write(newLine, 0, 1);
519                s = "   Model:             " + _model;
520                w.write(s, 0, s.length());
521            }
522            if (!(_loco1DccAddress.isEmpty())) {
523                w.write(newLine, 0, 1);
524                s = "   Lead Address:      " + _loco1DccAddress + "  " + _loco1Direction;
525                w.write(s, 0, s.length());
526            }
527            if (!(_loco2DccAddress.isEmpty())) {
528                w.write(newLine, 0, 1);
529                s = "   Rear Address:      " + _loco2DccAddress + "  " + _loco2Direction;
530                w.write(s, 0, s.length());
531            }
532            if (!(_loco3DccAddress.isEmpty())) {
533                w.write(newLine, 0, 1);
534                s = "   Mid1 Address:      " + _loco3DccAddress + "  " + _loco3Direction;
535                w.write(s, 0, s.length());
536            }
537            if (!(_loco4DccAddress.isEmpty())) {
538                w.write(newLine, 0, 1);
539                s = "   Mid2 Address:      " + _loco4DccAddress + "  " + _loco4Direction;
540                w.write(s, 0, s.length());
541            }
542            if (!(_loco5DccAddress.isEmpty())) {
543                w.write(newLine, 0, 1);
544                s = "   Mid3 Address:      " + _loco5DccAddress + "  " + _loco5Direction;
545                w.write(s, 0, s.length());
546            }
547            if (!(_loco6DccAddress.isEmpty())) {
548                w.write(newLine, 0, 1);
549                s = "   Mid4 Address:      " + _loco6DccAddress + "  " + _loco6Direction;
550                w.write(s, 0, s.length());
551            }
552
553            // If there is a comment field, then wrap it using the new
554            // wrapComment method and print it
555            if (!(_comment.isEmpty())) {
556                Vector<String> commentVector = wrapComment(_comment, textSpace);
557
558                // Now have a vector of text pieces and line feeds that will all
559                // fit in the allowed space. Print each piece, prefixing the
560                // first one
561                // with the label and indenting any remainding.
562                int k = 0;
563                w.write(newLine, 0, 1);
564                s = "   Comment:           "
565                        + commentVector.elementAt(k);
566                w.write(s, 0, s.length());
567                k++;
568                while (k < commentVector.size()) {
569                    String token = commentVector.elementAt(k);
570                    if (!token.equals("\n")) {
571                        s = indent + token;
572                    } else {
573                        s = token;
574                    }
575                    w.write(s, 0, s.length());
576                    k++;
577                }
578            }
579            w.write(newLine, 0, 1);
580        } catch (IOException e) {
581            log.error("Error printing ConsistRosterEntry", e);
582        }
583    }
584
585    /**
586     * Take a String comment field and perform line wrapping on it. String must
587     * be non-null and may or may not have \n characters embedded. textSpace is
588     * the width of the space to print for wrapping purposes. The comment is
589     * wrapped on a word wrap basis
590     *
591     * This is exactly the same as RosterEntry.wrapComment
592     * @param comment string comment from consist roster entry
593     * @param textSpace size of space to wrap text into
594     * @return wrap formated comment
595     */
596    public Vector<String> wrapComment(String comment, int textSpace) {
597        // Tokenize the string using \n to separate the text on mulitple lines
598        // and create a vector to hold the processed text pieces
599        StringTokenizer commentTokens = new StringTokenizer(comment, "\n", true);
600        Vector<String> textVector = new Vector<>(commentTokens.countTokens());
601        String newLine = "\n";
602        while (commentTokens.hasMoreTokens()) {
603            String commentToken = commentTokens.nextToken();
604            int startIndex = 0;
605            int endIndex;
606            // Check each token to see if it needs to have a line wrap.
607            // Get a piece of the token, either the size of the allowed space or
608            // a shorter piece if there isn't enough text to fill the space
609            if (commentToken.length() < startIndex + textSpace) {
610                //the piece will fit.
611                textVector.addElement(commentToken);
612            } else {
613                //Piece too long to fit. Extract a piece the size of the textSpace
614                //and check for farthest right space for word wrapping.
615                if (log.isDebugEnabled()) {
616                    log.debug("token: /{}/", commentToken);
617                }
618                while (startIndex < commentToken.length()) {
619                    String tokenPiece = commentToken.substring(startIndex, startIndex + textSpace);
620                    if (log.isDebugEnabled()) {
621                        log.debug("loop: /{}/ {}", tokenPiece, tokenPiece.lastIndexOf(" "));
622                    }
623                    if (tokenPiece.lastIndexOf(" ") == -1) {
624                        //If no spaces, put the whole piece in the vector and add a line feed, then
625                        //increment the startIndex to reposition for extracting next piece
626                        textVector.addElement(tokenPiece);
627                        textVector.addElement(newLine);
628                        startIndex += textSpace;
629                    } else {
630                        //If there is at least one space, extract up to and including the
631                        //last space and put in the vector as well as a line feed
632                        endIndex = tokenPiece.lastIndexOf(" ") + 1;
633                        log.debug("tokenPiece: /{}/ {} {}", tokenPiece, startIndex, endIndex);
634                        textVector.addElement(tokenPiece.substring(0, endIndex));
635                        textVector.addElement(newLine);
636                        startIndex += endIndex;
637                    }
638                    //Check the remaining piece to see if it fits -Loco2rtIndex now points
639                    //to the start of the next piece
640                    if (commentToken.substring(startIndex).length() < textSpace) {
641                        // It will fit so just insert it, otherwise will cycle through the
642                        // while loop and the checks above will take care of the remainder.
643                        // Line feed is not required as this is the last part of the token.
644                        tokenPiece = commentToken.substring(startIndex);
645                        textVector.addElement(tokenPiece);
646                        startIndex += textSpace;
647                    }
648                }
649            }
650        }
651        return textVector;
652    }
653
654    // members to remember all the info
655    protected String _fileName = null;
656
657    protected String _id = "";
658    protected String _consistNumber = "";
659    protected String _roadName = "";
660    protected String _roadNumber = "";
661    protected String _model = "";
662    protected String _loco1DccAddress = "";
663    protected boolean _isLoco1LongAddress = true;
664    protected String _loco1Direction = "";
665    protected String _loco2DccAddress = "";
666    protected boolean _isLoco2LongAddress = true;
667    protected String _loco2Direction = "";
668    protected String _loco3DccAddress = "";
669    protected boolean _isLoco3LongAddress = true;
670    protected String _loco3Direction = "";
671    protected String _loco4DccAddress = "";
672    protected boolean _isLoco4LongAddress = true;
673    protected String _loco4Direction = "";
674    protected String _loco5DccAddress = "";
675    protected boolean _isLoco5LongAddress = true;
676    protected String _loco5Direction = "";
677    protected String _loco6DccAddress = "";
678    protected boolean _isLoco6LongAddress = true;
679    protected String _loco6Direction = "";
680
681    protected String _comment = "";
682
683    // initialize logging
684    private final static Logger log = LoggerFactory.getLogger(NceConsistRosterEntry.class);
685
686}