/**
   * Constructs a PropertyConnector that synchronizes the two bound bean properties as specified by
   * the given pairs of bean and associated property name. If <code>Bean1#property1Name</code>
   * changes it updates <code>Bean2#property2Name</code> and vice versa. If a bean does not provide
   * support for bound properties, changes will not be observed. If a bean property is read-only,
   * this connector will not listen to the other bean's property and so won't update the read-only
   * property.
   *
   * <p>In case you don't need the PropertyConnector instance, you better use the static method
   * {@link #connect(Object, String, Object, String)}. This constructor may confuse developers if
   * you just use the side effects performed in the constructor; this is because it is quite
   * unconventional to instantiate an object that you never use.
   *
   * @param bean1 the bean that owns the first property
   * @param property1Name the name of the first property
   * @param bean2 the bean that owns the second property
   * @param property2Name the name of the second property
   * @throws NullPointerException if a bean or property name is <code>null</code>
   * @throws IllegalArgumentException if the beans are identical and the property name are equal, or
   *     if both properties are read-only
   */
  public PropertyConnector(Object bean1, String property1Name, Object bean2, String property2Name) {
    if (bean1 == null) throw new NullPointerException("Bean1 must not be null.");
    if (bean2 == null) throw new NullPointerException("Bean2 must not be null.");
    if (property1Name == null) throw new NullPointerException("PropertyName1 must not be null.");
    if (property2Name == null) throw new NullPointerException("PropertyName2 must not be null.");
    if ((bean1 == bean2) && (property1Name.equals(property2Name)))
      throw new IllegalArgumentException(
          "Cannot connect a bean property to itself on the same bean.");

    this.bean1 = bean1;
    this.bean2 = bean2;
    this.bean1Class = bean1.getClass();
    this.bean2Class = bean2.getClass();
    this.property1Name = property1Name;
    this.property2Name = property2Name;

    property1Descriptor = getPropertyDescriptor(bean1Class, property1Name);
    property2Descriptor = getPropertyDescriptor(bean2Class, property2Name);

    // Used to check if property2 shall be observed,
    // i.e. if a listener shall be registered with property2.
    boolean property1Writable = property1Descriptor.getWriteMethod() != null;

    // Used to check if property1 shall be observed,
    // i.e. if a listener shall be registered with property1.
    boolean property2Writable = property2Descriptor.getWriteMethod() != null;
    // Reject to connect to read-only properties
    if (!property1Writable && !property2Writable)
      throw new IllegalArgumentException("Cannot connect two read-only properties.");

    boolean property1Observable = BeanUtils.supportsBoundProperties(bean1Class);
    boolean property2Observable = BeanUtils.supportsBoundProperties(bean2Class);
    // We do not reject the case where two unobservable beans
    // are connected; this allows a hand-update using #updateProperty1
    // and #updateProperty2.

    // Observe property1 if and only if bean1 provides support for
    // bound bean properties, and if updates can be written to property2.
    if (property1Observable && property2Writable) {
      property1ChangeHandler =
          new PropertyChangeHandler(bean1, property1Descriptor, bean2, property2Descriptor);
      addPropertyChangeHandler(bean1, bean1Class, property1ChangeHandler);
    } else {
      property1ChangeHandler = null;
    }

    // Observe property2 if and only if bean2 provides support for
    // bound bean properties, and if updates can be written to property1.
    if (property2Observable && property1Writable) {
      property2ChangeHandler =
          new PropertyChangeHandler(bean2, property2Descriptor, bean1, property1Descriptor);
      addPropertyChangeHandler(bean2, bean2Class, property2ChangeHandler);
    } else {
      property2ChangeHandler = null;
    }
  }
 /**
  * Looks up, lazily initializes and returns a <code>PropertyDescriptor</code> for the given Java
  * Bean and property name.
  *
  * @param beanClass the Java Bean class used to lookup the property from
  * @param propertyName the name of the property
  * @return the descriptor for the given bean and property name
  * @throws PropertyNotFoundException if the property could not be found
  */
 private static PropertyDescriptor getPropertyDescriptor(Class beanClass, String propertyName) {
   try {
     return BeanUtils.getPropertyDescriptor(beanClass, propertyName);
   } catch (IntrospectionException e) {
     throw new PropertyNotFoundException(propertyName, beanClass, e);
   }
 }
 private void setValueSilently(
     Object sourceBean,
     PropertyDescriptor sourcePropertyDescriptor,
     Object targetBean,
     PropertyDescriptor targetPropertyDescriptor,
     Object newValue) {
   if (property1ChangeHandler != null) {
     removePropertyChangeHandler(bean1, bean1Class, property1ChangeHandler);
   }
   if (property2ChangeHandler != null) {
     removePropertyChangeHandler(bean2, bean2Class, property2ChangeHandler);
   }
   try {
     // Set the new value in the target bean.
     BeanUtils.setValue(targetBean, targetPropertyDescriptor, newValue);
   } catch (PropertyVetoException e) {
     // Silently ignore this situation here, will be handled below.
   }
   // The target bean setter may have modified the new value.
   // Read the value set in the target bean.
   Object value = BeanUtils.getValue(targetBean, targetPropertyDescriptor);
   // If the new value and the value read differ,
   // update the source bean's value.
   // This ignores that the source bean setter may modify the value again.
   // But we won't end in a loop.
   if (!BindingUtils.equals(value, newValue)) {
     boolean sourcePropertyWritable = sourcePropertyDescriptor.getWriteMethod() != null;
     if (sourcePropertyWritable) {
       try {
         BeanUtils.setValue(sourceBean, sourcePropertyDescriptor, value);
       } catch (PropertyVetoException e) {
         // Ignore. The value set is a modified variant
         // of a value that had been accepted before.
       }
     }
   }
   if (property1ChangeHandler != null) {
     addPropertyChangeHandler(bean1, bean1Class, property1ChangeHandler);
   }
   if (property2ChangeHandler != null) {
     addPropertyChangeHandler(bean2, bean2Class, property2ChangeHandler);
   }
 }
 /**
  * A property in the observed bean has changed. First checks, if this listener should handle the
  * event, because the event's property name is the one to be observed or the event indicates
  * that any property may have changed. In case the event provides no new value, it is read from
  * the source bean.
  *
  * @param evt the property change event to be handled
  */
 public void propertyChange(PropertyChangeEvent evt) {
   String sourcePropertyName = sourcePropertyDescriptor.getName();
   if ((evt.getPropertyName() == null) || (evt.getPropertyName().equals(sourcePropertyName))) {
     Object newValue = evt.getNewValue();
     if (newValue == null) {
       newValue = BeanUtils.getValue(sourceBean, sourcePropertyDescriptor);
     }
     setValueSilently(
         sourceBean, sourcePropertyDescriptor, targetBean, targetPropertyDescriptor, newValue);
   }
 }
 /**
  * Used to remove this class' PropertyChangeHandler from the given bean if it is not <code>null
  * </code>.
  *
  * @param bean the bean to remove the property change listener from
  * @param listener the property change listener to be removed
  * @throws PropertyUnboundException if the bean does not support bound properties
  * @throws PropertyNotBindableException if the property change handler cannot be removed
  *     successfully
  */
 private static void removePropertyChangeHandler(
     Object bean, Class beanClass, PropertyChangeListener listener) {
   if (bean != null) {
     BeanUtils.removePropertyChangeListener(bean, beanClass, listener);
   }
 }
 /**
  * Reads the value of the first bean property and sets it as new value of the second bean
  * property.
  *
  * @see #updateProperty1()
  */
 public void updateProperty2() {
   Object property1Value = BeanUtils.getValue(bean1, property1Descriptor);
   setValueSilently(bean1, property1Descriptor, bean2, property2Descriptor, property1Value);
 }