/** * Parse the given property name into the corresponding property name tokens. * * @param propertyName the property name to parse * @return representation of the parsed property tokens */ private PropertyTokenHolder getPropertyNameTokens(String propertyName) { PropertyTokenHolder tokens = new PropertyTokenHolder(); String actualName = null; List keys = new ArrayList(2); int searchIndex = 0; while (searchIndex != -1) { int keyStart = propertyName.indexOf(PROPERTY_KEY_PREFIX, searchIndex); searchIndex = -1; if (keyStart != -1) { int keyEnd = propertyName.indexOf(PROPERTY_KEY_SUFFIX, keyStart + PROPERTY_KEY_PREFIX.length()); if (keyEnd != -1) { if (actualName == null) { actualName = propertyName.substring(0, keyStart); } String key = propertyName.substring(keyStart + PROPERTY_KEY_PREFIX.length(), keyEnd); if ((key.startsWith("'") && key.endsWith("'")) || (key.startsWith("\"") && key.endsWith("\""))) { key = key.substring(1, key.length() - 1); } keys.add(key); searchIndex = keyEnd + PROPERTY_KEY_SUFFIX.length(); } } } tokens.actualName = (actualName != null ? actualName : propertyName); tokens.canonicalName = tokens.actualName; if (!keys.isEmpty()) { tokens.canonicalName += PROPERTY_KEY_PREFIX + StringUtils.collectionToDelimitedString( keys, PROPERTY_KEY_SUFFIX + PROPERTY_KEY_PREFIX) + PROPERTY_KEY_SUFFIX; tokens.keys = (String[]) keys.toArray(new String[keys.size()]); } return tokens; }
private void setPropertyValue(PropertyTokenHolder tokens, Object newValue) throws BeansException { String propertyName = tokens.canonicalName; if (tokens.keys != null) { // apply indexes and map keys: fetch value for all keys but the last one PropertyTokenHolder getterTokens = new PropertyTokenHolder(); getterTokens.canonicalName = tokens.canonicalName; getterTokens.actualName = tokens.actualName; getterTokens.keys = new String[tokens.keys.length - 1]; System.arraycopy(tokens.keys, 0, getterTokens.keys, 0, tokens.keys.length - 1); Object propValue = null; try { propValue = getPropertyValue(getterTokens); } catch (NotReadablePropertyException ex) { throw new NotWritablePropertyException( getRootClass(), this.nestedPath + propertyName, "Cannot access indexed value in property referenced " + "in indexed property path '" + propertyName + "'", ex); } // set value for last key String key = tokens.keys[tokens.keys.length - 1]; if (propValue == null) { throw new NullValueInNestedPathException( getRootClass(), this.nestedPath + propertyName, "Cannot access indexed value in property referenced " + "in indexed property path '" + propertyName + "': returned null"); } else if (propValue.getClass().isArray()) { Class requiredType = propValue.getClass().getComponentType(); int arrayIndex = Integer.parseInt(key); Object oldValue = null; try { if (this.extractOldValueForEditor) { oldValue = Array.get(propValue, arrayIndex); } Object convertedValue = doTypeConversionIfNecessary( propertyName, propertyName, oldValue, newValue, requiredType); Array.set(propValue, Integer.parseInt(key), convertedValue); } catch (IllegalArgumentException ex) { PropertyChangeEvent pce = new PropertyChangeEvent( this.rootObject, this.nestedPath + propertyName, oldValue, newValue); throw new TypeMismatchException(pce, requiredType, ex); } catch (IndexOutOfBoundsException ex) { throw new InvalidPropertyException( getRootClass(), this.nestedPath + propertyName, "Invalid array index in property path '" + propertyName + "'", ex); } } else if (propValue instanceof List) { List list = (List) propValue; int index = Integer.parseInt(key); Object oldValue = null; if (this.extractOldValueForEditor && index < list.size()) { oldValue = list.get(index); } Object convertedValue = doTypeConversionIfNecessary(propertyName, propertyName, oldValue, newValue, null); if (index < list.size()) { list.set(index, convertedValue); } else if (index >= list.size()) { for (int i = list.size(); i < index; i++) { try { list.add(null); } catch (NullPointerException ex) { throw new InvalidPropertyException( getRootClass(), this.nestedPath + propertyName, "Cannot set element with index " + index + " in List of size " + list.size() + ", accessed using property path '" + propertyName + "': List does not support filling up gaps with null elements"); } } list.add(convertedValue); } } else if (propValue instanceof Map) { Map map = (Map) propValue; Object oldValue = null; if (this.extractOldValueForEditor) { oldValue = map.get(key); } Object convertedValue = doTypeConversionIfNecessary(propertyName, propertyName, oldValue, newValue, null); map.put(key, convertedValue); } else { throw new InvalidPropertyException( getRootClass(), this.nestedPath + propertyName, "Property referenced in indexed property path '" + propertyName + "' is neither an array nor a List nor a Map; returned value was [" + newValue + "]"); } } else { PropertyDescriptor pd = getPropertyDescriptorInternal(propertyName); if (pd == null || pd.getWriteMethod() == null) { throw new NotWritablePropertyException(getRootClass(), this.nestedPath + propertyName); } Method readMethod = pd.getReadMethod(); Method writeMethod = pd.getWriteMethod(); Object oldValue = null; if (this.extractOldValueForEditor && readMethod != null) { try { oldValue = readMethod.invoke(this.object, new Object[0]); } catch (Exception ex) { logger.debug( "Could not read previous value of property '" + this.nestedPath + propertyName, ex); } } try { Object convertedValue = doTypeConversionIfNecessary( propertyName, propertyName, oldValue, newValue, pd.getPropertyType()); if (pd.getPropertyType().isPrimitive() && (convertedValue == null || "".equals(convertedValue))) { throw new IllegalArgumentException( "Invalid value [" + newValue + "] for property '" + pd.getName() + "' of primitive type [" + pd.getPropertyType() + "]"); } if (logger.isDebugEnabled()) { logger.debug( "About to invoke write method [" + writeMethod + "] on object of class [" + this.object.getClass().getName() + "]"); } writeMethod.invoke(this.object, new Object[] {convertedValue}); if (logger.isDebugEnabled()) { logger.debug( "Invoked write method [" + writeMethod + "] with value of type [" + pd.getPropertyType().getName() + "]"); } } catch (InvocationTargetException ex) { PropertyChangeEvent propertyChangeEvent = new PropertyChangeEvent( this.rootObject, this.nestedPath + propertyName, oldValue, newValue); if (ex.getTargetException() instanceof ClassCastException) { throw new TypeMismatchException( propertyChangeEvent, pd.getPropertyType(), ex.getTargetException()); } else { throw new MethodInvocationException(propertyChangeEvent, ex.getTargetException()); } } catch (IllegalArgumentException ex) { PropertyChangeEvent pce = new PropertyChangeEvent( this.rootObject, this.nestedPath + propertyName, oldValue, newValue); throw new TypeMismatchException(pce, pd.getPropertyType(), ex); } catch (IllegalAccessException ex) { PropertyChangeEvent pce = new PropertyChangeEvent( this.rootObject, this.nestedPath + propertyName, oldValue, newValue); throw new MethodInvocationException(pce, ex); } } }
@SuppressWarnings("unchecked") private void setPropertyValue(PropertyTokenHolder tokens, PropertyValue pv) throws BeansException { String propertyName = tokens.canonicalName; String actualName = tokens.actualName; if (tokens.keys != null) { // Apply indexes and map keys: fetch value for all keys but the last one. PropertyTokenHolder getterTokens = new PropertyTokenHolder(); getterTokens.canonicalName = tokens.canonicalName; getterTokens.actualName = tokens.actualName; getterTokens.keys = new String[tokens.keys.length - 1]; System.arraycopy(tokens.keys, 0, getterTokens.keys, 0, tokens.keys.length - 1); Object propValue; try { propValue = getPropertyValue(getterTokens); } catch (NotReadablePropertyException ex) { throw new NotWritablePropertyException( getRootClass(), this.nestedPath + propertyName, "Cannot access indexed value in property referenced " + "in indexed property path '" + propertyName + "'", ex); } // Set value for last key. String key = tokens.keys[tokens.keys.length - 1]; if (propValue == null) { // null map value case if (this.autoGrowNestedPaths) { // TODO: cleanup, this is pretty hacky int lastKeyIndex = tokens.canonicalName.lastIndexOf('['); getterTokens.canonicalName = tokens.canonicalName.substring(0, lastKeyIndex); propValue = setDefaultValue(getterTokens); } else { throw new NullValueInNestedPathException( getRootClass(), this.nestedPath + propertyName, "Cannot access indexed value in property referenced " + "in indexed property path '" + propertyName + "': returned null"); } } if (propValue.getClass().isArray()) { PropertyDescriptor pd = getCachedIntrospectionResults().getPropertyDescriptor(actualName); Class requiredType = propValue.getClass().getComponentType(); int arrayIndex = Integer.parseInt(key); Object oldValue = null; try { if (isExtractOldValueForEditor() && arrayIndex < Array.getLength(propValue)) { oldValue = Array.get(propValue, arrayIndex); } Object convertedValue = convertIfNecessary( propertyName, oldValue, pv.getValue(), requiredType, TypeDescriptor.nested(property(pd), tokens.keys.length)); Array.set(propValue, arrayIndex, convertedValue); } catch (IndexOutOfBoundsException ex) { throw new InvalidPropertyException( getRootClass(), this.nestedPath + propertyName, "Invalid array index in property path '" + propertyName + "'", ex); } } else if (propValue instanceof List) { PropertyDescriptor pd = getCachedIntrospectionResults().getPropertyDescriptor(actualName); Class requiredType = GenericCollectionTypeResolver.getCollectionReturnType( pd.getReadMethod(), tokens.keys.length); List list = (List) propValue; int index = Integer.parseInt(key); int size = list.size(); Object oldValue = null; if (isExtractOldValueForEditor() && index < size) { oldValue = list.get(index); } Object convertedValue = convertIfNecessary( propertyName, oldValue, pv.getValue(), requiredType, TypeDescriptor.nested(property(pd), tokens.keys.length)); if (index >= size && index < this.autoGrowCollectionLimit) { for (int i = size; i < index; i++) { try { list.add(null); } catch (NullPointerException ex) { throw new InvalidPropertyException( getRootClass(), this.nestedPath + propertyName, "Cannot set element with index " + index + " in List of size " + size + ", accessed using property path '" + propertyName + "': List does not support filling up gaps with null elements"); } } list.add(convertedValue); } else { try { list.set(index, convertedValue); } catch (IndexOutOfBoundsException ex) { throw new InvalidPropertyException( getRootClass(), this.nestedPath + propertyName, "Invalid list index in property path '" + propertyName + "'", ex); } } } else if (propValue instanceof Map) { PropertyDescriptor pd = getCachedIntrospectionResults().getPropertyDescriptor(actualName); Class mapKeyType = GenericCollectionTypeResolver.getMapKeyReturnType( pd.getReadMethod(), tokens.keys.length); Class mapValueType = GenericCollectionTypeResolver.getMapValueReturnType( pd.getReadMethod(), tokens.keys.length); Map map = (Map) propValue; // IMPORTANT: Do not pass full property name in here - property editors // must not kick in for map keys but rather only for map values. TypeDescriptor typeDescriptor = mapKeyType != null ? TypeDescriptor.valueOf(mapKeyType) : TypeDescriptor.valueOf(Object.class); Object convertedMapKey = convertIfNecessary(null, null, key, mapKeyType, typeDescriptor); Object oldValue = null; if (isExtractOldValueForEditor()) { oldValue = map.get(convertedMapKey); } // Pass full property name and old value in here, since we want full // conversion ability for map values. Object convertedMapValue = convertIfNecessary( propertyName, oldValue, pv.getValue(), mapValueType, TypeDescriptor.nested(property(pd), tokens.keys.length)); map.put(convertedMapKey, convertedMapValue); } else { throw new InvalidPropertyException( getRootClass(), this.nestedPath + propertyName, "Property referenced in indexed property path '" + propertyName + "' is neither an array nor a List nor a Map; returned value was [" + pv.getValue() + "]"); } } else { PropertyDescriptor pd = pv.resolvedDescriptor; if (pd == null || !pd.getWriteMethod().getDeclaringClass().isInstance(this.object)) { pd = getCachedIntrospectionResults().getPropertyDescriptor(actualName); if (pd == null || pd.getWriteMethod() == null) { if (pv.isOptional()) { logger.debug( "Ignoring optional value for property '" + actualName + "' - property not found on bean class [" + getRootClass().getName() + "]"); return; } else { PropertyMatches matches = PropertyMatches.forProperty(propertyName, getRootClass()); throw new NotWritablePropertyException( getRootClass(), this.nestedPath + propertyName, matches.buildErrorMessage(), matches.getPossibleMatches()); } } pv.getOriginalPropertyValue().resolvedDescriptor = pd; } Object oldValue = null; try { Object originalValue = pv.getValue(); Object valueToApply = originalValue; if (!Boolean.FALSE.equals(pv.conversionNecessary)) { if (pv.isConverted()) { valueToApply = pv.getConvertedValue(); } else { if (isExtractOldValueForEditor() && pd.getReadMethod() != null) { final Method readMethod = pd.getReadMethod(); if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers()) && !readMethod.isAccessible()) { if (System.getSecurityManager() != null) { AccessController.doPrivileged( new PrivilegedAction<Object>() { public Object run() { readMethod.setAccessible(true); return null; } }); } else { readMethod.setAccessible(true); } } try { if (System.getSecurityManager() != null) { oldValue = AccessController.doPrivileged( new PrivilegedExceptionAction<Object>() { public Object run() throws Exception { return readMethod.invoke(object); } }, acc); } else { oldValue = readMethod.invoke(object); } } catch (Exception ex) { if (ex instanceof PrivilegedActionException) { ex = ((PrivilegedActionException) ex).getException(); } if (logger.isDebugEnabled()) { logger.debug( "Could not read previous value of property '" + this.nestedPath + propertyName + "'", ex); } } } valueToApply = convertForProperty(propertyName, oldValue, originalValue, pd); } pv.getOriginalPropertyValue().conversionNecessary = (valueToApply != originalValue); } final Method writeMethod = (pd instanceof GenericTypeAwarePropertyDescriptor ? ((GenericTypeAwarePropertyDescriptor) pd).getWriteMethodForActualAccess() : pd.getWriteMethod()); if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers()) && !writeMethod.isAccessible()) { if (System.getSecurityManager() != null) { AccessController.doPrivileged( new PrivilegedAction<Object>() { public Object run() { writeMethod.setAccessible(true); return null; } }); } else { writeMethod.setAccessible(true); } } final Object value = valueToApply; if (System.getSecurityManager() != null) { try { AccessController.doPrivileged( new PrivilegedExceptionAction<Object>() { public Object run() throws Exception { writeMethod.invoke(object, value); return null; } }, acc); } catch (PrivilegedActionException ex) { throw ex.getException(); } } else { writeMethod.invoke(this.object, value); } } catch (TypeMismatchException ex) { throw ex; } catch (InvocationTargetException ex) { PropertyChangeEvent propertyChangeEvent = new PropertyChangeEvent( this.rootObject, this.nestedPath + propertyName, oldValue, pv.getValue()); if (ex.getTargetException() instanceof ClassCastException) { throw new TypeMismatchException( propertyChangeEvent, pd.getPropertyType(), ex.getTargetException()); } else { throw new MethodInvocationException(propertyChangeEvent, ex.getTargetException()); } } catch (Exception ex) { PropertyChangeEvent pce = new PropertyChangeEvent( this.rootObject, this.nestedPath + propertyName, oldValue, pv.getValue()); throw new MethodInvocationException(pce, ex); } } }