All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.dellroad.stuff.vaadin7.FieldBuilder Maven / Gradle / Ivy

The newest version!

/*
 * Copyright (C) 2022 Archie L. Cobbs. All rights reserved.
 */

package org.dellroad.stuff.vaadin7;

import com.vaadin.data.Validator;
import com.vaadin.data.fieldgroup.BeanFieldGroup;
import com.vaadin.data.util.BeanItem;
import com.vaadin.data.util.converter.Converter;
import com.vaadin.shared.ui.combobox.FilteringMode;
import com.vaadin.shared.ui.datefield.Resolution;
import com.vaadin.ui.AbstractField;
import com.vaadin.ui.AbstractSelect;
import com.vaadin.ui.AbstractTextField;
import com.vaadin.ui.CheckBox;
import com.vaadin.ui.ComboBox;
import com.vaadin.ui.DateField;
import com.vaadin.ui.Field;
import com.vaadin.ui.ListSelect;
import com.vaadin.ui.PasswordField;
import com.vaadin.ui.TextArea;
import com.vaadin.ui.TextField;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.annotation.Annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;

import org.dellroad.stuff.java.MethodAnnotationScanner;

/**
 * Automatically builds and binds fields for a Java bean annotated with {@link FieldBuilder} annotations.
 *
 * 
 * 
 * 
 *
 * The various nested {@link FieldBuilder} annotation types annotate Java bean property "getter" methods and specify
 * how the the bean properties of that class should be edited using {@link AbstractField}s. This allows all information
 * about how to edit a Java model class to stay contained within that class.
 *
 * 

* This class supports two types of annotations: first, the {@link ProvidesField @ProvidesField} annotation annotates * a method that knows how to build an {@link AbstractField} suitable for editing the bean property specified by * its {@link ProvidesField#value value()}. So {@link ProvidesField @ProvidesField} is analgous to * {@link ProvidesProperty @ProvidesProperty}, except that it defines an editing field rather than a container property. * *

* The {@link FieldBuilder.AbstractField @FieldBuilder.AbstractField} hierarchy annotations are the other type of annotation. * These annotations annotate a Java bean property getter method and specify how to configure an {@link AbstractField} subclass * instance to edit the bean property corresponding to the getter method. * {@link FieldBuilder.AbstractField @FieldBuilder.AbstractField} is the top level annotation in a hierarchy of annotations * that correspond to the {@link AbstractField} class hierarchy. {@link FieldBuilder.AbstractField @FieldBuilder.AbstractField} * corresponds to {@link AbstractField}, and its properties configure corresponding {@link AbstractField} properties. * More specific annotations correspond to the various {@link AbstractField} subclasses, * for example {@link ComboBox @FieldBuilder.ComboBox} corresponds to {@link ComboBox}. * When using more specific annotations, the "superclass" annotations configure the corresponding superclass' properties. * *

* A simple example shows how these annotations are used: * *


 * // Use a 10x40 TextArea to edit the "description" property
 * @FieldBuilder.AbstractField(caption = "Description:")
 * @FieldBuilder.TextArea(columns = 40, rows = 10)
 * public String getDescription() {
 *     return this.description;
 * }
 *
 * // Use my own custom field to edit the "foobar" property
 * @FieldBuilder.ProvidesField("foobar")
 * private MyCustomField createFoobarField() {
 *     ...
 * }
 * 
* *

* A {@link FieldBuilder} instance will read these annotations and build the fields automatically. For example: * *


 * // Create fields based on FieldGroup.* annotations
 * Person person = new Person("Joe Smith", 100);
 * BeanFieldGroup<Person> fieldGroup = FieldBuilder.buildFieldGroup(person);
 *
 * // Layout the fields in a form
 * FormLayout layout = new FormLayout();
 * for (Field<?> field : fieldGroup.getFields())
 *     layout.addComponent(field);
 * 
* *

* For all annotations in the {@link FieldBuilder.AbstractField @FieldBuilder.AbstractField} hierarchy, leaving properties * set to their default values results in the default behavior. * * @see AbstractSelect * @see AbstractTextField * @see CheckBox * @see ComboBox * @see DateField * @see EnumComboBox * @see ListSelect * @see PasswordField * @see TextArea * @see TextField */ public class FieldBuilder { /** * Introspect for {@link FieldBuilder} annotations on property getter methods of the * {@link BeanFieldGroup}'s data source, and then build and bind the corresponding fields. * * @param fieldGroup field group to configure * @throws IllegalArgumentException if {@code fieldGroup} is null * @throws IllegalArgumentException if {@code fieldGroup} does not yet * {@linkplain BeanFieldGroup#setItemDataSource(Object) have a data source} */ public void buildAndBind(BeanFieldGroup fieldGroup) { // Sanity check if (fieldGroup == null) throw new IllegalArgumentException("null beanType"); final BeanItem beanItem = fieldGroup.getItemDataSource(); if (beanItem == null) throw new IllegalArgumentException("fieldGroup does not yet have a data source"); // Scan bean properties to build fields for (Map.Entry> entry : this.buildBeanPropertyFields(beanItem.getBean()).entrySet()) fieldGroup.bind(entry.getValue(), entry.getKey()); } /** * Introspect for {@link FieldBuilder} annotations on property getter methods and build * a mapping from Java bean property name to a field that may be used to edit that property. * Only bean properties that have {@link FieldBuilder} annotations are detected. * * @param bean Java bean * @return mapping from bean property name to field * @throws IllegalArgumentException if {@code bean} is null * @throws IllegalArgumentException if invalid or conflicting annotations are encountered */ public Map> buildBeanPropertyFields(Object bean) { // Sanity check if (bean == null) throw new IllegalArgumentException("null bean"); // Look for all bean property getter methods BeanInfo beanInfo; try { beanInfo = Introspector.getBeanInfo(bean.getClass()); } catch (IntrospectionException e) { throw new RuntimeException("unexpected exception", e); } final HashMap getterMap = new HashMap<>(); // contains all getter methods for (PropertyDescriptor propertyDescriptor : beanInfo.getPropertyDescriptors()) { Method method = propertyDescriptor.getReadMethod(); // Work around Introspector stupidity where it returns overridden superclass' getter method if (method != null && method.getClass() != bean.getClass()) { for (Class c = bean.getClass(); c != null && c != method.getClass(); c = c.getSuperclass()) { try { method = c.getDeclaredMethod(method.getName(), method.getParameterTypes()); } catch (Exception e) { continue; } break; } } // Add getter, if appropriate if (method != null && method.getReturnType() != void.class && method.getParameterTypes().length == 0) getterMap.put(propertyDescriptor.getName(), method); } // Scan getters for FieldBuilder.* annotations other than FieldBuidler.ProvidesField final HashMap> map = new HashMap<>(); // contains @FieldBuilder.* fields for (Map.Entry entry : getterMap.entrySet()) { final String propertyName = entry.getKey(); final Method method = entry.getValue(); // Get annotations, if any final List> applierList = this.buildApplierList(method); if (applierList.isEmpty()) continue; // Build field map.put(propertyName, this.buildField(applierList, "method " + method)); } // Scan all methods for @FieldBuilder.ProvidesField annotations final HashMap providerMap = new HashMap<>(); // contains @FieldBuilder.ProvidesField methods this.buildProviderMap(providerMap, bean.getClass()); // Check for conflicts between @FieldBuidler.ProvidesField and other annotations and add fields to map for (Map.Entry entry : providerMap.entrySet()) { final String propertyName = entry.getKey(); final Method method = entry.getValue(); // Verify field is not already defined if (map.containsKey(propertyName)) { throw new IllegalArgumentException("conflicting annotations exist for property `" + propertyName + "': annotation @" + ProvidesField.class.getName() + " on method " + method + " cannot be combined with other @FieldBuilder.* annotation types"); } // Invoke method to create field Field field; try { method.setAccessible(true); } catch (RuntimeException e) { // ignore } try { field = (Field)method.invoke(bean); } catch (Exception e) { throw new RuntimeException("error invoking @" + ProvidesField.class.getName() + " annotation on method " + method, e); } // Save field map.put(propertyName, field); } // Done return map; } // This method exists solely to bind the generic type private void buildProviderMap(Map providerMap, Class type) { final MethodAnnotationScanner scanner = new MethodAnnotationScanner<>(type, ProvidesField.class); for (MethodAnnotationScanner.MethodInfo methodInfo : scanner.findAnnotatedMethods()) this.buildProviderMap(providerMap, methodInfo.getMethod().getDeclaringClass(), methodInfo.getMethod().getName()); } // Used by buildBeanPropertyFields() to validate @FieldBuilder.ProvidesField annotations private void buildProviderMap(Map providerMap, Class type, String methodName) { // Terminate recursion if (type == null) return; // Check the method in this class do { // Get method Method method; try { method = type.getDeclaredMethod(methodName); } catch (NoSuchMethodException e) { break; } // Get annotation final ProvidesField providesField = method.getAnnotation(ProvidesField.class); if (providesField == null) break; // Validate method return type is compatible with Field if (!Field.class.isAssignableFrom(method.getReturnType())) { throw new IllegalArgumentException("invalid @" + ProvidesField.class.getName() + " annotation on method " + method + ": return type " + method.getReturnType().getName() + " is not a subtype of " + Field.class.getName()); } // Check for two methods declaring fields for the same property final String propertyName = providesField.value(); final Method otherMethod = providerMap.get(propertyName); if (otherMethod != null && !otherMethod.getName().equals(methodName)) { throw new IllegalArgumentException("conflicting @" + ProvidesField.class.getName() + " annotations exist for property `" + propertyName + "': both method " + otherMethod + " and method " + method + " are specified"); } // Save method if (otherMethod == null) providerMap.put(propertyName, method); } while (false); // Recurse on interfaces for (Class iface : type.getInterfaces()) this.buildProviderMap(providerMap, iface, methodName); // Recurse on superclass this.buildProviderMap(providerMap, type.getSuperclass(), methodName); } /** * Create a {@link BeanFieldGroup} using the given instance, introspect for {@link FieldBuilder} annotations * on property getter methods of the given bean's class, and build and bind the corresponding fields, and return * the result. * * @param bean model bean annotated with {@link FieldBuilder} annotations * @param bean type * @return new {@link BeanFieldGroup} * @throws IllegalArgumentException if {@code bean} is null */ @SuppressWarnings("unchecked") public static BeanFieldGroup buildFieldGroup(T bean) { // Sanity check if (bean == null) throw new IllegalArgumentException("null bean"); // Create field group final BeanFieldGroup fieldGroup = new BeanFieldGroup<>((Class)bean.getClass()); fieldGroup.setItemDataSource(bean); new FieldBuilder().buildAndBind(fieldGroup); return fieldGroup; } /** * Instantiate and configure an {@link AbstractField} according to the given scanned annotations. * * @param appliers annotation appliers * @param description description of the field (used for exception messages) * @return new field */ protected com.vaadin.ui.AbstractField buildField(Collection> appliers, String description) { // Get comparator that sorts by class hierarcy, narrower types first; note Collections.sort() is stable, // so for any specific annotation type, that annotation on subtype appears before that annotation on supertype. final Comparator> comparator = new Comparator>() { @Override public int compare(AnnotationApplier a1, AnnotationApplier a2) { final Class> type1 = a1.getFieldType(); final Class> type2 = a2.getFieldType(); if (type1 == type2) return 0; if (type1.isAssignableFrom(type2)) return 1; if (type2.isAssignableFrom(type1)) return -1; return 0; } }; // Sanity check for duplicates and conflicts final ArrayList> applierList = new ArrayList<>(appliers); Collections.sort(applierList, comparator); for (int i = 0; i < applierList.size() - 1; ) { final AnnotationApplier a1 = applierList.get(i); final AnnotationApplier a2 = applierList.get(i + 1); // Let annotations on subclass override annotations on superclass if (a1.getAnnotation().annotationType() == a2.getAnnotation().annotationType()) { applierList.remove(i + 1); continue; } // Check for conflicting annotation types (e.g., both FieldBuilder.TextField and FieldBuilder.DateField) if (comparator.compare(a1, a2) == 0) { throw new IllegalArgumentException("conflicting annotations of type " + a1.getAnnotation().annotationType().getName() + " and " + a2.getAnnotation().annotationType().getName() + " for " + description); } i++; } // Determine field type Class> type = null; AnnotationApplier typeApplier = null; for (AnnotationApplier applier : applierList) { // Pick up type() if specified if (applier.getActualFieldType() == null) continue; if (type == null) { type = applier.getActualFieldType(); typeApplier = applier; continue; } // Verify the field type specified by a narrower annotation has compatible narrower field type if (!applier.getActualFieldType().isAssignableFrom(type) && typeApplier != null) { throw new IllegalArgumentException("conflicting field types specified by annotations of type " + typeApplier.getAnnotation().annotationType().getName() + " (type() = " + type.getName() + ") and " + applier.getAnnotation().annotationType().getName() + " (type() = " + applier.getActualFieldType().getName() + ") for " + description); } } if (type == null) throw new IllegalArgumentException("cannot determine field type; no type() specified for " + description); // Instantiate field final com.vaadin.ui.AbstractField field = FieldBuilder.instantiate(type); // Configure the field for (AnnotationApplier applier : applierList) this.apply(applier, field); // Done return field; } // This method exists solely to bind the generic type private > void apply(AnnotationApplier applier, com.vaadin.ui.AbstractField field) { applier.applyTo(applier.getFieldType().cast(field)); } private static T instantiate(Class type) { Constructor constructor; try { constructor = type.getDeclaredConstructor(); } catch (ReflectiveOperationException e) { throw new RuntimeException("cannot instantiate " + type + " because no zero-arg constructor could be found", e); } try { constructor.setAccessible(true); } catch (RuntimeException e) { // ignore } try { return constructor.newInstance(); } catch (ReflectiveOperationException e) { throw new RuntimeException("cannot instantiate " + type + " using its zero-arg constructor", e); } } /** * Find all relevant annotations on the given method as well as on any supertype methods it overrides. * The method must be a getter method taking no arguments. Annotations are ordered so that annotations * on a method in type X appear before annotations on an overridden method in type Y, a supertype of X. * * @param method annotated getter method * @return appliers for annotations found * @throws IllegalArgumentException if {@code method} is null * @throws IllegalArgumentException if {@code method} has parameters */ protected List> buildApplierList(Method method) { // Sanity check if (method == null) throw new IllegalArgumentException("null method"); if (method.getParameterTypes().length > 0) throw new IllegalArgumentException("method takes parameters"); // Recurse final ArrayList> list = new ArrayList<>(); this.buildApplierList(method.getDeclaringClass(), method.getName(), list); return list; } private void buildApplierList(Class type, String methodName, List> list) { // Terminate recursion if (type == null) return; // Check class Method method; try { method = type.getMethod(methodName); } catch (NoSuchMethodException e) { method = null; } if (method != null) list.addAll(this.buildDirectApplierList(method)); // Recurse on interfaces for (Class iface : type.getInterfaces()) this.buildApplierList(iface, methodName, list); // Recurse on superclass this.buildApplierList(type.getSuperclass(), methodName, list); } /** * Find all relevant annotations declared directly on the given {@link Method}. * * @param method method to inspect * @return annotations found * @throws IllegalArgumentException if {@code method} is null */ protected List> buildDirectApplierList(Method method) { // Sanity check if (method == null) throw new IllegalArgumentException("null method"); // Build list final ArrayList> list = new ArrayList<>(); for (Annotation annotation : method.getDeclaredAnnotations()) { final AnnotationApplier applier = this.getAnnotationApplier(method, annotation); if (applier != null) list.add(applier); } return list; } /** * Get the {@link AnnotationApplier} that applies the given annotation. * Subclasses can add support for additional annotation types by overriding this method. * * @param method method to inspect * @param annotation method annotation to inspect * @return corresponding {@link AnnotationApplier}, or null if annotation is unknown */ protected AnnotationApplier getAnnotationApplier(Method method, Annotation annotation) { if (annotation instanceof FieldBuilder.AbstractField) return new AbstractFieldApplier(method, (FieldBuilder.AbstractField)annotation); if (annotation instanceof FieldBuilder.AbstractSelect) return new AbstractSelectApplier(method, (FieldBuilder.AbstractSelect)annotation); if (annotation instanceof FieldBuilder.CheckBox) return new CheckBoxApplier(method, (FieldBuilder.CheckBox)annotation); if (annotation instanceof FieldBuilder.ComboBox) return new ComboBoxApplier(method, (FieldBuilder.ComboBox)annotation); if (annotation instanceof FieldBuilder.EnumComboBox) return new EnumComboBoxApplier(method, (FieldBuilder.EnumComboBox)annotation); if (annotation instanceof FieldBuilder.ListSelect) return new ListSelectApplier(method, (FieldBuilder.ListSelect)annotation); if (annotation instanceof FieldBuilder.DateField) return new DateFieldApplier(method, (FieldBuilder.DateField)annotation); if (annotation instanceof FieldBuilder.AbstractTextField) return new AbstractTextFieldApplier(method, (FieldBuilder.AbstractTextField)annotation); if (annotation instanceof FieldBuilder.TextField) return new TextFieldApplier(method, (FieldBuilder.TextField)annotation); if (annotation instanceof FieldBuilder.TextArea) return new TextAreaApplier(method, (FieldBuilder.TextArea)annotation); if (annotation instanceof FieldBuilder.PasswordField) return new PasswordFieldApplier(method, (FieldBuilder.PasswordField)annotation); return null; } // AnnotationApplier /** * Class that knows how to apply annotation properties to a corresponding field. */ protected abstract static class AnnotationApplier> { protected final Method method; protected final A annotation; protected final Class fieldType; protected AnnotationApplier(Method method, A annotation, Class fieldType) { if (method == null) throw new IllegalArgumentException("null method"); if (annotation == null) throw new IllegalArgumentException("null annotation"); if (fieldType == null) throw new IllegalArgumentException("null fieldType"); this.method = method; this.annotation = annotation; this.fieldType = fieldType; } public final Method getMethod() { return this.method; } public final A getAnnotation() { return this.annotation; } public final Class getFieldType() { return this.fieldType; } public abstract Class getActualFieldType(); public abstract void applyTo(F field); } /** * Applies properties from a {@link FieldBuilder.AbstractField} annotation to a {@link com.vaadin.ui.AbstractField}. */ private static class AbstractFieldApplier extends AnnotationApplier> { @SuppressWarnings("unchecked") AbstractFieldApplier(Method method, FieldBuilder.AbstractField annotation) { super(method, annotation, (Class>)(Object)com.vaadin.ui.AbstractField.class); } @Override @SuppressWarnings("unchecked") public Class> getActualFieldType() { return (Class>)(Object)(this.annotation.type() != com.vaadin.ui.AbstractField.class ? this.annotation.type() : null); } @Override @SuppressWarnings("unchecked") public void applyTo(com.vaadin.ui.AbstractField field) { if (this.annotation.width().length() > 0) field.setWidth(this.annotation.width()); if (this.annotation.height().length() > 0) field.setHeight(this.annotation.height()); if (this.annotation.caption().length() > 0) field.setCaption(this.annotation.caption()); if (this.annotation.description().length() > 0) field.setDescription(this.annotation.description()); field.setEnabled(this.annotation.enabled()); field.setImmediate(this.annotation.immediate()); field.setReadOnly(this.annotation.readOnly()); field.setBuffered(this.annotation.buffered()); field.setInvalidAllowed(this.annotation.invalidAllowed()); field.setInvalidCommitted(this.annotation.invalidCommitted()); field.setValidationVisible(this.annotation.validationVisible()); field.setRequired(this.annotation.required()); field.setTabIndex(this.annotation.tabIndex()); if (this.annotation.converter() != Converter.class) field.setConverter(FieldBuilder.instantiate(this.annotation.converter())); for (Class validatorType : this.annotation.validators()) field.addValidator(FieldBuilder.instantiate(validatorType)); for (String styleName : this.annotation.styleNames()) field.addStyleName(styleName); if (this.annotation.conversionError().length() > 0) field.setConversionError(this.annotation.conversionError()); if (this.annotation.requiredError().length() > 0) field.setRequiredError(this.annotation.requiredError()); } } /** * Applies properties from a {@link FieldBuilder.AbstractSelect} annotation to a {@link com.vaadin.ui.AbstractSelect}. */ private static class AbstractSelectApplier extends AnnotationApplier { AbstractSelectApplier(Method method, FieldBuilder.AbstractSelect annotation) { super(method, annotation, com.vaadin.ui.AbstractSelect.class); } @Override public Class getActualFieldType() { return this.annotation.type() != com.vaadin.ui.AbstractSelect.class ? this.annotation.type() : null; } @Override public void applyTo(com.vaadin.ui.AbstractSelect field) { field.setItemCaptionMode(this.annotation.itemCaptionMode()); if (this.annotation.itemCaptionPropertyId().length() > 0) field.setItemCaptionPropertyId(this.annotation.itemCaptionPropertyId()); if (this.annotation.itemIconPropertyId().length() > 0) field.setItemIconPropertyId(this.annotation.itemIconPropertyId()); if (this.annotation.nullSelectionItemId().length() > 0) field.setNullSelectionItemId(this.annotation.nullSelectionItemId()); field.setMultiSelect(this.annotation.multiSelect()); field.setNewItemsAllowed(this.annotation.newItemsAllowed()); field.setNullSelectionAllowed(this.annotation.nullSelectionAllowed()); } } /** * Applies properties from a {@link FieldBuilder.CheckBox} annotation to a {@link com.vaadin.ui.CheckBox}. */ private static class CheckBoxApplier extends AnnotationApplier { CheckBoxApplier(Method method, FieldBuilder.CheckBox annotation) { super(method, annotation, com.vaadin.ui.CheckBox.class); } @Override public Class getActualFieldType() { return this.annotation.type(); } @Override public void applyTo(com.vaadin.ui.CheckBox field) { } } /** * Applies properties from a {@link FieldBuilder.ComboBox} annotation to a {@link com.vaadin.ui.ComboBox}. */ private static class ComboBoxApplier extends AnnotationApplier { ComboBoxApplier(Method method, FieldBuilder.ComboBox annotation) { super(method, annotation, com.vaadin.ui.ComboBox.class); } @Override public Class getActualFieldType() { return this.annotation.type(); } @Override public void applyTo(com.vaadin.ui.ComboBox field) { if (this.annotation.inputPrompt().length() > 0) field.setInputPrompt(this.annotation.inputPrompt()); if (this.annotation.pageLength() != -1) field.setPageLength(this.annotation.pageLength()); field.setScrollToSelectedItem(this.annotation.scrollToSelectedItem()); field.setTextInputAllowed(this.annotation.textInputAllowed()); field.setFilteringMode(this.annotation.filteringMode()); } } /** * Applies properties from a {@link FieldBuilder.EnumComboBox} annotation to a {@link org.dellroad.stuff.vaadin7.EnumComboBox}. */ private static class EnumComboBoxApplier extends AnnotationApplier { EnumComboBoxApplier(Method method, FieldBuilder.EnumComboBox annotation) { super(method, annotation, org.dellroad.stuff.vaadin7.EnumComboBox.class); } @Override public Class getActualFieldType() { return this.annotation.type(); } @Override @SuppressWarnings({ "unchecked", "rawtypes" }) public void applyTo(org.dellroad.stuff.vaadin7.EnumComboBox field) { Class enumClass = this.annotation.enumClass(); if (enumClass == Enum.class) { try { enumClass = this.method.getReturnType().asSubclass(Enum.class); } catch (ClassCastException e) { throw new IllegalArgumentException("invalid @EnumComboBox annotation on non-Enum method " + this.getMethod()); } } field.setEnumDataSource(enumClass); } } /** * Applies properties from a {@link FieldBuilder.ListSelect} annotation to a {@link com.vaadin.ui.ListSelect}. */ private static class ListSelectApplier extends AnnotationApplier { ListSelectApplier(Method method, FieldBuilder.ListSelect annotation) { super(method, annotation, com.vaadin.ui.ListSelect.class); } @Override public Class getActualFieldType() { return this.annotation.type(); } @Override public void applyTo(com.vaadin.ui.ListSelect field) { if (this.annotation.rows() != -1) field.setRows(this.annotation.rows()); } } /** * Applies properties from a {@link FieldBuilder.DateField} annotation to a {@link com.vaadin.ui.DateField}. */ private static class DateFieldApplier extends AnnotationApplier { DateFieldApplier(Method method, FieldBuilder.DateField annotation) { super(method, annotation, com.vaadin.ui.DateField.class); } @Override public Class getActualFieldType() { return this.annotation.type(); } @Override public void applyTo(com.vaadin.ui.DateField field) { if (this.annotation.dateFormat().length() > 0) field.setDateFormat(this.annotation.dateFormat()); if (this.annotation.parseErrorMessage().length() > 0) field.setParseErrorMessage(this.annotation.parseErrorMessage()); if (this.annotation.dateOutOfRangeMessage().length() > 0) field.setDateOutOfRangeMessage(this.annotation.dateOutOfRangeMessage()); field.setResolution(this.annotation.resolution()); field.setShowISOWeekNumbers(this.annotation.showISOWeekNumbers()); if (this.annotation.timeZone().length() > 0) field.setTimeZone(TimeZone.getTimeZone(this.annotation.timeZone())); field.setLenient(this.annotation.lenient()); } } /** * Applies properties from a {@link FieldBuilder.AbstractTextField} annotation to a {@link com.vaadin.ui.AbstractTextField}. */ private static class AbstractTextFieldApplier extends AnnotationApplier { AbstractTextFieldApplier(Method method, FieldBuilder.AbstractTextField annotation) { super(method, annotation, com.vaadin.ui.AbstractTextField.class); } @Override public Class getActualFieldType() { return this.annotation.type(); } @Override public void applyTo(com.vaadin.ui.AbstractTextField field) { field.setNullRepresentation(this.annotation.nullRepresentation()); field.setNullSettingAllowed(this.annotation.nullSettingAllowed()); field.setTextChangeEventMode(this.annotation.textChangeEventMode()); field.setTextChangeTimeout(this.annotation.textChangeTimeout()); if (this.annotation.inputPrompt().length() > 0) field.setInputPrompt(this.annotation.inputPrompt()); field.setColumns(this.annotation.columns()); if (this.annotation.maxLength() != -1) field.setMaxLength(this.annotation.maxLength()); } } /** * Applies properties from a {@link FieldBuilder.TextField} annotation to a {@link com.vaadin.ui.TextField}. */ private static class TextFieldApplier extends AnnotationApplier { TextFieldApplier(Method method, FieldBuilder.TextField annotation) { super(method, annotation, com.vaadin.ui.TextField.class); } @Override public Class getActualFieldType() { return this.annotation.type(); } @Override public void applyTo(com.vaadin.ui.TextField field) { } } /** * Applies properties from a {@link FieldBuilder.TextArea} annotation to a {@link com.vaadin.ui.TextArea}. */ private static class TextAreaApplier extends AnnotationApplier { TextAreaApplier(Method method, FieldBuilder.TextArea annotation) { super(method, annotation, com.vaadin.ui.TextArea.class); } @Override public Class getActualFieldType() { return this.annotation.type(); } @Override public void applyTo(com.vaadin.ui.TextArea field) { field.setWordwrap(this.annotation.wordwrap()); if (this.annotation.rows() != -1) field.setRows(this.annotation.rows()); } } /** * Applies properties from a {@link FieldBuilder.PasswordField} annotation to a {@link com.vaadin.ui.PasswordField}. */ private static class PasswordFieldApplier extends AnnotationApplier { PasswordFieldApplier(Method method, FieldBuilder.PasswordField annotation) { super(method, annotation, com.vaadin.ui.PasswordField.class); } @Override public Class getActualFieldType() { return this.annotation.type(); } @Override public void applyTo(com.vaadin.ui.PasswordField field) { } } // Annotations /** * Specifies that the annotated method will return an {@link com.vaadin.ui.AbstractField} suitable for * editing the specified property. * *

* Annotated methods must take zero arguments and return a type compatible with {@link com.vaadin.ui.AbstractField}. * * @see FieldBuilder */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) @Documented public @interface ProvidesField { /** * The name of the property that the annotated method's return value edits. * * @return property name */ String value(); } /** * Specifies how a Java property should be edited in Vaadin using an {@link com.vaadin.ui.AbstractField}. * * @see FieldBuilder */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) @Documented public @interface AbstractField { /** * Get the {@link AbstractField} type that will edit the property. * *

* Although this property has a default value, it must be overridden either in this annotation, or * by also including a more specific annotation such as {@link TextField}. * * @return field type */ @SuppressWarnings("rawtypes") Class type() default com.vaadin.ui.AbstractField.class; /** * Get style names. * * @return style names * @see com.vaadin.ui.AbstractComponent#addStyleName */ String[] styleNames() default {}; /** * Get width. * * @return field width * @see com.vaadin.ui.AbstractComponent#setWidth(String) */ String width() default ""; /** * Get height. * * @return field height * @see com.vaadin.ui.AbstractComponent#setHeight(String) */ String height() default ""; /** * Get the caption associated with this field. * * @return field caption * @see com.vaadin.ui.AbstractComponent#setCaption */ String caption() default ""; /** * Get the description associated with this field. * * @return field description * @see com.vaadin.ui.AbstractComponent#setDescription */ String description() default ""; /** * Get whether this field is enabled. * * @return true to enable field * @see com.vaadin.ui.AbstractComponent#setEnabled */ boolean enabled() default true; /** * Get whether this field is immediate. * * @return true for immediate mode * @see com.vaadin.ui.AbstractComponent#setImmediate */ boolean immediate() default false; /** * Get whether this field is read-only. * * @return true for read-only * @see com.vaadin.ui.AbstractComponent#setReadOnly */ boolean readOnly() default false; /** * Get the {@link Converter} type that convert field value to data model type. * The specified class must have a no-arg constructor and compatible type. * *

* The default value of this property is {@link Converter}, which means do not set a specific * {@link Converter} on the field. * * @return field value converter * @see com.vaadin.ui.AbstractField#setConverter(Converter) */ @SuppressWarnings("rawtypes") Class converter() default Converter.class; /** * Get {@link Validator} types to add to this field. All such types must have no-arg constructors. * * @return field validators * @see com.vaadin.ui.AbstractField#addValidator */ Class[] validators() default {}; /** * Get whether this field is buffered. * * @return true for buffered mode * @see com.vaadin.ui.AbstractField#setBuffered */ boolean buffered() default false; /** * Get whether invalid values are allowed. * * @return true to allow invalid values * @see com.vaadin.ui.AbstractField#setInvalidAllowed */ boolean invalidAllowed() default true; /** * Get whether invalid values should be committed. * * @return true to allow invalid values to be committed * @see com.vaadin.ui.AbstractField#setInvalidCommitted */ boolean invalidCommitted() default false; /** * Get error message when value cannot be converted to data model type. * * @return error message * @see com.vaadin.ui.AbstractField#setConversionError */ String conversionError() default "Could not convert value to {0}"; /** * Get the error that is shown if this field is required, but empty. * * @return error message * @see com.vaadin.ui.AbstractField#setRequiredError */ String requiredError() default ""; /** * Get whether automatic visible validation is enabled. * * @return true for automatic visible validation * @see com.vaadin.ui.AbstractField#setValidationVisible */ boolean validationVisible() default true; /** * Get whether field is required. * * @return true if value is required * @see com.vaadin.ui.AbstractField#setRequired */ boolean required() default false; /** * Get tabular index. * * @return field tab index * @see com.vaadin.ui.AbstractField#setTabIndex */ int tabIndex() default 0; } /** * Specifies how a Java property should be edited in Vaadin using an {@link com.vaadin.ui.AbstractSelect}. * * @see FieldBuilder.AbstractField * @see FieldBuilder */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) @Documented public @interface AbstractSelect { /** * Get the {@link com.vaadin.ui.AbstractSelect} type that will edit the property. * *

* Although this property has a default value, it must be overridden either in this annotation, or * by also including a more specific annotation such as {@link ComboBox}. * * @return field type */ Class type() default com.vaadin.ui.AbstractSelect.class; /** * Get the item caption mode. * * @return caption mode * @see com.vaadin.ui.AbstractSelect#setItemCaptionMode */ com.vaadin.ui.AbstractSelect.ItemCaptionMode itemCaptionMode() default com.vaadin.ui.AbstractSelect.ItemCaptionMode.EXPLICIT_DEFAULTS_ID; /** * Get the item caption property ID (which must be a string). * * @return caption property ID * @see com.vaadin.ui.AbstractSelect#setItemCaptionPropertyId */ String itemCaptionPropertyId() default ""; /** * Get the item icon property ID (which must be a string). * * @return icon property ID * @see com.vaadin.ui.AbstractSelect#setItemIconPropertyId */ String itemIconPropertyId() default ""; /** * Get the null selection item ID. * * @return null selection item ID * @see com.vaadin.ui.AbstractSelect#setNullSelectionItemId */ String nullSelectionItemId() default ""; /** * Get multi-select setting. * * @return true to allow multiple select * @see com.vaadin.ui.AbstractSelect#setMultiSelect */ boolean multiSelect() default false; /** * Get whether new items are allowed. * * @return true to allow new items * @see com.vaadin.ui.AbstractSelect#setNewItemsAllowed */ boolean newItemsAllowed() default false; /** * Get whether null selection is allowed. * * @return true to allow null selection * @see com.vaadin.ui.AbstractSelect#setNullSelectionAllowed */ boolean nullSelectionAllowed() default true; } /** * Specifies how a Java property should be edited in Vaadin using an {@link com.vaadin.ui.CheckBox}. * * @see FieldBuilder.AbstractField * @see FieldBuilder */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) @Documented public @interface CheckBox { /** * Get the {@link com.vaadin.ui.CheckBox} type that will edit the property. Type must have a no-arg constructor. * * @return field type */ Class type() default com.vaadin.ui.CheckBox.class; } /** * Specifies how a Java property should be edited in Vaadin using an {@link com.vaadin.ui.ComboBox}. * * @see FieldBuilder.AbstractField * @see FieldBuilder.AbstractSelect * @see FieldBuilder */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) @Documented public @interface ComboBox { /** * Get the {@link com.vaadin.ui.ComboBox} type that will edit the property. Type must have a no-arg constructor. * * @return field type */ Class type() default com.vaadin.ui.ComboBox.class; /** * Get the input prompt. * * @return input prompt * @see com.vaadin.ui.ComboBox#setInputPrompt */ String inputPrompt() default ""; /** * Get the page length. * * @return page length, or -1 for none * @see com.vaadin.ui.ComboBox#setPageLength */ int pageLength() default -1; /** * Get whether to scroll to the selected item. * * @return true to scroll to the selected item * @see com.vaadin.ui.ComboBox#setScrollToSelectedItem */ boolean scrollToSelectedItem() default true; /** * Get whether text input is allowed. * * @return true to allow text input * @see com.vaadin.ui.ComboBox#setTextInputAllowed */ boolean textInputAllowed() default true; /** * Get the filtering mode. * * @return filtering mode * @see com.vaadin.ui.ComboBox#setFilteringMode */ FilteringMode filteringMode() default FilteringMode.STARTSWITH; } /** * Specifies how a Java property should be edited in Vaadin using an {@link org.dellroad.stuff.vaadin7.EnumComboBox}. * * @see FieldBuilder.AbstractField * @see FieldBuilder.AbstractSelect * @see FieldBuilder.ComboBox * @see FieldBuilder */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) @Documented public @interface EnumComboBox { /** * Get the {@link org.dellroad.stuff.vaadin7.EnumComboBox} type that will edit the property. * Type must have a no-arg constructor. * * @return field type */ Class type() default org.dellroad.stuff.vaadin7.EnumComboBox.class; /** * Get the {@link Enum} type to choose from. If left as default, the type will be inferred from * the getter method return type, which must be an {@link Enum} type. * * @return enum type * @see org.dellroad.stuff.vaadin7.EnumComboBox#setEnumDataSource */ @SuppressWarnings("rawtypes") Class enumClass() default Enum.class; } /** * Specifies how a Java property should be edited in Vaadin using an {@link com.vaadin.ui.ListSelect}. * * @see FieldBuilder.AbstractField * @see FieldBuilder.AbstractSelect * @see FieldBuilder */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) @Documented public @interface ListSelect { /** * Get the {@link com.vaadin.ui.ListSelect} type that will edit the property. Type must have a no-arg constructor. * * @return field type */ Class type() default com.vaadin.ui.ListSelect.class; /** * Get the number of rows in the editor. * * @return number of rows, or -1 for none * @see com.vaadin.ui.ListSelect#setRows */ int rows() default -1; } /** * Specifies how a Java property should be edited in Vaadin using an {@link com.vaadin.ui.DateField}. * * @see FieldBuilder.AbstractField * @see FieldBuilder */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) @Documented public @interface DateField { /** * Get the {@link com.vaadin.ui.DateField} type that will edit the property. Type must have a no-arg constructor. * * @return field type */ Class type() default com.vaadin.ui.DateField.class; /** * Get the date format string. * * @return date format string * @see com.vaadin.ui.DateField#setDateFormat */ String dateFormat() default ""; /** * Get the date parse error message. * * @return date parse error message * @see com.vaadin.ui.DateField#setParseErrorMessage */ String parseErrorMessage() default ""; /** * Get the date out of range error message. * * @return date out of range error message * @see com.vaadin.ui.DateField#setDateOutOfRangeMessage */ String dateOutOfRangeMessage() default ""; /** * Get the date resolution. * * @return date resolution * @see com.vaadin.ui.DateField#setResolution */ Resolution resolution() default Resolution.DAY; /** * Get whether to show ISO week numbers. * * @return whether to show ISO week numbers * @see com.vaadin.ui.DateField#setShowISOWeekNumbers */ boolean showISOWeekNumbers() default false; /** * Get the time zone (in string form). * * @return time zone name * @see com.vaadin.ui.DateField#setTimeZone */ String timeZone() default ""; /** * Get lenient mode. * * @return true for lenient mode * @see com.vaadin.ui.DateField#setLenient */ boolean lenient() default false; } /** * Specifies how a Java property should be edited in Vaadin using a {@link com.vaadin.ui.AbstractTextField}. * * @see FieldBuilder.AbstractField * @see FieldBuilder */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) @Documented public @interface AbstractTextField { /** * Get the {@link AbstractTextField} type that will edit the property. * * @return field type */ Class type() default com.vaadin.ui.TextField.class; /** * Get the representation of null. * * @return representation for null value * @see com.vaadin.ui.AbstractTextField#setNullRepresentation */ String nullRepresentation() default "null"; /** * Get whether null value may be set. * * @return whether null value is allowed * @see com.vaadin.ui.AbstractTextField#setNullSettingAllowed */ boolean nullSettingAllowed() default false; /** * Get text change event mode. * * @return text change event mode * @see com.vaadin.ui.AbstractTextField#setTextChangeEventMode */ com.vaadin.ui.AbstractTextField.TextChangeEventMode textChangeEventMode() default com.vaadin.ui.AbstractTextField.TextChangeEventMode.LAZY; /** * Get text change event timeout. * * @return text change event timeout in seconds, or -1 to not override * @see com.vaadin.ui.AbstractTextField#setTextChangeTimeout */ int textChangeTimeout() default -1; /** * Get the input prompt. * * @return input prompt * @see com.vaadin.ui.AbstractTextField#setInputPrompt */ String inputPrompt() default ""; /** * Get the number of columns. * * @return number of columns, or zero to not override * @see com.vaadin.ui.AbstractTextField#setColumns */ int columns() default 0; /** * Get the maximum length. * * @return maximum length, or -1 to not override * @see com.vaadin.ui.AbstractTextField#setMaxLength */ int maxLength() default -1; } /** * Specifies how a Java property should be edited in Vaadin using a {@link com.vaadin.ui.TextField}. * * @see FieldBuilder.AbstractField * @see FieldBuilder.AbstractTextField * @see FieldBuilder */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) @Documented public @interface TextField { /** * Get the {@link TextField} type that will edit the property. * * @return field type */ Class type() default com.vaadin.ui.TextField.class; } /** * Specifies how a Java property should be edited in Vaadin using a {@link com.vaadin.ui.TextArea}. * * @see FieldBuilder.AbstractField * @see FieldBuilder.AbstractTextField * @see FieldBuilder */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) @Documented public @interface TextArea { /** * Get the {@link TextArea} type that will edit the property. * * @return field type */ Class type() default com.vaadin.ui.TextArea.class; /** * Set the number of rows. * * @return number of rows, or -1 for none * @see com.vaadin.ui.TextArea#setRows */ int rows() default -1; /** * Set wordwrap mode. * * @return word wrap mode * @see com.vaadin.ui.TextArea#setWordwrap */ boolean wordwrap() default true; } /** * Specifies how a Java property should be edited in Vaadin using a {@link com.vaadin.ui.PasswordField}. * * @see FieldBuilder.AbstractField * @see FieldBuilder.AbstractTextField * @see FieldBuilder */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) @Documented public @interface PasswordField { /** * Get the {@link PasswordField} type that will edit the property. * * @return field type */ Class type() default com.vaadin.ui.PasswordField.class; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy