Java tutorial
/** * Copyright (c) 2015 Bosch Software Innovations GmbH and others. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html */ package org.eclipse.hawkbit.ui.common; import static com.google.common.base.Preconditions.checkNotNull; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; import org.eclipse.hawkbit.ui.artifacts.smtable.SoftwareModuleAddUpdateWindow; import org.eclipse.hawkbit.ui.components.SPUIComponentProvider; import org.eclipse.hawkbit.ui.decorators.SPUIButtonStyleNoBorderWithIcon; import org.eclipse.hawkbit.ui.management.targettable.TargetAddUpdateWindowLayout; import org.eclipse.hawkbit.ui.utils.SPUIStyleDefinitions; import org.eclipse.hawkbit.ui.utils.UIComponentIdProvider; import org.eclipse.hawkbit.ui.utils.UIMessageIdProvider; import org.eclipse.hawkbit.ui.utils.VaadinMessageSource; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; import org.vaadin.hene.flexibleoptiongroup.FlexibleOptionGroupItemComponent; import com.google.common.collect.Maps; import com.vaadin.data.Container.ItemSetChangeEvent; import com.vaadin.data.Container.ItemSetChangeListener; import com.vaadin.data.Property.ValueChangeEvent; import com.vaadin.data.Property.ValueChangeListener; import com.vaadin.data.Validator; import com.vaadin.data.validator.NullValidator; import com.vaadin.event.FieldEvents.TextChangeEvent; import com.vaadin.event.FieldEvents.TextChangeListener; import com.vaadin.event.FieldEvents.TextChangeNotifier; import com.vaadin.event.ShortcutAction.KeyCode; import com.vaadin.server.FontAwesome; import com.vaadin.ui.AbstractComponent; import com.vaadin.ui.AbstractField; import com.vaadin.ui.AbstractLayout; import com.vaadin.ui.AbstractOrderedLayout; import com.vaadin.ui.Alignment; import com.vaadin.ui.Button; import com.vaadin.ui.Button.ClickEvent; import com.vaadin.ui.Button.ClickListener; import com.vaadin.ui.CheckBox; import com.vaadin.ui.Component; import com.vaadin.ui.Field; import com.vaadin.ui.GridLayout; import com.vaadin.ui.HorizontalLayout; import com.vaadin.ui.Label; import com.vaadin.ui.Link; import com.vaadin.ui.TabSheet; import com.vaadin.ui.Table; import com.vaadin.ui.VerticalLayout; import com.vaadin.ui.Window; import com.vaadin.ui.themes.ValoTheme; /** * * Table pop-up-windows including a minimize and close icon in the upper right * corner and a save and cancel button at the bottom. Is not intended to reuse. * */ public class CommonDialogWindow extends Window { private static final long serialVersionUID = 1L; private final VerticalLayout mainLayout = new VerticalLayout(); private final String caption; private final Component content; private final String helpLink; private Button saveButton; private Button cancelButton; private HorizontalLayout buttonsLayout; private final ClickListener cancelButtonClickListener; private final ClickListener closeClickListener = this::onCloseEvent; private final transient Map<Component, Object> orginalValues; private List<AbstractField<?>> allComponents; private final VaadinMessageSource i18n; private transient SaveDialogCloseListener closeListener; /** * Constructor. * * @param caption * the caption * @param content * the content * @param helpLink * the helpLinks * @param closeListener * the saveDialogCloseListener * @param cancelButtonClickListener * the cancelButtonClickListener * @param layout * the abstract layout * @param i18n * the i18n service */ public CommonDialogWindow(final String caption, final Component content, final String helpLink, final SaveDialogCloseListener closeListener, final ClickListener cancelButtonClickListener, final AbstractLayout layout, final VaadinMessageSource i18n) { checkNotNull(closeListener); this.caption = caption; this.content = content; this.helpLink = helpLink; this.closeListener = closeListener; this.cancelButtonClickListener = cancelButtonClickListener; this.allComponents = getAllComponents(layout); this.orginalValues = Maps.newHashMapWithExpectedSize(allComponents.size()); this.i18n = i18n; init(); } private void onCloseEvent(final ClickEvent clickEvent) { if (!clickEvent.getButton().equals(saveButton)) { close(); return; } if (!closeListener.canWindowSaveOrUpdate()) { return; } closeListener.saveOrUpdate(); if (closeListener.canWindowClose()) { close(); } } @Override public void close() { super.close(); orginalValues.clear(); removeListeners(); allComponents.clear(); this.saveButton.setEnabled(false); } private void removeListeners() { for (final AbstractField<?> field : allComponents) { removeTextListener(field); removeValueChangeListener(field); removeItemSetChangeistener(field); } } private void removeItemSetChangeistener(final AbstractField<?> field) { if (!(field instanceof Table)) { return; } for (final Object listener : field.getListeners(ItemSetChangeEvent.class)) { if (listener instanceof ChangeListener) { ((Table) field).removeItemSetChangeListener((ChangeListener) listener); } } } private void removeTextListener(final AbstractField<?> field) { if (!(field instanceof TextChangeNotifier)) { return; } for (final Object listener : field.getListeners(TextChangeEvent.class)) { if (listener instanceof ChangeListener) { ((TextChangeNotifier) field).removeTextChangeListener((ChangeListener) listener); } } } private void removeValueChangeListener(final AbstractField<?> field) { for (final Object listener : field.getListeners(ValueChangeEvent.class)) { if (listener instanceof ChangeListener) { field.removeValueChangeListener((ChangeListener) listener); } } } private final void init() { if (content instanceof AbstractOrderedLayout) { ((AbstractOrderedLayout) content).setSpacing(true); ((AbstractOrderedLayout) content).setMargin(true); } if (content instanceof GridLayout) { addStyleName("marginTop"); } if (content != null) { mainLayout.addComponent(content); mainLayout.setExpandRatio(content, 1.0F); } createMandatoryLabel(); final HorizontalLayout buttonLayout = createActionButtonsLayout(); mainLayout.addComponent(buttonLayout); mainLayout.setComponentAlignment(buttonLayout, Alignment.TOP_CENTER); setCaption(caption); setCaptionAsHtml(true); setContent(mainLayout); setResizable(false); center(); setModal(true); addStyleName("fontsize"); setOrginaleValues(); addComponentListeners(); } /** * saves the original values in a Map so we can use them for detecting * changes */ public final void setOrginaleValues() { for (final AbstractField<?> field : allComponents) { Object value = field.getValue(); if (field instanceof Table) { value = ((Table) field).getContainerDataSource().getItemIds(); } orginalValues.put(field, value); } saveButton.setEnabled(isSaveButtonEnabledAfterValueChange(null, null)); } /** * Clears the original values in case no value changed check is wished */ public final void clearOriginalValues() { orginalValues.clear(); } protected void addCloseListenerForSaveButton() { saveButton.addClickListener(closeClickListener); } protected void addCloseListenerForCancelButton() { cancelButton.addClickListener(closeClickListener); } /** * adds a listener to a component. Depending on the type of component a * valueChange-, textChange- or itemSetChangeListener will be added. */ public void addComponentListeners() { // avoid duplicate registration removeListeners(); for (final AbstractField<?> field : allComponents) { if (field instanceof TextChangeNotifier) { ((TextChangeNotifier) field).addTextChangeListener(new ChangeListener(field)); } if (field instanceof Table) { ((Table) field).addItemSetChangeListener(new ChangeListener(field)); } field.addValueChangeListener(new ChangeListener(field)); } } private boolean isSaveButtonEnabledAfterValueChange(final Component currentChangedComponent, final Object newValue) { return isMandatoryFieldNotEmptyAndValid(currentChangedComponent, newValue) && isValuesChanged(currentChangedComponent, newValue); } private boolean isValuesChanged(final Component currentChangedComponent, final Object newValue) { for (final AbstractField<?> field : allComponents) { Object originalValue = orginalValues.get(field); if (field instanceof CheckBox && originalValue == null) { originalValue = Boolean.FALSE; } final Object currentValue = getCurrentValue(currentChangedComponent, newValue, field); if (!Objects.equals(originalValue, currentValue)) { return true; } } return false; } private static Object getCurrentValue(final Component currentChangedComponent, final Object newValue, final AbstractField<?> field) { Object currentValue = field.getValue(); if (field instanceof Table) { currentValue = ((Table) field).getContainerDataSource().getItemIds(); } if (field.equals(currentChangedComponent)) { currentValue = newValue; } return currentValue; } private boolean shouldMandatoryLabelShown() { for (final AbstractField<?> field : allComponents) { if (field.isRequired()) { return true; } } return false; } private boolean isMandatoryFieldNotEmptyAndValid(final Component currentChangedComponent, final Object newValue) { boolean valid = true; final List<AbstractField<?>> requiredComponents = allComponents.stream().filter(AbstractField::isRequired) .filter(AbstractField::isEnabled).collect(Collectors.toList()); requiredComponents .addAll(allComponents.stream().filter(this::hasNullValidator).collect(Collectors.toList())); for (final AbstractField field : requiredComponents) { Object value = getCurrentValue(currentChangedComponent, newValue, field); if (Set.class.equals(field.getType())) { value = emptyToNull((Collection<?>) value); } if (value == null) { return false; } // We need to loop through all of components for validity testing. // Otherwise the UI will only mark the first field with errors and // then stop. Setting the value is necessary because not all // required input fields have empty string validator, but emptiness // is checked during isValid() call. Setting the value could be // redundant, check if it could be removed in the future. field.setValue(value); if (!field.isValid()) { valid = false; } } return valid; } private static Object emptyToNull(final Collection<?> c) { return CollectionUtils.isEmpty(c) ? null : c; } private boolean hasNullValidator(final Component component) { if (component instanceof AbstractField<?>) { final AbstractField<?> fieldComponent = (AbstractField<?>) component; for (final Validator validator : fieldComponent.getValidators()) { if (validator instanceof NullValidator) { return true; } } } return false; } private static List<AbstractField<?>> getAllComponents(final AbstractLayout abstractLayout) { final List<AbstractField<?>> components = new ArrayList<>(); final Iterator<Component> iterate = abstractLayout.iterator(); while (iterate.hasNext()) { final Component c = iterate.next(); if (c instanceof AbstractLayout) { components.addAll(getAllComponents((AbstractLayout) c)); } if (c instanceof AbstractField) { components.add((AbstractField<?>) c); } if (c instanceof FlexibleOptionGroupItemComponent) { components.add(((FlexibleOptionGroupItemComponent) c).getOwner()); } if (c instanceof TabSheet) { final TabSheet tabSheet = (TabSheet) c; components.addAll(getAllComponentsFromTabSheet(tabSheet)); } } return components; } private static List<AbstractField<?>> getAllComponentsFromTabSheet(final TabSheet tabSheet) { final List<AbstractField<?>> components = new ArrayList<>(); for (final Iterator<Component> i = tabSheet.iterator(); i.hasNext();) { final Component component = i.next(); if (component instanceof AbstractLayout) { components.addAll(getAllComponents((AbstractLayout) component)); } } return components; } private HorizontalLayout createActionButtonsLayout() { buttonsLayout = new HorizontalLayout(); buttonsLayout.setSizeFull(); buttonsLayout.setSpacing(true); buttonsLayout.setSpacing(true); buttonsLayout.addStyleName("actionButtonsMargin"); createSaveButton(); createCancelButton(); addHelpLink(); return buttonsLayout; } private void createMandatoryLabel() { if (!shouldMandatoryLabelShown()) { return; } final Label mandatoryLabel = new Label(i18n.getMessage("label.mandatory.field")); mandatoryLabel.addStyleName(SPUIStyleDefinitions.SP_TEXTFIELD_ERROR + " " + ValoTheme.LABEL_TINY); if (content instanceof TargetAddUpdateWindowLayout) { ((TargetAddUpdateWindowLayout) content).getFormLayout().addComponent(mandatoryLabel); } else if (content instanceof SoftwareModuleAddUpdateWindow) { ((SoftwareModuleAddUpdateWindow) content).getFormLayout().addComponent(mandatoryLabel); } mainLayout.addComponent(mandatoryLabel); } private void createCancelButton() { cancelButton = SPUIComponentProvider.getButton(UIComponentIdProvider.CANCEL_BUTTON, i18n.getMessage(UIMessageIdProvider.BUTTON_CANCEL), "", "", true, FontAwesome.TIMES, SPUIButtonStyleNoBorderWithIcon.class); cancelButton.setSizeUndefined(); cancelButton.addStyleName("default-color"); addCloseListenerForCancelButton(); if (cancelButtonClickListener != null) { cancelButton.addClickListener(cancelButtonClickListener); } buttonsLayout.addComponent(cancelButton); buttonsLayout.setComponentAlignment(cancelButton, Alignment.MIDDLE_LEFT); buttonsLayout.setExpandRatio(cancelButton, 1.0F); } private void createSaveButton() { saveButton = SPUIComponentProvider.getButton(UIComponentIdProvider.SAVE_BUTTON, i18n.getMessage(UIMessageIdProvider.BUTTON_SAVE), "", "", true, FontAwesome.SAVE, SPUIButtonStyleNoBorderWithIcon.class); saveButton.setSizeUndefined(); saveButton.addStyleName("default-color"); addCloseListenerForSaveButton(); saveButton.setEnabled(false); saveButton.setClickShortcut(KeyCode.ENTER); buttonsLayout.addComponent(saveButton); buttonsLayout.setComponentAlignment(saveButton, Alignment.MIDDLE_RIGHT); buttonsLayout.setExpandRatio(saveButton, 1.0F); } private void addHelpLink() { if (StringUtils.isEmpty(helpLink)) { return; } final Link helpLinkComponent = SPUIComponentProvider.getHelpLink(i18n, helpLink); buttonsLayout.addComponent(helpLinkComponent); buttonsLayout.setComponentAlignment(helpLinkComponent, Alignment.MIDDLE_RIGHT); } public AbstractComponent getButtonsLayout() { return this.buttonsLayout; } private class ChangeListener implements ValueChangeListener, TextChangeListener, ItemSetChangeListener { private static final long serialVersionUID = 1L; private final Field<?> field; public ChangeListener(final Field<?> field) { this.field = field; } @Override public void textChange(final TextChangeEvent event) { saveButton.setEnabled(isSaveButtonEnabledAfterValueChange(field, event.getText())); } @Override public void valueChange(final ValueChangeEvent event) { saveButton.setEnabled(isSaveButtonEnabledAfterValueChange(field, field.getValue())); } @Override public void containerItemSetChange(final ItemSetChangeEvent event) { if (!(field instanceof Table)) { return; } final Table table = (Table) field; saveButton.setEnabled( isSaveButtonEnabledAfterValueChange(table, table.getContainerDataSource().getItemIds())); } } /** * Adds the component manually to the allComponents-List and adds a * ValueChangeListener to it. Necessary in Update Distribution Type as the * CheckBox concerned is an ItemProperty... * * @param component * AbstractField */ public void updateAllComponents(final AbstractField<?> component) { allComponents.add(component); component.addValueChangeListener(new ChangeListener(component)); } public VerticalLayout getMainLayout() { return mainLayout; } public void setSaveButtonEnabled(final boolean enabled) { saveButton.setEnabled(enabled); } public void setCancelButtonEnabled(final boolean enabled) { cancelButton.setEnabled(enabled); } /** * Check if the safe action can executed. After a the save action the * listener checks if the dialog can closed. * */ public interface SaveDialogCloseListener { /** * Checks if the safe action can executed. * * @return <code>true</code> = save action can executed * <code>false</code> = cannot execute safe action . */ boolean canWindowSaveOrUpdate(); /** * Checks if the window can closed after the safe action is executed * * @return <code>true</code> = window will close <code>false</code> = * will not closed. */ default boolean canWindowClose() { return true; } /** * Saves/Updates action. Is called if canWindowSaveOrUpdate is * <code>true</code>. * */ void saveOrUpdate(); } /** * Updates the field allComponents. All components existing on the given * layout are added to the list of allComponents * * @param layout * AbstractLayout */ public void updateAllComponents(final AbstractLayout layout) { allComponents = getAllComponents(layout); } }