org.apache.bval.jsr303.NestedMetaProperty Maven / Gradle / Ivy
/*
* 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.bval.jsr303;
import org.apache.bval.model.MetaBean;
import org.apache.bval.model.MetaProperty;
import org.apache.bval.util.PropertyAccess;
import org.apache.commons.lang.StringUtils;
import javax.validation.ValidationException;
import java.lang.reflect.*;
import java.util.*;
/**
* Description: handle nested property paths
*/
final class NestedMetaProperty {
private MetaBean metaBean;
private MetaProperty metaProperty;
private final String propertyPath;
private boolean nested;
private Object value;
/**
* Create a new NestedMetaProperty instance.
* @param path
* @param value
*/
public NestedMetaProperty(String path, Object value) {
this.propertyPath = path;
this.value = value;
}
/**
* Parse this {@link NestedMetaProperty}.
*/
void parse() {
try {
StringTokenizer tokens = new StringTokenizer(propertyPath, ".[]", true);
while (tokens.hasMoreTokens()) {
String token = tokens.nextToken();
if ("[".equals(token)) {
String sindex = tokens.nextToken();
int idx = Integer.parseInt(sindex);
token = tokens.nextToken();
if (!"]".equals(token)) {
throw new ValidationException(
"']' missing, invalid property format: " + propertyPath);
}
useIndexedValue(idx);
resolveMetaBean();
} else if (!".".equals(token)) { // it is a property name
MetaProperty mp = getMetaBean().getProperty(token);
if (mp == null) {
throw new UnknownPropertyException(
"unknown property '" + token + "' in " + getMetaBean().getId());
}
if (getValue() != null) {
setValue(PropertyAccess.getProperty(getValue(), token));
}
setMetaProperty(mp);
resolveMetaBean();
}
}
} catch (ValidationException ex) {
throw ex; // route exception
} catch (Exception ex) { // wrap exception
throw new ValidationException(
"invalid property format: " + propertyPath, ex);
}
}
/**
* Get the resolved MetaProperty.
* @return MetaProperty
*/
public MetaProperty getMetaProperty() {
return metaProperty;
}
/**
* Get the property path.
* @return String path
*/
public String getPropertyPath() {
return propertyPath;
}
/**
* Learn whether the {@link MetaProperty} represented is indeed nested.
* @return boolean
*/
public boolean isNested() {
return nested;
}
/**
* Set the MetaProperty directly
* @param aMetaProperty to set
*/
public void setMetaProperty(MetaProperty aMetaProperty) {
if (this.metaProperty != null) {
this.nested = true;
}
this.metaProperty = aMetaProperty;
}
/**
* Get the resolved MetaBean.
* @return MetaBean
*/
public MetaBean getMetaBean() {
return metaBean;
}
/**
* Set the MetaBean directly
* @param metaBean to set
*/
public void setMetaBean(MetaBean metaBean) {
this.metaBean = metaBean;
}
/**
* Get the property value referenced.
* @return Object
*/
public Object getValue() {
return value;
}
/**
* Set the property value directly.
* @param value to set
*/
public void setValue(Object value) {
this.value = value;
}
private void useIndexedValue(int idx) {
setValue(getAtIndex(getValue(), idx));
}
private Object getAtIndex(Object value, int idx) {
if (value == null) return null;
if (value instanceof Iterable) {
Iterator iter = ((Iterable) value).iterator();
for (int i = 0; i <= idx; i++) {
value = iter.next();
}
return value;
} else if (value.getClass().isArray()) {
return getAtIndex(Arrays.asList(value), idx);
} else {
throw new ValidationException("cannot access indexed value from " + value);
}
}
/**
* Get the declared type of a Member.
* @param member
* @return generic type
*/
static Type typeOf(Member member) {
if (member instanceof Field) {
return ((Field) member).getGenericType();
}
if (member instanceof Method) {
return ((Method) member).getGenericReturnType();
}
throw new IllegalArgumentException("Member " + member + " is neither a field nor a method");
}
/**
* Get the component type of an indexed type.
* @param type
* @return Type
*/
static Type getIndexedType(Type type) {
Type indexedType = type;
if (isCollection(type) && type instanceof ParameterizedType) {
ParameterizedType paramType = (ParameterizedType) type;
Class collectionClass = getCollectionClass(type);
if (Collection.class.isAssignableFrom(collectionClass)) {
indexedType = paramType.getActualTypeArguments()[0];
} else if (Map.class.isAssignableFrom(collectionClass)) {
indexedType = paramType.getActualTypeArguments()[1];
}
} else if (isArray(type) && type instanceof GenericArrayType) {
GenericArrayType arrayTye = (GenericArrayType) type;
indexedType = arrayTye.getGenericComponentType();
}
return indexedType;
}
/**
* Resolve the raw type of a Collection.
* @param type
* @return Class if found
*/
@SuppressWarnings("unchecked")
static Class> getCollectionClass(Type type) {
if (type instanceof Class && isCollectionClass((Class) type)) {
return (Class>) type;
}
if (type instanceof ParameterizedType) {
return getCollectionClass(((ParameterizedType) type).getRawType());
}
if (type instanceof WildcardType) {
Type[] upperBounds = ((WildcardType) type).getUpperBounds();
if (upperBounds.length == 0) {
return null;
}
return getCollectionClass(upperBounds[0]);
}
return null;
}
/**
* Learn whether a particular type represents an array.
* @param type
* @return boolean
*/
static boolean isArray(Type type) {
if (type instanceof Class) {
return ((Class) type).isArray();
}
return type instanceof GenericArrayType;
}
/**
* Learn whether type
is a {@link Collection} type.
* @param type the type to check.
* @return Returns true
if type
is a collection type or false
otherwise.
*/
static boolean isCollection(Type type) {
return getCollectionClass(type) != null;
}
/**
* Learn whether clazz
implements either {@link Collection} or {@link Map}.
* @param clazz
* @return boolean
*/
//TODO should all these Collection references be Iterable?
static boolean isCollectionClass(Class clazz) {
return Collection.class.isAssignableFrom(clazz) || Map.class.isAssignableFrom(clazz);
}
// enhancement: HACK ALERT! improve support for nested property types, this is not correct in all cases
private void resolveMetaBean() {
if (metaProperty.getMetaBean() == null) {
return;
}
if (getValue() != null) {
metaBean = metaProperty.getMetaBean().resolveMetaBean(getValue());
} else {
Member member = null;
try {
member = metaBean.getBeanClass().getDeclaredField(metaProperty.getName());
} catch (NoSuchFieldException e) {
String getter = "get" + StringUtils.capitalize(metaProperty.getName());
try {
member = metaBean.getBeanClass().getDeclaredMethod(getter);
} catch (NoSuchMethodException e1) {
try {
member = metaBean.getBeanClass().getField(metaProperty.getName());
} catch (NoSuchFieldException e2) {
try {
member = metaBean.getBeanClass().getMethod(getter);
} catch (NoSuchMethodException e3) {
}
}
}
}
if (member != null) {
Type type = getIndexedType(typeOf(member));
if (type != null) {
metaBean = metaProperty.getMetaBean().resolveMetaBean(type);
}
}
}
}
}