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 */
017package org.apache.camel.util;
018
019import java.util.ArrayList;
020import java.util.List;
021import java.util.regex.Matcher;
022import java.util.regex.Pattern;
023
024/**
025 * Helper for Camel OGNL (Object-Graph Navigation Language) expressions.
026 *
027 * @version 
028 */
029public final class OgnlHelper {
030
031    private static final Pattern INDEX_PATTERN = Pattern.compile("^(.*)\\[(.*)\\]$");
032
033    private OgnlHelper() {
034    }
035
036    /**
037     * Tests whether or not the given String is a Camel OGNL expression.
038     * <p/>
039     * An expression is considered an OGNL expression when it contains either one of the following chars: . or [
040     *
041     * @param expression  the String
042     * @return <tt>true</tt> if a Camel OGNL expression, otherwise <tt>false</tt>. 
043     */
044    public static boolean isValidOgnlExpression(String expression) {
045        if (ObjectHelper.isEmpty(expression)) {
046            return false;
047        }
048
049        // the brackets should come in a pair
050        int bracketBegin = StringHelper.countChar(expression, '[');
051        int bracketEnd = StringHelper.countChar(expression, ']');
052        if (bracketBegin > 0 && bracketEnd > 0) {
053            return bracketBegin == bracketEnd;
054        }
055
056        return expression.contains(".");
057    }
058
059    public static boolean isInvalidValidOgnlExpression(String expression) {
060        if (ObjectHelper.isEmpty(expression)) {
061            return false;
062        }
063
064        if (!expression.contains(".") && !expression.contains("[") && !expression.contains("]")) {
065            return false;
066        }
067
068        // the brackets should come in pair
069        int bracketBegin = StringHelper.countChar(expression, '[');
070        int bracketEnd = StringHelper.countChar(expression, ']');
071        if (bracketBegin > 0 || bracketEnd > 0) {
072            return bracketBegin != bracketEnd;
073        }
074        
075        // check for double dots
076        if (expression.contains("..")) {
077            return true;
078        }
079
080        return false;
081    }
082
083    /**
084     * Tests whether or not the given Camel OGNL expression is using the null safe operator or not.
085     *
086     * @param ognlExpression the Camel OGNL expression
087     * @return <tt>true</tt> if the null safe operator is used, otherwise <tt>false</tt>.
088     */
089    public static boolean isNullSafeOperator(String ognlExpression) {
090        if (ObjectHelper.isEmpty(ognlExpression)) {
091            return false;
092        }
093
094        return ognlExpression.startsWith("?");
095    }
096
097    /**
098     * Removes any leading operators from the Camel OGNL expression.
099     * <p/>
100     * Will remove any leading of the following chars: ? or .
101     *
102     * @param ognlExpression  the Camel OGNL expression
103     * @return the Camel OGNL expression without any leading operators.
104     */
105    public static String removeLeadingOperators(String ognlExpression) {
106        if (ObjectHelper.isEmpty(ognlExpression)) {
107            return ognlExpression;
108        }
109
110        if (ognlExpression.startsWith("?")) {
111            ognlExpression = ognlExpression.substring(1);
112        }
113        if (ognlExpression.startsWith(".")) {
114            ognlExpression = ognlExpression.substring(1);
115        }
116
117        return ognlExpression;
118    }
119
120    /**
121     * Removes any trailing operators from the Camel OGNL expression.
122     *
123     * @param ognlExpression  the Camel OGNL expression
124     * @return the Camel OGNL expression without any trailing operators.
125     */
126    public static String removeTrailingOperators(String ognlExpression) {
127        if (ObjectHelper.isEmpty(ognlExpression)) {
128            return ognlExpression;
129        }
130
131        if (ognlExpression.contains("[")) {
132            return ObjectHelper.before(ognlExpression, "[");
133        }
134        return ognlExpression;
135    }
136
137    public static String removeOperators(String ognlExpression) {
138        return removeLeadingOperators(removeTrailingOperators(ognlExpression));
139    }
140
141    public static KeyValueHolder<String, String> isOgnlIndex(String ognlExpression) {
142        Matcher matcher = INDEX_PATTERN.matcher(ognlExpression);
143        if (matcher.matches()) {
144
145            // to avoid empty strings as we want key/value to be null in such cases
146            String key = matcher.group(1);
147            if (ObjectHelper.isEmpty(key)) {
148                key = null;
149            }
150
151            // to avoid empty strings as we want key/value to be null in such cases
152            String value = matcher.group(2);
153            if (ObjectHelper.isEmpty(value)) {
154                value = null;
155            }
156
157            return new KeyValueHolder<String, String>(key, value);
158        }
159
160        return null;
161    }
162
163    /**
164     * Regular expression with repeating groups is a pain to get right
165     * and then nobody understands the reg exp afterwards.
166     * So we use a bit ugly/low-level Java code to split the OGNL into methods.
167     *
168     * @param ognl the ognl expression
169     * @return a list of methods, will return an empty list, if ognl expression has no methods
170     */
171    public static List<String> splitOgnl(String ognl) {
172        List<String> methods = new ArrayList<String>();
173
174        // return an empty list if ognl is empty
175        if (ObjectHelper.isEmpty(ognl)) {
176            return methods;
177        }
178
179        StringBuilder sb = new StringBuilder();
180
181        int j = 0; // j is used as counter per method
182        boolean squareBracket = false; // special to keep track if we are inside a square bracket block, eg: [foo]
183        boolean parenthesisBracket = false; // special to keep track if we are inside a parenthesis block, eg: bar(${body}, ${header.foo})
184        for (int i = 0; i < ognl.length(); i++) {
185            char ch = ognl.charAt(i);
186            // special for starting a new method
187            if (j == 0 || (j == 1 && ognl.charAt(i - 1) == '?')
188                    || (ch != '.' && ch != '?' && ch != ']')) {
189                sb.append(ch);
190                // special if we are doing square bracket
191                if (ch == '[' && !parenthesisBracket) {
192                    squareBracket = true;
193                } else if (ch == '(') {
194                    parenthesisBracket = true;
195                } else if (ch == ')') {
196                    parenthesisBracket = false;
197                }
198                j++; // advance
199            } else {
200                if (ch == '.' && !squareBracket && !parenthesisBracket) {
201                    // only treat dot as a method separator if not inside a square bracket block
202                    // as dots can be used in key names when accessing maps
203
204                    // a dit denotes end of this method and a new method is to be invoked
205                    String s = sb.toString();
206
207                    // reset sb
208                    sb.setLength(0);
209
210                    // pass over ? to the new method
211                    if (s.endsWith("?")) {
212                        sb.append("?");
213                        s = s.substring(0, s.length() - 1);
214                    }
215
216                    // add the method
217                    methods.add(s);
218
219                    // reset j to begin a new method
220                    j = 0;
221                } else if (ch == ']' && !parenthesisBracket) {
222                    // append ending ] to method name
223                    sb.append(ch);
224                    String s = sb.toString();
225
226                    // reset sb
227                    sb.setLength(0);
228
229                    // add the method
230                    methods.add(s);
231
232                    // reset j to begin a new method
233                    j = 0;
234
235                    // no more square bracket
236                    squareBracket = false;
237                }
238
239                // and don't lose the char if its not an ] end marker (as we already added that)
240                if (ch != ']' || parenthesisBracket) {
241                    sb.append(ch);
242                }
243
244                // check for end of parenthesis
245                if (ch == ')') {
246                    parenthesisBracket = false;
247                }
248
249                // only advance if already begun on the new method
250                if (j > 0) {
251                    j++;
252                }
253            }
254        }
255
256        // add remainder in buffer when reached end of data
257        if (sb.length() > 0) {
258            methods.add(sb.toString());
259        }
260
261        return methods;
262    }
263
264}