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 }