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

Reply via email to