/* * Copyright (c) 2001-2017, Inversoft Inc., All Rights Reserved * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific * language governing permissions and limitations under the License. */ package org.primeframework.mvc.parameter.el; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; import org.primeframework.mvc.config.MVCConfiguration; import org.primeframework.mvc.parameter.convert.ConverterProvider; import org.primeframework.mvc.parameter.convert.ConverterStateException; import org.primeframework.mvc.parameter.convert.GlobalConverter; /** * This class is the evaluation context. * * @author Brian Pontarelli */ public class Expression { private final List<String> atoms; private final Map<String, String> attributes; private final ConverterProvider converterProvider; private final String expression; private Accessor accessor; private Object current; private int index; private Class<?> type; private final MVCConfiguration configuration; public Expression(ConverterProvider converterProvider, String expression, Object current, Map<String, String> attributes, MVCConfiguration configuration) { this.expression = expression; this.attributes = attributes; this.converterProvider = converterProvider; this.atoms = parse(expression); this.configuration = configuration; setCurrentObject(current); } public String getExpression() { return expression; } public Map<String, String> getAttributes() { return attributes; } public Accessor getCurrentAccessor() { return accessor; } public Object getCurrentObject() { return current; } public String getCurrentValueAsString() { Class<?> type = current.getClass(); GlobalConverter converter = converterProvider.lookup(type); if (converter == null) { throw new ConverterStateException("No type converter found for the type [" + type + "]"); } return converter.convertToString(type, attributes, expression, current); } public void setCurrentValue(String[] values) { accessor.set(current, values, this); } public void setCurrentValue(Object value) { accessor.set(current, value, this); } public Object traverseToEndForGet() { while (hasNext()) { next(); Object value = getCurrentValue(); if (value == null) { return null; } setCurrentObject(value); } return getCurrentObject(); } public void traverseToEndForSet() { while (hasNext()) { next(); if (hasNext()) { Object value = getCurrentValue(); if (value == null) { value = createValue(); } if (value != null) { setCurrentObject(value); } } } } private String peek() { return atoms.get(index); } private void next() { String atom = atoms.get(index++); // This is the indexed case, so the next atom is the index if (accessor != null && accessor.isIndexed()) { accessor = new IndexedAccessor(converterProvider, (MemberAccessor) accessor, atom); } else { if (Collection.class.isAssignableFrom(type) || current.getClass().isArray()) { GlobalConverter converter = converterProvider.lookup(Integer.class); Integer index = (Integer) converter.convertFromStrings(Integer.class, null, null, atom); accessor = new IndexedCollectionAccessor(converterProvider, accessor, index, accessor.getMemberAccessor()); } else if (Map.class.isAssignableFrom(type)) { accessor = new MapAccessor(converterProvider, accessor, atom, accessor.getMemberAccessor()); } else { accessor = new MemberAccessor(converterProvider, type, atom, expression, configuration); } } // Check if the new accessor is indexed and if there are no more atoms left. In this case, we error out. while (skip()) { if (!hasNext()) { throw new IndexExpressionException("Encountered an indexed property without an index in the expression [" + expression + "]"); } // Recurse until we hit a non-indexed atom next(); } } private boolean hasNext() { return index < atoms.size(); } private Object createValue() { // Peek at the next atom, in case this is an array Object key = hasNext() ? peek() : null; Object value = accessor.createValue(key); accessor.set(current, value, this); return value; } private Object getCurrentValue() { return accessor.get(current, this); } private boolean skip() { return accessor != null && accessor.isIndexed(); } private void setCurrentObject(Object object) { this.current = object; this.type = object.getClass(); } /** * This breaks the expression name down into manageable pieces. These are the individual instances of the Atom inner * class which store the name and the indices (which could be null or any object). This is broken on the '.' * character. * * @param expression The expression string to break down. * @return A new ArrayList of PropertyInfo objects. * @throws ExpressionException If the property string is invalid. */ private List<String> parse(String expression) throws ExpressionException { char[] ca = expression.toCharArray(); List<String> list = new ArrayList<>(); int index = 0; int position = 0; char[] buf = new char[128]; boolean insideBracket = false; boolean insideQuote = false; for (; index < ca.length; index++) { if (ca[index] == '.' && !insideQuote) { if (insideBracket || insideQuote) { throw new InvalidExpressionException("The expression string [" + expression + "] contains an invalid indices"); } if (position == 0) { throw new InvalidExpressionException("The expression string [" + expression + "] is invalid."); } list.add(new String(buf, 0, position)); position = 0; } else if (ca[index] == '[' && !insideQuote) { if (insideBracket) { throw new InvalidExpressionException("The expression string [" + expression + "] contains an invalid indices"); } list.add(new String(buf, 0, position)); insideBracket = true; position = 0; } else if (ca[index] == ']' && !insideQuote) { if (!insideBracket) { throw new InvalidExpressionException("The expression string [" + expression + "] contains an invalid indices"); } // Gobble up the period if there is one if (index + 1 < ca.length && ca[index + 1] == '.') { index++; } insideBracket = false; list.add(new String(buf, 0, position)); position = 0; } else if (ca[index] == '\'' || ca[index] == '\"') { if (!insideBracket) { throw new InvalidExpressionException("The expression string [" + expression + "] is invalid."); } insideQuote = !insideQuote; } else { buf[position++] = ca[index]; } } if (position > 0) { list.add(new String(buf, 0, position)); } return list; } }