2

I've been bashing my head in on this problem for a few days now. I've done my full share of Googling, and I'm hoping I can find someone here that is more experienced than I (not hard to find haha) that can decipher my problem.

Scenario: I developed a Java Applet that implements a Swing GUI. Background work: The Applet gathers records from a large "phone book" excel file (.csv) and stores them in a Map data structure. The phone book contains about 106,000 records, and on the 34,586th record, I get an ArrayIndexOutOfBoundsException that I cannot make sense of. The exception only occurs when I'm running the applet on my personal website. The applet runs perfectly fine with no errors while testing in my IDE (NetBeans) and running the .html file (the one that contains the applet) on my local machine. The output and exception that is thrown while running on my website is as follows (I cut most of the records to save space):

Java Console

Kary,Webber,2826 East 12th Ave.,Memphis,TN,38168,901-749-1834
Erinn,Rocha,2132 East Main Ave.,Memphis,TN,38168,865-414-5105
Gina,Lane,71 South First St. Apt. 11,Memphis,TN,38168,731-485-1129
Patsy,Hart,661 East 11th St.
java.lang.ArrayIndexOutOfBoundsException: 3
at Implementation.PersonnelDatabase.addRecordFromFields(PersonnelDatabase.java:192)
at Implementation.PersonnelDatabase.initDBFromFile(PersonnelDatabase.java:215)
at Implementation.PersonnelDatabase.processData(PersonnelDatabase.java:239)
at Implementation.PersonnelDatabaseApplet$2.doInBackground(PersonnelDatabaseApplet.java:78)
at Implementation.PersonnelDatabaseApplet$2.doInBackground(PersonnelDatabaseApplet.java:69)
at javax.swing.SwingWorker$1.call(Unknown Source)
at java.util.concurrent.FutureTask$Sync.innerRun(Unknown Source)
at java.util.concurrent.FutureTask.run(Unknown Source)
at javax.swing.SwingWorker.run(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.lang.Thread.run(Unknown Source)

As you can see, on the 34,586th record (beginning with Patsy,Hart), it outputs midway through her address. The full record is as follows: Patsy,Hart,661 East 11th St. Apt. 195,Memphis,TN,38168,555-555-5555 .

Below are the sections of code that are most logically influenced by the exception.

Object tag in HTML file

<object type="application/x-java-applet" height="400" width="300">
  <param name="codebase" value="classes" />
  <param name="code" value="Implementation/PersonnelDatabaseApplet.class" />
  <param name="archive" value="PersonnelDatabase.jar" />
  Applet failed to run. No Java plug-in was found.
</object>

PersonnelDatabase class (processes background data):

/*
 * Create a new record using an appropriately ordered set of fields and add it to the data base
 */
public void addRecordFromFields(String[] fields)
{
    // Read record attributes in, one at a time
    Record thisRecord = new Record();
    thisRecord.setFirstName(fields[0]);
    thisRecord.setLastName(fields[1]);
    thisRecord.setAddress(fields[2]);
    thisRecord.setCity(fields[3]);
    thisRecord.setState(fields[4]);
    thisRecord.setZipCode(fields[5]);
    thisRecord.setPhoneNo(fields[6]);
    addRecord(thisRecord);
}

// O( n )
/**
 * Destroy the current data base and load new data from a file.
 * @param filename the file to use as a source
 * @throws IOException: Either file not found or IO error
 */
public void initDBFromFile(URL url) throws IOException
{
    // Open and read the file
    InputStream in = url.openStream();
    BufferedReader filein = new BufferedReader(new InputStreamReader(in));
    // Read record file, parse lines, and add records to data base
    String line = filein.readLine();
    while(line != null) {
        System.err.println(line);
        String[] fields = line.split(",");
        addRecordFromFields(fields);
        line = filein.readLine();
    }
    filein.close();
}

/**
 * Loads the default library and provides for interaction with the data
 * via the JPanel GUI inputs.
 * @param args
 * @throws IOException 
 */
public String processData(String input, int selection, URL url)
{
    //Create the main library object
    PersonnelDatabase dbiLib = new PersonnelDatabase();
    System.err.println(url);
    // Try to read the default library
    try
    {
        dbiLib.initDBFromFile(url);
    }
    catch (IOException e)
    {
        System.err.println("File IO Error");
        e.printStackTrace();
        System.exit(1);
    }
    // Queries can be simulated by typing into the console in Eclipse, and using Ctrl-d (Ctrl-z in Windows) when finished.
    // For example: "searchLastName,Smith" would print a list of all people with the last name of Smith.
    Iterable<Record> result = null;
    String[] fields = new String[2];
    if (input.contains(",")) {
        fields = input.split(",");
    }

    switch(selection) {
        case 0: result = dbiLib.searchByFirstName(input); break;
        case 1: result = dbiLib.searchByLastName(input); break;
        case 2: result = dbiLib.searchByFullName(fields[0].trim(), fields[1].trim()); break;
        case 3: result = dbiLib.searchByCity(input); break;
        case 4: result = dbiLib.searchByState(input); break;
        case 5: result = dbiLib.searchByCityState(fields[0].trim(), fields[1].trim()); break;
        case 6: result = dbiLib.searchByZip(input); break;
        case 7: result = dbiLib.searchByPhoneNumber(input); break;
        case 8: String[] newFields = new String[fields.length-1];
                System.arraycopy(fields, 1, newFields, 0, fields.length-1);
                dbiLib.addRecordFromFields(newFields);
                return "Record added successfully!\nEnter a query or add another record.";
        default: return "Invalid query.\nEnter another query or add a record.";
    }

PersonnelDatabaseApplet class (initializes GUI, gathers input, and displays output):

public void init() {
    /* Create and display the applet */
    try {
        java.awt.EventQueue.invokeAndWait(new Runnable() {

            @Override
            public void run() {
                initComponents();
            }
        });
    } catch (Exception e) {
        System.err.println("Creation of GUI did not successfully complete.");
    }
}

// Process inputs in the background.
SwingWorker worker = new SwingWorker<String, Void>() {
    @Override
    public String doInBackground() {
        URL url = null;
        try {
            url = new URL(getCodeBase(), fileToRead);
        }
        catch(MalformedURLException e){}
        personnelDatabase = new PersonnelDatabase();
        final String output = personnelDatabase.processData(input, selection, url);
        return output;
    }

    @Override
    public void done() {
        processingLabel.setVisible(true);
        try {
            textToDisplay = get(15, TimeUnit.SECONDS);
        } catch (InterruptedException ignore) {
            ignore.printStackTrace();
        } catch (TimeoutException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            String why = null;
            Throwable cause = e.getCause();
            if(cause != null) {
                why = cause.getMessage();
                cause.printStackTrace();
            } else {
                why = e.getMessage();
                e.printStackTrace();
            }
            System.err.println("Error retrieving request: " + why);
            }
        if(worker.isDone() && textToDisplay != null) {
            processingLabel.setVisible(false);
            outputTextArea.setText(textToDisplay);
        }
    }
};

private void searchButtonActionPerformed(java.awt.event.ActionEvent evt) {                                             
selection = searchComboBox.getSelectedIndex();
input = valueTextField.getText();
processingLabel.setVisible(true);

worker.execute();

}

Link to the Applet on my personal website: http://www.ryan-taylor.me/Applied%20Maps/build/PersonnelDatabaseApplet.html

I'm fairly certain the error has nothing to do with the excel data itself as this program runs just fine in NetBeans and when running the html on my local machine. I'm guessing it has something to do with Swing (threads), but I'm not sure. I made changes to help with transferring data between Swing threads by making use of the SwingWorker, but I had no luck. I guess there's always the possibility I missed something when implementing it.

I also thought about signing the jar, but the file that I'm processing is stored online - not a local machine - so I didn't see a real need.

If anyone has any suggestions, I would greatly appreciate it!

madth3
  • 7,275
  • 12
  • 50
  • 74
Ryan
  • 3,414
  • 2
  • 27
  • 34
  • 2
    1) *"Java Applet - Dislikes the Web"* That would make more sense as *"The Web - Dislikes Java Applets"* A rich client (applet) embedded in a thin client (HTML) is an odd mix at the best of times. 2) For better help sooner, post an [SSCCE](http://sscce.org/). Hard code the data, unless that is the problem. 3) Instead of the object element, safest to use the [`deployJava.js`](http://download.oracle.com/javase/7/docs/technotes/guides/jweb/deployment_advice.html). – Andrew Thompson Jan 04 '13 at 01:56
  • These things are insecure and deprecated and heavy, and dependent. Just saying. – Hamed Al-Khabaz Jan 04 '13 at 02:02
  • 4) `name="code" value="Implementation/PersonnelDatabaseApplet.class"` Should probably be `value="Implementation.PersonnelDatabaseApplet"`. It is the fully qualified class name, rather than the file name or relative path. – Andrew Thompson Jan 04 '13 at 02:03
  • Thanks for the input guys. I know that Applet like this is kind of a joke nowadays, but I'm not using it professionally. I'm simply doing it for my own use. – Ryan Jan 04 '13 at 02:09
  • Please tell us that's not real data. – trashgod Jan 04 '13 at 02:27
  • Haha no. It's all randomly generated. – Ryan Jan 04 '13 at 15:58

3 Answers3

3

Looks like the array index that's out of bounds is 3, because the input line contains only three fields and you're trying to access the fourth one (index 3) without checking to verify that it actually exists. The error is in

thisRecord.setCity(fields[3]);

because the array fields has only three elements. At

    String[] fields = line.split(",");
    addRecordFromFields(fields);

when you get to

Patsy,Hart,661 East 11th St.

array fields will be created with only 3 entries.

If the number of fields is expected to be constant then you should reject input lines that don't have the right number of fields. If the number of fields can vary, then you must check the actual number returned and extract only those elements that actually exist.

Jim Garrison
  • 85,615
  • 20
  • 155
  • 190
  • Jim Garrison, The full record is as follows: Patsy,Hart,661 East 11th St. Apt. 195,Memphis,TN,38168,555-555-5555 The problem is the fact that the Applet quits reading the rest of the record for some reason. The other half of the record exists in the excel sheet, but it's just not getting processed. – Ryan Jan 04 '13 at 02:15
  • 1
    So you really had two questions. The first was to make sense of the `ArrayIndexOutOfBoundsException`, which my answer resolves. The second is why the input seems to be truncated. The simplest possibility is that the failing input record actually contains a newline character at the end of the third field. Can you open the CSV file in a text editor and verify that that line does not contain a newline? – Jim Garrison Jan 04 '13 at 02:23
2

It looks like something is causing the file to be truncated when you run the applet in a browser. My guess is that you are fetching the file from a web server, and either the server or the browser is silently enforcing some download limit. (Or may be the file got truncated when you uploaded it ...)

Stephen C
  • 698,415
  • 94
  • 811
  • 1,216
  • Or an errant data byte being misinterpreted as _end-of-file_ in a platform-dependent way. – trashgod Jan 04 '13 at 03:09
  • I feel like a complete idiot. Of course it would be something so simple. Upon uploading the data file to the server, my WiFi connection must have burped out for a bit, cancelling my upload a portion of the way through. Boy did I try to over-complicate this or what. Thanks Stephen and everyone else for your help! – Ryan Jan 04 '13 at 04:03
  • @trashgod - that is theoretically possible. However, any platform in which a "bad" data byte (in a bytestream!) is interpreted as an EOF is pretty broken ... IMO. – Stephen C Jan 04 '13 at 05:38
1

I suspect a data race between your worker and another thread that is exposed by network latency. Two things may bear closer scrutiny:

  1. Using invokeAndWait() to initialize the GUI is reasonable, as long as no further processing is done on the initial thread.

  2. Your worker is unusual in not invoking publish(), as a way to process() records on the event dispatch thread, for example.

Community
  • 1
  • 1
trashgod
  • 203,806
  • 29
  • 246
  • 1,045
  • @thrashgod, I have no need to publish background data (records) on-the-go. I simply concatenate each record to a "result" String and return that single value (after the full search is complete) to the event dispatch thread. – Ryan Jan 04 '13 at 17:39
  • @Ryan: Sounds reasonable; you can also `setProgress()` to let any `PropertyChangeListener` know how things are going. – trashgod Jan 04 '13 at 19:36