Cloning components

Hi guys,

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.

public static Component deepCopy(HasComponents component){
    try {
        Class<?> clazz = component.getClass();
        Constructor<?> ctor = clazz.getConstructor();
        Component clone = (Component)ctor.newInstance();
        ComponentContainer cloner = (ComponentContainer)clone;
        BeanUtils.copyProperties(clone, component);
        Iterator<Component> it = component.iterator();
        while (it.hasNext()){
            Component c = it.next();
            if (c instanceof ComponentContainer){
                cloner.addComponent(deepCopy((HasComponents)c));
            } else {
                cloner.addComponent(c);
            }
        }
        return cloner;
    } catch (Exception e) {
        log.error("Error cloning component", e);
        return null;
    }
}

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]

Please help

Thanks,
Roland

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()]
));

HTH
Marco

Thanks for helping, Marco

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]

You can give a try to
cloning library
.

HTH
Marco

Great for the community that you also work on weekends :slight_smile: 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.

IMO that would be a cleaner solution rather than cloning.
Good work and let me know about your progress

Best regards
Marco