blob: 0ffa2eaf173a034e8f3db6c4e3067f4a40837ef2 [file] [log] [blame]
/*******************************************************************************
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.ofbiz.base.util.string;
import java.math.BigDecimal;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import javax.el.ArrayELResolver;
import javax.el.BeanELResolver;
import javax.el.CompositeELResolver;
import javax.el.ELContext;
import javax.el.ELResolver;
import javax.el.ExpressionFactory;
import javax.el.FunctionMapper;
import javax.el.ListELResolver;
import javax.el.MapELResolver;
import javax.el.PropertyNotFoundException;
import javax.el.PropertyNotWritableException;
import javax.el.ResourceBundleELResolver;
import javax.el.ValueExpression;
import javax.el.VariableMapper;
import javolution.util.FastList;
import javolution.util.FastMap;
import org.ofbiz.base.util.Debug;
import org.ofbiz.base.util.StringUtil;
import org.ofbiz.base.util.UtilGenerics;
import org.ofbiz.base.util.UtilMisc;
import org.ofbiz.base.util.collections.LocalizedMap;
/** Implements the Unified Expression Language (JSR-245). */
public class UelUtil {
protected static final String module = UelUtil.class.getName();
public static final String localizedMapLocaleKey = LocalizedMap.class.getName() + "_locale".replace(".", "_");
protected static final ExpressionFactory exprFactory = JuelConnector.newExpressionFactory();
protected static final ELResolver defaultResolver = new ExtendedCompositeResolver() {
{
add(new ExtendedMapResolver(false));
add(new ExtendedListResolver(false));
add(new ArrayELResolver(false));
add(new NodeELResolver()); // Below the most common but must be kept above BeanELResolver
add(new ResourceBundleELResolver());
add(new BeanELResolver(false));
}
};
/** Evaluates a Unified Expression Language expression and returns the result.
* @param context Evaluation context (variables)
* @param expression UEL expression
* @return Result object
* @throws <code>javax.el.*</code> exceptions
*/
public static Object evaluate(Map<String, ? extends Object> context, String expression) {
return evaluate(context, expression, Object.class);
}
/** Evaluates a Unified Expression Language expression and returns the result.
* @param context Evaluation context (variables)
* @param expression UEL expression
* @param expectedType The expected object Class to return
* @return Result object
* @throws <code>javax.el.*</code> exceptions
*/
@SuppressWarnings("unchecked")
public static Object evaluate(Map<String, ? extends Object> context, String expression, Class expectedType) {
ELContext elContext = new ReadOnlyContext(context);
ValueExpression ve = exprFactory.createValueExpression(elContext, expression, expectedType);
return ve.getValue(elContext);
}
/** Evaluates a Unified Expression Language expression and sets the resulting object
* to the specified value.
* @param context Evaluation context (variables)
* @param expression UEL expression
* @param expectedType The expected object Class to set
* @throws <code>javax.el.*</code> exceptions
*/
@SuppressWarnings("unchecked")
public static void setValue(Map<String, Object> context, String expression, Class expectedType, Object value) {
if (Debug.verboseOn()) {
Debug.logVerbose("UelUtil.setValue invoked, expression = " + expression + ", value = " + value, module);
}
ELContext elContext = new BasicContext(context);
ValueExpression ve = exprFactory.createValueExpression(elContext, expression, expectedType);
ve.setValue(elContext, value);
}
/** Evaluates a Unified Expression Language expression and sets the resulting object
* to null.
* @param context Evaluation context (variables)
* @param expression UEL expression
* @throws <code>javax.el.*</code> exceptions
*/
public static void removeValue(Map<String, Object> context, String expression) {
if (Debug.verboseOn()) {
Debug.logVerbose("UelUtil.removeValue invoked, expression = " + expression , module);
}
ELContext elContext = new BasicContext(context);
ValueExpression ve = exprFactory.createValueExpression(elContext, expression, Object.class);
ve.setValue(elContext, null);
}
protected static class BasicContext extends ELContext {
protected final Map<String, Object> variables;
protected final VariableMapper variableMapper;
public BasicContext(Map<String, Object> context) {
this.variableMapper = new BasicVariableMapper(this);
this.variables = context;
}
@Override
public ELResolver getELResolver() {
return defaultResolver;
}
@Override
public FunctionMapper getFunctionMapper() {
return UelFunctions.getFunctionMapper();
}
@Override
public VariableMapper getVariableMapper() {
return this.variableMapper;
}
}
protected static class ReadOnlyContext extends ELContext {
protected final Map<String, ? extends Object> variables;
protected final VariableMapper variableMapper;
public ReadOnlyContext(Map<String, ? extends Object> context) {
this.variableMapper = new ReadOnlyVariableMapper(this);
this.variables = UtilGenerics.cast(context);
}
@Override
public ELResolver getELResolver() {
return defaultResolver;
}
@Override
public FunctionMapper getFunctionMapper() {
return UelFunctions.getFunctionMapper();
}
@Override
public VariableMapper getVariableMapper() {
return this.variableMapper;
}
protected static class ReadOnlyVariableMapper extends VariableMapper {
protected final ReadOnlyContext elContext;
protected ReadOnlyVariableMapper(ReadOnlyContext elContext) {
this.elContext = elContext;
}
@Override
public ValueExpression resolveVariable(String variable) {
Object obj = UelUtil.resolveVariable(variable, this.elContext.variables, null);
if (obj != null) {
return new ReadOnlyExpression(obj);
}
return null;
}
@Override
public ValueExpression setVariable(String variable, ValueExpression expression) {
throw new PropertyNotWritableException();
}
}
}
protected static class BasicVariableMapper extends VariableMapper {
protected final BasicContext elContext;
protected BasicVariableMapper(BasicContext elContext) {
this.elContext = elContext;
}
/**
* Returns a BasicValueExpression containing the value of the named variable.
* Resolves against LocalizedMap if available.
* @param variable the variable's name
* @return a BasicValueExpression containing the variable's value or null if the variable is unknown
*/
@Override
public ValueExpression resolveVariable(String variable) {
Object obj = UelUtil.resolveVariable(variable, this.elContext.variables, null);
if (obj != null) {
return new BasicValueExpression(variable, this.elContext);
}
return null;
}
@Override
public ValueExpression setVariable(String variable, ValueExpression expression) {
Object originalObj = this.elContext.variables.put(variable, expression.getValue(this.elContext));
if (originalObj == null) {
return null;
}
return new ReadOnlyExpression(originalObj);
}
}
@SuppressWarnings("serial")
protected static class ReadOnlyExpression extends ValueExpression {
protected final Object object;
protected ReadOnlyExpression(Object object) {
this.object = object;
}
@Override
public Class<?> getExpectedType() {
return this.object.getClass();
}
@Override
public Class<?> getType(ELContext context) {
return this.getExpectedType();
}
@Override
public Object getValue(ELContext context) {
return this.object;
}
@Override
public boolean isReadOnly(ELContext context) {
return true;
}
@Override
public void setValue(ELContext context, Object value) {
throw new PropertyNotWritableException();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
try {
ReadOnlyExpression other = (ReadOnlyExpression) obj;
return this.object.equals(other.object);
} catch (ClassCastException e) {}
return false;
}
@Override
public String getExpressionString() {
return null;
}
@Override
public int hashCode() {
return this.object.hashCode();
}
@Override
public boolean isLiteralText() {
return false;
}
}
@SuppressWarnings("serial")
protected static class BasicValueExpression extends ValueExpression {
protected final BasicContext elContext;
protected final String varName;
public BasicValueExpression(String varName, BasicContext elContext) {
this.elContext = elContext;
this.varName = varName;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
try {
BasicValueExpression other = (BasicValueExpression) obj;
return this.varName.equals(other.varName);
} catch (ClassCastException e) {}
return false;
}
@Override
public int hashCode() {
return this.varName.hashCode();
}
@Override
public Object getValue(ELContext context) {
return this.elContext.variables.get(this.varName);
}
@Override
public String getExpressionString() {
return null;
}
@Override
public boolean isLiteralText() {
return false;
}
@Override
public Class<?> getType(ELContext context) {
return this.getExpectedType();
}
@Override
public boolean isReadOnly(ELContext context) {
return false;
}
@Override
public void setValue(ELContext context, Object value) {
this.elContext.variables.put(this.varName, value);
}
@Override
public String toString() {
return "ValueExpression(" + this.varName + ")";
}
@Override
public Class<?> getExpectedType() {
Object obj = this.elContext.variables.get(this.varName);
return obj == null ? null : obj.getClass();
}
}
/** Custom <code>CompositeELResolver</code> used to handle variable
* auto-vivify.
*/
protected static class ExtendedCompositeResolver extends CompositeELResolver {
@Override
public void setValue(ELContext context, Object base, Object property, Object val) {
super.setValue(context, base, property, val);
if (!context.isPropertyResolved() && base == null) {
if (Debug.verboseOn()) {
Debug.logVerbose("ExtendedCompositeResolver.setValue: base = " + base + ", property = " + property + ", value = " + val, module);
}
try {
BasicContext elContext = (BasicContext) context;
elContext.variables.put(property.toString(), val);
context.setPropertyResolved(true);
} catch (ClassCastException e) {}
}
}
}
/** Custom <code>ListELResolver</code> used to handle OFBiz
* <code>List</code> syntax.
*/
protected static class ExtendedListResolver extends ListELResolver {
protected boolean isReadOnly;
public ExtendedListResolver(boolean isReadOnly) {
super(isReadOnly);
this.isReadOnly = isReadOnly;
}
@Override
@SuppressWarnings("unchecked")
public void setValue(ELContext context, Object base, Object property, Object val) {
if (context == null) {
throw new NullPointerException();
}
if (base != null && base instanceof List) {
if (isReadOnly) {
throw new PropertyNotWritableException();
}
String str = property.toString();
if ("add".equals(str)) {
if (Debug.verboseOn()) {
Debug.logVerbose("ExtendedListResolver.setValue adding List element: base = " + base + ", property = " + property + ", value = " + val, module);
}
context.setPropertyResolved(true);
List list = (List) base;
list.add(val);
} else if (str.startsWith("insert@")) {
if (Debug.verboseOn()) {
Debug.logVerbose("ExtendedListResolver.setValue inserting List element: base = " + base + ", property = " + property + ", value = " + val, module);
}
context.setPropertyResolved(true);
String indexStr = str.replace("insert@", "");
int index = Integer.parseInt(indexStr);
List list = (List) base;
try {
list.add(index, val);
} catch (UnsupportedOperationException ex) {
throw new PropertyNotWritableException();
} catch (IndexOutOfBoundsException ex) {
throw new PropertyNotFoundException();
}
} else {
super.setValue(context, base, property, val);
}
}
}
}
/** Custom <code>MapELResolver</code> class used to accommodate
* <code>LocalizedMap</code> instances.
*/
protected static class ExtendedMapResolver extends MapELResolver {
public ExtendedMapResolver(boolean isReadOnly) {
super(isReadOnly);
}
@Override
@SuppressWarnings("unchecked")
public Object getValue(ELContext context, Object base, Object property) {
if (context == null) {
throw new NullPointerException();
}
if (base != null && base instanceof LocalizedMap) {
context.setPropertyResolved(true);
LocalizedMap map = (LocalizedMap) base;
Locale locale = null;
try {
VariableMapper vm = context.getVariableMapper();
ValueExpression ve = vm.resolveVariable(localizedMapLocaleKey);
if (ve != null) {
locale = (Locale) ve.getValue(context);
}
if (locale == null) {
ve = vm.resolveVariable("locale");
if (ve != null) {
locale = (Locale) ve.getValue(context);
}
}
} catch (Exception e) {
Debug.logWarning("Exception thrown while getting LocalizedMap element, locale = " + locale + ", exception " + e, module);
}
if (locale == null) {
if (Debug.verboseOn()) {
Debug.logVerbose("ExtendedMapResolver.getValue: unable to find Locale for LocalizedMap element, using default locale", module);
}
locale = Locale.getDefault();
}
return resolveVariable(property.toString(), (Map) map, locale);
}
if (base != null && base instanceof Map && property instanceof String) {
context.setPropertyResolved(true);
return resolveVariable(property.toString(), (Map) base, null);
}
return super.getValue(context, base, property);
}
}
/** Evaluates a property <code>Object</code> and returns a new
* <code>List</code> or <code>Map</code>. If <code>property</code>
* is not a String object type and it evaluates to an integer value,
* a new <code>List</code> instance is returned, otherwise a new
* <code>Map</code> instance is returned.
* @param property Property <code>Object</code> to be evaluated
* @return New <code>List</code> or <code>Map</code>
*/
public static Object autoVivifyListOrMap(Object property) {
String str = property.toString();
boolean isList = ("add".equals(str) || str.startsWith("insert@"));
if (!isList && !"java.lang.String".equals(property.getClass().getName())) {
Integer index = UtilMisc.toIntegerObject(property);
isList = (index != null);
}
if (isList) {
return FastList.newInstance();
} else {
return FastMap.newInstance();
}
}
/** Prepares an expression for evaluation by UEL.<p>The OFBiz syntax is
* converted to UEL-compatible syntax and the resulting expression is
* returned.</p>
* @see <a href="StringUtil.html#convertOperatorSubstitutions(java.lang.String)">StringUtil.convertOperatorSubstitutions(java.lang.String)</a>
* @param expression Expression to be converted
* @return Converted expression
*/
public static String prepareExpression(String expression) {
String result = StringUtil.convertOperatorSubstitutions(expression);
result = result.replace("[]", "['add']");
int openBrace = result.indexOf("[+");
int closeBrace = (openBrace == -1 ? -1 : result.indexOf(']', openBrace));
if (closeBrace != -1) {
String base = result.substring(0, openBrace);
String property = result.substring(openBrace+2, closeBrace).trim();
String end = result.substring(closeBrace + 1);
result = base + "['insert@" + property + "']" + end;
}
return result;
}
public static Object resolveVariable(String variable, Map<String, ? extends Object> variables, Locale locale) {
Object obj = null;
String createObjectType = null;
String name = variable;
if (variable.contains("$")) {
if (variable.endsWith("$string")) {
name = variable.substring(0, variable.length() - 7);
createObjectType = "string";
} else if (variable.endsWith("$null")) {
name = variable.substring(0, variable.length() - 5);
createObjectType = "null";
} else if (variable.endsWith("$boolean")) {
name = variable.substring(0, variable.length() - 8);
createObjectType = "boolean";
} else if (variable.endsWith("$integer")) {
name = variable.substring(0, variable.length() - 8);
createObjectType = "integer";
} else if (variable.endsWith("$long")) {
name = variable.substring(0, variable.length() - 5);
createObjectType = "long";
} else if (variable.endsWith("$double")) {
name = variable.substring(0, variable.length() - 7);
createObjectType = "double";
} else if (variable.endsWith("$bigDecimal")) {
name = variable.substring(0, variable.length() - 11);
createObjectType = "bigDecimal";
}
}
if (variables instanceof LocalizedMap<?>) {
if (locale == null) {
locale = (Locale) variables.get(localizedMapLocaleKey);
if (locale == null) {
locale = (Locale) variables.get("locale");
if (locale == null) {
locale = Locale.getDefault();
}
}
}
obj = ((LocalizedMap<?>) variables).get(name, locale);
} else {
obj = variables.get(name);
}
if (obj != null) {
return obj;
}
if (createObjectType != null) {
if ("string".equals(createObjectType)) {
return "";
} else if ("null".equals(createObjectType)) {
return null;
} else if ("boolean".equals(createObjectType)) {
return Boolean.FALSE;
} else if ("integer".equals(createObjectType)) {
return Integer.valueOf(0);
} else if ("long".equals(createObjectType)) {
return Long.valueOf(0);
} else if ("double".equals(createObjectType)) {
return Double.valueOf(0);
} else if ("bigDecimal".equals(createObjectType)) {
return BigDecimal.ZERO;
}
}
return null;
}
}