| /* |
| * 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.apache.wicket.core.util.lang; |
| |
| import java.lang.reflect.Array; |
| import java.lang.reflect.Field; |
| import java.lang.reflect.InvocationTargetException; |
| import java.lang.reflect.Method; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.concurrent.ConcurrentHashMap; |
| |
| import org.apache.wicket.Application; |
| import org.apache.wicket.Session; |
| import org.apache.wicket.WicketRuntimeException; |
| import org.apache.wicket.core.util.lang.PropertyResolverConverter; |
| import org.apache.wicket.util.convert.ConversionException; |
| import org.apache.wicket.util.lang.Generics; |
| import org.apache.wicket.util.string.Strings; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| /** |
| * This class parses expressions to lookup or set a value on the object that is given. <br/> |
| * The supported expressions are: |
| * <dl> |
| * <dt>"property"</dt> |
| * <dd> |
| * This could be a bean property with getter and setter. Or if a map is given as |
| * an object it will be lookup with the expression as a key when there is not getter for that |
| * property. |
| * </dd> |
| * <dt>"property1.property2"</dt> |
| * <dd> |
| * Both properties are looked up as described above. If property1 evaluates to |
| * null then if there is a setter (or if it is a map) and the Class of the property has a default |
| * constructor then the object will be constructed and set on the object. |
| * </dd> |
| * <dt>"method()"</dt> |
| * <dd> |
| * The corresponding method is invoked. |
| * </dd> |
| * <dt>"property.index" or "property[index]"</dt> |
| * <dd> |
| * If the property is a List or Array then the following expression can be a index on |
| * that list like: 'mylist.0'. The list will grow automatically if the index is greater than the size.<p> |
| * This expression will also map on indexed properties, i.e. {@code getProperty(index)} and {@code setProperty(index,value)} |
| * methods. |
| * </dd> |
| * <dt>"property.key" or "property[key]"</dt> |
| * <dd> |
| * If the property is a Map then the following expression can be a key in that map like: 'myMap.key'. |
| * </dd> |
| * </dl> |
| * <strong>Note that the {@link DefaultGetAndSetLocator} by default provides access to private members |
| * and methods. If guaranteeing encapsulation of the target objects is a big concern, you should consider |
| * using an alternative implementation.</strong> |
| * <p> |
| * <strong>Note: If a property evaluates to an instance of {@link org.apache.wicket.model.IModel} then |
| * the expression should use '.object' to work with its value.</strong> |
| * |
| * @author jcompagner |
| * @author svenmeier |
| */ |
| public final class PropertyResolver |
| { |
| /** Log. */ |
| private static final Logger log = LoggerFactory.getLogger(PropertyResolver.class); |
| |
| private final static int RETURN_NULL = 0; |
| private final static int CREATE_NEW_VALUE = 1; |
| private final static int RESOLVE_CLASS = 2; |
| |
| private final static ConcurrentHashMap<Object, IPropertyLocator> applicationToLocators = Generics.newConcurrentHashMap(2); |
| |
| private static final String GET = "get"; |
| private static final String IS = "is"; |
| private static final String SET = "set"; |
| |
| /** |
| * Looks up the value from the object with the given expression. If the expression, the object |
| * itself or one property evaluates to null then a null will be returned. |
| * |
| * @param expression |
| * The expression string with the property to be lookup. |
| * @param object |
| * The object which is evaluated. |
| * @return The value that is evaluated. Null something in the expression evaluated to null. |
| */ |
| public static Object getValue(final String expression, final Object object) |
| { |
| if (expression == null || expression.equals("") || object == null) |
| { |
| return object; |
| } |
| |
| ObjectWithGetAndSet objectWithGetAndSet = getObjectWithGetAndSet(expression, object, RETURN_NULL); |
| if (objectWithGetAndSet == null) |
| { |
| return null; |
| } |
| |
| return objectWithGetAndSet.getValue(); |
| } |
| |
| /** |
| * Set the value on the object with the given expression. If the expression can't be evaluated |
| * then a WicketRuntimeException will be thrown. If a null object is encountered then it will |
| * try to generate it by calling the default constructor and set it on the object. |
| * |
| * The value will be tried to convert to the right type with the given converter. |
| * |
| * @param expression |
| * The expression string with the property to be set. |
| * @param object |
| * The object which is evaluated to set the value on. |
| * @param value |
| * The value to set. |
| * @param converter |
| * The converter to convert the value if needed to the right type. |
| * @throws WicketRuntimeException |
| */ |
| public static void setValue(final String expression, final Object object, |
| final Object value, final PropertyResolverConverter converter) |
| { |
| if (Strings.isEmpty(expression)) |
| { |
| throw new WicketRuntimeException("Empty expression setting value: " + value + |
| " on object: " + object); |
| } |
| if (object == null) |
| { |
| throw new WicketRuntimeException( |
| "Attempted to set property value on a null object. Property expression: " + |
| expression + " Value: " + value); |
| } |
| |
| ObjectWithGetAndSet objectWithGetAndSet = getObjectWithGetAndSet(expression, object, CREATE_NEW_VALUE); |
| if (objectWithGetAndSet == null) |
| { |
| throw new WicketRuntimeException("Null object returned for expression: " + expression + |
| " for setting value: " + value + " on: " + object); |
| } |
| objectWithGetAndSet.setValue(value, converter == null ? new PropertyResolverConverter(Application.get() |
| .getConverterLocator(), Session.get().getLocale()) : converter); |
| } |
| |
| /** |
| * @param expression |
| * @param object |
| * @return class of the target property object |
| * @throws WicketRuntimeException if the cannot be resolved |
| */ |
| public static Class<?> getPropertyClass(final String expression, final Object object) |
| { |
| ObjectWithGetAndSet objectWithGetAndSet = getObjectWithGetAndSet(expression, object, RESOLVE_CLASS); |
| if (objectWithGetAndSet == null) |
| { |
| throw new WicketRuntimeException("Null object returned for expression: " + expression + |
| " for getting the target class of: " + object); |
| } |
| return objectWithGetAndSet.getTargetClass(); |
| } |
| |
| /** |
| * @param <T> |
| * @param expression |
| * @param clz |
| * @return class of the target Class property expression |
| * @throws WicketRuntimeException if class cannot be resolved |
| */ |
| @SuppressWarnings("unchecked") |
| public static <T> Class<T> getPropertyClass(final String expression, final Class<?> clz) |
| { |
| ObjectWithGetAndSet objectWithGetAndSet = getObjectWithGetAndSet(expression, null, RESOLVE_CLASS, clz); |
| if (objectWithGetAndSet == null) |
| { |
| throw new WicketRuntimeException("No Class returned for expression: " + expression + |
| " for getting the target class of: " + clz); |
| } |
| return (Class<T>)objectWithGetAndSet.getTargetClass(); |
| } |
| |
| /** |
| * @param expression |
| * @param object |
| * @return Field for the property expression |
| * @throws WicketRuntimeException if there is no such field |
| */ |
| public static Field getPropertyField(final String expression, final Object object) |
| { |
| ObjectWithGetAndSet objectWithGetAndSet = getObjectWithGetAndSet(expression, object, RESOLVE_CLASS); |
| if (objectWithGetAndSet == null) |
| { |
| throw new WicketRuntimeException("Null object returned for expression: " + expression + |
| " for getting the target class of: " + object); |
| } |
| return objectWithGetAndSet.getField(); |
| } |
| |
| /** |
| * @param expression |
| * @param object |
| * @return Getter method for the property expression |
| * @throws WicketRuntimeException if there is no getter method |
| */ |
| public static Method getPropertyGetter(final String expression, final Object object) |
| { |
| ObjectWithGetAndSet objectWithGetAndSet = getObjectWithGetAndSet(expression, object, RESOLVE_CLASS); |
| if (objectWithGetAndSet == null) |
| { |
| throw new WicketRuntimeException("Null object returned for expression: " + expression + |
| " for getting the target class of: " + object); |
| } |
| return objectWithGetAndSet.getGetter(); |
| } |
| |
| /** |
| * @param expression |
| * @param object |
| * @return Setter method for the property expression |
| * @throws WicketRuntimeException if there is no setter method |
| */ |
| public static Method getPropertySetter(final String expression, final Object object) |
| { |
| ObjectWithGetAndSet objectWithGetAndSet = getObjectWithGetAndSet(expression, object, RESOLVE_CLASS); |
| if (objectWithGetAndSet == null) |
| { |
| throw new WicketRuntimeException("Null object returned for expression: " + expression + |
| " for getting the target class of: " + object); |
| } |
| return objectWithGetAndSet.getSetter(); |
| } |
| |
| /** |
| * Just delegating the call to the original getObjectAndGetSetter passing the object type as |
| * parameter. |
| * |
| * @param expression |
| * @param object |
| * @param tryToCreateNull |
| * @return {@link ObjectWithGetAndSet} |
| */ |
| private static ObjectWithGetAndSet getObjectWithGetAndSet(final String expression, |
| final Object object, int tryToCreateNull) |
| { |
| return getObjectWithGetAndSet(expression, object, tryToCreateNull, object.getClass()); |
| } |
| |
| /** |
| * Receives the class parameter also, since this method can resolve the type for some |
| * expression, only knowing the target class |
| * |
| * @param expression |
| * @param object |
| * @param tryToCreateNull |
| * @param clz |
| * @return {@link ObjectWithGetAndSet} |
| */ |
| private static ObjectWithGetAndSet getObjectWithGetAndSet(final String expression, final Object object, final int tryToCreateNull, Class<?> clz) |
| { |
| String expressionBracketsSeperated = Strings.replaceAll(expression, "[", ".[").toString(); |
| int index = getNextDotIndex(expressionBracketsSeperated, 0); |
| while (index == 0 && expressionBracketsSeperated.startsWith(".")) |
| { |
| // eat dots at the beginning of the expression since they will confuse |
| // later steps |
| expressionBracketsSeperated = expressionBracketsSeperated.substring(1); |
| index = getNextDotIndex(expressionBracketsSeperated, 0); |
| } |
| int lastIndex = 0; |
| Object value = object; |
| String exp = expressionBracketsSeperated; |
| while (index != -1) |
| { |
| exp = expressionBracketsSeperated.substring(lastIndex, index); |
| if (exp.length() == 0) |
| { |
| exp = expressionBracketsSeperated.substring(index + 1); |
| break; |
| } |
| |
| IGetAndSet getAndSet = null; |
| try |
| { |
| getAndSet = getGetAndSet(exp, clz); |
| } |
| catch (WicketRuntimeException ex) |
| { |
| // expression by itself can't be found. try combined with the following |
| // expression (e.g. for a indexed property); |
| int temp = getNextDotIndex(expressionBracketsSeperated, index + 1); |
| if (temp == -1) |
| { |
| exp = expressionBracketsSeperated.substring(lastIndex); |
| break; |
| } else { |
| index = temp; |
| continue; |
| } |
| } |
| Object nextValue = null; |
| if (value != null) |
| { |
| nextValue = getAndSet.getValue(value); |
| } |
| if (nextValue == null) |
| { |
| if (tryToCreateNull == CREATE_NEW_VALUE) |
| { |
| nextValue = getAndSet.newValue(value); |
| if (nextValue == null) |
| { |
| return null; |
| } |
| } |
| else if (tryToCreateNull == RESOLVE_CLASS) |
| { |
| clz = getAndSet.getTargetClass(); |
| } |
| else |
| { |
| return null; |
| } |
| } |
| value = nextValue; |
| if (value != null) |
| { |
| // value can be null if we are in the RESOLVE_CLASS |
| clz = value.getClass(); |
| } |
| |
| lastIndex = index + 1; |
| index = getNextDotIndex(expressionBracketsSeperated, lastIndex); |
| if (index == -1) |
| { |
| exp = expressionBracketsSeperated.substring(lastIndex); |
| break; |
| } |
| } |
| IGetAndSet getAndSet = getGetAndSet(exp, clz); |
| return new ObjectWithGetAndSet(getAndSet, value); |
| } |
| |
| /** |
| * |
| * @param expression |
| * @param start |
| * @return next dot index |
| */ |
| private static int getNextDotIndex(final String expression, final int start) |
| { |
| boolean insideBracket = false; |
| for (int i = start; i < expression.length(); i++) |
| { |
| char ch = expression.charAt(i); |
| if (ch == '.' && !insideBracket) |
| { |
| return i; |
| } |
| else if (ch == '[') |
| { |
| insideBracket = true; |
| } |
| else if (ch == ']') |
| { |
| insideBracket = false; |
| } |
| } |
| return -1; |
| } |
| |
| private static IGetAndSet getGetAndSet(String exp, final Class<?> clz) |
| { |
| IPropertyLocator locator = getLocator(); |
| |
| IGetAndSet getAndSet = locator.get(clz, exp); |
| if (getAndSet == null) { |
| throw new WicketRuntimeException( |
| "Property could not be resolved for class: " + clz + " expression: " + exp); |
| } |
| |
| return getAndSet; |
| } |
| |
| /** |
| * Utility class: instantiation not allowed. |
| */ |
| private PropertyResolver() |
| { |
| } |
| |
| /** |
| * @author jcompagner |
| * |
| */ |
| private final static class ObjectWithGetAndSet |
| { |
| private final IGetAndSet getAndSet; |
| private final Object value; |
| |
| /** |
| * @param getAndSet |
| * @param value |
| */ |
| public ObjectWithGetAndSet(IGetAndSet getAndSet, Object value) |
| { |
| this.getAndSet = getAndSet; |
| this.value = value; |
| } |
| |
| /** |
| * @param value |
| * @param converter |
| */ |
| public void setValue(Object value, PropertyResolverConverter converter) |
| { |
| getAndSet.setValue(this.value, value, converter); |
| } |
| |
| /** |
| * @return The value |
| */ |
| public Object getValue() |
| { |
| return getAndSet.getValue(value); |
| } |
| |
| /** |
| * @return class of property value |
| */ |
| public Class<?> getTargetClass() |
| { |
| return getAndSet.getTargetClass(); |
| } |
| |
| /** |
| * @return Field or null if no field exists for expression |
| */ |
| public Field getField() |
| { |
| return getAndSet.getField(); |
| } |
| |
| /** |
| * @return Getter method or null if no getter exists for expression |
| */ |
| public Method getGetter() |
| { |
| return getAndSet.getGetter(); |
| } |
| |
| /** |
| * @return Setter method or null if no setter exists for expression |
| */ |
| public Method getSetter() |
| { |
| return getAndSet.getSetter(); |
| } |
| } |
| |
| /** |
| * @author jcompagner |
| */ |
| public interface IGetAndSet |
| { |
| /** |
| * @param object |
| * The object where the value must be taken from. |
| * |
| * @return The value of this property |
| */ |
| public Object getValue(final Object object); |
| |
| /** |
| * @return The target class of the object that as to be set. |
| */ |
| public Class<?> getTargetClass(); |
| |
| /** |
| * @param object |
| * The object where the new value must be set on. |
| * |
| * @return The new value for the property that is set back on that object. |
| */ |
| public Object newValue(Object object); |
| |
| /** |
| * @param object |
| * @param value |
| * @param converter |
| */ |
| public void setValue(final Object object, final Object value, |
| PropertyResolverConverter converter); |
| |
| /** |
| * @return Field or null if there is no field |
| */ |
| public Field getField(); |
| |
| /** |
| * @return Getter method or null if there is no getter |
| */ |
| public Method getGetter(); |
| |
| /** |
| * @return Setter of null if there is no setter |
| */ |
| public Method getSetter(); |
| } |
| |
| public static abstract class AbstractGetAndSet implements IGetAndSet |
| { |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public Field getField() |
| { |
| return null; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public Method getGetter() |
| { |
| return null; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public Method getSetter() |
| { |
| return null; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public Class<?> getTargetClass() |
| { |
| return null; |
| } |
| } |
| |
| private static final class MapGetAndSet extends AbstractGetAndSet |
| { |
| private final String key; |
| |
| MapGetAndSet(String key) |
| { |
| this.key = key; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public Object getValue(final Object object) |
| { |
| return ((Map<?, ?>)object).get(key); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| @SuppressWarnings("unchecked") |
| public void setValue(final Object object, final Object value, |
| final PropertyResolverConverter converter) |
| { |
| ((Map<String, Object>)object).put(key, value); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public Object newValue(final Object object) |
| { |
| // Map can't make a newValue or should it look what is more in the |
| // map and try to make one of the class if finds? |
| return null; |
| } |
| } |
| |
| private static final class ListGetAndSet extends AbstractGetAndSet |
| { |
| final private int index; |
| |
| ListGetAndSet(int index) |
| { |
| this.index = index; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public Object getValue(final Object object) |
| { |
| if (((List<?>)object).size() <= index) |
| { |
| return null; |
| } |
| return ((List<?>)object).get(index); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| @SuppressWarnings("unchecked") |
| public void setValue(final Object object, final Object value, |
| final PropertyResolverConverter converter) |
| { |
| List<Object> lst = (List<Object>)object; |
| |
| if (lst.size() > index) |
| { |
| lst.set(index, value); |
| } |
| else if (lst.size() == index) |
| { |
| lst.add(value); |
| } |
| else |
| { |
| while (lst.size() < index) |
| { |
| lst.add(null); |
| } |
| lst.add(value); |
| } |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public Object newValue(Object object) |
| { |
| // List can't make a newValue or should it look what is more in the |
| // list and try to make one of the class if finds? |
| return null; |
| } |
| } |
| |
| private static final class ArrayGetAndSet extends AbstractGetAndSet |
| { |
| private final int index; |
| private final Class<?> clzComponentType; |
| |
| ArrayGetAndSet(Class<?> clzComponentType, int index) |
| { |
| this.clzComponentType = clzComponentType; |
| this.index = index; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public Object getValue(Object object) |
| { |
| if (Array.getLength(object) > index) |
| { |
| return Array.get(object, index); |
| } |
| return null; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void setValue(Object object, Object value, PropertyResolverConverter converter) |
| { |
| value = converter.convert(value, clzComponentType); |
| Array.set(object, index, value); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public Object newValue(Object object) |
| { |
| Object value = null; |
| try |
| { |
| value = clzComponentType.newInstance(); |
| Array.set(object, index, value); |
| } |
| catch (Exception e) |
| { |
| log.warn("Cannot set new value " + value + " at index " + index + |
| " for array holding elements of class " + clzComponentType, e); |
| } |
| return value; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public Class<?> getTargetClass() |
| { |
| return clzComponentType; |
| } |
| } |
| |
| private static final class ArrayLengthGetAndSet extends AbstractGetAndSet |
| { |
| ArrayLengthGetAndSet() |
| { |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public Object getValue(final Object object) |
| { |
| return Array.getLength(object); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void setValue(final Object object, final Object value, |
| final PropertyResolverConverter converter) |
| { |
| throw new WicketRuntimeException("You can't set the length on an array:" + object); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public Object newValue(final Object object) |
| { |
| throw new WicketRuntimeException("Can't get a new value from a length of an array: " + |
| object); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public Class<?> getTargetClass() |
| { |
| return int.class; |
| } |
| } |
| |
| private static final class IndexedPropertyGetAndSet extends AbstractGetAndSet |
| { |
| final private Integer index; |
| final private Method getMethod; |
| private Method setMethod; |
| |
| IndexedPropertyGetAndSet(final Method method, final int index) |
| { |
| this.index = index; |
| getMethod = method; |
| getMethod.setAccessible(true); |
| } |
| |
| private static Method findSetter(final Method getMethod, final Class<?> clz) |
| { |
| String name = getMethod.getName(); |
| name = SET + name.substring(3); |
| try |
| { |
| return clz.getMethod(name, new Class[] { int.class, getMethod.getReturnType() }); |
| } |
| catch (Exception e) |
| { |
| log.debug("Can't find setter method corresponding to " + getMethod); |
| } |
| return null; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public Object getValue(Object object) |
| { |
| Object ret; |
| try |
| { |
| ret = getMethod.invoke(object, index); |
| } |
| catch (InvocationTargetException ex) |
| { |
| throw new WicketRuntimeException("Error calling index property method: " + |
| getMethod + " on object: " + object, ex.getCause()); |
| } |
| catch (Exception ex) |
| { |
| throw new WicketRuntimeException("Error calling index property method: " + |
| getMethod + " on object: " + object, ex); |
| } |
| return ret; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void setValue(final Object object, final Object value, |
| final PropertyResolverConverter converter) |
| { |
| if (setMethod == null) |
| { |
| setMethod = findSetter(getMethod, object.getClass()); |
| } |
| if (setMethod != null) |
| { |
| setMethod.setAccessible(true); |
| Object converted = converter.convert(value, getMethod.getReturnType()); |
| if (converted == null && value != null) |
| { |
| throw new ConversionException("Can't convert value: " + value + " to class: " + |
| getMethod.getReturnType() + " for setting it on " + object); |
| } |
| try |
| { |
| setMethod.invoke(object, index, converted); |
| } |
| catch (InvocationTargetException ex) |
| { |
| throw new WicketRuntimeException("Error index property calling method: " + |
| setMethod + " on object: " + object, ex.getCause()); |
| } |
| catch (Exception ex) |
| { |
| throw new WicketRuntimeException("Error index property calling method: " + |
| setMethod + " on object: " + object, ex); |
| } |
| } |
| else |
| { |
| throw new WicketRuntimeException("No set method defined for value: " + value + |
| " on object: " + object); |
| } |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public Class<?> getTargetClass() |
| { |
| return getMethod.getReturnType(); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public Object newValue(Object object) |
| { |
| if (setMethod == null) |
| { |
| setMethod = findSetter(getMethod, object.getClass()); |
| } |
| |
| if (setMethod == null) |
| { |
| log.warn("Null setMethod"); |
| return null; |
| } |
| |
| Class<?> clz = getMethod.getReturnType(); |
| Object value = null; |
| try |
| { |
| value = clz.newInstance(); |
| setMethod.invoke(object, index, value); |
| } |
| catch (Exception e) |
| { |
| log.warn("Cannot set new value " + value + " at index " + index, e); |
| } |
| return value; |
| } |
| } |
| |
| private static final class MethodGetAndSet extends AbstractGetAndSet |
| { |
| private final Method getMethod; |
| private final Method setMethod; |
| private final Field field; |
| |
| MethodGetAndSet(Method getMethod, Method setMethod, Field field) |
| { |
| this.getMethod = getMethod; |
| this.getMethod.setAccessible(true); |
| this.field = field; |
| this.setMethod = setMethod; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public final Object getValue(final Object object) |
| { |
| Object ret; |
| try |
| { |
| ret = getMethod.invoke(object, (Object[])null); |
| } |
| catch (InvocationTargetException ex) |
| { |
| throw new WicketRuntimeException("Error calling method: " + getMethod + |
| " on object: " + object, ex.getCause()); |
| } |
| catch (Exception ex) |
| { |
| throw new WicketRuntimeException("Error calling method: " + getMethod + |
| " on object: " + object, ex); |
| } |
| return ret; |
| } |
| |
| /** |
| * @param object |
| * @param value |
| * @param converter |
| */ |
| @Override |
| public final void setValue(final Object object, final Object value, |
| PropertyResolverConverter converter) |
| { |
| Class<?> type = null; |
| if (setMethod != null) |
| { |
| // getMethod is always there and if the value will be set through a setMethod then |
| // the getMethod return type will be its type. Else we have to look at the |
| // parameters if the setter but getting the return type is quicker |
| type = getMethod.getReturnType(); |
| } |
| else if (field != null) |
| { |
| type = field.getType(); |
| } |
| |
| Object converted = null; |
| if (type != null) |
| { |
| converted = converter.convert(value, type); |
| if (converted == null) |
| { |
| if (value != null) |
| { |
| throw new ConversionException("Method [" + getMethod + |
| "]. Can't convert value: " + value + " to class: " + |
| getMethod.getReturnType() + " for setting it on " + object); |
| } |
| else if (getMethod.getReturnType().isPrimitive()) |
| { |
| throw new ConversionException("Method [" + getMethod + |
| "]. Can't convert null value to a primitive class: " + |
| getMethod.getReturnType() + " for setting it on " + object); |
| } |
| } |
| } |
| |
| if (setMethod != null) |
| { |
| try |
| { |
| setMethod.invoke(object, converted); |
| } |
| catch (InvocationTargetException ex) |
| { |
| throw new WicketRuntimeException("Error calling method: " + setMethod + |
| " on object: " + object, ex.getCause()); |
| } |
| catch (Exception ex) |
| { |
| throw new WicketRuntimeException("Error calling method: " + setMethod + |
| " on object: " + object, ex); |
| } |
| } |
| else if (field != null) |
| { |
| try |
| { |
| field.set(object, converted); |
| } |
| catch (Exception ex) |
| { |
| throw new WicketRuntimeException("Error setting field: " + field + |
| " on object: " + object, ex); |
| } |
| } |
| else |
| { |
| throw new WicketRuntimeException("no set method defined for value: " + value + |
| " on object: " + object + " while respective getMethod being " + |
| getMethod.getName()); |
| } |
| } |
| |
| private static Method findSetter(Method getMethod, Class<?> clz) |
| { |
| String name = getMethod.getName(); |
| if (name.startsWith(GET)) |
| { |
| name = SET + name.substring(3); |
| } |
| else |
| { |
| name = SET + name.substring(2); |
| } |
| try |
| { |
| Method method = clz.getMethod(name, new Class[] { getMethod.getReturnType() }); |
| if (method != null) |
| { |
| method.setAccessible(true); |
| } |
| return method; |
| } |
| catch (NoSuchMethodException e) |
| { |
| Method[] methods = clz.getMethods(); |
| for (Method method : methods) |
| { |
| if (method.getName().equals(name)) |
| { |
| Class<?>[] parameterTypes = method.getParameterTypes(); |
| if (parameterTypes.length == 1) |
| { |
| if (parameterTypes[0].isAssignableFrom(getMethod.getReturnType())) |
| { |
| return method; |
| } |
| } |
| } |
| } |
| log.debug("Cannot find setter corresponding to " + getMethod); |
| } |
| catch (Exception e) |
| { |
| log.debug("Cannot find setter corresponding to " + getMethod); |
| } |
| return null; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public Object newValue(Object object) |
| { |
| if (setMethod == null) |
| { |
| log.warn("Null setMethod"); |
| return null; |
| } |
| |
| Class<?> clz = getMethod.getReturnType(); |
| Object value = null; |
| try |
| { |
| value = clz.newInstance(); |
| setMethod.invoke(object, value); |
| } |
| catch (Exception e) |
| { |
| log.warn("Cannot set new value " + value, e); |
| } |
| return value; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public Class<?> getTargetClass() |
| { |
| return getMethod.getReturnType(); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public Method getGetter() |
| { |
| return getMethod; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public Method getSetter() |
| { |
| return setMethod; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public Field getField() |
| { |
| return field; |
| } |
| } |
| |
| /** |
| * @author jcompagner |
| */ |
| private static class FieldGetAndSet extends AbstractGetAndSet |
| { |
| private final Field field; |
| |
| /** |
| * Construct. |
| * |
| * @param field |
| */ |
| public FieldGetAndSet(final Field field) |
| { |
| super(); |
| this.field = field; |
| this.field.setAccessible(true); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public Object getValue(final Object object) |
| { |
| try |
| { |
| return field.get(object); |
| } |
| catch (Exception ex) |
| { |
| throw new WicketRuntimeException("Error getting field value of field " + field + |
| " from object " + object, ex); |
| } |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public Object newValue(final Object object) |
| { |
| Class<?> clz = field.getType(); |
| Object value = null; |
| try |
| { |
| value = clz.newInstance(); |
| field.set(object, value); |
| } |
| catch (Exception e) |
| { |
| log.warn("Cannot set field " + field + " to " + value, e); |
| } |
| return value; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void setValue(final Object object, Object value, |
| final PropertyResolverConverter converter) |
| { |
| value = converter.convert(value, field.getType()); |
| try |
| { |
| field.set(object, value); |
| } |
| catch (Exception ex) |
| { |
| throw new WicketRuntimeException("Error setting field value of field " + field + |
| " on object " + object + ", value " + value, ex); |
| } |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public Class<?> getTargetClass() |
| { |
| return field.getType(); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public Field getField() |
| { |
| return field; |
| } |
| } |
| |
| /** |
| * Clean up cache for this app. |
| * |
| * @param application |
| */ |
| public static void destroy(Application application) |
| { |
| applicationToLocators.remove(application); |
| } |
| |
| /** |
| * Sets the {@link IPropertyLocator} for the given application. |
| * |
| * If the Application is null then it will be the default if no application is found. So if you |
| * want to be sure that your {@link IPropertyLocator} is handled in all situations then call this |
| * method twice with your implementations. One time for the application and the second time with |
| * null. |
| * |
| * @param application |
| * to use or null if the default must be set. |
| * @param classCache |
| */ |
| @Deprecated |
| public static void setClassCache(final Application application, final IClassCache classCache) |
| { |
| setLocator(application, new IPropertyLocator() { |
| |
| private DefaultGetAndSetLocator locator = new DefaultGetAndSetLocator(); |
| |
| @Override |
| public IGetAndSet get(Class<?> clz, String name) { |
| Map<String, IGetAndSet> map = classCache.get(clz); |
| if (map == null) { |
| map = new ConcurrentHashMap<String, IGetAndSet>(8); |
| classCache.put(clz, map); |
| } |
| |
| IGetAndSet getAndSetter = map.get(name); |
| if (getAndSetter == null) { |
| getAndSetter = locator.get(clz, name); |
| map.put(name, getAndSetter); |
| } |
| |
| return getAndSetter; |
| } |
| }); |
| } |
| |
| /** |
| * Get the current {@link IPropertyLocator}. |
| * |
| * @return locator for the current {@link Application} or a general one if no current application is present |
| * @see Application#get() |
| */ |
| public static IPropertyLocator getLocator() |
| { |
| Object key; |
| if (Application.exists()) |
| { |
| key = Application.get(); |
| } |
| else |
| { |
| key = PropertyResolver.class; |
| } |
| IPropertyLocator result = applicationToLocators.get(key); |
| if (result == null) |
| { |
| IPropertyLocator tmpResult = applicationToLocators.putIfAbsent(key, result = new CachingGetAndSetLocator(new DefaultGetAndSetLocator())); |
| if (tmpResult != null) |
| { |
| result = tmpResult; |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * Set a locator for the given application. |
| * |
| * @param application application, may be {@code null} |
| * @param locator locator |
| */ |
| public static void setLocator(final Application application, final IPropertyLocator locator) |
| { |
| if (application == null) |
| { |
| applicationToLocators.put(PropertyResolver.class, locator); |
| } |
| else |
| { |
| applicationToLocators.put(application, locator); |
| } |
| } |
| |
| /** |
| * Specify an {@link IPropertyLocator} instead. |
| */ |
| @Deprecated |
| public static interface IClassCache |
| { |
| /** |
| * Put the class into the cache, or if that class shouldn't be cached do nothing. |
| * |
| * @param clz |
| * @param values |
| */ |
| void put(Class<?> clz, Map<String, IGetAndSet> values); |
| |
| /** |
| * Returns the class map from the cache. |
| * |
| * @param clz |
| * @return the map of the given class |
| */ |
| Map<String, IGetAndSet> get(Class<?> clz); |
| } |
| |
| /** |
| * A locator of properties. |
| * |
| * @see https://issues.apache.org/jira/browse/WICKET-5623 |
| */ |
| public static interface IPropertyLocator |
| { |
| /** |
| * Get {@link IGetAndSet} for a property. |
| * |
| * @param clz owning class |
| * @param exp identifying the property |
| * @return getAndSet or {@code null} if non located |
| */ |
| IGetAndSet get(Class<?> clz, String exp); |
| } |
| |
| public static class CachingGetAndSetLocator implements IPropertyLocator |
| { |
| private final ConcurrentHashMap<String, IGetAndSet> map = Generics.newConcurrentHashMap(16); |
| |
| /** |
| * Special token to put into the cache representing no located {@link IGetAndSet}. |
| */ |
| private IGetAndSet NONE = new AbstractGetAndSet() { |
| |
| @Override |
| public Object getValue(Object object) { |
| return null; |
| } |
| |
| @Override |
| public Object newValue(Object object) { |
| return null; |
| } |
| |
| @Override |
| public void setValue(Object object, Object value, PropertyResolverConverter converter) { |
| } |
| }; |
| |
| private IPropertyLocator locator; |
| |
| public CachingGetAndSetLocator(IPropertyLocator locator) { |
| this.locator = locator; |
| } |
| |
| @Override |
| public IGetAndSet get(Class<?> clz, String exp) { |
| String key = clz.getName() + "#" + exp; |
| |
| IGetAndSet located = map.get(key); |
| if (located == null) { |
| located = locator.get(clz, exp); |
| if (located == null) { |
| located = NONE; |
| } |
| map.put(key, located); |
| } |
| |
| if (located == NONE) { |
| located = null; |
| } |
| |
| return located; |
| } |
| } |
| |
| /** |
| * Default implementation supporting <em>Java Beans</em> properties, maps, lists and method invocations. |
| */ |
| public static class DefaultGetAndSetLocator implements IPropertyLocator |
| { |
| @Override |
| public IGetAndSet get(Class<?> clz, String exp) { |
| IGetAndSet getAndSet = null; |
| |
| Method method = null; |
| Field field; |
| if (exp.startsWith("[")) |
| { |
| // if expression begins with [ skip method finding and use it as |
| // a key/index lookup on a map. |
| exp = exp.substring(1, exp.length() - 1); |
| } |
| else if (exp.endsWith("()")) |
| { |
| // if expression ends with (), don't test for setters just skip |
| // directly to method finding. |
| method = findMethod(clz, exp); |
| } |
| else |
| { |
| method = findGetter(clz, exp); |
| } |
| if (method == null) |
| { |
| if (List.class.isAssignableFrom(clz)) |
| { |
| try |
| { |
| int index = Integer.parseInt(exp); |
| getAndSet = new ListGetAndSet(index); |
| } |
| catch (NumberFormatException ex) |
| { |
| // can't parse the exp as an index, maybe the exp was a |
| // method. |
| method = findMethod(clz, exp); |
| if (method != null) |
| { |
| getAndSet = new MethodGetAndSet(method, MethodGetAndSet.findSetter( |
| method, clz), null); |
| } |
| else |
| { |
| field = findField(clz, exp); |
| if (field != null) |
| { |
| getAndSet = new FieldGetAndSet(field); |
| } |
| else |
| { |
| throw new WicketRuntimeException( |
| "The expression '" + |
| exp + |
| "' is neither an index nor is it a method or field for the list " + |
| clz); |
| } |
| } |
| } |
| } |
| else if (Map.class.isAssignableFrom(clz)) |
| { |
| getAndSet = new MapGetAndSet(exp); |
| } |
| else if (clz.isArray()) |
| { |
| try |
| { |
| int index = Integer.parseInt(exp); |
| getAndSet = new ArrayGetAndSet(clz.getComponentType(), index); |
| } |
| catch (NumberFormatException ex) |
| { |
| if (exp.equals("length") || exp.equals("size")) |
| { |
| getAndSet = new ArrayLengthGetAndSet(); |
| } |
| else |
| { |
| throw new WicketRuntimeException("Can't parse the expression '" + exp + |
| "' as an index for an array lookup"); |
| } |
| } |
| } |
| else |
| { |
| field = findField(clz, exp); |
| if (field == null) |
| { |
| method = findMethod(clz, exp); |
| if (method == null) |
| { |
| int index = exp.indexOf('.'); |
| if (index != -1) |
| { |
| String propertyName = exp.substring(0, index); |
| String propertyIndex = exp.substring(index + 1); |
| try |
| { |
| int parsedIndex = Integer.parseInt(propertyIndex); |
| // if so then it could be a getPropertyIndex(int) |
| // and setPropertyIndex(int, object) |
| String name = Character.toUpperCase(propertyName.charAt(0)) + |
| propertyName.substring(1); |
| method = clz.getMethod(GET + name, new Class[] { int.class }); |
| getAndSet = new IndexedPropertyGetAndSet(method, parsedIndex); |
| } |
| catch (Exception e) |
| { |
| throw new WicketRuntimeException( |
| "No get method defined for class: " + clz + |
| " expression: " + propertyName); |
| } |
| } |
| } |
| else |
| { |
| getAndSet = new MethodGetAndSet(method, MethodGetAndSet.findSetter( |
| method, clz), null); |
| } |
| } |
| else |
| { |
| getAndSet = new FieldGetAndSet(field); |
| } |
| } |
| } |
| else |
| { |
| field = findField(clz, exp); |
| getAndSet = new MethodGetAndSet(method, MethodGetAndSet.findSetter(method, clz), |
| field); |
| } |
| |
| return getAndSet; |
| } |
| |
| /** |
| * @param clz |
| * @param expression |
| * @return introspected field |
| */ |
| private Field findField(final Class<?> clz, final String expression) |
| { |
| Field field = null; |
| try |
| { |
| field = clz.getField(expression); |
| } |
| catch (Exception e) |
| { |
| Class<?> tmp = clz; |
| while (tmp != null && tmp != Object.class) |
| { |
| Field[] fields = tmp.getDeclaredFields(); |
| for (Field aField : fields) |
| { |
| if (aField.getName().equals(expression)) |
| { |
| aField.setAccessible(true); |
| return aField; |
| } |
| } |
| tmp = tmp.getSuperclass(); |
| } |
| log.debug("Cannot find field " + clz + "." + expression); |
| } |
| return field; |
| } |
| |
| /** |
| * @param clz |
| * @param expression |
| * @return The method for the expression null if not found |
| */ |
| private Method findGetter(final Class<?> clz, final String expression) |
| { |
| String name = Character.toUpperCase(expression.charAt(0)) + expression.substring(1); |
| Method method = null; |
| try |
| { |
| method = clz.getMethod(GET + name, (Class[])null); |
| } |
| catch (Exception ignored) |
| { |
| } |
| if (method == null) |
| { |
| try |
| { |
| method = clz.getMethod(IS + name, (Class[])null); |
| } |
| catch (Exception e) |
| { |
| log.debug("Cannot find getter " + clz + "." + expression); |
| } |
| } |
| return method; |
| } |
| |
| private Method findMethod(final Class<?> clz, String expression) |
| { |
| if (expression.endsWith("()")) |
| { |
| expression = expression.substring(0, expression.length() - 2); |
| } |
| Method method = null; |
| try |
| { |
| method = clz.getMethod(expression, (Class[])null); |
| } |
| catch (Exception e) |
| { |
| log.debug("Cannot find method " + clz + "." + expression); |
| } |
| return method; |
| } |
| } |
| } |