/*******************************************************************************
* Copyright 2013 Geoscience Australia
*
* 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 au.gov.ga.earthsci.editable;
import java.beans.IntrospectionException;
import org.eclipse.sapphire.Element;
import org.eclipse.sapphire.ElementList;
import org.eclipse.sapphire.Property;
import org.eclipse.sapphire.modeling.EditFailedException;
import org.eclipse.sapphire.modeling.Status;
import org.eclipse.sapphire.modeling.Status.Severity;
import org.eclipse.sapphire.modeling.annotations.ReadOnly;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Abstract {@link IBinder} implementation that uses a {@link BeanProperty} to
* get/set property values using reflection.
*
* @author Michael de Hoog (michael.dehoog@ga.gov.au)
*/
public abstract class AbstractBeanPropertyBinder<T, E, P extends Property> implements IBinder<T, E, P>
{
private static final Logger logger = LoggerFactory.getLogger(AbstractBeanPropertyBinder.class);
private BeanProperty beanProperty;
private boolean beanPropertyInited = false;
private T valueForValidation;
private boolean valueForValidationSet;
/**
* Convert the given object read from the bean property to a type supported
* by the Sapphire property system (eg String for value properties, list for
* list properties, etc).
*
* @param value
* Value read from the bean property
* @param property
* Sapphire property being read
* @param beanProperty
* Bean property that was used to read the value
* @param element
* Model element
* @return Converted value
*/
protected abstract T convertTo(Object value, P property, BeanProperty beanProperty, Element element);
/**
* Convert the given value from Sapphire to an object to be used for setting
* on the bean property via reflection. The returned type must support the
* bean property's setter type.
*
* @param value
* Value to convert
* @param property
* Sapphire property that was set
* @param beanProperty
* Bean property that will be used to set the returned value
* @param element
* Model element
* @return Converted value, <code>null</code> if the setter shouldn't be
* invoked
*/
protected abstract Conversion convertFrom(T value, P property, BeanProperty beanProperty, Element element);
protected BeanProperty getBeanProperty()
{
return beanProperty;
}
@Override
public T get(E object, P property, Element element)
{
if (valueForValidationSet)
{
return valueForValidation;
}
initBeanPropertyIfRequired(object, property);
if (beanProperty == null)
{
return null;
}
Object value;
try
{
value = beanProperty.get();
}
catch (Exception e)
{
logger.error("Error invoking '" + property.name() + "' property getter", e); //$NON-NLS-1$ //$NON-NLS-2$
return null;
}
return convertTo(value, property, beanProperty, element);
}
@Override
public void set(T value, E object, P property, Element element)
{
initBeanPropertyIfRequired(object, property);
if (beanProperty == null || beanProperty.isReadOnly())
{
return;
}
boolean resetValueForValidation = true;
try
{
valueForValidation = value;
valueForValidationSet = true;
element.property(property.definition()).refresh();
Status status = element.property(property.definition()).validation();
if (status.severity() == Severity.ERROR)
{
resetValueForValidation = false;
return;
}
Conversion conversion = convertFrom(value, property, beanProperty, element);
if (conversion == null)
{
return;
}
try
{
beanProperty.set(conversion.result);
}
catch (Exception e)
{
logger.error("Error invoking '" + property.name() + "' property setter", e); //$NON-NLS-1$ //$NON-NLS-2$
throw new EditFailedException();
}
}
finally
{
if (resetValueForValidation)
{
valueForValidation = null;
valueForValidationSet = false;
}
}
}
private void initBeanPropertyIfRequired(E object, P property)
{
if (!beanPropertyInited)
{
//use a flag instead of a null check so that, if an exception occurs during instatiation, it is not retried
beanPropertyInited = true;
try
{
beanProperty =
new BeanProperty(object, property.definition(), property.definition().getAnnotation(
ReadOnly.class) != null || property instanceof ElementList<?>);
}
catch (IntrospectionException e)
{
logger.error("Error initializing bean property", e); //$NON-NLS-1$
}
}
}
/**
* Helper class returned from the
* {@link AbstractBeanPropertyBinder#convertFrom} method.
*/
protected static class Conversion
{
public final Object result;
public Conversion(Object result)
{
this.result = result;
}
}
}