4 Replies Latest reply: Dec 27, 2012 12:41 PM by eno g. - oracle RSS

    Exception when selecting table row while the data is still being loaded

    eno g. - oracle

      I've assigned an ObservableList to a tableview like below.

      ObservableList<RowLine> data = FXCollections.observableArrayList();
      TableView<RowLine> tableView = new TableView<RowLine>(data);

      I then pass 'data' to a thread which reads a file and loads all its lines to the 'data' like so:

      Thread fileReader = new Thread(new FileReader(aOpenFiles[file].toPath(), data));

      Once the thread starts parsing a large file, the Table is also getting populated, but if I select any of the rows that appear before the file is completely read I get the following exception.

      Am I doing this wrong?
      Exception in thread "Thread-4" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
           at java.util.ArrayList.rangeCheck(ArrayList.java:603)
           at java.util.ArrayList.get(ArrayList.java:381)
           at com.sun.javafx.collections.ObservableListWrapper.get(ObservableListWrapper.java:63)
           at javafx.scene.control.TableView$TableViewArrayListSelectionModel$7.get(TableView.java:1213)
           at javafx.scene.control.TableView$TableViewArrayListSelectionModel$7.get(TableView.java:1211)
           at com.sun.javafx.scene.control.ReadOnlyUnbackedObservableList$1.get(ReadOnlyUnbackedObservableList.java:138)
           at com.sun.javafx.scene.control.behavior.TableViewBehaviorBase$1.onChanged(TableViewBehaviorBase.java:276)
           at com.sun.javafx.collections.ListListenerHelper$Generic.fireValueChangedEvent(ListListenerHelper.java:315)
           at com.sun.javafx.collections.ListListenerHelper.fireValueChangedEvent(ListListenerHelper.java:72)
           at com.sun.javafx.scene.control.ReadOnlyUnbackedObservableList.callObservers(ReadOnlyUnbackedObservableList.java:74)
           at javafx.scene.control.TableView$TableViewArrayListSelectionModel$4.onChanged(TableView.java:1186)
           at com.sun.javafx.collections.ListListenerHelper$SingleChange.fireValueChangedEvent(ListListenerHelper.java:158)
           at com.sun.javafx.collections.ListListenerHelper.fireValueChangedEvent(ListListenerHelper.java:72)
           at com.sun.javafx.collections.BaseObservableList.callObservers(BaseObservableList.java:98)
           at com.sun.javafx.collections.ListChangeBuilder.commit(ListChangeBuilder.java:137)
           at com.sun.javafx.collections.ListChangeBuilder.endChange(ListChangeBuilder.java:156)
           at com.sun.javafx.collections.BaseObservableList.endChange(BaseObservableList.java:70)
           at com.sun.javafx.collections.BaseModifiableObservableList.add(BaseModifiableObservableList.java:111)
           at java.util.AbstractList.add(AbstractList.java:108)
           at javafx.scene.control.TableView$TableViewArrayListSelectionModel.select(TableView.java:1457)
           at javafx.scene.control.TableView$TableViewArrayListSelectionModel.clearAndSelect(TableView.java:1431)
           at javafx.scene.control.TableView$TableViewArrayListSelectionModel.clearAndSelect(TableView.java:1133)
           at com.sun.javafx.scene.control.behavior.TableCellBehaviorBase.simpleSelect(TableCellBehaviorBase.java:278)
           at com.sun.javafx.scene.control.behavior.TableCellBehaviorBase.doSelect(TableCellBehaviorBase.java:226)
           at com.sun.javafx.scene.control.behavior.TableCellBehaviorBase.mousePressed(TableCellBehaviorBase.java:146)
           at com.sun.javafx.scene.control.skin.BehaviorSkinBase$1.handle(BehaviorSkinBase.java:77)
           at com.sun.javafx.scene.control.skin.BehaviorSkinBase$1.handle(BehaviorSkinBase.java:71)
           at com.sun.javafx.event.CompositeEventHandler$NormalEventHandlerRecord.handleBubblingEvent(CompositeEventHandler.java:218)
           at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:80)
           at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:238)
           at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
           at com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59)
           at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
           at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:113)
           at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
           at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:113)
           at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
           at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:113)
           at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
           at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:113)
           at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
           at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:113)
           at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
           at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:113)
           at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
           at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:54)
           at javafx.event.Event.fireEvent(Event.java:202)
           at javafx.scene.Scene$MouseHandler.process(Scene.java:3388)
           at javafx.scene.Scene$MouseHandler.process(Scene.java:3227)
           at javafx.scene.Scene$MouseHandler.access$1900(Scene.java:3179)
           at javafx.scene.Scene.impl_processMouseEvent(Scene.java:1580)
           at javafx.scene.Scene$ScenePeerListener.mouseEvent(Scene.java:2325)
           at com.sun.javafx.tk.quantum.GlassViewEventHandler.handleMouseEvent(GlassViewEventHandler.java:287)
           at com.sun.glass.ui.View.handleMouseEvent(Unknown Source)
           at com.sun.glass.ui.View.notifyMouse(Unknown Source)
           at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
           at com.sun.glass.ui.win.WinApplication.access$300(Unknown Source)
           at com.sun.glass.ui.win.WinApplication$3$1.run(Unknown Source)
           at java.lang.Thread.run(Thread.java:722)
        • 1. Re: Exception when selecting table row while the data is still being loaded
          It depends on how your FileReader.run() method is implemented. This method is being invoked in a background thread. (Incidentally, I'm assuming here that you have a custom class called FileReader, which implements Runnable. Since you're passing a FileReader object to the constructor of Thread, this seems likely. There's a FileReader class in java.io which you'll presumably need somewhere too.)

          The important thing to note is that your list (the object you pass in as 'data') should only be modified from the JavaFX thread (i.e. not from your background thread). This is because data is the data currently displayed by the table, so modifying it causes a change to the display of the table. You can make modifications to data in FileReader.run() as long as you do so by wrapping them in a call to Platform.invokeLater(...).

          So if your FileReader.run() method looks something like this:
          public class FileReader {
            private File theFile ;
            private List<RowLine> data ;
            public void run() {
              try (BufferedReader in = new BufferedReader(new java.io.FileReader(theFile))) {
                String line ;
                while ((line = in.readLine()!=null) {
                  RowLine rowLine = // process line
                  data.add(rowLine); // uh-oh: modified data currently reference by the TableView from a background thread
              } catch (...) { ...}
          things will probably go badly wrong.

          If, instead, you do
          public void run() {
            try (...) {
             while ((line = in.readLine()!=null) {
               final RowLine rowLine = // process line
               Platform.runLater(new Runnable() {
                 public void run () {
             } catch (...) {...}
          you should be ok, though you may need to think a bit about this. Carefully.

          If this works, you'd see the table progressively being populated with data as the lines are read from the file.

          A safer and easier solution is probably to read the data into a new list, and once it's all read in, then set the data in the table. You can use a Task for this:
          final File file = aOpenFiles[file];
          Task<ObservableList<RowLine>> readFileTask = new Task<ObservableList<RowLine>>() {
            public ObservableList<RowLine> call() throws Exception {
              ObservableList<Data> data = FXCollections.observableArrayList();
              // Instead of the following you can use your existing FileReader implementation with: new FileReader(theFile, data).run();
              // Or you can refactor your FileReader class to be a subclass of Task and override call() instead of run(), then just use it instead of this anonymous inner class implementation
              try (BufferedReader in = new BufferedReader(new java.io.FileReader(file))) {
                String line ;
                while ((line=in.readLine())!=null) {
                  RowLine rowLine = // process line
                  data.add(rowLine); // perfectly safe, data is not contained in the tableView yet
              return data ;
          task.setOnSucceeded(new EventHandler<WorkerStateEvent>() {
            public void handle(WorkerStateEvent event) {
              tableView.setData(task.getValue()); // this is executed on the FX application thread, so this call is safe
          new Thread(task).start();
          In this version, the background thread will read all the data, then display it in the table once it's all read (no progressive adding of rows). If you clear the data before you start, and set the placeholder to an indeterminate progress bar/indicator, then the user can at least see that something is happening. If you want the user to be able to cancel loading, Task has an API which will help you do that.

          [Caveat: I haven't tried to compile this code, so there are likely typos here.]

          It's not immediately clear to me why misusing threads would cause the actual error you're seeing, but I don't know the internal implementation of TableView, and it's feasible this is the problem. At any rate, you can't really hope to troubleshoot it if the threading is not correct.
          • 2. Re: Exception when selecting table row while the data is still being loaded
            eno g. - oracle
            Hi, apologies for leaving out relevant details :). the FileReader Class is actually MyFileReader and implements Runnable just as you guessed. If I used fileReader.run() instead of start() there are no problems as the data is not being rendered until the thread is done.

            I did wrap the data modification with a Platform.runLater and the exception was not thrown but my app seemed to hang. So that probably deserves a second look.

            Thanks for the Task API implementation. Definitely like having the option to interrupt the task if I have to (I'm expecting to run out of memory :) )

            What I'm trying to achieve is the ability to open and load multiple files simultaneously instead of waiting until they are all done loading. Any ideas on how I can properly achieve this?

            Thanks James.
            • 3. Re: Exception when selecting table row while the data is still being loaded
              This works OK. Only ~3K lines of data, and over the internet connection I'm leeching off my sister-in-law for the weekend it appears to update just twice from the user perspective.

              To see the "update only when done" version, change the declaration of data in the call() method to
              ObservableList<CensusData> data = FXCollections.observableArrayList();
              and uncomment the code that listens for an onSucceeded state on the task.

              You should be able to launch as many of these tasks running in parallel as you need. They can either write to the same table (order of rows in the table between different tasks will be undetermined) or each write to an individual table; just depends how the data variable is allocated or how the onSucceeded handler is implemented.
              import java.io.InputStreamReader;
              import java.io.LineNumberReader;
              import java.net.URL;
              import java.net.URLConnection;
              import javafx.application.Application;
              import javafx.application.Platform;
              import javafx.beans.property.IntegerProperty;
              import javafx.beans.property.SimpleIntegerProperty;
              import javafx.beans.property.SimpleStringProperty;
              import javafx.beans.property.StringProperty;
              import javafx.collections.ObservableList;
              import javafx.concurrent.Task;
              import javafx.concurrent.WorkerStateEvent;
              import javafx.event.ActionEvent;
              import javafx.event.EventHandler;
              import javafx.scene.Scene;
              import javafx.scene.control.Button;
              import javafx.scene.control.Label;
              import javafx.scene.control.TableColumn;
              import javafx.scene.control.TableView;
              import javafx.scene.control.TextField;
              import javafx.scene.control.cell.PropertyValueFactory;
              import javafx.scene.layout.BorderPane;
              import javafx.scene.layout.HBox;
              import javafx.stage.Stage;
              public class RemoteLoadingTable extends Application {
                public void start(Stage primaryStage) {
                  final BorderPane root = new BorderPane();
                  final HBox controls = new HBox(5);
                  final TextField urlText = new TextField(
                  final Button goButton = new Button("Go");
                  controls.getChildren().addAll(urlText, goButton);
                  final Label linesReadLabel = new Label("Lines read: 0");
                  final TableView<CensusData> tableView = new TableView<CensusData>();
                  EventHandler<ActionEvent> loadDataHandler = new EventHandler<ActionEvent>() {
                    public void handle(ActionEvent event) {
                      final Task<ObservableList<CensusData>> loadTask = new Task<ObservableList<CensusData>>() {
                        protected ObservableList<CensusData> call() throws Exception {
                          final ObservableList<CensusData> data = tableView.getItems();
                          try {
                            URL url = new URL(urlText.getText());
                            URLConnection urlConnection = url.openConnection();
                            final LineNumberReader in = new LineNumberReader(
                                new InputStreamReader(urlConnection.getInputStream()));
                            String header = in.readLine(); // ignore
                            String line;
                            while ((line = in.readLine()) != null) {
                              String[] values = line.split(",");
                              final CensusData censusData = new CensusData(values[5],
                                  values[6], Integer.parseInt(values[7]),
                                  Integer.parseInt(values[8]), Integer.parseInt(values[9]),
                              Platform.runLater(new Runnable() {
                                public void run() {
                                  linesReadLabel.setText("Lines read: " + in.getLineNumber());
                          } catch (Exception e) {
                            // TODO Auto-generated catch block
                          return data;
                      // loadTask.setOnSucceeded(new EventHandler<WorkerStateEvent>() {
                      // @Override
                      // public void handle(WorkerStateEvent event) {
                      // tableView.setItems(loadTask.getValue());
                      // }
                      // });
                      new Thread(loadTask).start();
                  TableColumn<CensusData, String> stateCol = new TableColumn<CensusData, String>(
                  stateCol.setCellValueFactory(new PropertyValueFactory<CensusData, String>(
                  TableColumn<CensusData, String> countyCol = new TableColumn<CensusData, String>(
                  countyCol.setCellValueFactory(new PropertyValueFactory<CensusData, String>(
                  TableColumn<CensusData, Integer> census2010PopCol = new TableColumn<CensusData, Integer>(
                      "Census 2010 Population");
                      .setCellValueFactory(new PropertyValueFactory<CensusData, Integer>(
                  TableColumn<CensusData, Integer> estimateBase2010Col = new TableColumn<CensusData, Integer>(
                      "Estimate Base 2010");
                      .setCellValueFactory(new PropertyValueFactory<CensusData, Integer>(
                  TableColumn<CensusData, Integer> popEstimate2010Col = new TableColumn<CensusData, Integer>(
                      "Population Estimate 2010");
                      .setCellValueFactory(new PropertyValueFactory<CensusData, Integer>(
                  TableColumn<CensusData, Integer> popEstimate2011Col = new TableColumn<CensusData, Integer>(
                      "Population Estimate 2011");
                      .setCellValueFactory(new PropertyValueFactory<CensusData, Integer>(
                  tableView.getColumns().addAll(stateCol, countyCol, census2010PopCol,
                      estimateBase2010Col, popEstimate2010Col, popEstimate2011Col);
                  Scene scene = new Scene(root, 600, 800);
                public static void main(String[] args) {
                public static class CensusData {
                  private StringProperty stateName;
                  private StringProperty countyName;
                  private IntegerProperty census2010pop;
                  private IntegerProperty estimateBase2010;
                  private IntegerProperty popEstimate2010;
                  private IntegerProperty popEstimate2011;
                  public CensusData(String stateName, String countyName, int census2010Pop,
                      int estimateBase2010, int popEstimate2010, int popEstimate2011) {
                    this.stateName = new SimpleStringProperty(stateName);
                    this.countyName = new SimpleStringProperty(countyName);
                    this.census2010pop = new SimpleIntegerProperty(census2010Pop);
                    this.estimateBase2010 = new SimpleIntegerProperty(estimateBase2010);
                    this.popEstimate2010 = new SimpleIntegerProperty(popEstimate2010);
                    this.popEstimate2011 = new SimpleIntegerProperty(popEstimate2011);
                  public StringProperty stateNameProperty() {
                    return stateName;
                  public StringProperty countyNameProperty() {
                    return countyName;
                  public IntegerProperty census2010PopProperty() {
                    return census2010pop;
                  public IntegerProperty estimateBase2010Property() {
                    return estimateBase2010;
                  public IntegerProperty popEstimate2010Property() {
                    return popEstimate2010;
                  public IntegerProperty popEstimate2011Property() {
                    return popEstimate2011;
                  public String getStateName() {
                    return stateName.get();
                  public String getCountyName() {
                    return countyName.get();
                  public int getCensus2010Pop() {
                    return census2010pop.get();
                  public int getEstimateBase2010() {
                    return estimateBase2010.get();
                  public int getPopEstimate2010() {
                    return popEstimate2010.get();
                  public int getPopEstimate2011() {
                    return popEstimate2011.get();
                  public void setStateName(String state) {
                  public void setCountyName(String county) {
                  public void setCensus2010Pop(int census2010Pop) {
                  public void setEstimateBase2010(int estimateBase2010) {
                  public void setPopEstimate2010(int popEstimate2010) {
                  public void setPopEstimate2011(int popEstimate2011) {
              • 4. Re: Exception when selecting table row while the data is still being loaded
                eno g. - oracle
                Back online! Thanks again James! That looks good, I'll start playing around with it now. Looks like all I might have to do now is instead of passing the data to each fileReader I should pass the tableView. One of my columns should be a timestamp so I'll just sort by that so it should take care of the ordering.