ArrayIndexOutOfBoundsException with FilterTable which extends CustomTable

I use in FilterTable a custom lazy container. It seems that our custom container is too lazy :slight_smile:
When scrolling sometimes we get an ArrayIndexOutOfBoundsException (near row 73 here:

pageBuffer[CELL_GENERATED_ROW]
[indexInOldBuff

):

java.lang.ArrayIndexOutOfBoundsException: 12 at com.vaadin.ui.CustomTable.parseItemIdToCells(CustomTable.java:2309) at com.vaadin.ui.CustomTable.getVisibleCellsNoCache(CustomTable.java:2187) at com.vaadin.ui.CustomTable.refreshRenderedCells(CustomTable.java:1709) at com.vaadin.ui.CustomTable.enableContentRefreshing(CustomTable.java:3234) at com.vaadin.ui.CustomTable.changeVariables(CustomTable.java:3080) at com.vaadin.server.communication.ServerRpcHandler.changeVariables(ServerRpcHandler.java:603) at com.vaadin.server.communication.ServerRpcHandler.handleInvocations(ServerRpcHandler.java:422) at com.vaadin.server.communication.ServerRpcHandler.handleRpc(ServerRpcHandler.java:273) at com.vaadin.server.communication.UidlRequestHandler.synchronizedHandleRequest(UidlRequestHandler.java:79) at com.vaadin.server.SynchronizedRequestHandler.handleRequest(SynchronizedRequestHandler.java:41) at com.vaadin.server.VaadinService.handleRequest(VaadinService.java:1409) at com.vaadin.server.VaadinServlet.service(VaadinServlet.java:364) at javax.servlet.http.HttpServlet.service(HttpServlet.java:729) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:291) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206) at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206) at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:219) at org.apache.catalina.core.StandardContextValve.__invoke(StandardContextValve.java:106) at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java) at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:614) at org.apache.catalina.core.StandardHostValve.__invoke(StandardHostValve.java:142) at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java) at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79) at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:616) at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88) at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:518) at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1091) at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:673) at org.apache.tomcat.util.net.AprEndpoint$SocketProcessor.doRun(AprEndpoint.java:2503) at org.apache.tomcat.util.net.AprEndpoint$SocketProcessor.run(AprEndpoint.java:2492) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) at java.lang.Thread.run(Thread.java:745) Our Container might be the problem, but I don’t know where to start figure out why.

The CustomTable’s parsteItemIdtocells:

[code]
/**
* Update a cache array for a row, register any relevant listeners etc.
*
* This is an internal method extracted from
* {@link #getVisibleCellsNoCache(int, int, boolean)} and should be removed
* when the CustomTable is rewritten.
*/
private void parseItemIdToCells(Object cells, Object id, int i,
int firstIndex, RowHeaderMode headmode, int cols, Object colids,
int firstIndexNotInCache, boolean iscomponent,
HashSet<Property<?>> oldListenedProperties) {

    cells[CELL_ITEMID]

[i]
= id;
cells[CELL_KEY]
[i]
= itemIdMapper.key(id);
if (headmode != ROW_HEADER_MODE_HIDDEN) {
switch (headmode) {
case INDEX:
cells[CELL_HEADER]
[i]
= String.valueOf(i + firstIndex + 1);
break;
default:
try {
cells[CELL_HEADER]
[i]
= getItemCaption(id);
} catch (Exception e) {
exceptionsDuringCachePopulation.add(e);
cells[CELL_HEADER]
[i]
= “”;
}
}
try {
cells[CELL_ICON]
[i]
= getItemIcon(id);
} catch (Exception e) {
exceptionsDuringCachePopulation.add(e);
cells[CELL_ICON]
[i]
= null;
}
}

    GeneratedRow generatedRow = rowGenerator != null ? rowGenerator
            .generateRow(this, id) : null;
    cells[CELL_GENERATED_ROW]

[i]
= generatedRow;

    for (int j = 0; j < cols; j++) {
        if (isColumnCollapsed(colids[j]

)) {
continue;
}
Property<?> p = null;
Object value = “”;
boolean isGeneratedRow = generatedRow != null;
boolean isGeneratedColumn = columnGenerators.containsKey(colids[j]
);
boolean isGenerated = isGeneratedRow || isGeneratedColumn;

        if (!isGenerated) {
            try {
                p = getContainerProperty(id, colids[j]

);
} catch (Exception e) {
exceptionsDuringCachePopulation.add(e);
value = null;
}
}

        if (isGeneratedRow) {
            if (generatedRow.isSpanColumns() && j > 0) {
                value = null;
            } else if (generatedRow.isSpanColumns() && j == 0
                    && generatedRow.getValue() instanceof Component) {
                value = generatedRow.getValue();
            } else if (generatedRow.getText().length > j) {
                value = generatedRow.getText()[j]

;
}
} else {
// check if current pageBuffer already has row
int index = firstIndex + i;
if (p != null || isGenerated) {
int indexInOldBuffer = index - pageBufferFirstIndex;
if (index < firstIndexNotInCache
&& index >= pageBufferFirstIndex
&& pageBuffer[CELL_GENERATED_ROW]
[indexInOldBuffer]
== null
&& id.equals(pageBuffer[CELL_ITEMID]
[indexInOldBuffer]
)) {
// we already have data in our cache,
// recycle it instead of fetching it via
// getValue/getPropertyValue
value = pageBuffer[CELL_FIRSTCOL + j]
[indexInOldBuffer]
;
if (!isGeneratedColumn && iscomponent[j]

                            || !(value instanceof Component)) {
                        listenProperty(p, oldListenedProperties);
                    }
                } else {
                    if (isGeneratedColumn) {
                        ColumnGenerator cg = columnGenerators
                                .get(colids[j]

);
try {
value = cg.generateCell(this, id, colids[j]
);
} catch (Exception e) {
exceptionsDuringCachePopulation.add(e);
value = null;
}
if (value != null && !(value instanceof Component)
&& !(value instanceof String)) {
// Avoid errors if a generator returns
// something
// other than a Component or a String
value = value.toString();
}
} else if (iscomponent[j]
) {
try {
value = p.getValue();
} catch (Exception e) {
exceptionsDuringCachePopulation.add(e);
value = null;
}
listenProperty(p, oldListenedProperties);
} else if (p != null) {
try {
value = getPropertyValue(id, colids[j]
, p);
} catch (Exception e) {
exceptionsDuringCachePopulation.add(e);
value = null;
}
/*
* If returned value is Component (via fieldfactory
* or overridden getPropertyValue) we expect it to
* listen property value changes. Otherwise if
* property emits value change events, table will
* start to listen them and refresh content when
* needed.
*/
if (!(value instanceof Component)) {
listenProperty(p, oldListenedProperties);
}
} else {
try {
value = getPropertyValue(id, colids[j]
, null);
} catch (Exception e) {
exceptionsDuringCachePopulation.add(e);
value = null;
}
}
}
}
}

        if (value instanceof Component) {
            registerComponent((Component) value);
        }
        cells[CELL_FIRSTCOL + j]


= value;
}
}

[/i]
[/i]
[/i]
[/i]
[/i]
[/i]
[/i]
[/i]
[/code][i]
[i]
[i]
[i]
[i]
[i]
[i]
[i]
[i]
If there would be check for index (row 69), no problem would come

private void parseItemIdToCells(Object cells, Object id, int i,
            int firstIndex, RowHeaderMode headmode, int cols, Object colids,
            int firstIndexNotInCache, boolean iscomponent,
            HashSet<Property<?>> oldListenedProperties) {

        cells[CELL_ITEMID]
[i]
 = id;
        cells[CELL_KEY]
[i]
 = itemIdMapper.key(id);
        if (headmode != ROW_HEADER_MODE_HIDDEN) {
            switch (headmode) {
                case INDEX:
                    cells[CELL_HEADER]
[i]
 = String.valueOf(i + firstIndex + 1);
                    break;
                default:
                    try {
                        cells[CELL_HEADER]
[i]
 = getItemCaption(id);
                    } catch (Exception e) {
                        exceptionsDuringCachePopulation.add(e);
                        cells[CELL_HEADER]
[i]
 = "";
                    }
            }
            try {
                cells[CELL_ICON]
[i]
 = getItemIcon(id);
            } catch (Exception e) {
                exceptionsDuringCachePopulation.add(e);
                cells[CELL_ICON]
[i]
 = null;
            }
        }

        GeneratedRow generatedRow = rowGenerator != null ? rowGenerator
                .generateRow(this, id) : null;
        cells[CELL_GENERATED_ROW]
[i]
 = generatedRow;

        for (int j = 0; j < cols; j++) {
            if (isColumnCollapsed(colids[j]
)) {
                continue;
            }
            Property<?> p = null;
            Object value = "";
            boolean isGeneratedRow = generatedRow != null;
            boolean isGeneratedColumn = columnGenerators.containsKey(colids[j]
);
            boolean isGenerated = isGeneratedRow || isGeneratedColumn;

            if (!isGenerated) {
                try {
                    p = getContainerProperty(id, colids[j]
);
                } catch (Exception e) {
                    exceptionsDuringCachePopulation.add(e);
                    value = null;
                }
            }

            if (isGeneratedRow) {
                if (generatedRow.isSpanColumns() && j > 0) {
                    value = null;
                } else if (generatedRow.isSpanColumns() && j == 0
                        && generatedRow.getValue() instanceof Component) {
                    value = generatedRow.getValue();
                } else if (generatedRow.getText().length > j) {
                    value = generatedRow.getText()[j]
;
                }
            } else {
                // check if current pageBuffer already has row
                int index = firstIndex + i;
                if (p != null || isGenerated) {
                    int indexInOldBuffer = index - pageBufferFirstIndex;
                    int max1 = pageBuffer != null ? pageBuffer[CELL_GENERATED_ROW]
.length-1 : 0;
                    int max2 = pageBuffer != null ? pageBuffer[CELL_ITEMID]
.length-1 : 0;
                    indexInOldBuffer = indexInOldBuffer > max1 ? max1 : indexInOldBuffer;
                    indexInOldBuffer = indexInOldBuffer > max2 ? max2 : indexInOldBuffer;
                    try {
                        if (index < firstIndexNotInCache
                                && index >= pageBufferFirstIndex
                                && pageBuffer[CELL_GENERATED_ROW]
[indexInOldBuffer]
 == null
                                && id.equals(pageBuffer[CELL_ITEMID]
[indexInOldBuffer]
)) {
                            // we already have data in our cache,
                            // recycle it instead of fetching it via
                            // getValue/getPropertyValue
                            value = pageBuffer[CELL_FIRSTCOL + j]
[indexInOldBuffer]
;
                            if (!isGeneratedColumn && iscomponent[j]

                                    || !(value instanceof Component)) {
                                listenProperty(p, oldListenedProperties);
                            }
                        } else {
                            if (isGeneratedColumn) {
                                ColumnGenerator cg = columnGenerators
                                        .get(colids[j]
);
                                try {
                                    value = cg.generateCell(this, id, colids[j]
);
                                } catch (Exception e) {
                                    exceptionsDuringCachePopulation.add(e);
                                    value = null;
                                }
                                if (value != null && !(value instanceof Component)
                                        && !(value instanceof String)) {
                                    // Avoid errors if a generator returns
                                    // something
                                    // other than a Component or a String
                                    value = value.toString();
                                }
                            } else if (iscomponent[j]
) {
                                try {
                                    value = p.getValue();
                                } catch (Exception e) {
                                    exceptionsDuringCachePopulation.add(e);
                                    value = null;
                                }
                                listenProperty(p, oldListenedProperties);
                            } else if (p != null) {
                                try {
                                    value = getPropertyValue(id, colids[j]
, p);
                                } catch (Exception e) {
                                    exceptionsDuringCachePopulation.add(e);
                                    value = null;
                                }
                                /*
                                 * If returned value is Component (via fieldfactory
                                 * or overridden getPropertyValue) we expect it to
                                 * listen property value changes. Otherwise if
                                 * property emits value change events, table will
                                 * start to listen them and refresh content when
                                 * needed.
                                 */
                                if (!(value instanceof Component)) {
                                    listenProperty(p, oldListenedProperties);
                                }
                            } else {
                                try {
                                    value = getPropertyValue(id, colids[j]
, null);
                                } catch (Exception e) {
                                    exceptionsDuringCachePopulation.add(e);
                                    value = null;
                                }
                            }
                        }
                    } catch (Exception ex) {
                        System.out.println("indexInOldBuffer>=max1" + indexInOldBuffer + " > " + max1);
                        System.out.println("indexInOldBuffer>=max2: " + indexInOldBuffer + " > " + max2);
                        throw new RuntimeException(ex);
                    }
                }
            }

            if (value instanceof Component) {
                registerComponent((Component) value);
            }
            cells[CELL_FIRSTCOL + j]
[i]
 = value;
        }
    }
[/i]
[/i]
[/i]
[/i]
[/i]
[/i]
[/i]
[/i]
[/i]

[i]
[i]
[i]
[i]
[i]
[i]
[i]
[i]
[i]

[/i]
[/i]
[/i]
[/i]
[/i]
[/i]
[/i]
[/i]
[/i]
[/i]
[/i]
[/i]
[/i]
[/i]
[/i]
[/i]
[/i]
[/i]

My Customer get same Exception sometimes. But it can not be reproduced. Bug is reported in github.

https://github.com/vaadin/framework/issues/9195