/*******************************************************************************
* Copyright (c) 2008 Hallvard Traetteberg.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Hallvard Traetteberg - initial API and implementation
******************************************************************************/
package org.eclipse.e4.tm.builder;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.StringTokenizer;
import org.eclipse.e4.tm.builder.swt.SwtBuilder;
import org.eclipse.emf.common.notify.Adapter;
import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.common.notify.impl.AdapterImpl;
import org.eclipse.emf.common.util.ECollections;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
public class AbstractBinder extends AdapterImpl implements IBinder {
public final static String ANNOTATION_URI = SwtBuilder.getAnnotationUri();
protected IBinderContext context = null;
// IBinder methods
public Object update(EObject eObject, Object object, IBinderContext context) {
this.context = context;
try {
if (object == null) {
object = create(eObject);
context.putObject(eObject, object);
context.fireObjectHandled(IBuilderListener.BUILD, eObject, object);
if (object != null) {
EList<Adapter> adapters = eObject.eAdapters();
if (adapters.contains(this)) {
System.err.println("Duplicate adapter for " + eObject);
}
adapters.add(this);
if (eObject != object) {
updateFeatures(eObject, object);
} else {
for (EObject child: eObject.eContents()) {
context.update(child);
}
}
}
} else {
context.fireObjectHandled(IBuilderListener.UPDATE, eObject, object);
}
} finally {
context = null;
}
return object;
}
public void dispose(EObject eObject, Object object, IBinderContext context) {
eObject.eAdapters().remove(this);
context.putObject(eObject, null);
for (EObject child: eObject.eContents()) {
context.dispose(child);
}
if (object != null) {
AbstractBuilder.dispose(object);
}
}
public <T> T adapt(Object value, Class<T> c) {
return (c.isInstance(value) ? (T)value : null);
}
protected <T> T getParent(EObject eObject, Class<T> c) {
EObject parent = eObject.eContainer();
if (parent == null) {
return context.getRootObject(c);
} else {
return context.getObject(parent, c);
}
}
//
protected void updateFeatures(EObject eObject, Object object) {
List<EStructuralFeature> features = eObject.eClass().getEAllStructuralFeatures();
for (Iterator<EStructuralFeature> it = features.iterator(); it.hasNext();) {
EStructuralFeature feature = it.next();
if (shouldHandleFeature(feature) && (! shouldntHandleFeature(feature))) {
update(eObject, feature, object, true);
}
}
}
protected void update(EObject control, EStructuralFeature feature, Object object, boolean isInit) {
if (feature instanceof EAttribute) {
copyFeatureValue2Property(control, control.eGet(feature), feature, object, isInit);
} else if (feature.isMany()) {
EList<EObject> values = (EList<EObject>)control.eGet(feature);
for (Iterator<EObject> it = values.iterator(); it.hasNext();) {
EObject value = it.next();
if (value != null) {
context.update(value);
}
}
} else {
EObject featureValue = (EObject)control.eGet(feature);
if (featureValue != null) {
Object value = context.update(featureValue);
copyFeatureValue2Property(control, value, feature, object, isInit);
}
}
}
protected Object convertValue(EStructuralFeature feature, Object value) {
return value;
}
protected Exception copyFeatureValue2Property(EObject eObject, Object value, EStructuralFeature feature, Object object, boolean isInit) {
String access = getAccessMethod(feature, eObject.eClass());
String name = getRealName(feature);
value = convertValue(feature, value);
boolean equals = featureEqualsProperty(feature, eObject.eClass(), value, object);
Exception ex = null;
if ("field".equals(access)) {
if (! equals) {
ex = context.setFieldProperty(object, name, value);
}
} else if ("property".equals(access)) {
if (! equals) {
ex = context.setSetterProperty(object, name, value);
}
} else if ("event".equals(access)) {
if (isInit && feature != null) {
handleEventFeature(eObject, feature, object);
}
} else if ("binder".equals(access)) {
// implicitly handled by the specific binder
} else if (access != null && access.length() > 0) {
if (! equals) {
// getter setter
int pos = access.indexOf(' ');
if (pos >= 0) {
String setter = access.substring(pos + 1).trim();
ex = context.setMethodProperty(object, setter, new Object[]{value});
}
}
}
if (ex != null) {
System.err.println("Couldn't set " + name + " " + access + " of " + object + ": " + ex);
Throwable cause = ex.getCause();
if (cause != null && cause != ex) {
System.err.println("Cause: " + cause);
}
}
return ex;
}
protected String getAccessMethod(EStructuralFeature feature, EClass realClass) {
return AbstractBuilder.getFeatureAnnotation(feature, realClass, ANNOTATION_URI, "access", null);
}
protected boolean featureEqualsProperty(EStructuralFeature feature, EClass realClass, Object value, Object object) {
return (! feature.isMany()) && equals(value, getPropertyValue(feature, realClass, object, UNDEFINED));
}
protected boolean equals(Object value, Object oldValue) {
return oldValue == value || (oldValue != null && oldValue.equals(value));
}
protected boolean shouldHandleFeature(EStructuralFeature feature) {
return true;
}
protected boolean shouldntHandleFeature(EStructuralFeature feature) {
if (feature instanceof EReference && ((EReference)feature).isContainer()) {
return true;
}
return false;
}
protected void handleEventFeature(final EObject control, final EStructuralFeature feature, Object object) {
}
protected String getRealName(final EStructuralFeature feature) {
return AbstractBuilder.getAnnotation(feature, ANNOTATION_URI, "realName", feature.getName());
}
protected String getClassAnnotation(EObject control) {
return AbstractBuilder.getClassAnnotation(control.eClass(), ANNOTATION_URI, "style", null);
}
protected Class<?> getToolkitClass(EObject eObject, boolean warn) {
String eClassName = eObject.eClass().getName();
String className = AbstractBuilder.getClassAnnotation(eObject.eClass(), ANNOTATION_URI, "realName", null);
if (className == null) {
className = AbstractBuilder.getClassAnnotation(eObject.eClass(), ANNOTATION_URI, "javaClass", eClassName);
}
String packageName = AbstractBuilder.getClassAnnotation(eObject.eClass(), ANNOTATION_URI, "javaPackage", null);
if (packageName == null) {
packageName = AbstractBuilder.getAnnotation(eObject.eClass().getEPackage(), ANNOTATION_URI, "javaPackage", null);
}
className = (packageName != null ? (packageName + ".") : "") + (className.indexOf('.') < 0 ? Character.toUpperCase(className.charAt(0)) + className.substring(1) : className);
try {
return Class.forName(className);
} catch (ClassNotFoundException e) {
if (warn) {
System.err.println("Exception when getting toolkit class for " + this + ": " + e);
}
}
return null;
}
protected Object create(EObject control) {
boolean genericBinder = this.getClass() == AbstractBinder.class;
Class<?> toolkitClass = getToolkitClass(control, ! genericBinder);
if (toolkitClass == null && genericBinder) {
return control;
}
try {
return createForClass(toolkitClass);
} catch (Exception e) {
System.err.println("Exception when creating toolkit object of " + toolkitClass + ": " + e);
}
return null;
}
protected Object createForClass(Class<?> swtClass) throws Exception {
Exception e = null;
try {
return swtClass.newInstance();
} catch (InstantiationException ie1) {
e = ie1;
Constructor<?>[] constructors = swtClass.getConstructors();
for (int i = 0; i < constructors.length; i++) {
try {
return createWithConstructor(constructors[i]);
} catch (InstantiationException ie2) {
e = ie2;
}
}
}
throw e;
}
protected Object createWithConstructor(Constructor<?> constructor) throws Exception {
Class<?>[] types = constructor.getParameterTypes();
Object[] arguments = new Object[types.length];
for (int i = 0; i < types.length; i++) {
arguments[i] = getDefaultFor(types[i]);
}
return constructor.newInstance(arguments);
}
protected Object getDefaultFor(Class<?> c) {
if (Number.class.isAssignableFrom(c)) {
return 0;
} else if (c == int.class || c == short.class || c == byte.class) {
return 0;
} else if (c == double.class || c == float.class) {
return 0.0;
} else if (c == char.class) {
return '\0';
} else if (c == boolean.class) {
return false;
}
return null;
}
protected Object UNDEFINED = new Object();
protected void invalidateFeature(EObject eObject, EStructuralFeature feature, Object object, boolean isEvent) {
if (isEvent) {
Object value = getPropertyValue(feature, eObject.eClass(), object, UNDEFINED);
if (value != UNDEFINED) {
setFeatureValue(eObject, feature, value);
}
} else {
updateInvalidFeature(eObject, feature, object);
}
}
protected void updateInvalidFeature(EObject eObject, EStructuralFeature feature, Object object) {
update(eObject, feature, object, false);
}
protected Object getPropertyValue(EStructuralFeature feature, EClass realClass, Object object, Object def) {
String access = getAccessMethod(feature, realClass);
Object value = def;
if (access != null) {
String name = getRealName(feature);
try {
if ("field".equals(access)) {
value = context.getFieldProperty(object, name);
} else if ("property".equals(access)) {
value = context.getGetterProperty(object, name);
} else if ("binder".equals(access)) {
} else if (access != null) {
/* getter setter */
int pos = access.indexOf(' ');
if (pos >= 0) {
String getter = access.substring(0, pos).trim();
value = context.getMethodProperty(object, getter, null);
}
} else {
}
} catch (Exception e) {
}
}
return value;
}
protected void setFeatureValue(EObject eObject, EStructuralFeature feature, Object value) {
Class<?> type = feature.getEType().getInstanceClass();
if (feature.isMany()) {
List<Object> values = Collections.EMPTY_LIST;
if (value instanceof List) {
values = (List<Object>)value;
} else if (value instanceof Collection) {
values = new ArrayList<Object>((Collection<?>)value);
} else if (value.getClass().isArray()) {
values = new ArrayList<Object>();
int length = Array.getLength(value);
for (int i = 0; i < length; i++) {
Object o = Array.get(value, i);
Object o2 = context.adapt(o, type);
values.add(o2);
}
}
EList<Object> eList = (EList<Object>)eObject.eGet(feature);
ECollections.setEList(eList, values);
} else {
Object value2 = context.adapt(value, type);
eObject.eSet(feature, value2);
}
}
// notification handling
protected void notifyChanged(EObject eObject, EStructuralFeature feature, Notification notification) {
Object object = context.getObject(eObject, Object.class);
boolean isEvent = "event".equals(getAccessMethod(feature, eObject.eClass()));
invalidateFeature(eObject, feature, object, isEvent);
String invalidates = AbstractBuilder.getFeatureAnnotation(feature, null, ANNOTATION_URI, "invalidates", null);
for (StringTokenizer tokens = SwtBuilder.getSeparatedTokens(invalidates); tokens.hasMoreTokens();) {
String featureName = tokens.nextToken();
if (Character.isUpperCase(featureName.charAt(0))) {
// propagate up
context.invalidateFeature(eObject, featureName);
} else {
EStructuralFeature invalidatedFeature = eObject.eClass().getEStructuralFeature(featureName);
if (invalidatedFeature != null) {
invalidateFeature(eObject, invalidatedFeature, object, isEvent);
}
}
}
}
public void notifyChanged(Notification notification) {
if (notification.getNotifier() instanceof EObject && notification.getFeature() instanceof EStructuralFeature) {
notifyChanged((EObject)notification.getNotifier(), (EStructuralFeature)notification.getFeature(), notification);
}
}
//
public boolean validateFeature(EObject eObject, Object object, String featureName, IBinderContext context) {
if ("Object".equals(featureName)) {
context.dispose(eObject);
context.update(eObject);
return true;
}
return false;
}
public void updateStyle(EObject eObject, Object object, IBinderContext context) {
}
}