Loom makes a distinction between a property node (e.g. friends) and a property path (e.g. friends[5].parent.name). All nodes of a property path will be automatically instantiated if the assigned value is not null.
Loom does not require a public setter or getter for property nodes but it will use them if found.
Enumeration classes are automatically converted to/from their String representation.
public class Child {
private List friends;
private Adult[] parents = new Adult[2];
}
<l:inputText name="child.friends[5].name"/>
<l:inputText name="child.parents[0].name"/>
By default collection classes will be instantiated as ArrayList, HashMap and HashSet.
Arrays will be resized as needed. Note that unless you have previously instantiated it by hand with a given fixed size, Lists are always preferable to arrays because they include better resizing code.
Loom supports Lists with simple (String, integer, etc) or complex (any bean class) contents, which will be inferred from the generics type.
public class Child {
private Map family;
}
<l:inputText name="child.family[dad].name"/>
<l:inputText name="child.family[mom].name"/>
Sets receive special treatment at web applications. Since they are not indexed, expressions like "child.friends[5].name" are meaningless, but Sets can still be used with simple contents to support the typical interface where a list of checkboxes specify a Set of elements:
public class Child {
private Set selectedFriends;
}
<c:foreach var="friend" items="${action.child.friends}" varStatus="status">
<l:inputCheckbox name="child.selectedFriends" value="${friend.id}"/> ${friend.name}
</c:foreach>
A value attribute specified in a checkbox and bound to a Set property will be added if the component is checked. The browser will not send any checkbox that is not checked, so the framework will only add the existing values and not remove the missing ones.
Sets cannot be traversed, so they must always be the end node of any property path. They can only contain simple objects (String, Integer, etc) that are not locale-dependent.
Loom stores all possible property paths inside a static structure. This means that if you try to use a subclass at any point of the property path, it will fail. Consider the following case:
public class File {
private String name;
}
public class ImageFile extends File {
private int width;
}
public class FileAction extends AbstractActionImpl {
private File file;
}
Loom fields are bidirectional, which means that it must support pulling values from the java model as well as pushing new values into it. Cases like this are not supported:
<-- this is ok --> <l:inputText name="file.name" /> <-- this is NOT OK --> <l:inputText name="file.width" />
Admittedly, in this example the framework could pull read-only values from file.width (assuming that the user has pre-instantiated the whole path before) but it could not create the path for assigning new values, since it does not have way of guessing the subclass to instantiate.
There are two ways to deal with polymorphism:
This is the easiest of all cases, when you only need to display some attributes but you don't need to inject them back. This can be done using EL expressions:
<input type="hidden" name="file.name" value="${action.file.width}">
Another way of fixing this is to create a different Action to deal with the subclass, and use generics to reuse code:
public class FileAction<F extends File> extends AbstractAction {
private F file;
}
public class ImageFileAction extends FileAction<ImageFile> {
}
<-- this is ok _IF_ action is an instance of ImageFileAction --> <l:inputText name="file.width" />