Firstly, my apologies for the length of this email (and if there are lots of typos). It might be easier to just jump to the example BXML fragments which demonstrate possibilities of the proposed change, and then skip back to the more wordy stuff.
I will post some proof of concept code in the morning if there is any interest in this. * Notes The examples are meant to demonstrate ideas, so might seem a little contrived. Although Transformables are designed to be triggered by BXMLSerializer, they can just as easily be used programmatically. The transform() method could be provided with a specific classloader by BXMLSerializer if required * References http://pivot.apache.org/tutorials/bxml-primer.html Specifically the section titled 'Class Instances' http://svn.apache.org/repos/asf/pivot/trunk/core/src/org/apache/pivot/beans/BXMLSerializer.java * Summary Make a backwardly compatible change to BXMLSerializer to enable support for a simple 'Transformable<T>' interface. public interface Transformable<T> { public Collection<T> transform(); } The interface would have a single method named 'transform()' which would return a java.util.Collection<T>. A bean implementing the interface would be constructed as normal by BXMLSerializer, but would have its 'transform()' method called when the bean would usually be returned (if it was the root element) or added to the parent object somehow (if it had a parent). If the bean is the root element, the Collection would be returned as the result of the BXMLSerializer.readObject(InputStream) method; If the bean is not the root element, BXMLSerializer would the iterate over the items in the returned Collection and process them in exactly the same way as the original bean would have been processed. i.e. If the original bean was going to be added to a Sequence defined by a DefaultProperty, then each of the items in the Collection would be added to the same Sequence instead. i.e. If the original bean was going to be set as the value for a writeable property, each of the items in the Collection would be set as values for the same writeable property, in the order defined by the Collection's iterator) * What difference would that make? This simple change would provide flexibility by allowing a special type of 'Class Instance' (an implementation of Transformable) to decide how many items to return (and of what types) when it is 'created', rather than exactly 1 object as currently happens. * How could that help me? It essentially turns Transformable objects into macros that can perform arbitrary tasks. Example 1 - User defined, simplified API for creating object graphs This is the original problem that lead to the proposed idea. Being able to create simplified APIs for Pivot Components means that designers could be provided with a much smaller and simpler 'toolkit' to work with. Having a smaller API would also be helpful for people who add in auto-complete functionality into their editor tools. Even just the ability to refer to a Component by a different name can be very useful, especially when there are similarly named widgets in various UI frameworks. <!-- This would create and populate a layout without needing to know how it was being implemented behind the scenes --> <ThreeColumnLayout> <leftColumn> ... </leftColumn> <middleColumn> ... </middleColumn> <rightColumn> ... </rightColumn> </ThreeColumnLayout> Here is a more complete example... TablePaneBuilder implements Transformable<TablePane> and has 'columns' and 'components' properties. The latter being a Sequence<Component> annotated as the DefaultProperty. Its 'transform()' method returns a newly created TablePane with the requested number of columns, and sufficient rows to populate the cells using the supplied Components. @DefaultProperty("components") public class TablePaneBuilder implements Transformable<TablePane> { private final List<Component> components = new ArrayList<Component>(); private int columns = 1; public Sequence<Component> getComponents() { return components; } public int getColumns() { return columns; } public void setColumns(int columns) { if (columns < 1) { throw new IllegalArgumentException(); } this.columns = columns; } @Override public Collection<TablePane> transform() { final TablePane tablePane = new TablePane(); for (int i = 0; i < columns; i++) { tablePane.getColumns().add(new Column()); } TablePane.Row row = null; for (Component component : components) { if (row == null || row.getLength() == columns) { row = new TablePane.Row(); tablePane.getRows().add(row); } row.add(component); } return Arrays.asList(tablePane); } } Usage in BXML <TablePaneBuilder columns="2" > <!-- Row 1 --> <wtk:Label text="Label 1" /> <wtk:Label text="Label 2" /> <!-- Row 2 --> <wtk:PushButton buttonData="PushButton 1" /> <wtk:PushButton buttonData="PushButton 2" /> <!-- Row 3 --> <wtk:TextInput text="TextInput 1" /> <wtk:TextInput text="TextInput 2" /> </TablePaneBuilder> Example 2 - Ability to instantiate classes which do not have a no-arg constructor public final class UnfriendlyPOJO { public final int mandatory; public UnfriendlyPOJO(int mandatory) { this.mandatory = mandatory; } } public class UnfriendlyPOJOBuilder implements Transformable<UnfriendlyPOJO> { private int mandatory; public int getMandatory() { return mandatory; } public void setMandatory(int mandatory) { this.mandatory = mandatory; } @Override public Collection<UnfriendlyPOJO> transform() { return Arrays.asList(new UnfriendlyPOJO(mandatory)); } } <UnfriendlyPOJOBuilder mandatory="99" /> Example 3 - Can be used to hide attribute ordering restrictions This BXML will work, as the selectedIndex is being set once the ListView has data <wtk:ListView listData="['One', 'Two', 'Three']" selectedIndex="0"/> But this BXML will fail, as the selectedIndex is being set before the ListView has data <wtk:ListView selectedIndex="0" listData="['One', 'Two', 'Three']"/> @DefaultProperty("listView") public class ListViewBuilder implements Transformable<ListView> { private final ListView listView = new ListView(); private int selectedIndex; private List<?> listData; public int getSelectedIndex() { return selectedIndex; } public void setSelectedIndex(int selectedIndex) { this.selectedIndex = selectedIndex; } public void setListData(String listData) { try { this.listData = JSONSerializer.parseList(listData); } catch (SerializationException exception) { throw new IllegalArgumentException(exception); } } @Override public Collection<ListView> transform() { listView.setListData(listData); // Ensure that the index selection happens after the list population listView.setSelectedIndex(selectedIndex); return Arrays.asList(listView); } } <ListViewBuilder selectedIndex="0" listData="['One', 'Two', 'Three']" /> Example 4 - Provide similar functionality to named styles only for properties The same could essentially be achieved by subclassing a Component and hard coding some properties into its constructor. Using a Transformable is another option, and could also be used with final classes. @DefaultProperty("textInput") public class PasswordInput implements Transformable<TextInput> { private TextInput textInput = null; public void setTextInput(TextInput textInput) { this.textInput = textInput; } @Override public Collection<TextInput> transform() { if (textInput == null) { textInput = new TextInput(); } textInput.setPassword(true); textInput.setPrompt("Enter password"); return Arrays.asList(textInput); } } <!-- Creates and returns a new TextInput --> <PasswordInput /> <!-- Uses the supplied TextInput --> <PasswordInput> <wtk:TextInput tooltipText="Using supplied TextInput" text="secret" /> </PasswordInput> This idea can be taken further by a Transformable that 'clones' specified objects (0..n times). It would create new instances based on the supplied source objects, and then iterate over the source's properties and styles while setting the same values on the newly created 'clone'. (I knocked up a working example of this in about 20 mins) Example 5 - Transformable that returns an empty Collection This could be used to conditionally include sections of BXML file based on any arbitrary logic. (Result of a webquery, result of a JVM scripting function, JVM version, availability of classes on the classpath, Operating System, user name etc) If the critera is met, the Transformable would return a Collection containing the BXML, otherwise just an empty Collection. <WindowsOS> <WindowsWidget /> <bxml:reference id="$commonToAllOperatingSystems"/> ... </WindowsOS> <MacOS> <MacWidget /> <bxml:reference id="$commonToAllOperatingSystems"/> ... </MacOS> Example 6 - Enhanced BXML include <!-- Include all files that match the regular expression or wildcard syntax, retrieving them asynchronously --> <Include async="true" regex="/path/to/bxml/files/widget[5-7].bxml" /> or even <Async> <Include regex="/path/to/bxml/files/widget[5-7].bxml" /> </Async> The Async would queue a callback to populate the same position with data once it had been retrieved from a potentially slow source. (This is just an example of the sorts of options which are made available by this change - not a proposal to create such a new 'Includer') Example 7 - Dynamic UI creation (think automatic CRUD forms for a POJO/bean) <!-- Show a 'person' bean in a form --> <FormBuilder editable="true" source="$person" fieldOrder="['id', 'title', 'firstName', 'lastName', 'age']" /> or <!-- Show a 'person' bean in a name/value TableView where the values are editable --> <TableViewBuilder editable="true" source="$person" fieldOrder="['id', 'title', 'firstName', 'lastName', 'age']" /> (These examples are for generic XXXBuilders, but they could obviously have internal knowledge of certain types.) Example 8 - Reuseable and sharable Due to the simplicity of the interface, Transformables can easily be shared among Pivot developers. This could lead to an online repository of reuseable components (even just wiki pages of source). There have been many mailing list questions about how to achieve particular layouts. Common layouts could be implemented as Transformables and mailed to the list for easy reuse. (I suppose Transformables could be considered similar to a JSP tag library) Chris
