Search code examples
javaoopindexoutofboundsexception

Reading in stops after handling exception inside loop


I'd have a pretty strange question here. After throwing and handling my ReaderException exception my read-in still stops at the first occurence of the exception. Can somebody please explain why is this happening?

Input:

Hotel Paradis;Strada Ciocarliei, Cluj-Napoca 400124;46.779862;23.611739;7;200;8;250;1;400
Hotel Sunny Hill;Strada Fagetului 31A, Cluj-Napoca 400497;46.716030;23.573740;4;150;6;190
Golden Tulip Ana Dome;Strada Observatorului 129, Cluj-Napoca 400352;46.751989;23.576580;0;330;0;350;0;600

Code:

public HotelDescriptor readLine(final String line) throws ReaderException {
    System.out.println(line);
    String info[] = line.split(";");
    for (String i:info)
        System.out.println(i);
    String tempname = info[0];
    String tempaddress = info[1];
    float templatitudeh = Float.parseFloat(info[2]);
    float templongitudeh = Float.parseFloat(info[3]);
    int singleroom = Integer.parseInt(info[4]);
    int singleprice = Integer.parseInt(info[5]);
    int doubleroom = Integer.parseInt(info[6]);
    int doubleprice = Integer.parseInt(info[7]);
    int suiteroom = Integer.parseInt(info[8]);
    int suiteprice = Integer.parseInt(info[9]);

    Hotel tempHotel = new Hotel(tempname, tempaddress, templatitudeh, templongitudeh, singleroom, singleprice, doubleroom, doubleprice, suiteroom, suiteprice);
    System.out.println(tempHotel.getName());
    return tempHotel;
    }
public List<HotelDescriptor> readFile(final String hotels) {
    try (BufferedReader buff = new BufferedReader(new FileReader(hotels))) {
        String line = "";
        while ((line = buff.readLine() )!= null) {try {
                hotelData.add(readLine(line));
            } catch (ReaderException e){
                e.printStackTrace();
            } catch (ArrayIndexOutOfBoundsException ex){
                ex.printStackTrace();
            }
            //line = buff.readLine();
        }
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
    return hotelData;
}

Solution

  • I take it that hotelData is declared as a Class field (class global).

    When reading in a text file you should take into consideration some anomalies that can happen (or not). Simple steps can be taken to ensure that reading of that text file will be relatively successful. If your application is creating the text file then your success rate raises considerably since you can control how it is written but, if your application is not creating the text file or the text file is compiled from remote sources then the success rate can be reduced unless steps are taken to ensure expected results.

    In my opinion:

    • A text file should be identifiable so as to ensure that the proper text file is actually being read to process. If the text data is from a CSV file then a CSV Header line should be the very first line within the file and a read of this line should be done and compared to so as to verify that the correct file is being accessed. This is especially true if the file is to be selectable by any number of Users (perhaps by way of a file chooser). If a File Identifier (or Descriptor) line does not exist within your text file as the first line of the file then perhaps you should consider using one even if it is considered a Comment Line where the line might start with perhaps a semi-colon (;) as the first character of the line. Anything that can identify the file as being the correct file to process.
    • Blank lines and any lines deemed to be Comment Lines should be ignored. This includes any file lines known not to be actual Data Lines whatever they may be. In general, a couple lines of code consisting of an if statement along with a few conditions can take care of this situation.
    • Never count on actual Data Lines (the lines of data you will be processing) to be holding all the required data expected. This is especially true when manipulating split delimited data with methods such as Integer.parseInt() or Float.parseFloat() as a mere examples. This is actually the biggest problem in your particular situation. Take note of the example data lines you have provided within your post. The first line consists of 10 delimited pieces of data, the second data line consists of 8 delimited pieces of data, and the third line consists of again, 10 pieces of delimited data. It is the second data line that is the issue here. When this line is split the result will be an Array (info[]) which will hold 8 elements (index 0 to 7) yet the readLine() method is expecting to always deal with an array consisting of 10 elements (index 0 to 9). While processing the second data line, guess what happens when the code line int suiteroom = Integer.parseInt(info[8]); is hit. That's right, you get a ArrayIndexOutOfBoundsException because there simply is no index 8 within the info[] array. You need to handle situations like this in your code and prepare to deal with them. Don't rely on exception handling to take care of business for you. The whole idea is to avoid exceptions if at all possible mind you there are times when it is necessary. I don't believe this is one of them.

    Without access to your code classes I'm just going to naturally assume that your method returns are valid and functioning as planned. With this in mind, here is how I would format the Hotels text file:

    My App Name - Hotels Data File
    
    ;Hotel Name; Hotel Address; Latitude; Longtitude; Single Room; Single Price; Double Room; Double Price; Suite Room; Suite Price
    
    Hotel Paradis;Strada Ciocarliei, Cluj-Napoca 400124;46.779862;23.611739;7;200;8;250;1;400
    Hotel Sunny Hill;Strada Fagetului 31A, Cluj-Napoca 400497;46.716030;23.573740;4;150;6;190
    Golden Tulip Ana Dome;Strada Observatorului 129, Cluj-Napoca 400352;46.751989;23.576580;0;330;0;350;0;600
    

    The first line of the file is the File Descriptor line. The second line is a Blank Line simply for easier viewing of the file. The third line is considered a Comment Line because in this case it starts with a semi-colon (;). It's actually up to you to decide what is to be in place to make a file line considered as a Comment line. This line simply acts as a Header Line and describes what each delimited piece of data on any Data Line means. The fourth line is of course yet another blank line and again, for easier viewing of the file. The remaining file lines are all Data Lines and these are the file lines you want to process.

    To read the file your methods might look like this:

    public HotelDescriptor readLine(final String line) {
        // Split on various possible combinations of how the
        // delimiter might be formated within a file line.
        String info[] = line.split(" ; |; |;"); 
        // Variables declaration and default initialization values
        String tempname = "";
        String tempaddress = "";
        float templatitudeh = 0.0f;
        float templongitudeh = 0.0f;
        int singleroom = 0;
        int singleprice = 0;
        int doubleroom = 0;
        int doubleprice = 0;
        int suiteroom = 0;
        int suiteprice = 0;
    
        String strg; // Used to hold the current Array Element in the for/loop
        String regExF = "-?\\d+(\\.\\d+)?"; // RegEx to validate a string float or double value.
        String regExI = "\\d+";             // RegEx to validate a string Integer value.
        for (int i = 0; i < info.length; i++) {
            strg = info[i].trim(); // remove leading/trailing spaces if any
            switch (i) {
                case 0:
                    tempname = info[i];
                    break;
                case 1:
                    tempaddress = info[i];
                    break;
                case 2:
                    // Is it a float or double numerical value
                    if (strg.matches(regExF)) {
                        templatitudeh = Float.parseFloat(info[i]);
                    }
                    break;
                case 3:
                    // Is it a float or double numerical value
                    if (strg.matches(regExF)) {
                        templongitudeh = Float.parseFloat(info[i]);
                    }
                    break;
                case 4:
                    // Is it a Integer numerical value
                    if (strg.matches(regExI)) {
                        singleroom = Integer.parseInt(info[i]);
                    }
                    break;
                case 5:
                    // Is it a Integer numerical value
                    if (strg.matches(regExI)) {
                        singleprice = Integer.parseInt(info[i]);
                    }
                    break;
                case 6:
                    // Is it a Integer numerical value
                    if (strg.matches(regExI)) {
                        doubleroom = Integer.parseInt(info[i]);
                    }
                    break;
                case 7:
                    // Is it a Integer numerical value
                    if (strg.matches(regExI)) {
                        doubleprice = Integer.parseInt(info[i]);
                    }
                    break;
                case 8:
                    // Is it a Integer numerical value
                    if (strg.matches(regExI)) {
                        suiteroom = Integer.parseInt(info[i]);
                    }
                    break;
                case 9:
                    // Is it a Integer numerical value
                    if (strg.matches(regExI)) {
                        suiteprice = Integer.parseInt(info[i]);
                    }
                    break;
            }
        }
    
        Hotel tempHotel = new Hotel(tempname, tempaddress, templatitudeh, templongitudeh, 
                singleroom, singleprice, doubleroom, doubleprice, suiteroom, suiteprice);
        System.out.println(tempHotel.getName());
        return tempHotel;
    }
    
    public List<HotelDescriptor> readFile(final String hotels) {
        try (BufferedReader buff = new BufferedReader(new FileReader(hotels))) {
            String line;
            int lineCounter = 0;
            while ((line = buff.readLine()) != null) {
                // Trim any leading or trailing spaces (spaces, tabs, etc)
                line = line.trim();
                lineCounter++; 
                // Is this the right file to read?
                if (lineCounter == 1) {
                    if (!line.equalsIgnoreCase("My App Name - Hotels Data File")) {
                        //No it isn't...
                        JOptionPane.showMessageDialog(this, "Invalid Hotels Data File!",
                                "Invalid Data File", JOptionPane.WARNING_MESSAGE);
                        break; // Get out of while loop
                    }
                    // Otherwise skip the File Descriptor line.
                    else { continue; }
                }
                // Is this a blank or Comment line...
                // Lines that start with ; are comment lines
                if (line.equals("") || line.startsWith(";")) {
                    // Yes it is...skip this line.
                    continue;
                }
                // Process the data line...
                hotelData.add(readLine(line));
            }
        }
        catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        catch (IOException e) {
            e.printStackTrace();
        }
    
        return hotelData;
    }
    

    In the readLine() method variables are initialized to hold default values should not all values be present on any given file Data Line. The switch block ensures that only supplied data line values are processed regardless of how data is provided, defaults fill in the rest. This eliminates the possibility of an ArrayIndexOutOfBoundsException from happening when working with the info[] Array.

    Where parseFloat() and parseInt() are used the string to be converted into its respective data type is first checked to ensure that it is a valid numerical representation of the data type we are converting to. The String.matches() method is used for this in conjunction with a regular expression.

    The code above can of course be optimized much further but I feel it provides a good description of what can be done to to increase the success of reading and processing the file(s).

    As a side note, it is also understandably confusing to call one of your own methods (readLine()) by the same name as a method used by BufferedReader. Up to you but perhaps this would be better named as processReadLine()

    Prices should be at least in either float or double data type