Notice: This Wiki is now read only and edits are no longer possible. Please see: https://gitlab.eclipse.org/eclipsefdn/helpdesk/-/wikis/Wiki-shutdown-plan for the plan.
JFace Data Binding/SSE
Contents
- 1 Target
- 2 JFace DOM-SSE Databinding
Target
The Structured Source Editing (SSE) project, a sub-project of WTP, provides an API to manage structured documents like DOM XML, CSS document...
The goal of JFace Data Binding/SSE is to support bindings to SSE nodes (DOM, CSS...). See (for the moment) TK-UI SVN but we are discussing with the Eclipse Team on where it can be made available.
This project can be used for instance when you wish to edit an XML content with an UI editor like :
- PDE which manage plugin.xml with an UI (Extensions page).
- JSF Web Tools which manage faces-config.xml with an UI.
In the JFace Data Binding/SSE sample, you can find a Shapes Editor which manages some XML content. Here you can see that SWT Text title UI is bound to the title attribute of the diagram element :
JFace DOM-SSE Databinding
Before explaining JFace DOM-SSE Databinding, if you don't know DOM-SSE, I advice you to read the section #What is DOM-SSE?
Features/Node instances observed
To explain JFace DOM-SSE Databinding features, take the sample Shapes DOM-SSE Editor, which binds the attribute title of the root element of the DOM Document with an SWT Text widget (title). The observed Node is the root element :
Element diagram = document.getDocumentElement();
This node instance may changes at any time, when removing the diagram element and then recreating it while directly editing the XML source.
We start with the XML content
<diagram title="My shapes diagram"
You can see, that XML is not well formed because diagram element is not closed. WST is able to build a W3C DOM Document even if XML content is not well formed.
Bind with an existing observed Node
When you open the XML shape file into Shapes DOM-SSE Editor, you can see XML editor is filled with XML content and SWT Text UI has "My shapes diagram" value :
In this case, diagram element instance is observed.
Observed Node = instance1.
Bind with null observed Node
Remove the XML content of the XML editor, the XML editor is blank and SWT Text UI has blank value :
In this case, root element is null and it's this null value wich is observed.
Observed Node = instance2.
Re-bind observed Node
Re-type the XML content into XML editor :
<diagram title="My shapes diagram"
Diagram element is created and append it to the DOM. SWT Text UI has again "My shapes diagram" value.
Observed Node = instance3.
Lazy observed Node
Imagine you have blank XML content into XML editor and that user type "M", into SWT Text title. With standard databinding, XML editor will not updated because observed Node doesn't exist. With JFace DOM-SSE Databinding you can manage that. It's called Lazy Observed Node.
To test that, set blank XML content into XML editor. this action remove diagram element instance3. Observed Node is null (Observed Node = instance4).
Type "M" into SWT Text title. This action generate diagram element and rebind it (Observed Node =instance5). This re-binding generate title attribute with "M" value :
Sample code
Before read below code, you must understand #Concept of JFace DOM-SSE Databinding. This sample show you how bind SWT Text title with title attribute of diagram element :
Realm realm = ... DataBindingContext context = ... // 1. Create container observed instance for DOM-SSE IInstanceObservedContainer container = new DOMModelInstanceObservedContainer(model, this); // 2. Create diagram observed with lazy mode. IObserving diagramObserving = new ILazyObserving() { public Object getObserved() { return document.getDocumentElement(); } public Object createObserved() { Element diagram = document.createElement("diagram"); return document.appendChild(diagram); } }; // 3. Create IObservableValue with InstanceObservables. // InstanceObservables#observeValue create an ObservedValue by using // diagramObserving and register it into the instance container IObservableValue diagramObservable = InstanceObservables.observeValue(container, diagramObserving); // Bind SWT Text 1 with title attribute of root Node. context.bindValue( SWTObservables.observeText(titleText, SWT.Modify), SSEDOMObservables.observeDetailAttrValue(realm,diagramObservable, "title"), null, null);
Concept
As you can see into the #Features/Node instances observed section, JFace DOM-SSE Databinding manage :
- Re-bind instance Observed Node.
- Lazy creation of Observed Node if need.
Master detail/ObservableValue
I have posted question about instance model change and you can find Matthew Hall answer at http://www.eclipse.org/newsportal/article.php?id=80276&group=eclipse.platform#80276. Thank's Matthew!
Beans context
I re-explain the problematic with Bean context which is the same for DOM Node. We wish bind "name" property of Person Bean instance with SWT Text title. How manage the re-binding when person instance change? Here Matthew Hall answer :
Realm realm = SWTObservables.getRealm(titleText.getDisplay()); // SWT IObservableValue observableValue1 = SWTObservables.observeText(titleText, SWT.Modify); // Beans IObservableValue personObservableValue = new WritableValue(realm, new Person(), Person.class); IObservableValue observableValue2 = BeansObservables.observeDetailValue(personObservableValue , "name"); // Binding DataBindingContext bindingContext = new DataBindingContext(realm); bindingContext.bindValue(observableValue1 , observableValue2 , null, null); // Here binding works. personObservableValue.setValue(new Person()); // Here binding has switched over to the new Person instance
The solution to manage that is to use master detail concept. Instead of using Person instance to bind with SWT Text observable value, we do :
- create the personObservableValue WritableValue which observe Person instance :
IObservableValue personObservableValue = new WritableValue(realm, new Person(), Person.class);
- create the IObservableValue observableValue2 by using master detail :
IObservableValue observableValue2 = BeansObservables.observeDetailValue(personObservableValue , "name");
- Use the observableValue2 to bind it with SWT Text observable value.
bindingContext.bindValue(observableValue1 , observableValue2 , null, null);
So when Person instance change, we must do like this :
personObservableValue .setValue(new Person());
DOM context
We can translate this problematic with DOM Node which observe attribute name of person element with JFace DOM Databinding:
<people> <person name="Matthew" /> </people>
DOM context/Simple IObservableValue
But before translating the problem of Node instance change, take a sample with DOM binding. Imagine you want display into Textarea area the XML content and you want bind the attribute name with an SWT Text :
To do that (without Master detail), you do the binding like this :
Document document = ... Element personElement = (Element)document.getElementsByTagName("person").item(0); Realm realm = SWTObservables.getRealm(titleText.getDisplay()); // SWT IObservableValue observableValue1 = SWTObservables.observeText(titleText, SWT.Modify); // DOM IObservableValue observableValue2 = DOMObservables.observeAttrValue(personElement , "name"); // Binding DataBindingContext bindingContext = new DataBindingContext(realm); bindingContext.bindValue(observableValue1 , observableValue2 , null, null);
When you change SWT Text, it update attribute value, and when you update attribute value into Textarea which display XML content, it update SWt Text. Binding works well.
BUT, if you remove person element :
And you re-type person element into Textarea, attribute binding is broken. Why? Because person observed Node personElement instance has changed. The following section explain how manage this problem with master detail concept.
DOM context/Master detail
Matthew solution can be translated with DOM context by using master detail :
Document document = ... Element personElement = (Element)document.getElementsByTagName("person").item(0); Realm realm = SWTObservables.getRealm(titleText.getDisplay()); // SWT IObservableValue observableValue1 = SWTObservables.observeText(titleText, SWT.Modify); // DOM IObservableValue personObservableValue = new WritableValue(realm, personElement, null); IObservableValue observableValue2 = DOMObservables.observeDetailValue(personObservableValue , "name"); // Binding DataBindingContext bindingContext = new DataBindingContext(realm); bindingContext.bindValue(observableValue1 , observableValue2 , null, null); // Here binding works. // DOM Node change somewhere in the code personElement = (Element)document.getElementsByTagName("person").item(0); personObservableValue.setValue(personElement); // Here binding has switched over to the new Element Person instance
DOM Node change somewhere in the code, what is it? If DOM Document implements DOM level2, you can observe the change of DOM by adding DOM EventListener and rebind person element observed, if Node instance has changed :
((EventTarget) document).addEventListener( DOMEventConstants.DOMSubtreeModified, new EventListener() { public void handleEvent(Event evt) { if (personElement == null || personElement.getParentNode() == null) { personElement=(Element)document.getElementsByTagName("person").item(0); personObservableValue.setValue(personElement); } } }, false);
You can improve this code by using IObserving interface to retreive observed person Node.
final IObserving personObserving = new IObserving() { public Object getObserved() { return document.getElementsByTagName("person").item(0); } }; ... ((EventTarget) document).addEventListener( DOMEventConstants.DOMSubtreeModified, new EventListener() { public void handleEvent(Event evt) { if (personElement == null || personElement.getParentNode() == null) { personElement=(Element)personObserving.getObserved(); personObservableValue.setValue(personElement); } } }, false);
Conclusion
The last code which manage re-binding of instance change is not generic, when you wish manage this logic with any XML content, but it show you we need :
- An writable value which observe Node instance which can be change. This is managed with #ObservedValue/LazyObservedValue
- A listener which re-attach binding by updating new instance Node by calling IObservableValue#setValue(Object value) of the writable value. This is managed with #IInstanceObservedContainer.
More we have seen sample with DOM which implements DOM level2 (EventTarget), but DOM-SSE doesn't implements DOM level2, so to JFace DOM-SSE Databinding provides a container instance concept to manage any container (DOM level2, DOM-SSE, EMF...).
- Manage a list of observed instance Node.
IObserving/ILazyObserving
Observed Node instance change every time and can be null. So when IObservableValue must be created to observe the Node, Node cannot be use directly because it can be null on start and instance change evry time. The first concept is to use org.eclipse.core.databinding.IObserving interface to observe Node :
package org.eclipse.core.databinding.observable; public interface IObserving { public Object getObserved(); }
In the diagram element case, IObserving is implemented like this :
IObserving diagramObserved = new IObserving() { public Object getObserved() { return document.getDocumentElement(); } };
To manage lazy observed Node, JFace DOM-SSE Databinding provides org.eclipse.ufacekit.core.databinding.instance.observable.ILazyObserving which extends IObserving and IObservedFactory :
package org.eclipse.ufacekit.core.databinding.instance.observable; import org.eclipse.core.databinding.observable.IObserving; public interface ILazyObserving extends IObserving, IObservedFactory { }
org.eclipse.ufacekit.core.databinding.instance.observableIObservedFactory have IObservedFactory#createObserved() method which is called when observed Node cannot be find when another IObservableValue (In Shapes DOM-SSE Editor context, SWT TextObservable) whish update the value of observed Node (attribute value) :
package org.eclipse.ufacekit.core.databinding.instance.observable; public interface IObservedFactory { Object createObserved(); }
In the diagram element case, ILazyObserving is implemented like this :
IObserving diagramObserved = new ILazyObserving() { public Object getObserved() { return document.getDocumentElement(); } public Object createObserved() { Element diagram = document.createElement("diagram"); return document.appendChild(diagram); } };
IInstanceObservedContainer
JFace DOM-SSE Databinding manage the rebinding of observed Node instance if need. To manage that, the first concept is to have a container of observed instance which is enable to manage the problematic of change observed instance. JFace DOM-SSE Databinding provides the org.eclipse.ufacekit.core.databinding.instance.IInstanceObservedContainer interface :
package org.eclipse.ufacekit.core.databinding.instance; import org.eclipse.core.databinding.observable.IObserving; public interface IInstanceObservedContainer { void addObserving(IObserving observing, IInstanceObservedChanged changed); void observeInstances(); void dispose(); }
IInstanceObservedContainer has 3 methods :
- addObserving : this method register the #IObserving/ILazyObserving into the container and add it an event listener #IInstanceObservedChanged which is called as soon as Node instance change.
- observeInstances : this method loop for each #IObserving/ILazyObserving which was registered and check if instance observed Node IObserving#getObserved() has change. If it has change, it call list of #IInstanceObservedChanged linked to the IObserving. Into DOM-SSE context, this method is called as soon as DOM change.
- dispose : this method unregister the observing registered and remove listener if need. Into DOM-SSE context, it's very IMPORTANT to call it when EditorPart is closed, to remove listener.
IInstanceObservedChanged
IInstanceObservedChanged is event listener which is called by a IInstanceObservedContainer implementation, as soon as Node instance change.
package org.eclipse.ufacekit.core.databinding.instance; public interface IInstanceObservedChanged { void instanceChanged(Object oldInstance, Object newInstance); }
AbstractInstanceObservedContainer
IInstanceObservedContainer can be implemented with several way to manage Bean, EMF, DOM instance. The class base is org.eclipse.ufacekit.core.databinding.instance.AbstractInstanceObservedContainer which implements the 3 #IInstanceObservedContainer methods.
public abstract class AbstractInstanceObservedContainer implements IInstanceObservedContainer { ... protected abstract void dispose(Object observed); protected abstract boolean isDirty(Object oldInstance, IObserving observing); ...
You must implements 2 methods :
- dispose : if you wish for instance remove listener for each observed Node instance when IInstanceObservedContainer #dispose() is called.
- isDirty : returns true if current Node instance (oldInstance) is dirty (instance has changed) and false otherwise.
StructuredModelInstanceObservedContainer
StructuredModelInstanceObservedContainer is abstract class container for SSE model. It extends AbstractInstanceObservedContainer and manage SSE Model. The constrctor of this class wait org.eclipse.wst.sse.core.internal.provisional.IStructuredModel instance. Into this constructor a org.eclipse.wst.sse.core.internal.provisional.IModelStateListener is added to the SSE model to call IInstanceObservedContainer#observeInstances() each time SSE model change :
private class InternalModelStateListener implements IModelStateListener { ... public void modelChanged(IStructuredModel model) { StructuredModelInstanceObservedContainer.super.observeInstances(); } ... }
DOMModelInstanceObservedContainer
DOMModelInstanceObservedContainer is class container for DOM-SSE model. It extends StructuredModelInstanceObservedContainer and manage DOM-SSE Model. It implements AbstractInstanceObservedContainer#isDirty(Object oldInstance, IObserving) like this :
protected boolean isDirty(Object oldInstance, IObserving observing) { if (oldInstance == null) return true; return (((Node) oldInstance).getParentNode() == null); }
When Node as not parent Node, it means that Node is not attached to a DOM Document. This case comes when Node instance has changed.
ObservedValue/LazyObservedValue
ObservedValue
package org.eclipse.ufacekit.core.databinding.instance.observable.value; import org.eclipse.core.databinding.observable.IObserving; import org.eclipse.core.databinding.observable.Realm; import org.eclipse.core.databinding.observable.value.WritableValue; import org.eclipse.ufacekit.core.databinding.instance.IInstanceObservedChanged; public class ObservedValue extends WritableValue implements IInstanceObservedChanged { private IObserving observing; public ObservedValue(Realm realm, IObserving observing) { super(realm, observing.getObserved(), null); this.observing = observing; } public ObservedValue(IObserving observing) { super(observing.getObserved(), null); this.observing = observing; } public void instanceChanged(Object oldInstance, Object newInstance) { super.setValue(newInstance); } public IObserving getObserving() { return observing; } }
LazyObservedValue
package org.eclipse.ufacekit.core.databinding.instance.observable.value; import org.eclipse.core.databinding.observable.Realm; import org.eclipse.ufacekit.core.databinding.instance.observable.ILazyObserving; import org.eclipse.ufacekit.core.databinding.instance.observable.IObservedFactory; public class LazyObservedValue extends ObservedValue implements IObservedFactory { private boolean creatingObserved = false; public LazyObservedValue(Realm realm, ILazyObserving observing) { super(realm, observing); } public LazyObservedValue(ILazyObserving observing) { super(observing); } public void instanceChanged(Object oldInstance, Object newInstance) { if (!creatingObserved) super.instanceChanged(oldInstance, newInstance); } public Object createObserved() { try { creatingObserved = true; return ((ILazyObserving) getObserving()).createObserved(); } finally { creatingObserved = false; } } }
InstanceObservables
InstanceObservables factory for IObservableValue :
package org.eclipse.ufacekit.core.databinding.instance; import org.eclipse.core.databinding.observable.IObserving; import org.eclipse.core.databinding.observable.value.IObservableValue; import org.eclipse.ufacekit.core.databinding.instance.observable.ILazyObserving; import org.eclipse.ufacekit.core.databinding.instance.observable.value.LazyObservedValue; import org.eclipse.ufacekit.core.databinding.instance.observable.value.ObservedValue; public class InstanceObservables { public static IObservableValue observeValue( IInstanceObservedContainer container, IObserving observing) { ObservedValue observableValue = createObservedValue(observing); container.addObserving(observing, observableValue); return observableValue; } private static ObservedValue createObservedValue(IObserving observing) { if (observing instanceof ILazyObserving) { return new LazyObservedValue((ILazyObserving) observing); } return new ObservedValue(observing); } }
LazyMasterDetailObservables
Return an instance of LazyDetailObservableValue. Same code than DetailObservableValue but manage lazy mode of IObserving :
... public void doSetValue(Object value) { if (innerObservableValue != null) innerObservableValue.setValue(value); else { // FIXME : Add code to manage lazy creation of observed instance... if (outerObservableValue instanceof IObservedFactory) { Object newObserved = ((IObservedFactory) outerObservableValue) .createObserved(); if (newObserved != null) { updateInnerObservableValue(outerObservableValue, newObserved); if (innerObservableValue != null) innerObservableValue.setValue(value); } } } }
Why JFace DOM-SSE Databinding?
This project provides the capability to manage binding with DOM-SSE nodes (IDOMNode). It is based on JFace DOM Databinding. Why having JFace DOM-SSE Databinding although JFace DOM Databinding manage DOM bindings.? The answer is :
- DOM event listener problem : INodeNotifier 2 EventTarget. JFace DOM Databinding use W3C DOM Event Model to observe DOM node change. On other words Node of DOM Document to bind must implement EventTarget. DOM-SSE have their own Event Model. Node of DOM-SSE Document implement INodeNotifier. Goal of JFace DOM-SSE Databinding is to adapt DOM-SSE event INodeNotifier to W3C DOM event EventTarget.
- Instance nodes problem. JFace DOM Databinding provides several observable to observe a Node instance. If Node instance change, binding is broken. Into XML editor context, the XML is typed by user, DOM is every time broken. Node instances change every time, so it's difficult to observe a Node instance, because instance change every time. Goal of JFace DOM-SSE Databinding is enable to observe Nodes. If Node is removed (user remove the XML content) and recreated (user retype XML content), JFace DOM-SSE Databinding is enable to recreate observable to observe the new Node instance.
What is DOM-SSE?
DOM-SSE is W3c DOM Document comming from IFile or TextEditor. XML Editor from WTP which is based on StructuredTextEditor manage internally DOM-SSE. The DOM-SSE is updated as soon as user type XML content. DOM-SSE is getted from WST IDOMModel like this :
IFile file = ... IModelManager manager = StructuredModelManager.getModelManager(); IDOMModel model = (IDOMModel)manager.getExistingModelForRead(file); if (model == null) { model = manager.getModelForRead(file); } Document document = model.getDocument();
Each Nodes of DOM Document implements org.eclipse.wst.xml.core.internal.provisional.document.IDOMNode interface which extends :
- W3C org.w3c.dom.Node.
- org.eclipse.wst.sse.core.internal.provisional.INodeNotifier.
DOM-SSE can be observed by using :
- IDOMModel#addModelStateListener(IModelStateListener listener) : as soon as DOM-SSE is updated (add/remove/update any Nodes), you can use IModelStateListener#modelChanged(IStructuredModel model) to detect that :
IDOModel model = ... model.addModelStateListener(new IModelStateListener(){ ... public void modelChanged(IStructuredModel model) { } });
- org.eclipse.wst.sse.core.internal.provisional.INodeNotifier provides the capability to add event listener and observe change of the node.
IDOMElement element = ... element.addAdapter(new INodeAdapter() { public boolean isAdapterForType(Object type) { return true; } public void notifyChanged(INodeNotifier notifier, int eventType, Object changedFeature, Object oldValue, Object newValue, int pos) { // Element has changed (attribute changed, ...) // Do something } });