Like the title already says the latest nightly build comes with 2 amazing features one already part of the code base since a long time (XPath to traverse the UI-DOM) the second one just hit SVN (plugable model-item mapping). I’m going to discuss them in short in this blog posting.
Plugable Model-Item-Mapping
You probably ask yourself. What’s this and why should I care. To understand the problem you must know how the current JFace-Viewers are working internally and which problems this can cause.
JFace-Viewers store strong Java-References between the domain model and the SWT-Item using TableItem#setData(Object)/TreeItem#setData(Object) and additionally if you turn on hashlookup (to speed up setting of selections, …) in an internal HashTable.
The problem with this is that your domain model stays resident in memory as long as the TableItem exists even if no one really needs it until you e.g. want to update the table-item. This implementation detail of current JFace-Viewers makes the use of CDO in UI less ideal because CDO can’t apply it’s clever memory management because your UI code holds strong references into your domain-object graph.
One can overcome this problem in JFace-Viewer world as well by writing some clever Content- and LabelProviders (The implementation is also available from our repository but not part of a build yet) but in my opinion not ideal from a users point of view. Moreover I think a viewer framework should have the possibility to plug-in “your” mapping strategy (e.g. provided by the domain-technoloy project you are using) according to the use case.
That’s why I decided that this feature has to be part of the core UFaceKit-Viewer-Framework and the support of it has just hit the SVN-Repository and is part of my shining new nightly athena build.
/******************************************************************************* * Copyright (c) 2010, BestSolution.at and others * 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: * Tom Schindl <tom.schindl@bestsolution.at> - Initial API and implementation *******************************************************************************/ package org.eclipse.ufacekit.ui.jface.cdo.viewers; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Map; import org.eclipse.emf.cdo.CDOObject; import org.eclipse.emf.cdo.common.id.CDOID; import org.eclipse.emf.cdo.view.CDOView; import org.eclipse.ufacekit.ui.jface.viewers.Viewer; import org.eclipse.ufacekit.ui.jface.viewers.mapping.AbstractTableItemModelMapping; public class CdoModelTableItemMapping<ModelElement extends CDOObject,Item extends org.eclipse.swt.widgets.Item> extends AbstractTableItemModelMapping<ModelElement, Item> { private CDOView view; private Map<CDOID, Item> map; public CdoModelTableItemMapping( CDOView view, Viewer<ModelElement, ?> viewer) { super(viewer); this.view = view; this.map = new HashMap<CDOID, Item>(); } @Override public void associate(ModelElement model, Item item) { if (map.containsKey(model.cdoID())) { throw new IllegalStateException("This mapping only supports one instance of a model element"); } item.setData(model.cdoID()); map.put(model.cdoID(), item); } @Override public void disassociate(Item item) { map.remove(item.getData()); item.setData(null); } @Override public void disassociateAll() { map.clear(); } @SuppressWarnings("unchecked") @Override public ModelElement lookup(Item item) { return (ModelElement) view.getObject( (CDOID)item.getData() ); } @Override public Collection<Item> lookup(ModelElement element) { Item item = (Item) map.get(element); if( item != null ) { return Collections.singleton(item); } return Collections.emptyList(); } }
This implementation is completely untested but I think you should get the point because we are not restoring the domain object but look it up from our local CDOView we can once more rely on CDOs clever memory management. Nice isn’t it?
XPath support to traverse your UI-DOM
This feature is part of UFaceKit sources since day one in the SVN-Repository but I added it not into the first nightly builds. I think the XPath support for UIs is a fairly unique feature of UFaceKit and the reflective API makes it extremly easy to implement it operations like e.g. applying changes to a many widgets.
package testproject; import java.util.Iterator; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.equinox.app.IApplication; import org.eclipse.equinox.app.IApplicationContext; import org.eclipse.ufacekit.core.xpath.common.XPathContext; import org.eclipse.ufacekit.core.xpath.common.XPathContextFactory; import org.eclipse.ufacekit.ui.core.UIDesktop; import org.eclipse.ufacekit.ui.core.UIFactory; import org.eclipse.ufacekit.ui.core.UIRunnable; import org.eclipse.ufacekit.ui.core.UIWidget; import org.eclipse.ufacekit.ui.core.controls.UIApplicationWindow; import org.eclipse.ufacekit.ui.core.controls.UIButton; import org.eclipse.ufacekit.ui.core.controls.UIComposite; import org.eclipse.ufacekit.ui.core.controls.UIInputField; import org.eclipse.ufacekit.ui.core.controls.UILabel; import org.eclipse.ufacekit.ui.core.controls.UIApplicationWindow.ApplicationWindowUIInfo; import org.eclipse.ufacekit.ui.core.controls.util.Rectangle; import org.eclipse.ufacekit.ui.core.form.UIGridFormBuilder; import org.eclipse.ufacekit.ui.core.layouts.GridLayoutData; import org.eclipse.ufacekit.ui.core.layouts.UIFillLayout; import org.eclipse.ufacekit.ui.core.layouts.GridLayoutData.Alignment; import org.eclipse.ufacekit.ui.core.xpath.UFacekitXPathContextFactory; import org.eclipse.ufacekit.ui.jface.core.JFaceFactory; import org.eclipse.ufacekit.ui.uform.UBeanForm; public class Application implements IApplication { public Object start(IApplicationContext context) throws Exception { JFaceFactory factory = new JFaceFactory(); final UIDesktop desktop = factory.newDesktop(); desktop.runWithDefaultRealm(new UIRunnable<UIDesktop>() { @Override protected IStatus run(UIDesktop arg0) { createUI(arg0); return Status.OK_STATUS; } }); desktop.run(); return IApplication.EXIT_OK; } private void createUI(UIDesktop d) { UIFactory<?> f = d.getFactory(); UIFillLayout l = f.newFillLayout(); final UIApplicationWindow window = f.newApplicationWindow(d, new ApplicationWindowUIInfo(l)); window.setText("UFaceKit - Hello World"); UIComposite comp = f.newComposite(window, new UIComposite.CompositeUIInfo(null, f.newGridLayout(1))); UILabel label = f.newLabel(comp, new UILabel.LabelUIInfo(GridLayoutData.fillHorizontalData())); label.setText("Form Example"); UBeanForm form = new UBeanForm(f); UIGridFormBuilder builder = UIGridFormBuilder.newInstance(comp, GridLayoutData.fillHorizontalData(), form); builder.newLabel("Firstname"); builder.newInputField(UIInputField.InputFieldBindingInfo.newTextFieldInfo(form.detailValue(Person.FIRSTNAME, String.class)) ); builder.newLabel("Surname"); builder.newInputField(UIInputField.InputFieldBindingInfo.newTextFieldInfo(form.detailValue(Person.SURNAME, String.class)) ); UIButton button = f.newButton(comp, new UIButton.ButtonUIInfo(new GridLayoutData(Alignment.END, Alignment.DEFAULT))); button.setText("Save"); button.setActionRunnable(new UIRunnable<UIButton>() { @Override protected IStatus run(UIButton b) { XPathContextFactory<UIWidget> factory = UFacekitXPathContextFactory.newInstance(); XPathContext context = factory.newContext(b.getParent()); Iterator<?> iterator = context.iterate("UIComposite/UIInputField"); boolean flag = true; while( iterator.hasNext() ) { UIInputField field = (UIInputField) iterator.next(); if( field.getText().equals("") ) { flag = false; field.getStyle().setBackground("#ff0000"); } else { field.getStyle().setBackgroundColor(null); } } if( ! flag ) { b.getDesktop().showErrorDialog( b.getWindow(), "Validation Error", "Required fields are marked read", new Status(IStatus.ERROR, Activator.PLUGIN_ID, ""), null ); } return Status.OK_STATUS; } }); window.open(); window.setBounds(new Rectangle(500, 400, 400, 250)); } public void stop() { // nothing to do } }