001package jmri.jmrix;
002
003import java.util.Objects;
004import javax.annotation.Nonnull;
005import jmri.util.StringUtil;
006import org.slf4j.Logger;
007import org.slf4j.LoggerFactory;
008
009/**
010 * Abstract base class for messages in a message/reply protocol.
011 * <p>
012 * Carries a sequence of characters, with accessors.
013 *
014 * @author Bob Jacobsen Copyright (C) 2003
015 */
016abstract public class AbstractMRMessage extends AbstractMessage {
017
018    /**
019     * Create a new AbstractMRMessage instance.
020     */
021    public AbstractMRMessage() {
022        setBinary(false);
023        setNeededMode(AbstractMRTrafficController.NORMALMODE);
024        setTimeout(SHORT_TIMEOUT);  // default value is the short timeout
025        setRetries(0); // default to no retries
026    }
027
028    /**
029     * Create a new AbstractMRMessage instance of a given byte size.
030     *
031     * @param i number of elements in message
032     */
033    public AbstractMRMessage(int i) {
034        this();
035        if (i < 1) {
036            log.error("invalid length {} in call to ctor", i);
037            throw new IllegalArgumentException("invalid length in call to ctor");
038        }
039        _nDataChars = i;
040        _dataChars = new int[i];
041    }
042
043    /**
044     * Copy an AbstractMRMessage to a new instance.
045     *
046     * @param m the message to copy
047     */
048    public AbstractMRMessage(@Nonnull AbstractMRMessage m) {
049        this();
050        Objects.requireNonNull(m, "copy ctor of null message");
051        _nDataChars = m._nDataChars;
052        _dataChars = new int[_nDataChars];
053        System.arraycopy(m._dataChars, 0, _dataChars, 0, _nDataChars);
054        setTimeout(m.getTimeout());
055        setRetries(m.getRetries());
056        setNeededMode(m.getNeededMode());
057    }
058
059    /**
060     * Create a new Message instance from a string.
061     *
062     * @param s String to use as message content
063     */
064    public AbstractMRMessage(String s) {
065        this(s.length());
066        for (int i = 0; i < _nDataChars; i++) {
067            _dataChars[i] = s.charAt(i);
068        }
069    }
070
071    public void setOpCode(int i) {
072        _dataChars[0] = i;
073    }
074
075    public int getOpCode() {
076        try {
077            return _dataChars[0];
078        } catch(ArrayIndexOutOfBoundsException e) {
079            return 0;
080        }
081    }
082
083    public String getOpCodeHex() {
084        return "0x" + Integer.toHexString(getOpCode());
085    }
086
087    // accessors to the bulk data
088
089    // state info
090    private int mNeededMode;
091
092    /**
093     * Set a needed mode.
094     * Final so that it can be called in constructors.
095     * @param pMode required mode value.
096     */
097    final public void setNeededMode(int pMode) {
098        mNeededMode = pMode;
099    }
100
101    /**
102     * Get needed mode.
103     * Final so that it can be called in constructors.
104     * @return mNeededMode required mode value.
105     */
106    final public int getNeededMode() {
107        return mNeededMode;
108    }
109
110    /**
111     * Is a reply expected to this message?
112     * <p>
113     * By default, a reply is expected to every message; either a reply or a
114     * timeout is needed before the next message can be sent.
115     * <p>
116     * If this returns false, the transmit queue will immediately go on to
117     * transmit the next message (if any).
118     * @return true by default in Abstract MR message.
119     */
120    public boolean replyExpected() {
121        return true;
122    }
123
124    // mode accessors
125    private boolean _isBinary;
126
127    /**
128     * Get if is binary.
129     * Final so that it can be called in constructors.
130     * @return true if binary, else false.
131     */
132    final public boolean isBinary() {
133        return _isBinary;
134    }
135
136    /**
137     * Set if Binary.
138     * final so that it can be called in constructors.
139     * @param b true if binary, else false.
140     */
141    final public void setBinary(boolean b) {
142        _isBinary = b;
143    }
144
145    /**
146     * Minimum timeout that's acceptable.
147     * <p>
148     * Also used as default for normal operations. Don't shorten this "to make
149     * recovery faster", as sometimes <i>internal</i> delays can slow processing
150     * down.
151     * <p>
152     * Units are milliseconds.
153     */
154    static protected final int SHORT_TIMEOUT = 2000;
155    static protected final int LONG_TIMEOUT = 60000;  // e.g. for programming options
156    
157    private int mTimeout;  // in milliseconds
158
159    /**
160     * Set Timeout.
161     * Final so that it can be called in constructors.
162     * @param t timeout value.
163     */
164    final public void setTimeout(int t) {
165        mTimeout = t;
166    }
167
168    /**
169     * Get Timeout.
170     * Final so that it can be called in constructors.
171     * @return timeout value.
172     */
173    final public int getTimeout() {
174        return mTimeout;
175    }
176
177    /* For some systems, we want to retry sending a message if the port
178     isn't ready for them. */
179    private int mRetries = 0; // number of retries, default = 0;
180
181    /**
182     * Set number of retries.
183     * Final so that it can be called in constructors
184     * @param i number of retries, actual value to set, not an increment.
185     */
186    final public void setRetries(int i) {
187        mRetries = i;
188    }
189
190    /**
191     * Get number of retries.
192     * final so that it can be called in constructors
193     * @return number of retries count.
194     */
195    final public int getRetries() {
196        return mRetries;
197    }
198
199    // display format
200
201    // contents (private)
202    public void addIntAsThree(int val, int offset) {
203        String s = "" + val;
204        if (s.length() != 3) {
205            s = "0" + s;  // handle <10
206        }
207        if (s.length() != 3) {
208            s = "0" + s;  // handle <100
209        }
210        setElement(offset, s.charAt(0));
211        setElement(offset + 1, s.charAt(1));
212        setElement(offset + 2, s.charAt(2));
213    }
214
215    /**
216     * Put an int value into the message as 
217     * two ASCII upper-case hex characters.
218     * @param val value to convert.
219     * @param offset offset in message.
220     */
221    public void addIntAsTwoHex(int val, int offset) {
222        String s = ("" + Integer.toHexString(val)).toUpperCase();
223        if (s.length() < 2) {
224            s = "0" + s;  // handle one digit
225        }
226        if (s.length() > 2) {
227            log.error("can't add as two hex digits: {}", s);
228        }
229        setElement(offset, s.charAt(0));
230        setElement(offset + 1, s.charAt(1));
231    }
232
233    /**
234     * Put an int value into the message as 
235     * three ASCII upper-case hex characters.
236     * @param val value to convert.
237     * @param offset offset in message.
238     */
239    public void addIntAsThreeHex(int val, int offset) {
240        String s = ("" + Integer.toHexString(val)).toUpperCase();
241        if (s.length() > 3) {
242            log.error("can't add as three hex digits: {}", s);
243        }
244        if (s.length() != 3) {
245            s = "0" + s;
246        }
247        if (s.length() != 3) {
248            s = "0" + s;
249        }
250        setElement(offset, s.charAt(0));
251        setElement(offset + 1, s.charAt(1));
252        setElement(offset + 2, s.charAt(2));
253    }
254
255    /**
256     * Put an int value into the message as 
257     * four ASCII upper-case hex characters.
258     * @param val value to convert.
259     * @param offset offset in message.
260     */
261    public void addIntAsFourHex(int val, int offset) {
262        String s = ("" + Integer.toHexString(val)).toUpperCase();
263        if (s.length() > 4) {
264            log.error("can't add as three hex digits: {}", s);
265        }
266        if (s.length() != 4) {
267            s = "0" + s;
268        }
269        if (s.length() != 4) {
270            s = "0" + s;
271        }
272        if (s.length() != 4) {
273            s = "0" + s;
274        }
275        setElement(offset, s.charAt(0));
276        setElement(offset + 1, s.charAt(1));
277        setElement(offset + 2, s.charAt(2));
278        setElement(offset + 3, s.charAt(3));
279    }
280
281    @Override
282    public String toString() {
283        String s = "";
284        for (int i = 0; i < _nDataChars; i++) {
285            if (_isBinary) {
286                if (i != 0) {
287                    s += " ";
288                }
289                s = StringUtil.appendTwoHexFromInt(_dataChars[i] & 255, s);
290            } else {
291                s += (char) _dataChars[i];
292            }
293        }
294        return s;
295    }
296
297    private final static Logger log = LoggerFactory.getLogger(AbstractMRMessage.class);
298
299}