Some of the applications of your idea seem to be inclusive of functions in other APIs like Flexjson's Transformers or Metawidget's dynamic UIs. All really great applications coming from a single enhancement, sounds excellent indeed.
http://flexjson.sourceforge.net http://metawidget.org Thom On 2011-07-02, at 4:36 PM, Chris Bartlett wrote: > 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