package com.vaadin.tests.components.table; import java.io.IOException; import java.io.ObjectOutputStream; import java.io.OutputStream; import java.io.Serializable; import java.util.HashMap; import com.vaadin.shared.ui.ContentMode; import com.vaadin.tests.components.TestBase; import com.vaadin.tests.util.TestUtils; import com.vaadin.ui.Button; import com.vaadin.ui.Button.ClickEvent; import com.vaadin.ui.Button.ClickListener; import com.vaadin.ui.CheckBox; import com.vaadin.ui.Component; import com.vaadin.ui.Label; import com.vaadin.v7.data.Container; import com.vaadin.v7.ui.DefaultFieldFactory; import com.vaadin.v7.ui.Field; import com.vaadin.v7.ui.Table; public class EditableTableLeak extends TestBase { private final Table table = new Table("ISO-3166 Country Codes and flags"); private final CheckBox useFieldFactory = new CheckBox( "Use a caching TableFieldFactory"); private final Label sizeLabel = new Label("", ContentMode.HTML); private long size = 0; static class DebugUtils { private static class ByteCountNullOutputStream extends OutputStream implements Serializable { private static final long serialVersionUID = 4220043426041762877L; private long bytes; @Override public void write(int b) { bytes++; } public long getBytes() { return bytes; } } public static long getSize(Object object) { try (ByteCountNullOutputStream os = new ByteCountNullOutputStream()) { ObjectOutputStream oos = new ObjectOutputStream(os); oos.writeObject(object); return os.getBytes(); } catch (IOException e) { e.printStackTrace(); return 0; } } } private static class CachingFieldFactory extends DefaultFieldFactory { private final HashMap<Object, HashMap<Object, Field<?>>> cache = new HashMap<>(); @Override public Field<?> createField(Container container, Object itemId, Object propertyId, Component uiContext) { if (cache.containsKey(itemId)) { if (cache.get(itemId) != null && cache.get(itemId).containsKey(propertyId)) { return cache.get(itemId).get(propertyId); } } Field<?> f = super.createField(container, itemId, propertyId, uiContext); if (!cache.containsKey(itemId)) { cache.put(itemId, new HashMap<Object, Field<?>>()); } cache.get(itemId).put(propertyId, f); return f; } } @Override protected void setup() { addComponent(useFieldFactory); useFieldFactory.addValueChangeListener(event -> { if (useFieldFactory.getValue()) { table.setTableFieldFactory(new CachingFieldFactory()); } else { table.setTableFieldFactory(DefaultFieldFactory.get()); } }); addComponent(table); table.setEditable(true); table.setWidth("100%"); table.setHeight("170px"); table.setSelectable(true); table.setContainerDataSource(TestUtils.getISO3166Container()); table.setColumnHeaders(new String[] { "Country", "Code" }); table.setColumnAlignment(TestUtils.iso3166_PROPERTY_SHORT, Table.ALIGN_CENTER); table.setColumnExpandRatio(TestUtils.iso3166_PROPERTY_NAME, 1); table.setColumnWidth(TestUtils.iso3166_PROPERTY_SHORT, 70); addComponent(sizeLabel); addComponent(new Button("Show size of the table", new ClickListener() { @Override public void buttonClick(ClickEvent event) { table.markAsDirtyRecursive(); updateSize(); } })); addComponent(new Button("Select the second row", new ClickListener() { @Override public void buttonClick(ClickEvent event) { table.select("AL"); updateSize(); } })); } private void updateSize() { System.gc(); long newSize = DebugUtils.getSize(table); sizeLabel.setValue("Size of the table: " + newSize + " bytes<br/>Delta: " + (newSize - size)); size = newSize; } @Override protected String getDescription() { return "Table leaks memory while scrolling/selecting when in editable mode"; } @Override protected Integer getTicketNumber() { return 6071; } }