blob: 766139d0bef9e790de2f232004a56ed5bc11c108 [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.apache.bval.jsr303.util;
import java.lang.reflect.Type;
import org.apache.bval.DynamicMetaBean;
import org.apache.bval.jsr303.Jsr303MetaBeanFactory;
import org.apache.bval.jsr303.UnknownPropertyException;
import org.apache.bval.jsr303.util.PathNavigation.CallbackProcedure;
import org.apache.bval.model.MetaBean;
import org.apache.bval.model.MetaProperty;
import org.apache.bval.model.ValidationContext;
import org.apache.bval.util.AccessStrategy;
import org.apache.bval.util.IndexedAccess;
import org.apache.bval.util.KeyedAccess;
import org.apache.bval.util.PropertyAccess;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.reflect.TypeUtils;
/**
* {@link ValidationContext} traversal {@link CallbackProcedure}.
*
* @version $Rev: 1137074 $ $Date: 2011-06-17 18:20:30 -0500 (Fri, 17 Jun 2011) $
*/
public class ValidationContextTraversal extends CallbackProcedure {
private static class NullSafePropertyAccess extends PropertyAccess {
/**
* Create a new NullSafePropertyAccess instance.
*
* @param clazz
* @param propertyName
*/
public NullSafePropertyAccess(Class<?> clazz, String propertyName) {
super(clazz, propertyName);
}
/**
* {@inheritDoc}
*/
@Override
public Object get(Object bean) {
return bean == null ? null : super.get(bean);
}
}
private final ValidationContext<?> validationContext;
private Type type;
private Class<?> rawType;
/**
* Create a new {@link ValidationContextTraversal} instance.
*
* @param validationContext
*/
public ValidationContextTraversal(ValidationContext<?> validationContext) {
this.validationContext = validationContext;
init();
}
/**
* Initialize from {@link ValidationContext}.
*/
public void init() {
this.rawType = validationContext.getMetaBean().getBeanClass();
this.type = this.rawType;
}
/**
* {@inheritDoc}
*/
public void handleIndexOrKey(String token) {
moveDownIfNecessary();
AccessStrategy access;
if (IndexedAccess.getJavaElementType(type) != null) {
try {
Integer index = token == null ? null : Integer.valueOf(token);
access = new IndexedAccess(type, index);
validationContext.setCurrentIndex(index);
} catch (NumberFormatException e) {
throw new UnknownPropertyException(String.format("Cannot parse %s as an array/iterable index", token),
e);
}
} else if (KeyedAccess.getJavaElementType(type) != null) {
access = new KeyedAccess(type, token);
validationContext.setCurrentKey(token);
} else {
throw new UnknownPropertyException(String.format("Cannot determine index/key type for %s", type));
}
Object value = validationContext.getBean();
Object child = value == null ? null : access.get(value);
setType(child == null ? access.getJavaType() : child.getClass());
validationContext.setBean(child,
validationContext.getMetaBean().resolveMetaBean(child == null ? rawType : child));
}
/**
* {@inheritDoc}
*/
public void handleProperty(String token) {
moveDownIfNecessary();
MetaBean metaBean = validationContext.getMetaBean();
if (metaBean instanceof DynamicMetaBean) {
metaBean = metaBean.resolveMetaBean(ObjectUtils.defaultIfNull(validationContext.getBean(), rawType));
validationContext.setMetaBean(metaBean);
}
MetaProperty mp = metaBean.getProperty(token);
if (mp == null) {
// TODO this could indicate a property hosted on a superclass; should we shunt the context traversal down a path based on that type?
PropertyAccess access = new PropertyAccess(rawType, token);
if (access.isKnown()) {
// add heretofore unknown, but valid, property on the fly:
mp = Jsr303MetaBeanFactory.addMetaProperty(metaBean, access);
} else {
throw new UnknownPropertyException("unknown property '" + token + "' in " + metaBean.getId());
}
}
validationContext.setMetaProperty(mp);
setType(mp.getType());
}
/**
* If we currently have a property, navigate the context such that the property becomes the bean, in preparation for
* another property.
*
* @param validationContext
*/
public void moveDownIfNecessary() {
MetaProperty mp = validationContext.getMetaProperty();
if (mp != null) {
if (mp.getMetaBean() == null) {
throw new UnknownPropertyException(String.format("Property %s.%s is not cascaded", mp
.getParentMetaBean().getId(), mp.getName()));
}
validationContext.moveDown(mp, new NullSafePropertyAccess(validationContext.getMetaBean().getBeanClass(),
mp.getName()));
}
}
/**
* Set the type of the expression processed thus far.
*
* @param type
*/
protected void setType(Type type) {
this.rawType = TypeUtils.getRawType(type, this.type);
this.type = type;
}
/**
* {@inheritDoc}
*/
public void handleGenericInIterable() {
throw new UnsupportedOperationException("Cannot navigate a ValidationContext to []");
}
/**
* @return the type
*/
public Type getType() {
return type;
}
/**
* @return the rawType
*/
public Class<?> getRawType() {
return rawType;
}
/**
* {@inheritDoc}
*/
@Override
protected void complete() {
super.complete();
if (validationContext.getMetaProperty() != null) {
return;
}
if (validationContext.getMetaBean() instanceof DynamicMetaBean) {
validationContext.setMetaBean(validationContext.getMetaBean().resolveMetaBean(
ObjectUtils.defaultIfNull(validationContext.getBean(), rawType)));
}
}
}