001package jmri.jmrit.logixng.util;
002
003import javax.annotation.CheckReturnValue;
004import javax.annotation.Nonnull;
005
006import jmri.InstanceManager;
007import jmri.Memory;
008import jmri.MemoryManager;
009import jmri.jmrit.logixng.*;
010import jmri.util.TypeConversionUtil;
011
012/**
013 * Utility methods to handle references
014 */
015public class ReferenceUtil {
016
017    // The methods in this class are protected instead of private to let the
018    // test class ReferenceUtilTest access the methods.
019    
020    /**
021     * Checks if the parameter is a reference or not.
022     * @param value the string to check
023     * @return true if value has a reference. falsw otherwise
024     */
025    static public boolean isReference(String value) {
026        if (value == null) return false;
027        // A reference starts with { and ends with }
028        return value.startsWith("{")
029                && value.endsWith("}")
030                && value.length() > 2;
031    }
032    
033    static protected String unescapeString(String value, int startIndex, int endIndex) {
034        boolean escaped = false;
035        
036        StringBuilder sb = new StringBuilder();
037        for (int i=startIndex; i < endIndex; i++) {
038            if (value.charAt(i) == '\\') escaped = !escaped;
039            else escaped = false;
040            
041            if (! escaped) sb.append(value.charAt(i));
042        }
043        
044        return sb.toString();
045    }
046    
047    /**
048     * Get the value.
049     * The value ends either with end of string, or with any of the characters
050     * comma, left square bracket, right square bracket or right curly bracket.
051     * These characters may be escaped and should then be ignored.
052     * @param reference the reference
053     * @param startIndex where in the string the value starts, since the
054     * reference string may contain several references.
055     * @param endIndex index of the end of the value. This is an output parameter.
056     * @return the value
057     */
058    static protected String getValue(String reference, int startIndex, IntRef endIndex) {
059        boolean escapeFound = false;
060        boolean escaped = false;
061        int end = startIndex;
062        while (end < reference.length()
063                && (escaped ||
064                    (reference.charAt(end) != ','
065                    && reference.charAt(end) != '['
066                    && reference.charAt(end) != ']'
067                    && reference.charAt(end) != '{'
068                    && reference.charAt(end) != '}'))) {
069            if (reference.charAt(end) == '\\') {
070                escaped = !escaped;
071                escapeFound = true;
072            } else {
073                escaped = false;
074            }
075            end++;
076        }
077        endIndex.v = end;
078        
079        if (startIndex == end) throw new IllegalArgumentException("Reference '"+reference+"' is not a valid reference");
080        if (escapeFound) return unescapeString(reference, startIndex, end);
081        else return reference.substring(startIndex, end);
082    }
083    
084    /**
085     * Get the reference or the value.The value ends either with end of string,
086     * or with any of the characters comma, left square bracket, right square
087     * bracket or right curly bracket.
088     * These characters may be escaped and should then be ignored.
089     * 
090     * @param symbolTable the symbol table
091     * @param reference the reference
092     * @param startIndex where in the string the value starts, since the
093     * reference string may contain several references.
094     * @param endIndex index of the end of the value. This is an output parameter.
095     * @return the value
096     */
097    static protected String getReferenceOrValue(SymbolTable symbolTable, String reference, int startIndex, IntRef endIndex) {
098        
099        while ((startIndex < reference.length()-1)
100                && (Character.isSpaceChar(reference.charAt(startIndex)))) {
101            startIndex++;
102        }
103        
104        String result;
105        
106        // Do we have a new reference?
107        if (reference.charAt(startIndex) == '{') {
108            result = getReference(symbolTable, reference, startIndex, endIndex);
109        } else {
110            result = getValue(reference, startIndex, endIndex);
111        }
112        
113        // Skip spaces
114        while ((endIndex.v < reference.length()) && Character.isSpaceChar(reference.charAt(endIndex.v))) {
115            endIndex.v++;
116        }
117        
118        return result;
119    }
120    
121    /**
122     * Get the value of a reference
123     * @param symbolTable the symbol table
124     * @param reference the reference
125     * @param startIndex where in the string the reference starts, since the
126     * reference string may contain several references.
127     * @param endIndex index of the end of the reference. This is an output parameter.
128     * @return the value of the reference
129     */
130    static protected String getReference(
131            SymbolTable symbolTable, String reference, int startIndex, IntRef endIndex) {
132        
133        // A reference must start with the char {
134        if (reference.charAt(startIndex) != '{') {
135            throw new IllegalArgumentException("Reference '"+reference+"' is not a valid reference");
136        }
137        
138        String leftValue;
139        String column;
140        String row;
141        
142        startIndex++;
143        
144        leftValue = getReferenceOrValue(symbolTable, reference, startIndex, endIndex);
145        
146        if (endIndex.v == reference.length()) {
147            throw new IllegalArgumentException("Reference '"+reference+"' is not a valid reference");
148        }
149        
150        if ((reference.charAt(endIndex.v) != '}') && (reference.charAt(endIndex.v) != '[')) {
151            throw new IllegalArgumentException("Reference '"+reference+"' is not a valid reference");
152        }
153        
154        
155        endIndex.v++;
156        
157        if ((endIndex.v == reference.length()) || (reference.charAt(endIndex.v-1) == '}')) {
158            
159            if ((symbolTable != null) && symbolTable.hasValue(leftValue)) {
160                return TypeConversionUtil.convertToString(symbolTable.getValue(leftValue), false);
161            }
162            MemoryManager memoryManager = InstanceManager.getDefault(MemoryManager.class);
163            Memory m = memoryManager.getNamedBean(leftValue.trim());
164            if (m != null) {
165                if (m.getValue() != null) return m.getValue().toString();
166                else throw new IllegalArgumentException("Memory '"+leftValue+"' has no value");
167            }
168            else throw new IllegalArgumentException("Memory '"+leftValue+"' is not found");
169        }
170        
171        // If we are here, we have a table reference. Find out column and row.
172        if (reference.charAt(endIndex.v-1) != '[') {
173            throw new IllegalArgumentException("Reference '"+reference+"' is not a valid reference");
174        }
175        
176        
177        // If we are here, we have a table reference. Find out column and row.
178        row = getReferenceOrValue(symbolTable, reference, endIndex.v, endIndex);
179        
180        endIndex.v++;
181        
182        // Skip spaces
183        while ((endIndex.v-2 < reference.length()) && Character.isSpaceChar(reference.charAt(endIndex.v-1))) {
184            endIndex.v++;
185        }
186        
187        int lastEndIndV = endIndex.v;
188        
189        // Skip spaces
190        while ((endIndex.v < reference.length()) && Character.isSpaceChar(reference.charAt(endIndex.v))) {
191            endIndex.v++;
192        }
193        
194        if ((reference.charAt(lastEndIndV-1) == ']')
195                && (reference.charAt(endIndex.v) == '}')) {
196            
197            endIndex.v++;
198            
199            NamedTableManager tableManager =
200                    InstanceManager.getDefault(NamedTableManager.class);
201            
202            NamedTable table = tableManager.getNamedBean(leftValue);
203            if (table != null) {
204                Object cell = table.getCell(row.trim());
205                return cell != null ? cell.toString() : null;
206            } else {
207                throw new IllegalArgumentException("Table '"+leftValue+"' is not found");
208            }
209        }
210        
211        if (endIndex.v == reference.length() || reference.charAt(lastEndIndV-1) != ',') {
212            throw new IllegalArgumentException("Reference '"+reference+"' is not a valid reference");
213        }
214        
215        column = getReferenceOrValue(symbolTable, reference, endIndex.v, endIndex);
216        
217        // Skip spaces
218        while ((endIndex.v < reference.length()) && Character.isSpaceChar(reference.charAt(endIndex.v))) {
219            endIndex.v++;
220        }
221        
222        if (endIndex.v == reference.length() || reference.charAt(endIndex.v) != ']') {
223            throw new IllegalArgumentException("Reference '"+reference+"' is not a valid reference");
224        }
225        
226//        if (((reference.charAt(endIndex.v) == ']')
227//                && (reference.charAt(endIndex.v+1) == '}'))) {
228        if ((reference.charAt(endIndex.v) == ']')) {
229            
230            endIndex.v++;
231            
232            NamedTableManager tableManager =
233                    InstanceManager.getDefault(NamedTableManager.class);
234            
235            NamedTable table = tableManager.getNamedBean(leftValue);
236            if (table != null) {
237                Object cell = table.getCell(row.trim(),column.trim());
238                // Skip spaces
239                while ((endIndex.v < reference.length()) && Character.isSpaceChar(reference.charAt(endIndex.v))) {
240                    endIndex.v++;
241                }
242                endIndex.v++;
243                return cell != null ? cell.toString() : null;
244            } else {
245                throw new IllegalArgumentException("Table '"+leftValue+"' is not found");
246            }
247        }
248        
249        throw new IllegalArgumentException("Reference '"+reference+"' is not a valid reference");
250    }
251    
252    @CheckReturnValue
253    @Nonnull
254    static public String getReference(SymbolTable symbolTable, String reference) {
255        if (!isReference(reference)) {
256            throw new IllegalArgumentException("Reference '"+reference+"' is not a valid reference");
257        }
258        
259        IntRef endIndex = new IntRef();
260        String ref = getReference(symbolTable, reference, 0, endIndex);
261        
262        if (endIndex.v != reference.length()) {
263            throw new IllegalArgumentException("Reference '"+reference+"' is not a valid reference");
264        }
265        
266        return ref;
267    }
268    
269    
270    /**
271     * Reference to an integer.
272     * This class is cheaper to use than AtomicInteger.
273     */
274    protected static class IntRef {
275        public int v;
276    }
277    
278//    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ReferenceUtil.class);
279}