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

com.holonplatform.vaadin7.internal.components.DefaultPropertyViewGroup Maven / Gradle / Ivy

/*
 * Copyright 2016-2017 Axioma srl.
 * 
 * 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.holonplatform.vaadin7.internal.components;

import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import com.holonplatform.core.internal.utils.ObjectUtils;
import com.holonplatform.core.property.Property;
import com.holonplatform.core.property.PropertyBox;
import com.holonplatform.core.property.PropertyRenderer;
import com.holonplatform.core.property.PropertyRendererRegistry.NoSuitableRendererAvailableException;
import com.holonplatform.core.property.VirtualProperty;
import com.holonplatform.vaadin7.Registration;
import com.holonplatform.vaadin7.components.PropertyBinding;
import com.holonplatform.vaadin7.components.PropertyBinding.PostProcessor;
import com.holonplatform.vaadin7.components.PropertyValueComponentSource;
import com.holonplatform.vaadin7.components.PropertyViewGroup;
import com.holonplatform.vaadin7.components.ValueComponent;
import com.holonplatform.vaadin7.components.ViewComponent;

/**
 * Default {@link PropertyViewGroup} implementation.
 *
 * @since 5.0.0
 */
public class DefaultPropertyViewGroup implements PropertyViewGroup, PropertyValueComponentSource {

	private static final long serialVersionUID = -2110591918893531742L;

	/**
	 * Current value
	 */
	private PropertyBox value;

	/**
	 * Property set
	 */
	@SuppressWarnings("rawtypes")
	private final List properties = new LinkedList<>();

	/**
	 * Bound ViewComponents
	 */
	@SuppressWarnings("rawtypes")
	private final Map propertyViews = new HashMap<>();

	/**
	 * Custom ViewComponentPropertyRenderers
	 */
	@SuppressWarnings("rawtypes")
	private final Map propertyRenderers = new HashMap<>(8);

	/**
	 * Hidden properties
	 */
	@SuppressWarnings("rawtypes")
	private final Map hiddenProperties = new HashMap<>();

	/**
	 * ViewComponent post-processors
	 */
	private final List>> postProcessors = new LinkedList<>();

	/**
	 * Value change listeners
	 */
	private final List> valueChangeListeners = new LinkedList<>();

	/**
	 * Ignore missing ViewComponents
	 */
	private boolean ignoreMissingViewComponent = false;

	/**
	 * Constructor
	 */
	public DefaultPropertyViewGroup() {
		super();
	}

	/**
	 * Add a property to the property set
	 * @param property Property to add
	 */
	@SuppressWarnings("rawtypes")
	protected void addProperty(Property property) {
		if (property != null) {
			properties.add(property);
		}
	}

	/*
	 * (non-Javadoc)
	 * @see com.holonplatform.vaadin.components.PropertySetBound#getProperties()
	 */
	@SuppressWarnings("rawtypes")
	@Override
	public Iterable getProperties() {
		return Collections.unmodifiableList(properties);
	}

	/*
	 * (non-Javadoc)
	 * @see com.holonplatform.vaadin.components.PropertySetBound#hasProperty(com.holonplatform.core.property.Property)
	 */
	@Override
	public boolean hasProperty(Property property) {
		ObjectUtils.argumentNotNull(property, "Property must be not null");
		return properties.contains(property);
	}

	/*
	 * (non-Javadoc)
	 * @see com.holonplatform.vaadin.components.PropertySetBound#propertyStream()
	 */
	@SuppressWarnings("rawtypes")
	@Override
	public Stream propertyStream() {
		return properties.stream();
	}

	/*
	 * (non-Javadoc)
	 * @see com.holonplatform.vaadin.components.PropertyViewGroup#getViewComponents()
	 */
	@SuppressWarnings("rawtypes")
	@Override
	public Iterable getViewComponents() {
		return properties.stream().filter(p -> propertyViews.containsKey(p)).map(p -> propertyViews.get(p))
				.collect(Collectors.toList());
	}

	/*
	 * (non-Javadoc)
	 * @see com.holonplatform.vaadin.components.PropertyViewGroup#getViewComponent(com.holonplatform.core.property.
	 * Property)
	 */
	@SuppressWarnings("unchecked")
	@Override
	public  Optional> getViewComponent(Property property) {
		ObjectUtils.argumentNotNull(property, "Property must be not null");
		return Optional.ofNullable(propertyViews.get(property));
	}

	/*
	 * (non-Javadoc)
	 * @see com.holonplatform.vaadin.components.PropertyViewGroup#stream()
	 */
	@SuppressWarnings("unchecked")
	@Override
	public  Stream>> stream() {
		return propertyViews.entrySet().stream().map(e -> PropertyBinding.create(e.getKey(), e.getValue()));
	}

	/*
	 * (non-Javadoc)
	 * @see com.holonplatform.vaadin.components.PropertyValueComponentSource#getValueComponents()
	 */
	@SuppressWarnings("rawtypes")
	@Override
	public Iterable getValueComponents() {
		return properties.stream().filter(p -> propertyViews.containsKey(p)).map(p -> propertyViews.get(p))
				.collect(Collectors.toList());
	}

	/*
	 * (non-Javadoc)
	 * @see com.holonplatform.vaadin.components.PropertyValueComponentSource#getValueComponent(com.holonplatform.core.
	 * property.Property)
	 */
	@SuppressWarnings("unchecked")
	@Override
	public  Optional> getValueComponent(Property property) {
		ObjectUtils.argumentNotNull(property, "Property must be not null");
		return Optional.ofNullable(propertyViews.get(property));
	}

	/*
	 * (non-Javadoc)
	 * @see com.holonplatform.vaadin.components.PropertyValueComponentSource#streamOfValueComponents()
	 */
	@SuppressWarnings("unchecked")
	@Override
	public Stream>> streamOfValueComponents() {
		return propertyViews.entrySet().stream().map(e -> PropertyBinding.create(e.getKey(), e.getValue()));
	}

	/*
	 * (non-Javadoc)
	 * @see com.holonplatform.vaadin.components.PropertyViewGroup#clear()
	 */
	@Override
	public void clear() {
		setValue(null);
	}

	/*
	 * (non-Javadoc)
	 * @see com.holonplatform.vaadin.components.PropertyViewGroup#getValue()
	 */
	@Override
	public PropertyBox getValue() {
		return value;
	}

	/*
	 * (non-Javadoc)
	 * @see com.holonplatform.vaadin.components.ValueHolder#isEmpty()
	 */
	@Override
	public boolean isEmpty() {
		return getValue().propertyValues().filter(v -> v.hasValue()).findAny().isPresent();
	}

	/*
	 * (non-Javadoc)
	 * @see com.holonplatform.vaadin.components.ValueHolder#addValueChangeListener(com.holonplatform.vaadin.components.
	 * ValueHolder.ValueChangeListener)
	 */
	@Override
	public Registration addValueChangeListener(ValueChangeListener listener) {
		ObjectUtils.argumentNotNull(listener, "ValueChangeListener must be not null");
		valueChangeListeners.add(listener);
		return () -> removeValueChangeListener(listener);
	}

	/**
	 * Removes a {@link ValueChangeListener}.
	 * @param listener the listener to remove
	 */
	public void removeValueChangeListener(ValueChangeListener listener) {
		if (listener != null) {
			valueChangeListeners.remove(listener);
		}
	}

	/**
	 * Emits the value change event
	 * @param value the changed value
	 */
	protected void fireValueChange(PropertyBox value) {
		final ValueChangeEvent valueChangeEvent = new DefaultValueChangeEvent<>(this, value);
		valueChangeListeners.forEach(l -> l.valueChange(valueChangeEvent));
	}

	/*
	 * (non-Javadoc)
	 * @see com.holonplatform.vaadin.components.PropertyViewGroup#setValue(com.holonplatform.core.property.PropertyBox)
	 */
	@SuppressWarnings({ "unchecked", "rawtypes" })
	@Override
	public void setValue(PropertyBox propertyBox) {
		this.value = propertyBox;
		if (propertyBox == null) {
			// reset all values
			properties.forEach(p -> propertyViews.get(p).clear());
		} else {
			properties.forEach(p -> {
				final ViewComponent vc = propertyViews.get(p);
				if (vc != null) {
					Object value = getPropertyValue(propertyBox, p);
					if (value != null) {
						// ignore read-only
						boolean ro = vc.isReadOnly();
						if (ro)
							vc.setReadOnly(false);
						vc.setValue(value);
						if (ro)
							vc.setReadOnly(true);
					} else {
						vc.clear();
					}
				}
			});
		}

		// fire value change
		fireValueChange(propertyBox);
	}

	/**
	 * Get the value of given property using given propertyBox.
	 * @param  Property type
	 * @param propertyBox PropertyBox
	 * @param property Property
	 * @return Property value
	 */
	protected  T getPropertyValue(PropertyBox propertyBox, Property property) {
		if (VirtualProperty.class.isAssignableFrom(property.getClass())) {
			if (((VirtualProperty) property).getValueProvider() != null) {
				return ((VirtualProperty) property).getValueProvider().getPropertyValue(propertyBox);
			}
			return null;
		}
		if (propertyBox.contains(property)) {
			return propertyBox.getValue(property);
		}
		return null;
	}

	/**
	 * Whether to ignore missing property ViewComponents
	 * @return true if missing property ViewComponents must be ignored
	 */
	protected boolean isIgnoreMissingViewComponent() {
		return ignoreMissingViewComponent;
	}

	/**
	 * Set whether to ignore missing property ViewComponents
	 * @param ignoreMissingViewComponent true to ignore missing property ViewComponents
	 */
	protected void setIgnoreMissingViewComponent(boolean ignoreMissingViewComponent) {
		this.ignoreMissingViewComponent = ignoreMissingViewComponent;
	}

	/**
	 * Register a {@link ViewComponent} {@link PostProcessor}.
	 * @param postProcessor the post-processor to register
	 */
	protected void addViewComponentPostProcessor(PostProcessor> postProcessor) {
		if (postProcessor != null) {
			postProcessors.add(postProcessor);
		}
	}

	/**
	 * Set a property as hidden in UI
	 * @param property Property to set as hidden
	 */
	@SuppressWarnings("rawtypes")
	public void setPropertyHidden(Property property) {
		if (property != null && !hiddenProperties.containsKey(property)) {
			hiddenProperties.put(property, null);
		}
	}

	@SuppressWarnings("rawtypes")
	protected boolean isPropertyHidden(Property property) {
		return property != null && hiddenProperties.containsKey(property);
	}

	/**
	 * Set the {@link ViewComponentPropertyRenderer} to use with given property
	 * @param  Property type
	 * @param property Property
	 * @param renderer Renderer
	 */
	protected  void setPropertyRenderer(Property property, ViewComponentPropertyRenderer renderer) {
		if (property != null && renderer != null) {
			propertyRenderers.put(property, renderer);
		}
	}

	/**
	 * Render given property as a {@link ViewComponent}, using custom {@link ViewComponentPropertyRenderer} or default
	 * {@link Property#render(Class)} method
	 * @param  Property type
	 * @param property Property to render
	 * @return Rendered ViewComponent
	 */
	@SuppressWarnings({ "unchecked", "rawtypes" })
	protected  Optional render(Property property) {
		// check custom renderer
		if (propertyRenderers.containsKey(property)) {
			final ViewComponentPropertyRenderer r = propertyRenderers.get(property);
			return Optional.ofNullable(r.render(property));
		}
		// use registry
		return property.renderIfAvailable(ViewComponent.class);
	}

	/**
	 * Configure ViewComponent before binding
	 * @param property Property
	 * @param viewComponent ViewComponent to configure
	 */
	protected void configureViewComponent(final Property property, final ViewComponent viewComponent) {
		// Configurators
		postProcessors.forEach(fc -> fc.process(property, viewComponent));
	}

	/**
	 * Build and bind {@link ViewComponent}s to the properties of the property set.
	 */
	@SuppressWarnings({ "unchecked", "rawtypes" })
	public void build() {
		propertyViews.clear();
		// render and bind ViewComponents
		properties.forEach(p -> {
			// exclude hidden properties
			if (!isPropertyHidden(p)) {
				final Optional viewComponent = render(p);
				if (!viewComponent.isPresent() && !isIgnoreMissingViewComponent()) {
					throw new NoSuitableRendererAvailableException(
							"No ViewComponent render available to render the property [" + p.toString()
									+ "] as a ViewComponent");
				}
				viewComponent.ifPresent(v -> {
					// configure
					configureViewComponent(p, v);
					// bind
					propertyViews.put(p, v);
				});
			}
		});
	}

	// Builder

	static class InternalBuilder extends AbstractBuilder {

		/*
		 * (non-Javadoc)
		 * @see com.holonplatform.vaadin.internal.components.DefaultPropertyViewGroup.AbstractBuilder#builder()
		 */
		@Override
		protected InternalBuilder builder() {
			return this;
		}

		/*
		 * (non-Javadoc)
		 * @see com.holonplatform.vaadin.components.PropertyViewGroup.Builder#build()
		 */
		@Override
		public DefaultPropertyViewGroup build() {
			instance.build();
			return instance;
		}

	}

	public static class DefaultBuilder extends AbstractBuilder
			implements PropertyViewGroupBuilder {

		/*
		 * (non-Javadoc)
		 * @see com.holonplatform.vaadin.internal.components.DefaultPropertyViewGroup.AbstractBuilder#builder()
		 */
		@Override
		protected PropertyViewGroupBuilder builder() {
			return this;
		}

		/*
		 * (non-Javadoc)
		 * @see com.holonplatform.vaadin.components.PropertyViewGroup.Builder#build()
		 */
		@Override
		public PropertyViewGroup build() {
			instance.build();
			return instance;
		}

	}

	public static abstract class AbstractBuilder>
			implements Builder {

		protected final DefaultPropertyViewGroup instance = new DefaultPropertyViewGroup();

		/**
		 * Actual builder
		 * @return Builder
		 */
		protected abstract B builder();

		/*
		 * (non-Javadoc)
		 * @see
		 * com.holonplatform.vaadin.components.PropertyViewGroup.Builder#properties(com.holonplatform.core.property.
		 * Property[])
		 */
		@SuppressWarnings({ "rawtypes", "unchecked" })
		@Override
		public 

B properties(P... properties) { if (properties != null) { for (P property : properties) { instance.addProperty(property); } } return builder(); } /* * (non-Javadoc) * @see com.holonplatform.vaadin.components.PropertyViewGroup.Builder#properties(java.lang.Iterable) */ @SuppressWarnings("rawtypes") @Override public

B properties(Iterable

properties) { ObjectUtils.argumentNotNull(properties, "Properties must be not null"); for (P property : properties) { instance.addProperty(property); } return builder(); } /* * (non-Javadoc) * @see com.holonplatform.vaadin.components.PropertyViewGroup.Builder#hidden(com.holonplatform.core.property. * Property) */ @Override public B hidden(Property property) { ObjectUtils.argumentNotNull(property, "Property must be not null"); instance.setPropertyHidden(property); return builder(); } /* * (non-Javadoc) * @see com.holonplatform.vaadin.components.PropertyViewGroup.Builder#bind(com.holonplatform.core.property. * Property, com.holonplatform.core.property.PropertyRenderer) */ @Override public B bind(Property property, PropertyRenderer, T> renderer) { ObjectUtils.argumentNotNull(renderer, "PropertyRenderer must be not null"); return bind(property, p -> renderer.render(p)); } /* * (non-Javadoc) * @see com.holonplatform.vaadin.components.PropertyViewGroup.Builder#bind(com.holonplatform.core.property. * Property, com.holonplatform.vaadin.components.PropertyViewGroup.ViewComponentPropertyRenderer) */ @Override public B bind(Property property, ViewComponentPropertyRenderer renderer) { ObjectUtils.argumentNotNull(property, "Property must be not null"); ObjectUtils.argumentNotNull(renderer, "PropertyRenderer must be not null"); instance.setPropertyRenderer(property, renderer); return builder(); } /* * (non-Javadoc) * @see * com.holonplatform.vaadin.components.PropertyViewGroup.Builder#withPostProcessor(com.holonplatform.vaadin. * components.PropertyBinding.PostProcessor) */ @Override public B withPostProcessor(PostProcessor> postProcessor) { ObjectUtils.argumentNotNull(postProcessor, "PostProcessor must be not null"); instance.addViewComponentPostProcessor(postProcessor); return builder(); } /* * (non-Javadoc) * @see com.holonplatform.vaadin.components.PropertyViewGroup.Builder#ignoreMissingViewComponents(boolean) */ @Override public B ignoreMissingViewComponents(boolean ignoreMissingViewComponents) { instance.setIgnoreMissingViewComponent(ignoreMissingViewComponents); return builder(); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy