Wrappers
Sadly, JTimber doesn't support arrays and collections natively. Adding it to the runtime hook (bytecode manipulator) would be a rather tedious and inflexible solution. Therefore, JTimber also introduces the concept of wrappers in order to support arrays and collections in a very flexible way.
Problem
JTimber only supports parent tracking through direct references. For example, arrays and collections aren't supported natively. Moreover, any arrays and collections of a node are seen as its children, but not the elements of those arrays and collections. The following example illustrates the problems:
class Parent extends DefaultNode<Node<?>> {
private Child object = new Child();
private Child[] array = { new Child(), new Child() };
// Getters and setters
}
class Child extends DefaultParentAware<Parent> {
}
public static void main(String[] args) {
Parent parent = new Parent();
Child childObject = parent.getObject();
Child childArray = parent.getArray();
// Parents
assert childObject.getParents().get(0) == parent; // True
assert childArray[0].getParents().get(0) == parent // False; childArray[0] has no parents
// Children
assert parent.getChildren().get(0) == childObject; // True
assert parent.getChildren().get(1) == childArray[0]; // False; the second child of 'parent' is 'childArray', not 'childArray[0]'
}
Wrappers help solve these problems in a very flexible and expandable way.
Basic concept
Basically, a com.quartercode.jtimber.api.node.wrapper.Wrapper
wraps around a certain object; for this example, we'll take a List
. The node class, which wants to references some parent-aware objects through a list, then stores the wrapper instead of the original list implementation:
private List<Child> list = new ListWrapper<>(new ArrayList<Child>());
The wrapper itself is parent-aware. Therefore, the JTimber system notifies it of any parents. The list wrapper can then iterate through its list and delegate those parents to all elements. If a new element is added to the list, the wrapper adds its parents to the new item.
Apart from the parent-tracking passthrough functionality, each wrapper must implement the following method:
public List<Object> getActualChildren()
The method returns the objects the wrapper delegates its parents to. In case of our list example, this method would return all the elements of the wrapped list. Since you will probably never use this method explicitly, the exact method contract isn't discussed here. However, it can be extracted from the method's JavaDoc.
If you plan on implementing your own wrapper, make sure to first read through the source code of the built-in wrappers. You might also want to use the com.quartercode.jtimber.api.node.wrapper.AbstractWrapper
class as the superclass of your new wrapper.
@SubstituteWithWrapper
There are special cases where both the wrapper and the wrapped object are instances of the field type. For example, the following one is such a case:
private List<Child> list = new ListWrapper<>(new ArrayList<Child>());
In this example List
is the field type. Both the wrapper (ListWrapper
) and the wrapped object (ArrayList
) are instances of that field type.
In such a special case, you annotate the field with @SubstituteWithWrapper
. That annotation causes any object which is stored in the field to be automatically wrapped in the set wrapper class. Note that this mechanism also works for JAXB unmarshalling.
For example, the following code would do the same thing as the code above (plus the JAXB unmarshalling bit, which isn't shown above):
@SubstituteWithWrapper (ListWrapper.class)
private List<Child> list = new ArrayList<>();
Wrapper constructor argument type
By default, @SubstituteWithWrapper
selects the wrapper constructor which has one single argument of the field type. For example, for the listing above, JTimber would internally use the public ListWrapper(List)
constructor every time the field is set. However, there are some rare special cases where the wrapper constructor argument type is not equal to the field type. For example, the following code is such a case:
@SubstituteWithWrapper (ListWrapper.class)
private Collection<Child> collection = new ArrayList<>();
This code would throw an exception because ListWrapper
does not have a public ListWrapper(Collection)
constructor (Collection
is the field type). In such a case, you need to specify the wrapper constructor argument type manually. You can do that directly in the annotation:
@SubstituteWithWrapper (value = ListWrapper.class, wrapperConstructorArg = List.class)
private Collection<Child> collection = new ArrayList<>();
Built-in wrappers
JTimber comes with some built-in wrappers which cover your standard cases. Most users don't need to create their own wrappers since the built-in ones suffice for their requirements.
CollectionWrapper
The com.quartercode.jtimber.api.node.wrapper.collection.CollectionWrapper
wraps around any Collection
. It also implements the Collection
interface in order to provide all collection methods.
Usage example:
@SubstituteWithWrapper (CollectionWrapper.class)
private Collection<String> collection = new ArrayList<>();
JAXB compatibility: Yes, with @SubstituteWithWrapper
.
ListWrapper
The com.quartercode.jtimber.api.node.wrapper.collection.ListWrapper
wraps around any List
. It also implements the List
interface in order to provide all list methods.
Usage example:
@SubstituteWithWrapper (ListWrapper.class)
private List<String> list = new ArrayList<>();
Note that the list wrapper extends the CollectionWrapper
and inherits its functionality.
JAXB compatibility: Yes, with @SubstituteWithWrapper
.
SetWrapper
The com.quartercode.jtimber.api.node.wrapper.collection.SetWrapper
wraps around any Set
. It also implements the Set
interface in order to provide all list methods.
Usage example:
@SubstituteWithWrapper (SetWrapper.class)
private Set<String> list = new HashSet<>();
Note that the list wrapper extends the CollectionWrapper
and inherits its functionality.
JAXB compatibility: Yes, with @SubstituteWithWrapper
.
QueueWrapper
The com.quartercode.jtimber.api.node.wrapper.collection.QueueWrapper
wraps around any Queue
. It also implements the Queue
interface in order to provide all list methods.
Usage example:
@SubstituteWithWrapper (QueueWrapper.class)
private Queue<String> list = new LinkedList<>();
Note that the list wrapper extends the CollectionWrapper
and inherits its functionality.
JAXB compatibility: Yes, with @SubstituteWithWrapper
.
ArrayWrapper
The com.quartercode.jtimber.api.node.wrapper.collection.ArrayWrapper
wraps around any one-dimensional array. It implements some custom methods which allow access to the underlying array. However, the initial array is cloned in order to prevent the user from manually modifying it.
The custom methods offered by the array wrapper are the following (where E
is the generic parameter defining the element type):
public int length()
public E get(int index)
public void set(int index, E value)
public E[] cloneArray() // Returns a shallow clone of the wrapped array
Usage example:
private ArrayWrapper<String> array = new ArrayWrapper<>(new String[10]);
public static void main(String[] args) {
array.set(0, "abc");
array.set(3, "def");
assert array.get(0).equals("abc");
}
Warning: Theoretically, you could access the internal array through the getInternallyWrapped()
method. Don't do that! If you modify the returned array in any way, parent inconsistencies will probably arise!
JAXB compatibility: No, because no simple way, which doesn't leave a trace in the produced XML, is in sight.