I need your help implementing a function cloning components. I would like to define templates using the Vaadin Designer and at runtime make deep multiple copies of some components.
When I call this function with a HorizontalLayout holding a TextBox and a ComboBox it first clones the TextBox and then crashes on the ComboBox with this error:
java.util.ConcurrentModificationException: null
at java.util.LinkedList$ListItr.checkForComodification(LinkedList.java:966) ~[na:1.8.0]
at java.util.LinkedList$ListItr.next(LinkedList.java:888) ~[na:1.8.0]
at com.eqs.lei.util.VaadinUtils.deepCopy(VaadinUtils.java:53) ~[classes/:na]
You are modifying the component list while iterating over it.
I think you must first collect the cloned components in a separated list and then do clear the container and add the new components.
Try something like this
List<Component> clonedComponents = new ArraysList<>(cloner.getComponentCount());
Iterator<Component> it = component.iterator();
while (it.hasNext()){
Component c = it.next();
if (c instanceof ComponentContainer){
clonedComponents.add(deepCopy((HasComponents)c));
} else {
clonedComponents.add(c);
}
}
cloner.removeAllComponents();
cloner.addComponents(clonedComponents.toArray(new Component[clonedComponents.size()]
));
The following code now somehow clones a Vaadin component, but some attributes are not correct in the clone. The original TextField had a width of 100%, the clone has -1
public class Cloner {
private final static Log log = LogFactory.getLog(Cloner.class);
private List<Pair> tasks;
public Component clone(HasComponents component){
tasks = new ArrayList<Pair>();
Component result = deepCopy(component);
for (Pair pair: tasks){
((ComponentContainer)pair.parent).addComponent(pair.child);
}
return result;
}
private Component deepCopy(Component component){
try {
Class<?> clazz = component.getClass();
Constructor<?> ctor = clazz.getConstructor();
Component clone = (Component)ctor.newInstance();
BeanUtils.copyProperties(clone, component);
if (component instanceof HasComponents){
Iterator<Component> it = ((HasComponents)component).iterator();
while (it.hasNext()){
Component c = it.next();
//tasks.add(new Pair(component, c));
tasks.add(new Pair(clone, deepCopy(c)));
}
}
return clone;
} catch (Exception e) {
log.error("Error cloning component", e);
return null;
}
}
private class Pair {
Pair(Component parent, Component child){
this.parent = parent;
this.child = child;
}
private Component parent;
private Component child;
}
}
I think width is discarded by BeanUtils.copyProperties because it is not a “correct” property.
There setter keeps a String while the getter returns a float.
That seems to be the reason. Why does Vaadin not support Clonable?
Today I tried this approach:
DesignContext dc = Design.read(template);
target.addComponent(dc.getComponentById(template.getId()));
or
DesignContext dc = Design.read(template);
target.addComponent(dc.getRootComponent());
but get this exception:
java.lang.IllegalArgumentException: The class com.vaadin.ui.HorizontalLayout or any of its superclasses do not have an @DesignRoot annotation
at com.vaadin.ui.declarative.Design.read(Design.java:567) ~[vaadin-server-7.7.5.jar:7.7.5]
Great for the community that you also work on weekends Thanks again for helping
I think I will give up on cloning. Instead I will waste some resources by reading the designer file again by instantiating an extended design class which just returns my template component.