/* * Copyright (c) 2005-2016 Vincent Vandenschrick. All rights reserved. * * This file is part of the Jspresso framework. * * Jspresso is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Jspresso 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Jspresso. If not, see <http://www.gnu.org/licenses/>. */ package org.jspresso.framework.model.component.service; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import org.hibernate.Hibernate; import org.jspresso.framework.model.component.IComponent; import org.jspresso.framework.util.accessor.IAccessorFactory; import org.jspresso.framework.util.accessor.IAccessorFactoryProvider; import org.jspresso.framework.util.accessor.bean.BeanAccessorFactory; import org.jspresso.framework.util.bean.AccessorInfo; import org.jspresso.framework.util.bean.EAccessorType; import org.jspresso.framework.util.bean.IPropertyChangeCapable; import org.jspresso.framework.util.exception.NestedRuntimeException; /** * DependsOn notification forwarding helper. * * @author Vincent Vandenschrick */ public class DependsOnHelper { private DependsOnHelper() { // private helper constructor. } /** * Register depends on listeners. * * @param annotatedClass * the annotated class * @param sourceBean * the source bean * @param accessorFactoryProvider * the accessor factory */ public static void registerDependsOnListeners(Class<?> annotatedClass, IPropertyChangeCapable sourceBean, IAccessorFactoryProvider accessorFactoryProvider) { Method[] methods = annotatedClass.getMethods(); for (Method method : methods) { DependsOn dependsOn = method.getAnnotation(DependsOn.class); if (dependsOn != null) { IAccessorFactory accessorFactory = accessorFactoryProvider.getAccessorFactory(); AccessorInfo accessorInfo = accessorFactory.getAccessorInfo(method); String targetProperty = accessorInfo.getAccessedPropertyName(); EAccessorType accessorType = accessorInfo.getAccessorType(); String[] sourceProperties = dependsOn.value(); if (accessorType != EAccessorType.NONE && targetProperty != null) { if (sourceProperties != null && sourceProperties.length > 0) { String sourceCollectionProperty = dependsOn.sourceCollection(); if (sourceCollectionProperty != null && sourceCollectionProperty.length() > 0) { for (String sourceProperty : sourceProperties) { registerNotificationCollectionForwarding(sourceBean, accessorFactoryProvider, sourceCollectionProperty, sourceProperty, targetProperty); } } else { for (String sourceProperty : sourceProperties) { registerNotificationForwarding(sourceBean, accessorFactoryProvider, sourceProperty, targetProperty); } } } } else if (method.getParameterTypes().length == 0) { if (sourceProperties != null && sourceProperties.length > 0) { String sourceCollectionProperty = dependsOn.sourceCollection(); if (sourceCollectionProperty != null && sourceCollectionProperty.length() > 0) { for (String sourceProperty : sourceProperties) { registerNotificationCollectionForwarding(sourceBean, accessorFactoryProvider, sourceCollectionProperty, sourceProperty, method); } } else { for (String sourceProperty : sourceProperties) { registerNotificationForwarding(sourceBean, accessorFactoryProvider, sourceProperty, method); } } } } } } } /** * Registers a property change listener to forward property changes. * * @param sourceBean * the source bean. * @param accessorFactoryProvider * the accessor factory * @param sourceProperty * the name of the source property. * @param forwardedProperty * the name of the forwarded property. */ public static void registerNotificationForwarding(IPropertyChangeCapable sourceBean, IAccessorFactoryProvider accessorFactoryProvider, String sourceProperty, String forwardedProperty) { registerNotificationForwarding(sourceBean, accessorFactoryProvider, sourceProperty, new String[]{forwardedProperty}); } /** * Registers a property change listener to forward property changes. * * @param sourceBean * the source bean. * @param accessorFactoryProvider * the accessor factory * @param sourceProperty * the name of the source property. * @param forwardedMethod * the name of the forwarded method. */ public static void registerNotificationForwarding(IPropertyChangeCapable sourceBean, IAccessorFactoryProvider accessorFactoryProvider, String sourceProperty, Method forwardedMethod) { registerNotificationForwarding(sourceBean, accessorFactoryProvider, sourceProperty, new Method[]{forwardedMethod}); } /** * Registers a property change listener to forward property changes. * * @param sourceBean * the source bean. * @param accessorFactoryProvider * the accessor factory * @param sourceProperty * the name of the source property. * @param forwardedProperty * the name of the forwarded property. */ public static void registerNotificationForwarding(IPropertyChangeCapable sourceBean, IAccessorFactoryProvider accessorFactoryProvider, String sourceProperty, String... forwardedProperty) { registerNotificationForwarding(sourceBean, sourceBean, accessorFactoryProvider, sourceProperty, forwardedProperty); } private static void registerNotificationForwarding(IPropertyChangeCapable sourceBean, IPropertyChangeCapable targetBean, IAccessorFactoryProvider accessorFactoryProvider, String sourceProperty, String... forwardedProperty) { if (sourceBean == null) { return; } if (sourceBean == targetBean && Arrays.binarySearch(forwardedProperty, sourceProperty) >= 0) { throw new IllegalArgumentException("Forwarded properties " + Arrays.asList(forwardedProperty) + " cannot contain source property " + sourceProperty + " when registering notification forwarding"); } sourceBean.addPropertyChangeListener(sourceProperty, new ForwardingPropertyChangeListener(targetBean, accessorFactoryProvider, forwardedProperty)); } /** * Registers a property change listener to forward property changes. * * @param sourceBean * the source bean. * @param accessorFactoryProvider * the accessor factory * @param sourceProperty * the name of the source property. * @param forwardedMethod * the name of the forwarded method. */ public static void registerNotificationForwarding(IPropertyChangeCapable sourceBean, IAccessorFactoryProvider accessorFactoryProvider, String sourceProperty, Method... forwardedMethod) { registerNotificationForwarding(sourceBean, sourceBean, accessorFactoryProvider, sourceProperty, forwardedMethod); } private static void registerNotificationForwarding(IPropertyChangeCapable sourceBean, IPropertyChangeCapable targetBean, IAccessorFactoryProvider accessorFactoryProvider, String sourceProperty, Method... forwardedMethod) { if (sourceBean == null) { return; } sourceBean.addPropertyChangeListener(sourceProperty, new ForwardingPropertyChangeListener(targetBean, accessorFactoryProvider, forwardedMethod)); } /** * Registers notification forwarding from a collection's child property. * * @param sourceBean * the source bean. * @param accessorFactoryProvider * the accessor factory * @param sourceCollectionProperty * the collection property to listen to. * @param sourceElementProperty * the collection elements property to listen to. * @param forwardedProperty * the name of the forwarded property. */ public static void registerNotificationCollectionForwarding(IPropertyChangeCapable sourceBean, IAccessorFactoryProvider accessorFactoryProvider, String sourceCollectionProperty, String sourceElementProperty, String forwardedProperty) { registerNotificationCollectionForwarding(sourceBean, accessorFactoryProvider, sourceCollectionProperty, sourceElementProperty, new String[]{forwardedProperty}); } /** * Registers notification forwarding from a collection's child property. * * @param sourceBean * the source bean. * @param accessorFactoryProvider * the accessor factory * @param sourceCollectionProperty * the collection property to listen to. * @param sourceElementProperty * the collection elements property to listen to. * @param forwardedMethod * the name of the forwarded method. */ public static void registerNotificationCollectionForwarding(IPropertyChangeCapable sourceBean, IAccessorFactoryProvider accessorFactoryProvider, String sourceCollectionProperty, String sourceElementProperty, Method forwardedMethod) { registerNotificationCollectionForwarding(sourceBean, accessorFactoryProvider, sourceCollectionProperty, sourceElementProperty, new Method[]{forwardedMethod}); } /** * Registers notification forwarding from a collection's child property. * * @param sourceBean * the source bean. * @param accessorFactoryProvider * the accessor factory * @param sourceCollectionProperty * the collection property to listen to. * @param sourceElementProperty * the collection elements property to listen to. * @param forwardedProperty * the name of the forwarded property. */ @SuppressWarnings("unchecked") public static void registerNotificationCollectionForwarding(final IPropertyChangeCapable sourceBean, final IAccessorFactoryProvider accessorFactoryProvider, final String sourceCollectionProperty, final String sourceElementProperty, final String... forwardedProperty) { registerNotificationCollectionForwarding(sourceBean, accessorFactoryProvider, sourceCollectionProperty, sourceElementProperty, forwardedProperty, null); } /** * Registers notification forwarding from a collection's child property. * * @param sourceBean * the source bean. * @param accessorFactoryProvider * the accessor factory * @param sourceCollectionProperty * the collection property to listen to. * @param sourceElementProperty * the collection elements property to listen to. * @param forwardedMethod * the name of the forwarded method. */ @SuppressWarnings("unchecked") public static void registerNotificationCollectionForwarding(final IPropertyChangeCapable sourceBean, final IAccessorFactoryProvider accessorFactoryProvider, final String sourceCollectionProperty, final String sourceElementProperty, final Method... forwardedMethod) { registerNotificationCollectionForwarding(sourceBean, accessorFactoryProvider, sourceCollectionProperty, sourceElementProperty, null, forwardedMethod); } /** * Registers notification forwarding from a collection's child property. * * @param sourceBean * the source bean. * @param accessorFactoryProvider * the accessor factory * @param sourceCollectionProperty * the collection property to listen to. * @param sourceElementProperty * the collection elements property to listen to. * @param forwardedProperties * the name of the forwarded properties. * @param forwardedMethods * the forwarded methods */ @SuppressWarnings({"unchecked", "MethodCanBeVariableArityMethod"}) public static void registerNotificationCollectionForwarding(final IPropertyChangeCapable sourceBean, final IAccessorFactoryProvider accessorFactoryProvider, final String sourceCollectionProperty, final String sourceElementProperty, final String[] forwardedProperties, final Method[] forwardedMethods) { if (sourceBean == null) { return; } // listen normally to collection changes if (forwardedProperties != null) { registerNotificationForwarding(sourceBean, accessorFactoryProvider, sourceCollectionProperty, forwardedProperties); } if (forwardedMethods != null) { registerNotificationForwarding(sourceBean, accessorFactoryProvider, sourceCollectionProperty, forwardedMethods); } // setup collection listener to attach / detach property change listeners on // elements sourceBean.addPropertyChangeListener(sourceCollectionProperty, new PropertyChangeListener() { @SuppressWarnings({"rawtypes", "unchecked", "SuspiciousMethodCalls"}) @Override public void propertyChange(PropertyChangeEvent evt) { // add listeners if (evt.getNewValue() != null && evt.getNewValue() instanceof Collection<?> && Hibernate.isInitialized( evt.getNewValue())) { Collection<IPropertyChangeCapable> newChildren = new HashSet<>( (Collection<IPropertyChangeCapable>) evt.getNewValue()); if (evt.getOldValue() != null && evt.getOldValue() instanceof Collection<?> && Hibernate.isInitialized( evt.getOldValue())) { newChildren.removeAll((Collection<?>) evt.getOldValue()); } for (IPropertyChangeCapable child : newChildren) { if (child != null) { if (forwardedProperties != null) { registerNotificationForwarding(child, sourceBean, accessorFactoryProvider, sourceElementProperty, forwardedProperties); } if (forwardedMethods != null) { registerNotificationForwarding(child, sourceBean, accessorFactoryProvider, sourceElementProperty, forwardedMethods); } } } } // remove listeners if (evt.getOldValue() != null && evt.getOldValue() instanceof Collection<?> && Hibernate.isInitialized( evt.getOldValue())) { Collection<IPropertyChangeCapable> removedChildren = new HashSet<>( (Collection<IPropertyChangeCapable>) evt.getOldValue()); if (evt.getNewValue() != null && evt.getNewValue() instanceof Collection<?> && Hibernate.isInitialized( evt.getNewValue())) { removedChildren.removeAll((Collection<?>) evt.getNewValue()); } for (IPropertyChangeCapable child : removedChildren) { if (child != null) { for (PropertyChangeListener listener : child.getPropertyChangeListeners(sourceElementProperty)) { if (listener instanceof DependsOnHelper.ForwardingPropertyChangeListener) { if (Arrays.equals( ((DependsOnHelper.ForwardingPropertyChangeListener) listener).getForwardedProperties(), forwardedProperties) || Arrays.equals( ((DependsOnHelper.ForwardingPropertyChangeListener) listener).getForwardedMethods(), forwardedMethods)) { child.removePropertyChangeListener(sourceElementProperty, listener); } } } } } } } }); // Setup listener for initial collection if it exists Collection<IPropertyChangeCapable> initialChildren; if (sourceBean instanceof IComponent) { initialChildren = (Collection<IPropertyChangeCapable>) ((IComponent) sourceBean).straightGetProperty( sourceCollectionProperty); } else { try { initialChildren = new BeanAccessorFactory().createPropertyAccessor(sourceCollectionProperty, sourceBean.getClass()).getValue(sourceBean); } catch (IllegalAccessException | NoSuchMethodException | InvocationTargetException ex) { throw new NestedRuntimeException(ex); } } if (initialChildren != null && Hibernate.isInitialized(initialChildren)) { for (IPropertyChangeCapable child : initialChildren) { if (child != null) { registerNotificationForwarding(child, sourceBean, accessorFactoryProvider, sourceElementProperty, forwardedProperties); } } } } private static class ForwardingPropertyChangeListener implements PropertyChangeListener { private IPropertyChangeCapable targetBean; private String[] forwardedProperties; private Method[] forwardedMethods; private IAccessorFactoryProvider accessorFactoryProvider; /** * Constructs a new {@code ForwardingPropertyChangeListener} instance. * * @param targetBean * the target bean * @param accessorFactoryProvider * the accessor factory * @param forwardedProperties * the list of forwarded property names. */ public ForwardingPropertyChangeListener(IPropertyChangeCapable targetBean, IAccessorFactoryProvider accessorFactoryProvider, String... forwardedProperties) { this.targetBean = targetBean; this.accessorFactoryProvider = accessorFactoryProvider; this.forwardedProperties = forwardedProperties; } /** * Constructs a new {@code ForwardingPropertyChangeListener} instance. * * @param targetBean * the target bean * @param accessorFactoryProvider * the accessor factory * @param forwardedMethods * the list of forwarded methods. */ public ForwardingPropertyChangeListener(IPropertyChangeCapable targetBean, IAccessorFactoryProvider accessorFactoryProvider, Method... forwardedMethods) { this.targetBean = targetBean; this.accessorFactoryProvider = accessorFactoryProvider; this.forwardedMethods = forwardedMethods; } @Override public void propertyChange(PropertyChangeEvent evt) { if (forwardedProperties != null) { if (accessorFactoryProvider != null) { for (String prop : forwardedProperties) { if (targetBean.hasListeners(prop)) { Class<?> targetBeanComponentContract; if (targetBean instanceof IComponent) { targetBeanComponentContract = ((IComponent) targetBean).getComponentContract(); } else { targetBeanComponentContract = targetBean.getClass(); } try { IAccessorFactory accessorFactory = accessorFactoryProvider.getAccessorFactory(); Object newValue = accessorFactory.createPropertyAccessor(prop, targetBeanComponentContract).getValue( targetBean); targetBean.firePropertyChange(prop, IPropertyChangeCapable.UNKNOWN, newValue); } catch (IllegalAccessException | NoSuchMethodException ex) { throw new NestedRuntimeException(ex); } catch (InvocationTargetException ex) { if (ex.getTargetException() instanceof RuntimeException) { throw (RuntimeException) ex.getTargetException(); } throw new NestedRuntimeException(ex); } } } } } if (forwardedMethods != null) { for (Method method : forwardedMethods) { try { method.invoke(targetBean); } catch (IllegalAccessException ex) { throw new NestedRuntimeException(ex); } catch (InvocationTargetException ex) { if (ex.getTargetException() instanceof RuntimeException) { throw (RuntimeException) ex.getTargetException(); } throw new NestedRuntimeException(ex); } } } } /** * Gets the forwardedProperties. * * @return the forwardedProperties. */ public String[] getForwardedProperties() { return forwardedProperties; } /** * Get forwarded methods method [ ]. * * @return the method [ ] */ public Method[] getForwardedMethods() { return forwardedMethods; } } }