/* * Based on JUEL 2.2.1 code, 2006-2009 Odysseus Software GmbH * * 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.activiti.engine.impl.javax.el; import java.beans.FeatureDescriptor; import java.util.Iterator; import java.util.List; /** * Defines property resolution behavior on instances of java.util.List. This resolver handles base * objects of type java.util.List. It accepts any object as a property and coerces that object into * an integer index into the list. The resulting value is the value in the list at that index. This * resolver can be constructed in read-only mode, which means that isReadOnly will always return * true and {@link #setValue(ELContext, Object, Object, Object)} will always throw * PropertyNotWritableException. ELResolvers are combined together using {@link CompositeELResolver} * s, to define rich semantics for evaluating an expression. See the javadocs for {@link ELResolver} * for details. */ public class ListELResolver extends ELResolver { private final boolean readOnly; /** * Creates a new read/write ListELResolver. */ public ListELResolver() { this(false); } /** * Creates a new ListELResolver whose read-only status is determined by the given parameter. * * @param readOnly * true if this resolver cannot modify lists; false otherwise. */ public ListELResolver(boolean readOnly) { this.readOnly = readOnly; } /** * If the base object is a list, returns the most general type that this resolver accepts for * the property argument. Otherwise, returns null. Assuming the base is a List, this method will * always return Integer.class. This is because Lists accept integers as their index. * * @param context * The context of this evaluation. * @param base * The list to analyze. Only bases of type List are handled by this resolver. * @return null if base is not a List; otherwise Integer.class. */ @Override public Class<?> getCommonPropertyType(ELContext context, Object base) { return isResolvable(base) ? Integer.class : null; } /** * Always returns null, since there is no reason to iterate through set set of all integers. * * @param context * The context of this evaluation. * @param base * The list to analyze. Only bases of type List are handled by this resolver. * @return null. */ @Override public Iterator<FeatureDescriptor> getFeatureDescriptors(ELContext context, Object base) { return null; } /** * If the base object is a list, returns the most general acceptable type for a value in this * list. If the base is a List, the propertyResolved property of the ELContext object must be * set to true by this resolver, before returning. If this property is not true after this * method is called, the caller should ignore the return value. Assuming the base is a List, * this method will always return Object.class. This is because Lists accept any object as an * element. * * @param context * The context of this evaluation. * @param base * The list to analyze. Only bases of type List are handled by this resolver. * @param property * The index of the element in the list to return the acceptable type for. Will be * coerced into an integer, but otherwise ignored by this resolver. * @return If the propertyResolved property of ELContext was set to true, then the most general * acceptable type; otherwise undefined. * @throws PropertyNotFoundException * if the given index is out of bounds for this list. * @throws IllegalArgumentException * if the property could not be coerced into an integer. * @throws NullPointerException * if context is null * @throws ELException * if an exception was thrown while performing the property or variable resolution. * The thrown exception must be included as the cause property of this exception, if * available. */ @Override public Class<?> getType(ELContext context, Object base, Object property) { if (context == null) { throw new NullPointerException("context is null"); } Class<?> result = null; if (isResolvable(base)) { toIndex((List<?>) base, property); result = Object.class; context.setPropertyResolved(true); } return result; } /** * If the base object is a list, returns the value at the given index. The index is specified by * the property argument, and coerced into an integer. If the coercion could not be performed, * an IllegalArgumentException is thrown. If the index is out of bounds, null is returned. If * the base is a List, the propertyResolved property of the ELContext object must be set to true * by this resolver, before returning. If this property is not true after this method is called, * the caller should ignore the return value. * * @param context * The context of this evaluation. * @param base * The list to analyze. Only bases of type List are handled by this resolver. * @param property * The index of the element in the list to return the acceptable type for. Will be * coerced into an integer, but otherwise ignored by this resolver. * @return If the propertyResolved property of ELContext was set to true, then the value at the * given index or null if the index was out of bounds. Otherwise, undefined. * @throws PropertyNotFoundException * if the given index is out of bounds for this list. * @throws IllegalArgumentException * if the property could not be coerced into an integer. * @throws NullPointerException * if context is null * @throws ELException * if an exception was thrown while performing the property or variable resolution. * The thrown exception must be included as the cause property of this exception, if * available. */ @Override public Object getValue(ELContext context, Object base, Object property) { if (context == null) { throw new NullPointerException("context is null"); } Object result = null; if (isResolvable(base)) { int index = toIndex(null, property); List<?> list = (List<?>) base; result = index < 0 || index >= list.size() ? null : list.get(index); context.setPropertyResolved(true); } return result; } /** * If the base object is a list, returns whether a call to * {@link #setValue(ELContext, Object, Object, Object)} will always fail. If the base is a List, * the propertyResolved property of the ELContext object must be set to true by this resolver, * before returning. If this property is not true after this method is called, the caller should * ignore the return value. If this resolver was constructed in read-only mode, this method will * always return true. If a List was created using java.util.Collections.unmodifiableList(List), * this method must return true. Unfortunately, there is no Collections API method to detect * this. However, an implementation can create a prototype unmodifiable List and query its * runtime type to see if it matches the runtime type of the base object as a workaround. * * @param context * The context of this evaluation. * @param base * The list to analyze. Only bases of type List are handled by this resolver. * @param property * The index of the element in the list to return the acceptable type for. Will be * coerced into an integer, but otherwise ignored by this resolver. * @return If the propertyResolved property of ELContext was set to true, then true if calling * the setValue method will always fail or false if it is possible that such a call may * succeed; otherwise undefined. * @throws PropertyNotFoundException * if the given index is out of bounds for this list. * @throws IllegalArgumentException * if the property could not be coerced into an integer. * @throws NullPointerException * if context is null * @throws ELException * if an exception was thrown while performing the property or variable resolution. * The thrown exception must be included as the cause property of this exception, if * available. */ @Override public boolean isReadOnly(ELContext context, Object base, Object property) { if (context == null) { throw new NullPointerException("context is null"); } if (isResolvable(base)) { toIndex((List<?>) base, property); context.setPropertyResolved(true); } return readOnly; } /** * If the base object is a list, attempts to set the value at the given index with the given * value. The index is specified by the property argument, and coerced into an integer. If the * coercion could not be performed, an IllegalArgumentException is thrown. If the index is out * of bounds, a PropertyNotFoundException is thrown. If the base is a List, the propertyResolved * property of the ELContext object must be set to true by this resolver, before returning. If * this property is not true after this method is called, the caller can safely assume no value * was set. If this resolver was constructed in read-only mode, this method will always throw * PropertyNotWritableException. If a List was created using * java.util.Collections.unmodifiableList(List), this method must throw * PropertyNotWritableException. Unfortunately, there is no Collections API method to detect * this. However, an implementation can create a prototype unmodifiable List and query its * runtime type to see if it matches the runtime type of the base object as a workaround. * * @param context * The context of this evaluation. * @param base * The list to analyze. Only bases of type List are handled by this resolver. * @param property * The index of the element in the list to return the acceptable type for. Will be * coerced into an integer, but otherwise ignored by this resolver. * @param value * The value to be set at the given index. * @throws ClassCastException * if the class of the specified element prevents it from being added to this list. * @throws PropertyNotFoundException * if the given index is out of bounds for this list. * @throws PropertyNotWritableException * if this resolver was constructed in read-only mode, or if the set operation is * not supported by the underlying list. * @throws IllegalArgumentException * if the property could not be coerced into an integer. * @throws NullPointerException * if context is null * @throws ELException * if an exception was thrown while performing the property or variable resolution. * The thrown exception must be included as the cause property of this exception, if * available. */ @Override @SuppressWarnings("unchecked") public void setValue(ELContext context, Object base, Object property, Object value) { if (context == null) { throw new NullPointerException("context is null"); } if (isResolvable(base)) { if (readOnly) { throw new PropertyNotWritableException("resolver is read-only"); } List list = (List) base; int index = toIndex(list, property); try { list.set(index, value); } catch (UnsupportedOperationException e) { throw new PropertyNotWritableException(e); } catch (ArrayStoreException e) { throw new IllegalArgumentException(e); } context.setPropertyResolved(true); } } /** * Test whether the given base should be resolved by this ELResolver. * * @param base * The bean to analyze. * @param property * The name of the property to analyze. Will be coerced to a String. * @return base instanceof List */ private static final boolean isResolvable(Object base) { return base instanceof List<?>; } /** * Convert the given property to an index in (list) base. * * @param base * The bean to analyze. * @param property * The name of the property to analyze. Will be coerced to a String. * @return The index of property in base. * @throws IllegalArgumentException * if base property cannot be coerced to an integer. * @throws PropertyNotFoundException * if base is not null and the computed index is out of bounds for base. */ private static final int toIndex(List<?> base, Object property) { int index = 0; if (property instanceof Number) { index = ((Number) property).intValue(); } else if (property instanceof String) { try { index = Integer.valueOf((String) property); } catch (NumberFormatException e) { throw new IllegalArgumentException("Cannot parse list index: " + property); } } else if (property instanceof Character) { index = ((Character) property).charValue(); } else if (property instanceof Boolean) { index = ((Boolean) property).booleanValue() ? 1 : 0; } else { throw new IllegalArgumentException("Cannot coerce property to list index: " + property); } if (base != null && (index < 0 || index >= base.size())) { throw new PropertyNotFoundException("List index out of bounds: " + index); } return index; } }