package info.limpet.ui.data_provider.data;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.jexl3.JexlBuilder;
import org.apache.commons.jexl3.JexlContext;
import org.apache.commons.jexl3.JexlEngine;
import org.apache.commons.jexl3.JexlExpression;
import org.apache.commons.jexl3.MapContext;
import org.apache.commons.jexl3.internal.Script;
import org.eclipse.core.runtime.Status;
import org.eclipse.ui.views.properties.IPropertyDescriptor;
import org.eclipse.ui.views.properties.IPropertySource;
import info.limpet.UIProperty;
import info.limpet.ui.Activator;
/**
* An {@link IPropertySource} that uses {@link UIProperty} annotations to determine the set of
* {@link org.eclipse.ui.views.properties.PropertyDescriptor}s.
*
*/
public class ReflectivePropertySource implements IPropertySource
{
private final Object object;
private IPropertyDescriptor[] propertyDescriptors;
private Map<String, PropertyDescriptor> descriptorPerProperty;
/**
* a list of property handlers that we know about
*
*/
private ArrayList<PropertyTypeHandler> myHandlers;
public ReflectivePropertySource(Object object)
{
this.object = object;
/**
* define the handlers we know about. In the future this list could be extended by reading
* extensions from the plugin.xml
*
*/
myHandlers = new ArrayList<PropertyTypeHandler>();
myHandlers.add(PropertyTypeHandler.STRING);
myHandlers.add(PropertyTypeHandler.BOOLEAN);
myHandlers.add(PropertyTypeHandler.INTEGER);
myHandlers.add(PropertyTypeHandler.DOUBLE);
myHandlers.add(PropertyTypeHandler.UNIT);
myHandlers.add(PropertyTypeHandler.QUANTITY_RANGE);
myHandlers.add(PropertyTypeHandler.GEOMETRY);
// TODO: consider that some UI properties would like to provide custom
// handler
// or one that is not the default. Perhaps make the UIProperty annotation
// provide the handler class as well if needed
}
@Override
public Object getEditableValue()
{
return object;
}
@Override
public IPropertyDescriptor[] getPropertyDescriptors()
{
if (propertyDescriptors == null)
{
initPropertyDescriptors(object);
}
return propertyDescriptors;
}
private void initPropertyDescriptors(Object object)
{
descriptorPerProperty = new HashMap<String, PropertyDescriptor>();
List<IPropertyDescriptor> result = new ArrayList<IPropertyDescriptor>();
try
{
PropertyDescriptor[] beanPropertyDescriptors =
Introspector.getBeanInfo(object.getClass()).getPropertyDescriptors();
for (PropertyDescriptor beanPropertyDescriptor : beanPropertyDescriptors)
{
UIProperty annotation =
beanPropertyDescriptor.getReadMethod().getAnnotation(
UIProperty.class);
if (annotation != null)
{
// skip descriptor if not visible
if (!annotation.visibleWhen().isEmpty()
&& !evaluateVisibility(object, beanPropertyDescriptors,
annotation.visibleWhen()))
{
continue;
}
org.eclipse.ui.views.properties.PropertyDescriptor descriptor = null;
String propId = beanPropertyDescriptor.getName();
if (beanPropertyDescriptor.getWriteMethod() != null)
{
PropertyTypeHandler propertyTypeHandler =
getPropertyTypeHandler(beanPropertyDescriptor.getPropertyType());
descriptor =
propertyTypeHandler
.createPropertyDescriptor(propId, annotation);
}
else
{
// read only descriptor
descriptor =
new org.eclipse.ui.views.properties.PropertyDescriptor(propId,
annotation.name());
descriptor.setCategory(annotation.category());
}
result.add(descriptor);
descriptorPerProperty.put(propId, beanPropertyDescriptor);
}
}
}
catch (IntrospectionException e)
{
Activator.logError(Status.ERROR,
"Could not load property descriptors for class "
+ object.getClass().getName(), e);
}
propertyDescriptors =
result.toArray(new IPropertyDescriptor[result.size()]);
}
private boolean evaluateVisibility(Object object,
PropertyDescriptor[] beanPropertyDescriptors, String visibleWhen)
{
Map<String, PropertyDescriptor> descriptorsMap =
new HashMap<String, PropertyDescriptor>();
for (PropertyDescriptor pd : beanPropertyDescriptors)
{
descriptorsMap.put(pd.getName(), pd);
}
JexlEngine jexl = new JexlBuilder().create();
JexlExpression expression = jexl.createExpression(visibleWhen);
JexlContext context = new MapContext();
Set<List<String>> vars = ((Script) expression).getVariables();
for (List<String> varList : vars)
{
for (String varName : varList)
{
PropertyDescriptor propertyDescriptor = descriptorsMap.get(varName);
if (propertyDescriptor != null)
{
try
{
Object value = propertyDescriptor.getReadMethod().invoke(object);
context.set(varName, value);
}
catch (Exception e)
{
Activator.logError(Status.ERROR,
"Could not retrieve value for property " + varName, e);
}
}
}
}
return (boolean) expression.evaluate(context);
}
@Override
public Object getPropertyValue(Object id)
{
PropertyDescriptor descriptor = descriptorPerProperty.get(id);
try
{
Object value = descriptor.getReadMethod().invoke(object);
// editable properties use custom cell editor, thus value needs conversion
if (descriptor.getWriteMethod() != null)
{
PropertyTypeHandler propertyTypeHandler =
getPropertyTypeHandler(descriptor.getPropertyType());
value = propertyTypeHandler.toCellEditorValue(value, object);
}
return value;
}
catch (Exception e)
{
Activator.logError(Status.ERROR, "Could not retrieve value for property "
+ id, e);
return null;
}
}
@Override
public boolean isPropertySet(Object id)
{
PropertyDescriptor descriptor = descriptorPerProperty.get(id);
if (descriptor.getWriteMethod() != null)
{
PropertyTypeHandler propertyTypeHandler =
getPropertyTypeHandler(descriptor.getPropertyType());
UIProperty annotation =
descriptor.getReadMethod().getAnnotation(UIProperty.class);
return getPropertyValue(id) != propertyTypeHandler
.getDefaulValue(annotation);
}
return false;
}
@Override
public void resetPropertyValue(Object id)
{
PropertyDescriptor descriptor = descriptorPerProperty.get(id);
// editable property
if (descriptor.getWriteMethod() != null)
{
PropertyTypeHandler propertyTypeHandler =
getPropertyTypeHandler(descriptor.getPropertyType());
// delegate to set property
UIProperty annotation =
descriptor.getReadMethod().getAnnotation(UIProperty.class);
setPropertyValue(id, propertyTypeHandler.getDefaulValue(annotation));
}
}
@Override
public void setPropertyValue(Object id, Object value)
{
PropertyDescriptor descriptor = descriptorPerProperty.get(id);
PropertyTypeHandler propertyTypeHandler =
getPropertyTypeHandler(descriptor.getPropertyType());
value = propertyTypeHandler.toModelValue(value, object);
try
{
descriptor.getWriteMethod().invoke(object, value);
}
catch (Exception e)
{
Activator.logError(Status.ERROR,
"Could not set value for property " + id, e);
}
}
/**
* @param propertyType
* a property type obtained from the {@link PropertyDescriptor}
* @return
*/
public PropertyTypeHandler getPropertyTypeHandler(Class<?> propertyType)
{
PropertyTypeHandler result = null;
// loop through the handlers we know about
Iterator<PropertyTypeHandler> hIter = myHandlers.iterator();
while (hIter.hasNext())
{
PropertyTypeHandler thisP = hIter.next();
if (thisP.canHandle(propertyType))
{
result = thisP;
break;
}
}
if (result == null)
{
// TODO: handle other property types, such as
// number, complex types, Wrapper types - Boolean, Integer, etc
throw new UnsupportedOperationException("Property type not supported: "
+ propertyType);
}
return result;
}
}