/*
* Copyright 2000-2016 Vaadin Ltd.
*
* 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 com.vaadin.v7.data.fieldgroup;
import java.beans.IntrospectionException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import com.vaadin.data.BeanValidationBinder;
import com.vaadin.data.Binder;
import com.vaadin.data.util.BeanUtil;
import com.vaadin.v7.data.Item;
import com.vaadin.v7.data.util.BeanItem;
import com.vaadin.v7.data.validator.BeanValidator;
import com.vaadin.v7.ui.Field;
/**
*
* @deprecated As of 8.0, replaced by {@link Binder} and {@link BeanValidationBinder}
*/
@Deprecated
public class BeanFieldGroup<T> extends FieldGroup {
private final Class<T> beanType;
private static Boolean beanValidationImplementationAvailable = null;
private final Map<Field<?>, BeanValidator> defaultValidators;
public BeanFieldGroup(Class<T> beanType) {
this.beanType = beanType;
this.defaultValidators = new HashMap<Field<?>, BeanValidator>();
}
@Override
protected Class<?> getPropertyType(Object propertyId) {
if (getItemDataSource() != null) {
return super.getPropertyType(propertyId);
} else {
// Data source not set so we need to figure out the type manually
/*
* toString should never really be needed as propertyId should be of
* form "fieldName" or "fieldName.subField[.subField2]" but the
* method declaration comes from parent.
*/
try {
Class<?> type = BeanUtil.getPropertyType(beanType,
propertyId.toString());
if (type == null) {
throw new BindException(
"Cannot determine type of propertyId '" + propertyId
+ "'. The propertyId was not found in "
+ beanType.getName());
}
return type;
} catch (IntrospectionException e) {
throw new BindException("Cannot determine type of propertyId '"
+ propertyId + "'. Unable to introspect " + beanType,
e);
}
}
}
@Override
protected Object findPropertyId(java.lang.reflect.Field memberField) {
String fieldName = memberField.getName();
Item dataSource = getItemDataSource();
if (dataSource != null
&& dataSource.getItemProperty(fieldName) != null) {
return fieldName;
} else {
String minifiedFieldName = minifyFieldName(fieldName);
try {
return getFieldName(beanType, minifiedFieldName);
} catch (SecurityException e) {
} catch (NoSuchFieldException e) {
}
}
return null;
}
private static String getFieldName(Class<?> cls, String propertyId)
throws SecurityException, NoSuchFieldException {
for (java.lang.reflect.Field field1 : cls.getDeclaredFields()) {
if (propertyId.equals(minifyFieldName(field1.getName()))) {
return field1.getName();
}
}
// Try super classes until we reach Object
Class<?> superClass = cls.getSuperclass();
if (superClass != null && superClass != Object.class) {
return getFieldName(superClass, propertyId);
} else {
throw new NoSuchFieldException();
}
}
/**
* Helper method for setting the data source directly using a bean. This
* method wraps the bean in a {@link BeanItem} and calls
* {@link #setItemDataSource(Item)}.
* <p>
* For null values, a null item is passed to
* {@link #setItemDataSource(Item)} to be properly clear fields.
*
* @param bean
* The bean to use as data source.
*/
public void setItemDataSource(T bean) {
if (bean == null) {
setItemDataSource((Item) null);
} else {
setItemDataSource(new BeanItem<T>(bean, beanType));
}
}
@Override
public void setItemDataSource(Item item) {
if (item == null || (item instanceof BeanItem)) {
super.setItemDataSource(item);
} else {
throw new RuntimeException(getClass().getSimpleName()
+ " only supports BeanItems as item data source");
}
}
@Override
public BeanItem<T> getItemDataSource() {
return (BeanItem<T>) super.getItemDataSource();
}
private void ensureNestedPropertyAdded(Object propertyId) {
if (getItemDataSource() != null) {
// The data source is set so the property must be found in the item.
// If it is not we try to add it.
try {
getItemProperty(propertyId);
} catch (BindException e) {
// Not found, try to add a nested property;
// BeanItem property ids are always strings so this is safe
getItemDataSource().addNestedProperty((String) propertyId);
}
}
}
@Override
public void bind(Field field, Object propertyId) {
ensureNestedPropertyAdded(propertyId);
super.bind(field, propertyId);
}
@Override
public <T extends Field> T buildAndBind(String caption, Object propertyId,
Class<T> fieldType) throws BindException {
ensureNestedPropertyAdded(propertyId);
return super.buildAndBind(caption, propertyId, fieldType);
}
@Override
public void unbind(Field<?> field) throws BindException {
super.unbind(field);
BeanValidator removed = defaultValidators.remove(field);
if (removed != null) {
field.removeValidator(removed);
}
}
@Override
protected void configureField(Field<?> field) {
super.configureField(field);
// Add Bean validators if there are annotations
if (isBeanValidationImplementationAvailable()
&& !defaultValidators.containsKey(field)) {
BeanValidator validator = new BeanValidator(beanType,
getPropertyId(field).toString());
field.addValidator(validator);
if (field.getLocale() != null) {
validator.setLocale(field.getLocale());
}
defaultValidators.put(field, validator);
}
}
/**
* Checks whether a bean validation implementation (e.g. Hibernate Validator
* or Apache Bean Validation) is available.
*
* TODO move this method to some more generic location
*
* @return true if a JSR-303 bean validation implementation is available
*/
protected static boolean isBeanValidationImplementationAvailable() {
if (beanValidationImplementationAvailable != null) {
return beanValidationImplementationAvailable;
}
try {
Class<?> validationClass = Class
.forName("javax.validation.Validation");
Method buildFactoryMethod = validationClass
.getMethod("buildDefaultValidatorFactory");
Object factory = buildFactoryMethod.invoke(null);
beanValidationImplementationAvailable = (factory != null);
} catch (Exception e) {
// no bean validation implementation available
beanValidationImplementationAvailable = false;
}
return beanValidationImplementationAvailable;
}
/**
* Convenience method to bind Fields from a given "field container" to a
* given bean with buffering disabled.
* <p>
* The returned {@link BeanFieldGroup} can be used for further
* configuration.
*
* @see #bindFieldsBuffered(Object, Object)
* @see #bindMemberFields(Object)
* @since 7.2
* @param bean
* the bean to be bound
* @param objectWithMemberFields
* the class that contains {@link Field}s for bean properties
* @return the bean field group used to make binding
*/
public static <T> BeanFieldGroup<T> bindFieldsUnbuffered(T bean,
Object objectWithMemberFields) {
return createAndBindFields(bean, objectWithMemberFields, false);
}
/**
* Convenience method to bind Fields from a given "field container" to a
* given bean with buffering enabled.
* <p>
* The returned {@link BeanFieldGroup} can be used for further
* configuration.
*
* @see #bindFieldsUnbuffered(Object, Object)
* @see #bindMemberFields(Object)
* @since 7.2
* @param bean
* the bean to be bound
* @param objectWithMemberFields
* the class that contains {@link Field}s for bean properties
* @return the bean field group used to make binding
*/
public static <T> BeanFieldGroup<T> bindFieldsBuffered(T bean,
Object objectWithMemberFields) {
return createAndBindFields(bean, objectWithMemberFields, true);
}
private static <T> BeanFieldGroup<T> createAndBindFields(T bean,
Object objectWithMemberFields, boolean buffered) {
@SuppressWarnings("unchecked")
BeanFieldGroup<T> beanFieldGroup = new BeanFieldGroup<T>(
(Class<T>) bean.getClass());
beanFieldGroup.setItemDataSource(bean);
beanFieldGroup.setBuffered(buffered);
beanFieldGroup.bindMemberFields(objectWithMemberFields);
return beanFieldGroup;
}
}