Package com.tll.client.bind

Source Code of com.tll.client.bind.Binding$BindingInstance

package com.tll.client.bind;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import com.allen_sauer.gwt.log.client.Log;
import com.tll.client.convert.IConverter;
import com.tll.client.ui.IWidgetRef;
import com.tll.client.validate.ErrorClassifier;
import com.tll.client.validate.ErrorDisplay;
import com.tll.client.validate.IErrorHandler;
import com.tll.client.validate.IValidator;
import com.tll.client.validate.ValidationException;
import com.tll.common.bind.IBindable;
import com.tll.common.bind.IPropertyChangeListener;
import com.tll.common.bind.IndexedPropertyChangeEvent;
import com.tll.common.bind.PropertyChangeEvent;
import com.tll.util.PropertyPath;

/**
* Binding - This class represents a DataBinding between two objects. It also
* supports Child bindings.
* <p>
* <b>IMPT:</b> This construct currently does <em>not</em> support adding or
* removing relational type properties! In other words, the assumption in this
* class' logic is always a non-<code>null</code> ref on bound properties.
* @author jpk
*/
@SuppressWarnings("synthetic-access")
public final class Binding {

  /**
   * A data class containing the relevant data for one half of a binding
   * relationship.
   */
  private static final class BindingInstance {

    /**
     * The Object being bound.
     */
    public IBindable object;

    /**
     * The full OGNL compliant property path of the property being bound.
     */
    public String property;

    /**
     * A converter when needed.
     */
    public IConverter<Object, Object> converter;

    /**
     * A validator when needed.
     */
    public IValidator validator;

    /**
     * The error handler.
     */
    public IErrorHandler feedback;

    /**
     * List of property change listeners
     */
    private final ArrayList<IPropertyChangeListener> listeners = new ArrayList<IPropertyChangeListener>();

    /**
     * Constructor
     */
    private BindingInstance() {
      super();
    }

    public void addPropertyChangeListener(IPropertyChangeListener pcl) {
      listeners.add(pcl);
    }

    public void removePropertyChangeListener(IPropertyChangeListener pcl) {
      listeners.remove(pcl);
    }

    public void firePropertyChange(PropertyChangeEvent pce) {
      for(final IPropertyChangeListener l : listeners) {
        l.propertyChange(pce);
      }
    }
  } // BindingInstance

  /**
   * DefaultPropertyChangeListener - Listens for property changes for a property
   * in a given <em>instance</em> and propagates these changes to a
   * <em>targeted<em> {@link IPropertyChangeListener}.
   * @author jpk
   */
  private static final class DefaultPropertyChangeListener implements IPropertyChangeListener {

    private final BindingInstance instance;
    private final BindingInstance target;
    private ValidationException lastException;

    /**
     * Constructor
     * @param instance
     * @param target
     */
    DefaultPropertyChangeListener(BindingInstance instance, BindingInstance target) {
      this.instance = instance;
      this.target = target;
    }

    public void propertyChange(PropertyChangeEvent propertyChangeEvent) {
      Object value = propertyChangeEvent.getNewValue();

      if(
      if(instance.validator != null) {
        try {
          value = instance.validator.validate(value);
        }
        catch(final ValidationException ve) {
          if(instance.feedback != null) {
            if(lastException != null) {
              instance.feedback.resolveError((IWidgetRef) propertyChangeEvent.getSource(), ErrorClassifier.CLIENT, ErrorDisplay.ALL_FLAGS);
            }
            instance.feedback.handleErrors(ve.getErrors(), ErrorDisplay.ALL_FLAGS);
            lastException = ve;
            return;
          }
          lastException = ve;
          throw new RuntimeException(ve);
        }
      }

      if(instance.feedback != null) {
        instance.feedback.resolveError((IWidgetRef) propertyChangeEvent.getSource(), ErrorClassifier.CLIENT, ErrorDisplay.ALL_FLAGS);
      }

      lastException = null;

      if(instance.converter != null) {
        value = instance.converter.convert(value);
      }

      // resolve the property
      String targetProperty = target.property;
      if(propertyChangeEvent instanceof IndexedPropertyChangeEvent && !PropertyPath.isIndexed(targetProperty)) {
        targetProperty =
          PropertyPath.index(targetProperty, ((IndexedPropertyChangeEvent) propertyChangeEvent).getIndex());
      }

      Log.debug("Setting prop val on [ " + target.object + " ] for " + targetProperty + "..");
      try {
        target.object.setProperty(targetProperty, value);
      }
      catch(final Exception e) {
        throw new RuntimeException("Unable to set property '" + targetProperty + "': " + e.getMessage(), e);
      }
    }

    @Override
    public String toString() {
      return "[Listener on : " + instance.object + " ] ";
    }
  } // DefaultPropertyChangeListener

  private BindingInstance left;
  private BindingInstance right;
  private List<Binding> children;

  private boolean bound = false;

  /**
   * Constructor - Creates an empty Binding object. This is mostly useful for
   * top-of-tree parent Bindings.
   */
  public Binding() {
    super();
  }

  /**
   * Constructor
   * @param left The left hand object.
   * @param right The right hand object
   * @param property The common property name for <em>both</em> the left and
   *        right objects.
   * @throws BindingException When the binding can't be created usu. due to
   *         missing bindable properties.
   */
  public Binding(IBindable left, IBindable right, String property) throws BindingException {
    this(left, property, null, null, null, right, property, null, null, null);
  }

  /**
   * Constructor - The base constructor.
   * @param left
   * @param leftProperty
   * @param leftConverter
   * @param leftValidator
   * @param leftFeedback
   * @param right
   * @param rightProperty
   * @param rightConverter
   * @param rightValidator
   * @param rightFeedback
   * @throws BindingException When the binding can't be created usu. due to
   *         missing bindable properties.
   */
  public Binding(IBindable left, String leftProperty, IConverter<Object, Object> leftConverter,
      IValidator leftValidator,
      IErrorHandler leftFeedback, IBindable right,
      String rightProperty, IConverter<Object, Object> rightConverter, IValidator rightValidator,
      IErrorHandler rightFeedback)
  throws BindingException {

    this.left = createBindingInstance(left, leftProperty);
    this.left.converter = leftConverter;
    this.left.validator = leftValidator;
    this.left.feedback = leftFeedback;

    this.right = createBindingInstance(right, rightProperty);
    this.right.converter = rightConverter;
    this.right.validator = rightValidator;
    this.right.feedback = rightFeedback;

    this.left.listeners.add(new DefaultPropertyChangeListener(this.left, this.right));
    this.right.listeners.add(new DefaultPropertyChangeListener(this.right, this.left));
  }

  /**
   * Returns a list of child Bindings.
   * @return List of child bindings.
   */
  public List<Binding> getChildren() {
    return children =
      (children == null) ? new ArrayList<Binding>() : children;
  }

  /**
   * Add a property change listener either on the left side or the right side.
   * @param pcl the listener to add
   * @param toLeft add to left side (<code>true</code>) or right side?
   */
  public void addPropertyChangeListener(IPropertyChangeListener pcl, boolean toLeft) {
    if(toLeft)
      left.addPropertyChangeListener(pcl);
    else
      right.addPropertyChangeListener(pcl);
  }

  /**
   * Remove a property change listener either on the left side or the right side.
   * @param pcl the listener to remove
   * @param toLeft add to left side (<code>true</code>) or right side?
   */
  public void removePropertyChangeListener(IPropertyChangeListener pcl, boolean toLeft) {
    if(toLeft)
      left.removePropertyChangeListener(pcl);
    else
      right.removePropertyChangeListener(pcl);
  }

  /**
   * Sets the left hand property to the current value of the right.
   * @throws BindingException When a {@link PropertyChangeEvent} dispatch fails.
   */
  public void setLeft() throws BindingException {
    if((left != null) && (right != null)) {
      //Log.debug("Binding.setLeft..");
      try {
        right.firePropertyChange(new PropertyChangeEvent(right.object, right.property, null, right.object
            .getProperty(right.property)));
      }
      catch(final Exception e) {
        throw new BindingException(e);
      }
    }

    for(int i = 0; (children != null) && (i < children.size()); i++) {
      children.get(i).setLeft();
    }
  }

  /**
   * Sets the right object's property to the current value of the left.
   * @throws BindingException When a {@link PropertyChangeEvent} dispatch fails.
   */
  public void setRight() throws BindingException {
    if((left != null) && (right != null)) {
      //Log.debug("Binding.setRight..");
      try {
        left.firePropertyChange(new PropertyChangeEvent(left.object, left.property, null, left.object
            .getProperty(left.property)));
      }
      catch(final Exception e) {
        throw new BindingException(e);
      }
    }

    for(int i = 0; (children != null) && (i < children.size()); i++) {
      children.get(i).setRight();
    }
  }

  /**
   * Establishes a two-way binding between the objects.
   * <p>
   * NOTE: Addition of property change listeners to the bindable is presumed to
   * have the smarts to discriminate on the property name and remove any
   * existing having the same name.
   * @throws BindingException When a declared property is encountered that doesn't resolve.
   */
  public void bind() throws BindingException {
    if(!bound && (left != null) && (right != null)) {
      try {
        for(final IPropertyChangeListener l : left.listeners) {
          left.object.addPropertyChangeListener(left.property, l);
        }
        for(final IPropertyChangeListener l : right.listeners) {
          right.object.addPropertyChangeListener(right.property, l);
        }
      }
      catch(final Exception e) {
        throw new BindingException(e);
      }
    }

    for(int i = 0; (children != null) && (i < children.size()); i++) {
      children.get(i).bind();
    }

    bound = true;
  }

  /**
   * Breaks the two way binding and removes all listeners.
   */
  public void unbind() {
    if(bound && (left != null) && (right != null)) {
      for(final IPropertyChangeListener l : left.listeners) {
        left.object.removePropertyChangeListener(left.property, l);
      }
      for(final IPropertyChangeListener l : right.listeners) {
        right.object.removePropertyChangeListener(right.property, l);
      }
    }

    for(int i = 0; (children != null) && (i < children.size()); i++) {
      children.get(i).unbind();
    }

    bound = false;
  }

  /**
   * Creates an instance of {@link BindingInstance}.
   * @param bindable the required bindable
   * @param propertyName the required property name
   * @return A new {@link BindingInstance}.
   * @throws BindingException When the instance can't be created
   */
  private BindingInstance createBindingInstance(IBindable bindable, String propertyName) throws BindingException {
    int dotIndex = propertyName.indexOf(".");
    final BindingInstance instance = new BindingInstance();
    if(dotIndex != -1) {
      while(dotIndex != -1) {
        String pname = propertyName.substring(0, dotIndex);
        propertyName = propertyName.substring(dotIndex + 1);
        try {
          String discriminator = null;
          final int descIndex = pname.indexOf("[");

          if(descIndex != -1) {
            discriminator = pname.substring(descIndex + 1, pname.indexOf("]", descIndex));
            pname = pname.substring(0, descIndex);
          }

          if(discriminator != null) {
            // TODO Need a loop here to handle multi-dimensional/collections of
            // collections
            bindable = getDiscriminatedObject(bindable.getProperty(pname), discriminator);
          }
          else {
            final Object nested = bindable.getProperty(pname);
            if(nested instanceof IBindable == false) {
              throw new BindingException("Non-bindable property: " + pname);
            }
            bindable = (IBindable) nested;
          }
        }
        catch(final ClassCastException cce) {
          throw new BindingException("Nonbindable sub property: " + bindable + " . " + pname, cce);
        }
        catch(final BindingException e) {
          throw e;
        }
        catch(final Exception e) {
          throw new BindingException(e);
        }

        dotIndex = propertyName.indexOf(".");
      }
    }

    if(bindable == null) {
      throw new BindingException("Missing bindable property: " + propertyName);
    }
    instance.object = bindable;
    instance.property = propertyName;

    return instance;
  }

  @SuppressWarnings("unchecked")
  private IBindable getDiscriminatedObject(Object collectionOrArray, String discriminator) {
    final int equalsIndex = discriminator.indexOf("=");

    if(collectionOrArray instanceof Collection && (equalsIndex == -1)) {
      return getBindableAtCollectionIndex((Collection<IBindable>) collectionOrArray, Integer.parseInt(discriminator));
    }
    else if(collectionOrArray instanceof Collection) {
      return getBindableHavingPropertyOfValue((Collection<IBindable>) collectionOrArray, discriminator.substring(0,
          equalsIndex), discriminator.substring(equalsIndex + 1));
    }
    else if(collectionOrArray instanceof Map) {
      return getBindableAtMapKey((Map) collectionOrArray, discriminator);
    }
    else if(equalsIndex == -1) {
      return ((IBindable[]) collectionOrArray)[Integer.parseInt(discriminator)];
    }
    else {
      return getBindableHavingPropertyOfValue((IBindable[]) collectionOrArray, discriminator.substring(0, equalsIndex),
          discriminator.substring(equalsIndex + 1));
    }
  }

  private IBindable getBindableHavingPropertyOfValue(IBindable[] array, String propertyName, String stringValue) {
    for(final IBindable b : array) {
      try {
        if((b.getProperty(propertyName) + "").equals(stringValue)) {
          return b;
        }
      }
      catch(final Exception e) {
        throw new BindingException(e);
      }
    }
    throw new RuntimeException("No Object matching " + propertyName + "=" + stringValue);
  }

  private IBindable getBindableHavingPropertyOfValue(Iterable<IBindable> itr, String propertyName, String stringValue) {
    for(final IBindable b : itr) {
      try {
        if((b.getProperty(propertyName) + "").equals(stringValue)) {
          return b;
        }
      }
      catch(final Exception e) {
        throw new BindingException(e);
      }
    }

    throw new BindingException("No Object matching " + propertyName + "=" + stringValue);
  }

  private IBindable getBindableAtMapKey(Map<String, IBindable> map, String key) {
    IBindable result = null;

    for(final Iterator<Entry<String, IBindable>> it = map.entrySet().iterator(); it.hasNext();) {
      final Entry<String, IBindable> e = it.next();
      if(e.getKey().toString().equals(key)) {
        result = e.getValue();
        break;
      }
    }

    return result;
  }

  private IBindable getBindableAtCollectionIndex(Iterable<IBindable> collection, int index) {
    int i = 0;
    for(final IBindable b : collection) {
      if(i++ == index) return b;
    }
    throw new BindingException("Binding discriminator too high: " + index);
  }

  @Override
  public boolean equals(final Object obj) {
    if(obj == null) return false;
    if(obj.getClass() != getClass()) return false;
    final Binding other = (Binding) obj;
    if(left != other.left && (left == null || !left.equals(other.left))) {
      return false;
    }
    if(right != other.right && (right == null || !right.equals(other.right))) {
      return false;
    }
    return true;
  }

  /**
   * @return int based on hash of the two objects being bound.
   */
  @Override
  public int hashCode() {
    return right.object.hashCode() ^ left.object.hashCode();
  }

  /**
   * @return String value representing the binding.
   */
  @Override
  public String toString() {
    return "Binding [ \n + " + right.object + " \n " + left.object + "\n ]";
  }
}
TOP

Related Classes of com.tll.client.bind.Binding$DefaultPropertyChangeListener

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.