Some Eclipse Foundation services are deprecated, or will be soon. Please ensure you've read this important communication.

Bug 372106

Summary: Large Virtual Tree request items with an index higher than itemCount
Product: [RT] RAP Reporter: Moritz Post <mpost>
Component: RWTAssignee: Project Inbox <rap-inbox>
Status: RESOLVED INVALID QA Contact:
Severity: normal    
Priority: P3    
Version: 1.5   
Target Milestone: ---   
Hardware: PC   
OS: Windows 7   
Whiteboard:

Description Moritz Post CLA 2012-02-21 07:10:15 EST
Situation: A virtual tree has 500 top level items and each item has 100 children. When expanding one child and scrolling close to the range of the 100th  child the tree calls my ILazyTreeContentProvider.updateElement(Object, index) with an index higher than the item count on that child.

Stack trace:

2012-02-21 12:54:07.527:WARN:oejs.ServletHandler:ERROR:  /randy
java.lang.IndexOutOfBoundsException: Index: 102, Size: 100
	at java.util.ArrayList.RangeCheck(ArrayList.java:547)
	at java.util.ArrayList.get(ArrayList.java:322)
	at com.eclipsesource.randy.LazyContentProvider.updateElement(LazyContentProvider.java:30)
	at org.eclipse.jface.viewers.TreeViewer.virtualLazyUpdateWidget(TreeViewer.java:1050)
	at org.eclipse.jface.viewers.TreeViewer.access$2(TreeViewer.java:1027)
	at org.eclipse.jface.viewers.TreeViewer$2.handleEvent(TreeViewer.java:291)
	at org.eclipse.swt.internal.widgets.UntypedEventAdapter.dispatchEvent(UntypedEventAdapter.java:646)
	at org.eclipse.swt.internal.widgets.UntypedEventAdapter.update(UntypedEventAdapter.java:163)
	at org.eclipse.swt.internal.events.SetDataEvent.dispatchToObserver(SetDataEvent.java:46)
	at org.eclipse.rwt.internal.events.Event.processEvent(Event.java:46)
	at org.eclipse.swt.events.TypedEvent.processEvent(TypedEvent.java:166)
	at org.eclipse.swt.events.TypedEvent.executeNext(TypedEvent.java:188)
	at org.eclipse.swt.widgets.Display.runPendingMessages(Display.java:1134)
	at org.eclipse.swt.widgets.Display.readAndDispatch(Display.java:1124)
	at org.eclipse.rwt.internal.lifecycle.ProcessAction.execute(ProcessAction.java:27)
	at org.eclipse.rwt.internal.lifecycle.PhaseExecutor.execute(PhaseExecutor.java:34)
	at org.eclipse.rwt.internal.lifecycle.SimpleLifeCycle.execute(SimpleLifeCycle.java:133)
	at org.eclipse.rwt.internal.service.LifeCycleServiceHandler.runLifeCycle(LifeCycleServiceHandler.java:81)
	at org.eclipse.rwt.internal.service.LifeCycleServiceHandler.synchronizedService(LifeCycleServiceHandler.java:59)
	at org.eclipse.rwt.internal.service.LifeCycleServiceHandler.service(LifeCycleServiceHandler.java:49)
	at org.eclipse.rwt.internal.service.ServiceManager$HandlerDispatcher.service(ServiceManager.java:34)
	at org.eclipse.rwt.engine.RWTServlet.handleValidRequest(RWTServlet.java:68)
	at org.eclipse.rwt.engine.RWTServlet.doPost(RWTServlet.java:47)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:755)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:848)
	at org.eclipse.rap.rwt.osgi.internal.CutOffContextPathWrapper.service(CutOffContextPathWrapper.java:106)
	at org.eclipse.equinox.http.servlet.internal.ServletRegistration.service(ServletRegistration.java:61)
	at org.eclipse.equinox.http.servlet.internal.ProxyServlet.processAlias(ProxyServlet.java:128)
	at org.eclipse.equinox.http.servlet.internal.ProxyServlet.service(ProxyServlet.java:60)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:848)
	at org.eclipse.equinox.http.jetty.internal.HttpServerManager$InternalHttpServiceServlet.service(HttpServerManager.java:323)
	at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:575)
	at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:485)
	at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:231)
	at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1065)
	at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:412)
	at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:192)
	at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:999)
	at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:117)
	at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:111)
	at org.eclipse.jetty.server.Server.handle(Server.java:351)
	at org.eclipse.jetty.server.AbstractHttpConnection.handleRequest(AbstractHttpConnection.java:451)
	at org.eclipse.jetty.server.AbstractHttpConnection$RequestHandler.content(AbstractHttpConnection.java:931)
	at org.eclipse.jetty.http.HttpParser.parseNext(HttpParser.java:870)
	at org.eclipse.jetty.http.HttpParser.parseAvailable(HttpParser.java:238)
	at org.eclipse.jetty.server.AsyncHttpConnection.handle(AsyncHttpConnection.java:76)
	at org.eclipse.jetty.io.nio.SelectChannelEndPoint.handle(SelectChannelEndPoint.java:615)
	at org.eclipse.jetty.io.nio.SelectChannelEndPoint$1.run(SelectChannelEndPoint.java:45)
	at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:599)
	at org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:534)
	at java.lang.Thread.run(Thread.java:662)
Comment 1 Ivan Furnadjiev CLA 2012-02-21 07:17:56 EST
Moritz, could you provide a snippet to reproduce the issue?
Comment 2 Moritz Post CLA 2012-02-21 07:26:56 EST
My favorite answer :)

Tree:

  private void createVirtualTree( Display display, Composite parent ) {

    Composite composite = new Composite( parent, SWT.NONE );
    composite.setLayoutData( GridDataFactory.fillDefaults().grab( true, true ) );
    GridLayout layout = new GridLayout( 1, false );
    layout.marginHeight = 0;
    layout.marginWidth = 0;
    composite.setLayout( layout );
    tab2.setControl( composite );

    final TreeViewer treeViewer = new TreeViewer( composite, SWT.VIRTUAL
                                                             | SWT.H_SCROLL
                                                             | SWT.V_SCROLL );
    treeViewer.setUseHashlookup( true );
    treeViewer.setContentProvider( new LazyContentProvider( treeViewer ) );
    treeViewer.setLabelProvider( new LazyLabelProvider() );
    treeViewer.setInput( new TodoMockModel() );
    treeViewer.getTree().setToolTipText( "Large virtual tree" );
    treeViewer.getTree().setLayoutData( GridDataFactory.fillDefaults().grab( true, true ).create() );
  }

Provider:

public class LazyContentProvider implements ILazyTreeContentProvider {

  private static final long serialVersionUID = 1L;
  private final TreeViewer viewer;

  public LazyContentProvider( TreeViewer viewer ) {
    this.viewer = viewer;
  }

  public Object getParent( Object element ) {
    Object result = null;
    if( element instanceof Todo ) {
      result = ( ( Todo )element ).getParent();
    }
    return result;
  }

  public void updateElement( Object parent, int index ) {
    if( parent instanceof Category ) {
      Category parentItem = ( Category )parent;
      Todo child = parentItem.getTodos().get( index );
      if( child != null ) {
        viewer.replace( parent, index, child );
        viewer.setChildCount( child, 0 );
      }
    } else if( parent instanceof TodoMockModel ) {
      TodoMockModel parentItem = ( TodoMockModel )parent;
      List<Category> manyCategories = parentItem.getManyCategories();
      Category child = manyCategories.get( index );
      if( child != null ) {
        viewer.replace( parent, index, child );
        viewer.setChildCount( child, manyCategories.size() );
      }
    }
  }

  public void updateChildCount( Object element, int currentChildCount ) {
    if( element instanceof Category ) {
      Category category = ( Category )element;
      int childCount = category.getTodos().size();
      if( childCount != currentChildCount ) {
        viewer.setChildCount( element, childCount );
      }
    } else if( element instanceof TodoMockModel ) {
      TodoMockModel model = ( TodoMockModel )element;
      int childCount = model.getManyCategories().size();
      if( childCount != currentChildCount ) {
        viewer.setChildCount( element, childCount );
      }
    }
  }

  public void inputChanged( Viewer viewer, Object oldInput, Object newInput ) {
  }

  public void dispose() {
    // nothing
  }
}

public final class LazyLabelProvider extends CellLabelProvider {

  private static final long serialVersionUID = 1L;

  @Override
  public void update( final ViewerCell cell ) {
    Object element = cell.getElement();
    if( element instanceof Category ) {
      Category category = ( Category )element;
      cell.setText( category.getName() );
    } else if( element instanceof Todo ) {
      Todo todo = ( Todo )element;
      cell.setText( todo.getTitle() );
    }
  }

  @Override
  public String getToolTipText( final Object element ) {
    return "";
  }

}

Model:

public class TodoMockModel {

  public List<Category> getManyCategories() {
    List<Category> categories = new ArrayList<Category>();
    for( int i = 0; i < 500; i++ ) {
      Category cat = new Category();
      cat.setName( "Category " + i );
      categories.add( cat );
      for( int j = 0; j < 100; j++ ) {
        Todo todo = new Todo( cat, "(Cat " + i + ") -> Todo " + j );
        cat.getTodos().add( todo );
      }
    }
    return categories;
  }
}


public class Todo {

  private String title = "";
  private String description = "";
  private final Category parent;

  public Todo( Category parent, String summary ) {
    this.parent = parent;
    this.title = summary;
  }

  public Todo( Category parent, String summary, String description ) {
    this.parent = parent;
    this.title = summary;
    this.description = description;

  }

  public Category getParent() {
    return parent;
  }

  public String getTitle() {
    return title;
  }

  public void setTitle( String title ) {
    this.title = title;
  }

  public String getDescription() {
    return description;
  }

  public void setDescription( String description ) {
    this.description = description;
  }

  @Override
  public String toString() {
    return "Todo [title=" + title + ", description=" + description + "]";
  }

}

public class Category {

  private String name;
  private int sort;
  private final List<Todo> todos = new ArrayList<Todo>();

  public String getName() {
    return name;
  }

  public void setName( String name ) {
    this.name = name;
  }

  public int getSort() {
    return sort;
  }

  public void setSort( int sort ) {
    this.sort = sort;
  }

  public List<Todo> getTodos() {
    return todos;
  }

  @Override
  public String toString() {
    return "Category [name=" + name + ", sort=" + sort + ", todos=" + todos + "]";
  }

}
Comment 3 Moritz Post CLA 2012-02-21 07:28:29 EST
Remove the line 

tab2.setControl( composite );

in the createTree method.
Comment 4 Ivan Furnadjiev CLA 2012-02-21 08:59:27 EST
I think that this is a programming error. Replacing this line in LazyContentProvider#updateElement:
--------------------
viewer.setChildCount( child, manyCategories.size() );
--------------------
with:
--------------------
viewer.setChildCount( child, child.getTodos().size() );
--------------------
solves the problem. Before, for every child (TreeItem) the itemCount was set to 500 ( manyCategories.size() ), but the element list has only 100 elements.
Comment 5 Moritz Post CLA 2012-02-21 09:46:08 EST
Your assumption was correct ivan.

Closed as invalid.