001    /**
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.apache.camel.language.simple;
018    
019    import java.util.List;
020    import java.util.concurrent.CopyOnWriteArrayList;
021    
022    import org.apache.camel.language.simple.types.SimpleToken;
023    import org.apache.camel.language.simple.types.SimpleTokenType;
024    import org.apache.camel.language.simple.types.TokenType;
025    
026    /**
027     * Tokenizer to create {@link SimpleToken} from the input.
028     */
029    public final class SimpleTokenizer {
030    
031        // use CopyOnWriteArrayList so we can modify it in the for loop when changing function start/end tokens
032        private static final List<SimpleTokenType> KNOWN_TOKENS = new CopyOnWriteArrayList<SimpleTokenType>();
033    
034        static {
035            // add known tokens
036            KNOWN_TOKENS.add(new SimpleTokenType(TokenType.whiteSpace, " "));
037            KNOWN_TOKENS.add(new SimpleTokenType(TokenType.singleQuote, "'"));
038            KNOWN_TOKENS.add(new SimpleTokenType(TokenType.doubleQuote, "\""));
039            KNOWN_TOKENS.add(new SimpleTokenType(TokenType.functionStart, "${"));
040            KNOWN_TOKENS.add(new SimpleTokenType(TokenType.functionStart, "$simple{"));
041            KNOWN_TOKENS.add(new SimpleTokenType(TokenType.functionEnd, "}"));
042            KNOWN_TOKENS.add(new SimpleTokenType(TokenType.booleanValue, "true"));
043            KNOWN_TOKENS.add(new SimpleTokenType(TokenType.booleanValue, "false"));
044            KNOWN_TOKENS.add(new SimpleTokenType(TokenType.nullValue, "null"));
045            KNOWN_TOKENS.add(new SimpleTokenType(TokenType.escape, "\\"));
046    
047            // binary operators
048            KNOWN_TOKENS.add(new SimpleTokenType(TokenType.binaryOperator, "=="));
049            KNOWN_TOKENS.add(new SimpleTokenType(TokenType.binaryOperator, ">="));
050            KNOWN_TOKENS.add(new SimpleTokenType(TokenType.binaryOperator, "<="));
051            KNOWN_TOKENS.add(new SimpleTokenType(TokenType.binaryOperator, ">"));
052            KNOWN_TOKENS.add(new SimpleTokenType(TokenType.binaryOperator, "<"));
053            KNOWN_TOKENS.add(new SimpleTokenType(TokenType.binaryOperator, "!="));
054            KNOWN_TOKENS.add(new SimpleTokenType(TokenType.binaryOperator, "not is"));
055            KNOWN_TOKENS.add(new SimpleTokenType(TokenType.binaryOperator, "is"));
056            KNOWN_TOKENS.add(new SimpleTokenType(TokenType.binaryOperator, "not contains"));
057            KNOWN_TOKENS.add(new SimpleTokenType(TokenType.binaryOperator, "contains"));
058            KNOWN_TOKENS.add(new SimpleTokenType(TokenType.binaryOperator, "not regex"));
059            KNOWN_TOKENS.add(new SimpleTokenType(TokenType.binaryOperator, "regex"));
060            KNOWN_TOKENS.add(new SimpleTokenType(TokenType.binaryOperator, "not in"));
061            KNOWN_TOKENS.add(new SimpleTokenType(TokenType.binaryOperator, "in"));
062            KNOWN_TOKENS.add(new SimpleTokenType(TokenType.binaryOperator, "range"));
063            KNOWN_TOKENS.add(new SimpleTokenType(TokenType.binaryOperator, "not range"));
064    
065            // unary operators
066            KNOWN_TOKENS.add(new SimpleTokenType(TokenType.unaryOperator, "++"));
067            KNOWN_TOKENS.add(new SimpleTokenType(TokenType.unaryOperator, "--"));
068    
069            // logical operators
070            KNOWN_TOKENS.add(new SimpleTokenType(TokenType.logicalOperator, "&&"));
071            KNOWN_TOKENS.add(new SimpleTokenType(TokenType.logicalOperator, "||"));
072            // TODO: @deprecated logical operators, to be removed in Camel 3.0
073            KNOWN_TOKENS.add(new SimpleTokenType(TokenType.logicalOperator, "and"));
074            KNOWN_TOKENS.add(new SimpleTokenType(TokenType.logicalOperator, "or"));
075        }
076    
077        private SimpleTokenizer() {
078            // static methods
079        }
080    
081    
082        /**
083         * @see SimpleLanguage#changeFunctionStartToken(String...)
084         */
085        public static void changeFunctionStartToken(String... startToken) {
086            for (SimpleTokenType type : KNOWN_TOKENS) {
087                if (type.getType() == TokenType.functionStart) {
088                    KNOWN_TOKENS.remove(type);
089                }
090            }
091    
092            // add in start of list as its a more common token to be used
093            for (String token : startToken) {
094                KNOWN_TOKENS.add(0, new SimpleTokenType(TokenType.functionStart, token));
095            }
096        }
097    
098        /**
099         * @see SimpleLanguage#changeFunctionEndToken(String...)
100         */
101        public static void changeFunctionEndToken(String... endToken) {
102            for (SimpleTokenType type : KNOWN_TOKENS) {
103                if (type.getType() == TokenType.functionEnd) {
104                    KNOWN_TOKENS.remove(type);
105                }
106            }
107    
108            // add in start of list as its a more common token to be used
109            for (String token : endToken) {
110                KNOWN_TOKENS.add(0, new SimpleTokenType(TokenType.functionEnd, token));
111            }
112        }
113    
114        /**
115         * Create the next token
116         *
117         * @param expression  the input expression
118         * @param index       the current index
119         * @param allowEscape whether to allow escapes
120         * @param filter      defines the accepted token types to be returned (character is always used as fallback)
121         * @return the created token, will always return a token
122         */
123        public static SimpleToken nextToken(String expression, int index, boolean allowEscape, TokenType... filter) {
124            return doNextToken(expression, index, allowEscape, filter);
125        }
126    
127        /**
128         * Create the next token
129         *
130         * @param expression  the input expression
131         * @param index       the current index
132         * @param allowEscape whether to allow escapes
133         * @return the created token, will always return a token
134         */
135        public static SimpleToken nextToken(String expression, int index, boolean allowEscape) {
136            return doNextToken(expression, index, allowEscape);
137        }
138    
139        private static SimpleToken doNextToken(String expression, int index, boolean allowEscape, TokenType... filters) {
140    
141            boolean numericAllowed = acceptType(TokenType.numericValue, filters);
142            if (numericAllowed) {
143                // is it a numeric value
144                StringBuilder sb = new StringBuilder();
145                boolean digit = true;
146                while (digit && index < expression.length()) {
147                    digit = Character.isDigit(expression.charAt(index));
148                    if (digit) {
149                        char ch = expression.charAt(index);
150                        sb.append(ch);
151                        index++;
152                        continue;
153                    }
154                    // is it a dot or comma as part of a floating point number
155                    boolean decimalSeparator = '.' == expression.charAt(index) || ',' == expression.charAt(index);
156                    if (decimalSeparator && sb.length() > 0) {
157                        char ch = expression.charAt(index);
158                        sb.append(ch);
159                        index++;
160                        // assume its still a digit
161                        digit = true;
162                        continue;
163                    }
164                }
165                if (sb.length() > 0) {
166                    return new SimpleToken(new SimpleTokenType(TokenType.numericValue, sb.toString()), index);
167                }
168            }
169    
170            boolean escapeAllowed = allowEscape && acceptType(TokenType.escape, filters);
171            if (escapeAllowed) {
172                StringBuilder sb = new StringBuilder();
173                char ch = expression.charAt(index);
174                boolean escaped = '\\' == ch;
175                if (escaped && index < expression.length()) {
176                    // grab next character to escape
177                    char next = expression.charAt(++index);
178                    // special for new line, tabs and carriage return
179                    if ('n' == next) {
180                        sb.append("\n");
181                    } else if ('t' == next) {
182                        sb.append("\t");
183                    } else if ('r' == next) {
184                        sb.append("\r");
185                    } else {
186                        // append the next
187                        sb.append(next);
188                    }
189                    // force 2 as length
190                    return new SimpleToken(new SimpleTokenType(TokenType.character, sb.toString()), index, 2);
191                }
192            }
193    
194            // it could be any of the known tokens
195            String text = expression.substring(index);
196            for (SimpleTokenType token : KNOWN_TOKENS) {
197                if (acceptType(token.getType(), filters)) {
198                    if (text.startsWith(token.getValue())) {
199                        return new SimpleToken(token, index);
200                    }
201                }
202            }
203    
204            // fallback and create a character token
205            char ch = expression.charAt(index);
206            SimpleToken token = new SimpleToken(new SimpleTokenType(TokenType.character, "" + ch), index);
207            return token;
208        }
209    
210        private static boolean acceptType(TokenType type, TokenType... filters) {
211            if (filters == null || filters.length == 0) {
212                return true;
213            }
214            for (TokenType filter : filters) {
215                if (type == filter) {
216                    return true;
217                }
218            }
219            return false;
220        }
221    
222    }