/*
 * Copyright 2002-2010 the original author or authors.
 *
 * 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 org.jdal.ui.bind;

import java.beans.PropertyDescriptor;
import java.text.SimpleDateFormat;
import java.util.Date;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jdal.beans.PropertyUtils;
import org.jdal.beans.SpringConverter;
import org.jdal.ui.DefaultModelHolder;
import org.jdal.ui.ModelHolder;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeansException;
import org.springframework.beans.PropertyAccessException;
import org.springframework.beans.PropertyAccessorFactory;
import org.springframework.beans.propertyeditors.CustomDateEditor;
import org.springframework.core.convert.Property;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.BindingResult;

/**
 * Base class for Binders. Implement doBind() to do the binding.
 * Dereference ModelHolders when setting its as binded model.
 * 
 * @author Jose Luis Martin
 * @since 1.1
 */
public abstract class AbstractBinder extends SpringConverter implements PropertyBinder {
	
	private static final Log log = LogFactory.getLog(AbstractBinder.class);

	/** binded property name */ 
	protected String propertyName;
	/** last value, used to revert o detect cicles */
	protected Object oldValue;
	/** binded model */
	private ModelHolder<Object> modelHolder;
	/** component object */
	protected Object component;
	/** if true, binding is readOnly, ie from model to control */
	protected boolean readOnly = false;
	private ControlBindingErrorProcessor errorProcessor = new ControlBindingErrorProcessor();
	private BindingResult bindingResult;
	
	
	public AbstractBinder() {
		registerCustomEditor(Date.class, 
				new CustomDateEditor(SimpleDateFormat.getDateTimeInstance(), true));
	}
	
	/**
	 * {@inheritDoc}
	 */
	public final void bind(Object component, String propertyName, Object model) {
		bind(component, propertyName, model, false);
	}
	
	/**
	 * {@inheritDoc}
	 */
	public final void bind(Object component, String propertyName, Object model, boolean readOnly) {
		this.propertyName = propertyName;
		setModel(model);
		this.component = component;
		this.readOnly = readOnly;
		doBind();
	}

	/**
	 * Hook method to do bind via Component Listeners
	 * By default, do nothing
	 */
	protected void doBind() {
		
	}
	
	public final void refresh() {
		doRefresh();
	}
	
	public final void update() {
		if (!readOnly && modelHolder != null) {
			bindingResult = createBindingResult();
			doUpdate();
		}
	}
	
	/**
	 * Create a new Binding result, override to set nested paths on complex binders
	 * @return a new instance of BindingResult for this model
	 */
	protected BindingResult createBindingResult() {
		Object model = getModel();
		return new BeanPropertyBindingResult(model, model.getClass().getSimpleName());
	}

	abstract protected void doRefresh();
	abstract protected void doUpdate();

	/**
	 * Set value on binded object using the property name.
	 * @param value the value to set
	 */
	protected void setValue(Object value) {
		BeanWrapper wrapper = getBeanWrapper();
		Object convertedValue = convertIfNecessary(value, wrapper.getPropertyType(this.propertyName));
		try {
			wrapper.setPropertyValue(propertyName, convertedValue);
			oldValue = value;
		}
		catch (PropertyAccessException pae) {
			log.error(pae);
			errorProcessor.processPropertyAccessException(component, pae, bindingResult);
		}
	}
	
	/**
	 * Get value from model
	 * @return model value
	 */
	protected Object getValue() {
		BeanWrapper wrapper = getBeanWrapper();
		Object value = null;
		try {
			value = wrapper.getPropertyValue(propertyName);
		} 
		catch (BeansException e) {
			log.error(e);
		}
		
		return value;
	}
	
	private BeanWrapper getBeanWrapper() {
		BeanWrapper wrapper = PropertyAccessorFactory.forBeanPropertyAccess(getModel());
		wrapper.setConversionService(getConversionService());
		wrapper.registerCustomEditor(Date.class, 
				new CustomDateEditor(SimpleDateFormat.getDateTimeInstance(), true));
		
		return wrapper;
	}
	
	/**
	 * @return Property for property binder
	 */
	protected Property getProperty() {
		PropertyDescriptor pd = getPropertyDescriptor();
		return new Property(getModel().getClass(), pd.getReadMethod(), pd.getWriteMethod());
	}
	
	// Getters and Setters

	public String getPropertyName() {
		return propertyName;
	}


	public void setPropertyName(String propertyName) {
		this.propertyName = propertyName;
	}


	protected Object getOldValue() {
		return oldValue;
	}


	protected void setOldValue(Object oldValue) {
		this.oldValue = oldValue;
	}

	public Object getModel() {
		return modelHolder != null ? modelHolder.getModel() : null;
	}

	@SuppressWarnings("unchecked")
	public void setModel(Object model) {
		if (model instanceof ModelHolder) {
			this.modelHolder = (ModelHolder<Object>) model;
		}
		else {
			model = new DefaultModelHolder<Object>(model);
		}
	}

	/**
	 * @return the component
	 */
	public Object getComponent() {
		return component;
	}

	/**
	 * @param component the component to set
	 */
	public void setComponent(Object component) {
		this.component = component;
	}
	
	public BindingResult getBindingResult() {
		return bindingResult;
	}
	
	public Class<?> getPropertyType() {
		return getPropertyDescriptor().getPropertyType();
	}
	
	public PropertyDescriptor getPropertyDescriptor() {
		return PropertyUtils.getPropertyDescriptor(getModel().getClass(), propertyName);
	}
}

@@ -3031,27 +3031,27 @@
 Only && 
-modelHolder
+ getModel()
  != null
@@ -4116,24 +4116,69 @@
 etValue() %7B%0A
+%09%09if (getModel() == null)%0A%09%09%09return null;%0A%09%09%0A
 %09%09BeanWrappe

Fix NPE in AbstractBinder.