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

org.dellroad.stuff.vaadin8.GridColumnScanner Maven / Gradle / Ivy

The newest version!

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

package org.dellroad.stuff.vaadin8;

import com.vaadin.data.PropertySet;
import com.vaadin.data.ValueProvider;
import com.vaadin.ui.Grid;
import com.vaadin.ui.renderers.Renderer;
import com.vaadin.util.ReflectTools;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;

import org.dellroad.stuff.java.AnnotationUtil;
import org.dellroad.stuff.java.MethodAnnotationScanner;
import org.dellroad.stuff.java.ReflectUtil;

/**
 * Scans a Java class hierarchy for {@link GridColumn @GridColumn} annotated getter methods, and auto-generates and
 * configures corresponding {@link Grid.Column}s on a {@link Grid} instance.
 *
 * 

* This class will also introspect for {@link FieldBuilder} annotations and * {@linkplain Grid.Column#setEditorBinding(Binder.Binding) configure editor bindings} accordingly via * {@link FieldBuilder#setEditorBindings}. * *

* See {@link GridColumn} for an example of usage. * * @param Java class to be introspected * @see GridColumn @GridColumn * @see FieldBuilder#setEditorBindings */ public class GridColumnScanner { private final Class type; private final LinkedHashMap.MethodInfo> columnMap = new LinkedHashMap<>(); /** * Constructor. * * @param type Java class to be introspected * @throws IllegalArgumentException if {@code type} is null * @throws IllegalArgumentException if an annotated method with no {@linkplain GridColumn#value property name specified} * has a name which cannot be interpreted as a bean property "getter" method * @throws IllegalArgumentException if {@code type} has two {@link GridColumn @GridColumn}-annotated * fields or methods with the same {@linkplain GridColumn#value property name} */ public GridColumnScanner(Class type) { // Sanity check if (type == null) throw new IllegalArgumentException("null type"); this.type = type; // Scan for @GridColumn annotations final Set.MethodInfo> gridColumnMethods = new MethodAnnotationScanner(this.type, GridColumn.class).findAnnotatedMethods(); // Check for duplicate @GridColumn names final Comparator methodComparator = Comparator.comparing(Method::getDeclaringClass, ReflectUtil.getClassComparator(false)); final HashMap.MethodInfo> unorderedColumnMap = new HashMap<>(); for (MethodAnnotationScanner.MethodInfo methodInfo : gridColumnMethods) { final String propertyName = this.getPropertyName(methodInfo); // Check for name conflict final MethodAnnotationScanner.MethodInfo previousInfo = unorderedColumnMap.get(propertyName); if (previousInfo == null) { unorderedColumnMap.put(propertyName, methodInfo); continue; } // If there is a name conflict, the sub-type method declaration wins switch (methodComparator.compare(previousInfo.getMethod(), methodInfo.getMethod())) { case 0: throw new IllegalArgumentException("duplicate @" + GridColumn.class.getSimpleName() + " declaration for property `" + propertyName + "' on method " + previousInfo.getMethod() + " and " + methodInfo.getMethod() + " declared in the same class"); case 1: unorderedColumnMap.put(propertyName, methodInfo); break; default: break; } } // Order columns final ArrayList.MethodInfo>> columnList = new ArrayList<>(unorderedColumnMap.entrySet()); Collections.sort(columnList, Comparator..MethodInfo>>comparingDouble( entry -> entry.getValue().getAnnotation().order()) .thenComparing(Map.Entry::getKey)); for (Map.Entry.MethodInfo> entry : columnList) this.columnMap.put(entry.getKey(), entry.getValue()); } /** * Build a {@link Grid} with columns auto-generated from introspected {@link GridColumn @GridColumn} annotations. * *

* The implementation in {@link GridColumnScanner} simply invokes {@link #buildGrid() this.buildGrid(Grid::withPropertySet)}. * * @return new {@link Grid} */ public Grid buildGrid() { return this.buildGrid(Grid::withPropertySet); } /** * Build a {@link Grid} with columns auto-generated from introspected {@link GridColumn @GridColumn} annotations, * using the given function to instantiate the {@link Grid}. * * @param creator function that creates a new {@link Grid} instance given a {@link PropertySet} * @param {@link Grid} type * @return new {@link Grid} * @throws IllegalArgumentException if {@code creator} is null */ public > G buildGrid(Function, G> creator) { // Sanity check if (creator == null) throw new IllegalArgumentException("null creator"); // Build property set final SimplePropertySet propertySet = new SimplePropertySet<>(this.type); for (Map.Entry.MethodInfo> e : this.columnMap.entrySet()) { final String propertyName = e.getKey(); final MethodAnnotationScanner.MethodInfo methodInfo = e.getValue(); final Method getter = methodInfo.getMethod(); final Method setter = methodInfo.getSetter(); propertySet.add(ReflectTools.convertPrimitiveType(getter.getReturnType()), propertyName, methodInfo.getAnnotation().caption(), getter, setter); } // Create grid final G grid = creator.apply(propertySet); // Set field editors new FieldBuilder<>(this.type).buildAndBind().setEditorBindings(grid); // Get default annotation final GridColumn defaults = GridColumnScanner.getDefaults(); // Modify columns for (Map.Entry.MethodInfo> e : this.columnMap.entrySet()) { final String propertyName = e.getKey(); final MethodAnnotationScanner.MethodInfo methodInfo = e.getValue(); final GridColumn annotation = methodInfo.getAnnotation(); // Get column final Grid.Column column = grid.getColumn(propertyName); // Apply annotation values AnnotationUtil.applyAnnotationValues(column, "set", annotation, defaults, (methodList, name) -> name, ReflectUtil::instantiate); // Special handling for setRenderer() method with two parameters if (annotation.renderer() != defaults.renderer() && annotation.valueProvider() != defaults.valueProvider()) this.setRenderer(column, annotation.renderer(), annotation.valueProvider()); } // Order columns grid.setColumns(this.columnMap.keySet().toArray(new String[this.columnMap.size()])); // Done return grid; } @SuppressWarnings({ "unchecked", "rawtypes" }) private void setRenderer( Grid.Column column, Class rendererType, Class valueProviderType) { final Renderer

renderer = (Renderer

)ReflectUtil.instantiate(rendererType); final ValueProvider valueProvider = (ValueProvider)ReflectUtil.instantiate(valueProviderType); column.setRenderer(valueProvider, renderer); } @GridColumn private static GridColumn getDefaults() { return AnnotationUtil.getAnnotation(GridColumn.class, GridColumnScanner.class, "getDefaults"); } /** * Get the property name from the annotation. * * @param methodInfo method info * @return property name */ protected String getPropertyName(MethodAnnotationScanner.MethodInfo methodInfo) { return methodInfo.getAnnotation().value().length() > 0 ? methodInfo.getAnnotation().value() : methodInfo.getMethodPropertyName(); } /** * Get the type associated with this instance. * * @return backing object type */ public Class getType() { return this.type; } /** * Get the annotations found through introspection keyed by property name. * * @return columns keyed by property name and sorted based on {@link GridColumn#order}, then property name */ public Map.MethodInfo> getColumnMap() { return Collections.unmodifiableMap(this.columnMap); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy