001package jmri.jmrix.loconet.sdf;
002
003import java.util.ArrayList;
004import java.util.List;
005
006/**
007 * Common base for all the SDF macros defined by Digitrax for their sound
008 * definition language.
009 * <p>
010 * Each macro has a number of descriptive forms:
011 * <dl>
012 * <dt>name()<dd>Just the name, in MPASM form.
013 * <dt>toString()<dd>A brief description, with a terminating newline
014 * <dt>oneInstructionString()<dd>The entire single instruction in MPASM from,
015 * with a terminating newline
016 * <dt>allInstructionString()<dd>The instruction and all those logically grouped
017 * within it.
018 * </dl>
019 *
020 * SdfMacro and its subclasses don't do the notification needed to be Models in
021 * an MVC edit paradyme. This is because there are a lot of SdfMacros in
022 * realistic sound file, and the per-object overhead needed would be too large.
023 * Hence (or perhaps because of no need), there is no support for simultaneous
024 * editing of a single macro instruction updating multiple windows. You can have
025 * multiple editors open on a single SdfBuffer, but these are not interlocked
026 * against each other. (We could fix this by having a shared pool of "objects to
027 * be notified of changes in the SdfBuffer, acccessed by reference during
028 * editing (to avoid another dependency), but that's a project for another day)
029 *
030 * @author Bob Jacobsen Copyright (C) 2007
031 */
032public abstract class SdfMacro implements SdfConstants {
033
034    /**
035     * Name used by the macro in the SDF definition
036     *
037     * @return Fixed name associated with this type of instructio
038     */
039    abstract public String name();
040
041    /**
042     * Provide number of bytes defined by this macro
043     *
044     * @return Fixed numher of bytes defined (a constant for the instruction
045     *         type)
046     */
047    abstract public int length();
048
049    /**
050     * Provide a single-line simplified representation, including the trailing
051     * newline. This is used e.g. in the tree format section of the
052     * {@link jmri.jmrix.loconet.sdfeditor.EditorFrame}.
053     *
054     * @return newline-terminated string; never null
055     */
056    @Override
057    abstract public String toString();
058
059    /**
060     * Provide single instruction in MPASM format, including the trailing
061     * newline.
062     *
063     * @return Newline terminated string, never null
064     */
065    abstract public String oneInstructionString();
066
067    /**
068     * Provide instructions in MPASM format, including the trailing newline and
069     * all nested instructions.
070     *
071     * @return Newline terminated string, never null
072     * @param indent String inserted at the start of each output line, typically
073     *               some number of spaces.
074     */
075    abstract public String allInstructionString(String indent);
076
077    /**
078     * Access child (nested) instructions.
079     *
080     * @return List of children, which will be null except in case of nesting.
081     */
082    public List<SdfMacro> getChildren() {
083        return children;
084    }
085    /**
086     * Local member hold list of child (contained) instructions
087     */
088    ArrayList<SdfMacro> children = null;  // not changed unless there are some!
089
090    /**
091     * Total length, including contained instructions
092     *
093     * @return length of all parts
094     */
095    public int totalLength() {
096        int result = length();
097        List<SdfMacro> l = getChildren();
098        if (l == null) {
099            return result;
100        }
101        for (int i = 0; i < l.size(); i++) {
102            result += l.get(i).totalLength();
103        }
104        return result;
105    }
106
107    /**
108     * Store into a buffer.
109     * <p>
110     * This provides a default implementation for children, but each subclass
111     * needs to store its own data with setAtIndexAndInc().
112     *
113     * @param buffer load with all children
114     */
115    public void loadByteArray(SdfBuffer buffer) {
116        List<SdfMacro> l = getChildren();
117        if (l == null) {
118            return;
119        }
120        for (int i = 0; i < l.size(); i++) {
121            l.get(i).loadByteArray(buffer);
122        }
123    }
124
125    /**
126     * Get the next instruction macro in a buffer.
127     * <p>
128     * Note this uses the index contained in the SdfBuffer implementation, and
129     * has the side-effect of bumping that forward.
130     *
131     * @param buff The SdfBuffer being scanned for instruction macros.
132     * @return Object of SdfMacro subtype for specific next instruction
133     */
134    static public SdfMacro decodeInstruction(SdfBuffer buff) {
135        SdfMacro m;
136
137        // full 1st byte decoder
138        if ((m = ChannelStart.match(buff)) != null) {
139            return m;
140        } else if ((m = SdlVersion.match(buff)) != null) {
141            return m;
142        } else if ((m = SkemeStart.match(buff)) != null) {
143            return m;
144        } else if ((m = GenerateTrigger.match(buff)) != null) {
145            return m;
146        } else if ((m = EndSound.match(buff)) != null) {
147            return m;
148        } else // 7 bit decode
149        if ((m = DelaySound.match(buff)) != null) {
150            return m;
151        } else // 6 bit decode
152        if ((m = SkipOnTrigger.match(buff)) != null) {
153            return m;
154        } else // 5 bit decode
155        if ((m = InitiateSound.match(buff)) != null) {
156            return m;
157        } else if ((m = MaskCompare.match(buff)) != null) {
158            return m;
159        } else // 4 bit decode
160        if ((m = LoadModifier.match(buff)) != null) {
161            return m;
162        } else if ((m = BranchTo.match(buff)) != null) {
163            return m;
164        } else // 2 bit decode
165        if ((m = Play.match(buff)) != null) {
166            return m;
167        } else // generics
168        if ((m = FourByteMacro.match(buff)) != null) {
169            return m;
170        } else  { // only case left is TwoByteMacro, which has to match
171            return TwoByteMacro.match(buff);
172        }
173    }
174
175    /**
176     * Service method to unpack various bit-coded values for display, using a
177     * mask array.
178     * <p>
179     * Note that multiple values can be returned, e.g. this can be used to scan
180     * for individual bits set in a variable.
181     *
182     * @param input  Single value to be matched
183     * @param values Array of possible values which the input might match
184     * @param masks  Array of masks to be applied when comparing against the
185     *               corresponding items in the values array. This is separate
186     *               for each possible value to e.g. allow the encoding of a set
187     *               of independent bits.
188     * @param labels Should there be a match-under-mask of a value, the
189     *               corresponding label is returned
190     * @return "+" separated list of labels, or "&lt;ERROR&gt;" if none matched
191     */
192    String decodeFlags(int input, int[] values, int[] masks, String[] labels) {
193        String[] names = jmri.util.StringUtil.getNamesFromStateMasked(input, values, masks, labels);
194        if (names.length == 0) {
195            return labels[labels.length - 1];  // last name is non-of-above special case
196        } else if (names.length == 1) {
197            return names[0];
198        }
199        StringBuilder output = new StringBuilder(names[0]);
200        for (int i = 1; i < names.length; i++) {
201            output.append("+").append(names[i]);
202        }
203        return output.toString();
204    }
205
206    String decodeState(int input, int[] values, String[] labels) {
207        String val = jmri.util.StringUtil.getNameFromState(input, values, labels);
208        if (val == null) {
209            return labels[labels.length - 1];
210        }
211        return val;
212    }
213
214    // private final static Logger log = LoggerFactory.getLogger(SdfMacro.class);
215
216}