Coverage Report - org.openpermis.policy.bean.basic.BasicPropertyForwarder
 
Classes in this File Line Coverage Branch Coverage Complexity
BasicPropertyForwarder
89%
44/49
76%
23/30
5.8
 
 1  
 /*
 2  
  * Copyright (c) 2009, Swiss Federal Department of Defence Civil Protection and Sport
 3  
  *                     (http://www.vbs.admin.ch)
 4  
  * Copyright (c) 2009, Ergon Informatik AG (http://www.ergon.ch)
 5  
  * All rights reserved.
 6  
  *
 7  
  * Licensed under the Open Permis License which accompanies this distribution,
 8  
  * and is available at http://www.openpermis.org/BSDlicenceKent.txt
 9  
  */
 10  
 package org.openpermis.policy.bean.basic;
 11  
 
 12  
 import java.beans.PropertyChangeEvent;
 13  
 import java.beans.PropertyChangeListener;
 14  
 import java.beans.PropertyChangeSupport;
 15  
 import java.util.Collection;
 16  
 import java.util.IdentityHashMap;
 17  
 
 18  
 import org.slf4j.Logger;
 19  
 import org.slf4j.LoggerFactory;
 20  
 
 21  
 import org.openpermis.policy.bean.PartBean;
 22  
 
 23  
 
 24  
 /**
 25  
  * Forwarder for bean part property changes.
 26  
  * <p>Once registered at a bean part it will observe changes and register itself as listener
 27  
  * for any bean part value changes. Any changes in contained bean parts will then be forwarded.</p>
 28  
  * @since 0.1.0
 29  
  */
 30  
 public class BasicPropertyForwarder
 31  
         implements PropertyChangeListener
 32  
 {
 33  
 
 34  
         //---- Static
 35  
 
 36  
         /**
 37  
          * The logger object of this class.
 38  
          * @since 0.1.0
 39  
          */
 40  1
         private static final Logger LOGGER =
 41  
                 LoggerFactory.getLogger(BasicPropertyForwarder.class);
 42  
 
 43  
         /**
 44  
          * Enable to see verbose log output message forwarding.
 45  
          * @since 0.1.0
 46  
          */
 47  
         private static final boolean TRACE = false;
 48  
 
 49  
         //---- State
 50  
 
 51  
         /**
 52  
          * The part bean for which to forward changes in contained part beans.
 53  
          * @since 0.1.0
 54  
          */
 55  
         private final PartBean partBean;
 56  
 
 57  
         /**
 58  
          * The property change support to use for forwarding.
 59  
          * @since 0.1.0
 60  
          */
 61  
         private final PropertyChangeSupport propertyChangeSupport;
 62  
 
 63  
         /**
 64  
          * Maps the contained part beans this forwarder is attached to their property names.
 65  
          * @note We have to use an identity hash map here since the beans are mutable and will
 66  
          * thus change their hash code. Using the identity will allow us to track them anyway.
 67  
          * We could also have used the serial number of the part bean, but then we would have
 68  
          * to make sure that the serial number lies in the same context and we could not support
 69  
          * undefined serial numbers.
 70  
          * @since 0.1.0
 71  
          */
 72  
         private final IdentityHashMap<PartBean, String> propertyMap;
 73  
 
 74  
         //---- Constructors
 75  
 
 76  
         /**
 77  
          * Creates and registers new basic property forwarder for the specified part bean.
 78  
          * @param partBean the part bean for which to forward contained part bean property changes.
 79  
          * @param propertyChangeSupport the property change support to use for forwarding.
 80  
          * @since 0.1.0
 81  
          */
 82  
         public BasicPropertyForwarder (
 83  
                 PartBean partBean, PropertyChangeSupport propertyChangeSupport
 84  9680
         ) {
 85  9680
                 this.partBean = partBean;
 86  9680
                 this.propertyChangeSupport = propertyChangeSupport;
 87  9680
                 this.propertyMap = new IdentityHashMap<PartBean, String>();
 88  9680
                 this.partBean.addPropertyChangeListener(this);
 89  9680
         }
 90  
 
 91  
         //---- Methods
 92  
 
 93  
         /**
 94  
          * Detaches this forwarder as a change listener from the specified object.
 95  
          * <p>If the object is a {@link PartBean} it simply detaches itself.</p>
 96  
          * <p>If the object is a {@link Collection} it will detach itself deeply.</p>
 97  
          * @param propertyName the name of the property that caused the detach.
 98  
          * @param obj the object to detach from, may be {@code null}.
 99  
          * @since 0.1.0
 100  
          */
 101  
         protected void detach (String propertyName, Object obj) {
 102  6851
                 if (obj == null) {
 103  3967
                         return;
 104  
                 }
 105  2884
                 if (obj instanceof BasicAbstractCollection<?>) {
 106  1681
                         for (Object candidate : ((Iterable<?>) obj)) {
 107  2
                                 detach(propertyName, candidate);
 108  
                         }
 109  1203
                 } else if (obj instanceof PartBean) {
 110  253
                         final PartBean bean = (PartBean) obj;
 111  253
                         this.propertyMap.remove(bean);
 112  253
                         bean.removePropertyChangeListener(this);
 113  
                         if (TRACE) {
 114  
                                 LOGGER.debug(
 115  
                                         "[{}] detaching [{}] from [{}].",
 116  
                                         new Object[] {
 117  
                                                 this.partBean.toShortString(),
 118  
                                                 propertyName,
 119  
                                                 bean.toShortString()
 120  
                                         }
 121  
                                 );
 122  
                         }
 123  253
                 } else if (obj instanceof Collection<?>) {
 124  0
                         for (Object candidate : ((Collection<?>) obj)) {
 125  0
                                 detach(propertyName, candidate);
 126  
                         }
 127  
                 }
 128  2884
         }
 129  
 
 130  
         /**
 131  
          * Attaches this forwarder as a change listener to the specified object.
 132  
          * <p>If the object is a {@link PartBean} it simply attaches itself.</p>
 133  
          * <p>If the object is a {@link Collection} it will attach itself deeply.</p>
 134  
          * @param propertyName the name of the property that caused the attach.
 135  
          * @param obj the object to attach to, may be {@code null}.
 136  
          * @since 0.1.0
 137  
          */
 138  
         protected void attach (String propertyName, Object obj) {
 139  8920
                 if (obj == null) {
 140  299
                         return;
 141  
                 }
 142  8621
                 if (obj instanceof BasicAbstractCollection<?>) {
 143  1681
                         for (Object candidate : ((Iterable<?>) obj)) {
 144  2071
                                 attach(propertyName, candidate);
 145  
                         }
 146  6940
                 } else if (obj instanceof PartBean) {
 147  3203
                         final PartBean bean = (PartBean) obj;
 148  3203
                         bean.addPropertyChangeListener(this);
 149  3203
                         this.propertyMap.put(bean, propertyName);
 150  
                         if (TRACE) {
 151  
                                 LOGGER.debug(
 152  
                                         "[{}] attaching [{}] to [{}].",
 153  
                                         new Object[] {
 154  
                                                 this.partBean.toShortString(),
 155  
                                                 propertyName,
 156  
                                                 bean.toShortString()
 157  
                                         }
 158  
                                 );
 159  
                         }
 160  
                 }
 161  8621
         }
 162  
 
 163  
         /**
 164  
          * Forwards this event to listeners of the owner part bean.
 165  
          * <p>The event forwarded will have the same propagation ID as the event passed in.</p>
 166  
          * @param event the event to forward, source must not be {@code null}.
 167  
          * @since 0.1.0
 168  
          */
 169  
         protected void forward (PropertyChangeEvent event) {
 170  4
                 final Object source = event.getSource();
 171  4
                 if (!(source instanceof PartBean)) {
 172  0
                         return;
 173  
                 }
 174  4
                 final PartBean bean = (PartBean) source;
 175  4
                 final String propertyName = this.propertyMap.get(bean);
 176  4
                 if (propertyName == null) {
 177  0
                         return;
 178  
                 }
 179  4
                 final PropertyChangeEvent forwardEvent =
 180  
                         new PropertyChangeEvent(this.partBean, propertyName, null, null);
 181  4
                 forwardEvent.setPropagationId(event.getPropagationId());
 182  
                 if (TRACE) {
 183  
                         LOGGER.debug(
 184  
                                 "[{}] forwarding change of [{}] caused by a change of [{}] in [{}].",
 185  
                                 new Object[] {
 186  
                                         this.partBean.toShortString(),
 187  
                                         propertyName,
 188  
                                         event.getPropertyName(),
 189  
                                         bean.toShortString()
 190  
                                 }
 191  
                         );
 192  
                 }
 193  4
                 this.propertyChangeSupport.firePropertyChange(forwardEvent);
 194  4
         }
 195  
 
 196  
         //---- PropertyChangeListener
 197  
 
 198  
         /**
 199  
          * @since 0.1.0
 200  
          */
 201  
         public void propertyChange (PropertyChangeEvent event) {
 202  6853
                 final String propertyName = event.getPropertyName();
 203  6853
                 if (propertyName == null || propertyName.length() == 0) {
 204  0
                         return;
 205  
                 }
 206  6853
                 if (event.getSource() == this.partBean) {
 207  
                         if (TRACE) {
 208  
                                 LOGGER.debug(
 209  
                                         "[{}] reports a change in [{}] (old = [{}], new = [{}]).",
 210  
                                         new Object[] {
 211  
                                                 this.partBean.toShortString(),
 212  
                                                 propertyName,
 213  
                                                 event.getOldValue(),
 214  
                                                 event.getNewValue()
 215  
                                         }
 216  
                                 );
 217  
                         }
 218  6849
                         detach(propertyName, event.getOldValue());
 219  6849
                         attach(propertyName, event.getNewValue());
 220  
                 } else {
 221  4
                         forward(event);
 222  
                 }
 223  6853
         }
 224  
 
 225  
 }