Java tutorial
/******************************************************************************* * This file is part of OpenNMS(R). * * Copyright (C) 2013-2014 The OpenNMS Group, Inc. * OpenNMS(R) is Copyright (C) 1999-2014 The OpenNMS Group, Inc. * * OpenNMS(R) is a registered trademark of The OpenNMS Group, Inc. * * OpenNMS(R) is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation, either version 3 of the License, * or (at your option) any later version. * * OpenNMS(R) is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with OpenNMS(R). If not, see: * http://www.gnu.org/licenses/ * * For more information contact: * OpenNMS(R) Licensing <license@opennms.org> * http://www.opennms.org/ * http://www.opennms.com/ *******************************************************************************/ package org.opennms.features.vaadin.jmxconfiggenerator.ui.mbeans; import com.google.common.base.Predicate; import com.google.common.collect.Collections2; import com.google.common.collect.Maps; import com.vaadin.data.Container; import com.vaadin.data.Item; import com.vaadin.data.Property; import com.vaadin.data.Validator; import com.vaadin.data.Validator.InvalidValueException; import com.vaadin.data.validator.StringLengthValidator; import com.vaadin.event.FieldEvents; import com.vaadin.ui.AbstractComponent; import com.vaadin.ui.CheckBox; import com.vaadin.ui.ComboBox; import com.vaadin.ui.Component; import com.vaadin.ui.Field; import com.vaadin.ui.Table; import com.vaadin.ui.TableFieldFactory; import com.vaadin.ui.TextField; import org.opennms.features.vaadin.jmxconfiggenerator.Config; import org.opennms.features.vaadin.jmxconfiggenerator.data.MetaAttribItem; import org.opennms.features.vaadin.jmxconfiggenerator.data.MetaAttribItem.AttribType; import org.opennms.features.vaadin.jmxconfiggenerator.data.SelectableBeanItemContainer; import org.opennms.features.vaadin.jmxconfiggenerator.data.SelectionValueChangedListener; import org.opennms.netmgt.vaadin.core.UIHelper; import org.opennms.features.vaadin.jmxconfiggenerator.ui.mbeans.validation.AttributeNameValidator; import org.opennms.features.vaadin.jmxconfiggenerator.ui.mbeans.validation.UniqueAttributeNameValidator; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; /** * * @author Markus von Rden */ public class AttributesTable<T> extends Table implements SelectionValueChangedListener { private static Object[] computeKey(Object... input) { return input; } // This TableFieldFactory simply uses the already created fields and returns them (or null). private class ReferenceTableFieldFactory implements TableFieldFactory { @Override public Field<?> createField(Container container, Object itemId, Object propertyId, Component uiContext) { Object[] key = computeKey(itemId, propertyId); Field<?> field = findFieldByKey(fields, key); return field; } } // The default field factory for the attributes table. private class AttributesTableFieldFactory implements TableFieldFactory { private final Validator nameValidator = new AttributeNameValidator(); private final Validator lengthValidator = new StringLengthValidator( String.format("Maximum length is %d", Config.ATTRIBUTES_ALIAS_MAX_LENGTH), 0, Config.ATTRIBUTES_ALIAS_MAX_LENGTH, false); private final Validator uniqueAttributeNameValidator = new UniqueAttributeNameValidator(nameProvider, new UniqueAttributeNameValidator.FieldProvider() { public Map<Object, Field<String>> getObjectFieldMap() { Map<Object, Field<String>> validationMap = new HashMap<>(); for (Object eachSelectedAttribute : getContainerDataSource().getSelectedAttributes()) { validationMap.put(eachSelectedAttribute, aliasFieldsMap.get(eachSelectedAttribute)); } return validationMap; } }); @Override public Field<?> createField(final Container container, final Object itemId, Object propertyId, Component uiContext) { Field<?> field = null; if (MetaAttribItem.ALIAS.equals(propertyId.toString())) { field = new TableTextFieldWrapper(createAlias(itemId)); } if (MetaAttribItem.NAME.equals(propertyId.toString())) { field = createName(itemId); } if (MetaAttribItem.SELECTED.equals(propertyId.toString())) { field = createSelected(itemId); } if (MetaAttribItem.TYPE.equals(propertyId.toString())) { field = createType(itemId); } return field; } private TextField createName(final Object itemId) { TextField field = new TextField(); field.setWidth(100, Unit.PERCENTAGE); field.setReadOnly(true); return field; } private CheckBox createSelected(final Object itemId) { final CheckBox c = new CheckBox(); c.setBuffered(false); c.setImmediate(true); c.addValueChangeListener(new ValueChangeListener() { @Override public void valueChange(Property.ValueChangeEvent event) { Collection<Field<?>> fields = Collections2.filter(getFieldsForItemId(itemId), new Predicate<Field<?>>() { @Override public boolean apply(Field<?> eachField) { return eachField != c; } }); enableFields(fields, c.getValue()); } }); c.addValueChangeListener(validateOnValueChangeListener); return c; } private ComboBox createType(Object itemId) { ComboBox select = new ComboBox(); for (AttribType type : AttribType.values()) { select.addItem(type.name()); } select.setValue(AttribType.gauge); select.setNullSelectionAllowed(false); select.setData(itemId); select.setBuffered(false); select.setImmediate(true); select.addValueChangeListener(validateOnValueChangeListener); return select; } private TextField createAlias(Object itemId) { final TextField tf = new TextField(); tf.setValidationVisible(false); tf.setBuffered(false); tf.setImmediate(true); tf.setRequired(true); tf.setWidth(300, Unit.PIXELS); tf.setMaxLength(Config.ATTRIBUTES_ALIAS_MAX_LENGTH); tf.setRequiredError("You must provide a name."); tf.addValidator(nameValidator); tf.addValidator(lengthValidator); tf.addValidator(uniqueAttributeNameValidator); tf.addValueChangeListener(validateOnValueChangeListener); tf.setData(itemId); tf.setTextChangeTimeout(200); // by default there is no validation when updating a field, so we manually apply that tf.addTextChangeListener(new FieldEvents.TextChangeListener() { @Override public void textChange(FieldEvents.TextChangeEvent event) { tf.setValue(event.getText()); UIHelper.validateField(tf, true); } }); return tf; } } private final Map<Object, Field<String>> aliasFieldsMap = new HashMap<>(); private final Map<Object[], Field<?>> fields = new HashMap<>(); private final NameProvider nameProvider; private final TableFieldFactory tableFieldFactory; private final MBeansController controller; private final ValueChangeListener validateOnValueChangeListener = new ValueChangeListener() { @Override public void valueChange(Property.ValueChangeEvent event) { if (!blockValidation && isVisible()) { controller.validateCurrentSelection(); } } }; private Item parentItem; private Object parentData; private boolean blockValidation; public AttributesTable(NameProvider nameProvider, MBeansController controller) { this.nameProvider = nameProvider; this.controller = controller; this.tableFieldFactory = new AttributesTableFieldFactory(); setSizeFull(); setSelectable(false); setEditable(true); setValidationVisible(true); setReadOnly(true); setImmediate(true); setTableFieldFactory(new ReferenceTableFieldFactory()); setValidationVisible(false); } public void modelChanged(Item parentItem, Object parentData, SelectableBeanItemContainer<T> container) { try { blockValidation = true; if (getParentItem() == parentItem) return; setParentItem(parentItem); setParentData(parentData); // we initialize the tableFieldFactory because by default pagination is enabled, which // causes the table not to know all fields. But the validation is bound to a field, therefore // we need to create all fields in advance. aliasFieldsMap.clear(); fields.clear(); for (Object eachItemId : container.getItemIds()) { for (Object eachPropertyId : container.getContainerPropertyIds()) { Field<?> eachField = tableFieldFactory.createField(container, eachItemId, eachPropertyId, this); if (eachField != null) { fields.put(computeKey(eachItemId, eachPropertyId), eachField); if (MetaAttribItem.ALIAS.equals(eachPropertyId)) { aliasFieldsMap.put(eachItemId, (Field<String>) eachField); // we have to set this manually, otherwise validation does not work Item item = container.getItem(eachItemId); Property property = item.getItemProperty(eachPropertyId); ((Field<String>) eachField).setValue((String) property.getValue()); } } } } setContainerDataSource(container); setVisibleColumns(MetaAttribItem.SELECTED, MetaAttribItem.NAME, MetaAttribItem.ALIAS, MetaAttribItem.TYPE); // enable/disable fields according to parent selection Property itemProperty = parentItem.getItemProperty(MBeansTree.MetaMBeansTreeItem.SELECTED); enableFields(fields.values(), (Boolean) itemProperty.getValue()); if ((Boolean) itemProperty.getValue()) { updateCheckBoxes(); } // we initially validate to ensure the ui is ok ant not in a broken state. // Only to allow this validation we have to do the initialization of the fields (See above) manually. // This is not ideal, but it seems to be the only way. validateFields(true); } finally { blockValidation = false; } } public SelectableBeanItemContainer<T> getContainerDataSource() { return (SelectableBeanItemContainer<T>) super.getContainerDataSource(); } private static Field<?> findFieldByKey(Map<Object[], Field<?>> inputMap, Object... key) { for (Object[] eachKey : inputMap.keySet()) { if (Arrays.equals(eachKey, key)) { return inputMap.get(eachKey); } } return null; } private void enableFields(Collection<Field<?>> fields, boolean enabled) { for (Field<?> eachField : fields) { enableField(eachField, enabled); } } private void enableField(Field<?> field, boolean enabled) { field.setEnabled(enabled); // we revert changes, when we disable the fields if (!enabled) { field.discard(); // hide validation errors when disabled if (field instanceof AbstractComponent) { ((AbstractComponent) field).setComponentError(null); } } } @Override public void selectionValueChanged(SelectionValueChangedEvent selectionValueChangedEvent) { if (selectionValueChangedEvent.getBean() == getParentData()) { for (Object eachRowId : getItemIds()) { CheckBox checkBox = getCheckBoxForRow(eachRowId); updateCheckBox(checkBox, eachRowId, selectionValueChangedEvent.getNewValue()); checkBox.setEnabled(selectionValueChangedEvent.getNewValue()); // we have to check/uncheck the checkbox according to the parent's selection } } } private void updateCheckBoxes() { for (Object eachRowId : getItemIds()) { CheckBox checkBox = getCheckBoxForRow(eachRowId); updateCheckBox(checkBox, eachRowId, checkBox.getValue()); } } private void updateCheckBox(CheckBox checkBox, Object rowId, boolean enabled) { checkBox.setValue(enabled); Collection<Field<?>> columns = getFieldsForItemId(rowId); columns.remove(checkBox); enableFields(columns, checkBox.getValue()); } @Override public void validate() throws InvalidValueException { super.validate(); validateFields(false); } private void validateFields(boolean swallowValidationExceptions) throws InvalidValueException { // Some fields may or may not be selected. We have to consider this in the overall validation // therefore we filter out all not selected element. final Map<Object, Field<String>> filteredFieldsToValidate = Maps.filterEntries(aliasFieldsMap, new Predicate<Map.Entry<Object, Field<String>>>() { @Override public boolean apply(Map.Entry<Object, Field<String>> input) { return getContainerDataSource().isSelected((T) input.getKey()); } }); UIHelper.validateFields(new ArrayList<Field<?>>(filteredFieldsToValidate.values()), swallowValidationExceptions); } @Override public void discard() throws SourceException { super.discard(); for (Field<?> eachField : fields.values()) { eachField.discard(); } } @Override public boolean isValid() { try { validate(); return true; } catch (InvalidValueException invex) { return false; } } boolean isDirty() { for (Field<?> eachField : fields.values()) { if (eachField.isModified()) { return true; } } return false; } private List<Field<?>> getFieldsForItemId(Object itemId) { List<Field<?>> fieldList = new ArrayList<>(); for (Map.Entry<Object[], Field<?>> eachEntry : fields.entrySet()) { if (Objects.equals(itemId, eachEntry.getKey()[0])) { fieldList.add(eachEntry.getValue()); } } return fieldList; } private CheckBox getCheckBoxForRow(Object rowId) { for (Field<?> eachField : getFieldsForItemId(rowId)) { if (eachField instanceof CheckBox) { return (CheckBox) eachField; } } return null; } private Item getParentItem() { return parentItem; } private void setParentItem(Item parentItem) { this.parentItem = parentItem; } private Object getParentData() { return parentData; } private void setParentData(Object parentData) { this.parentData = parentData; } }