-1

I have a problem with my code since it allows creating documents which have Strings with measurements defined by the code itself. When I try to display them on the screen, the program gives me the following error:

java.io.EOFException
    at java.base/java.io.RandomAccessFile.readFully(RandomAccessFile.java:498)
    at java.base/java.io.RandomAccessFile.readFully(RandomAccessFile.java:472)
    at RandomFileHandlerAlex.decodeFiles(RandomFileHandlerAlex.java:104)
    at RandomFileHandlerAlex.main(RandomFileHandlerAlex.java:20)

I have been looking for ways to solve it by specifying the measurement by doing an error check to see if it meets the measurement or not, but nothing has worked for me. I am attaching the code below to see if someone can help me out.

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.Scanner;

public class RandomFileHandlerAlex {
    public static Scanner input = new Scanner(System.in);

    public static void main(String[] args) throws IOException {
        int selection = -1;
        while (selection != 0) {
            showMenu();
            selection = input.nextInt();
            input.nextLine(); // Clear buffer
            if (selection == 1) {
                handlePopulations();
            } else if (selection == 2) {
                handleRegion();
            } else if (selection == 3) {
                decodeFiles();
            } else if (selection == 4) {
                modifyFifthPopulationRecord();
            } else if (selection == 5) {
                addPopulationOrRegion();
            }
        }
    }

    // Method to display menu options
    public static void showMenu() {
        System.out.println("1. Enter populations");
        System.out.println("2. Enter region");
        System.out.println("3. Decode file");
        System.out.println("4. Edit 5th position");
        System.out.println("5. Add record");
        System.out.println("0. Exit");
    }

    // Method to handle population input and save it to a file
    public static void handlePopulations() {
        try (RandomAccessFile file = new RandomAccessFile("population.dat", "rw")) {
            Scanner scanner = new Scanner(System.in);
            System.out.println("---------Enter populations--------");
            file.seek(file.length());
            for (int i = 0; i < 8; i++) {
                System.out.println("Enter name:");
                String name = scanner.nextLine();
                name = String.format("%-60s", name); // Set fixed length

                System.out.println("Enter number of inhabitants:");
                int inhabitants = scanner.nextInt();

                System.out.println("Enter rt:");
                double rt = scanner.nextDouble();

                scanner.nextLine(); // Clear buffer

                System.out.println("Enter postal code:");
                String postalCode = scanner.nextLine();
                postalCode = String.format("%-5s", postalCode);

                file.writeUTF(name); // Write name as UTF-8
                file.writeInt(inhabitants); // Write inhabitants
                file.writeDouble(rt); // Write rt
                file.writeUTF(postalCode); // Write postal code as UTF-8
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    // Method to handle region input and save it to a file
    public static void handleRegion() {
        try (RandomAccessFile file = new RandomAccessFile("region.dat", "rw")) {
            System.out.println("---------Enter regions--------");
            file.seek(file.length());
            for (int i = 0; i < 8; i++) {
                System.out.println("Enter name:");
                String name = input.nextLine();
                name = String.format("%-30s", name); // Set fixed length

                System.out.println("Enter number of inhabitants:");
                int inhabitants = input.nextInt();

                System.out.println("Enter rt:");
                double rt = input.nextDouble();

                input.nextLine(); // Clear buffer

                file.writeUTF(name);
                file.writeInt(inhabitants);
                file.writeDouble(rt);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    // Method to read and display content of population file
    public static void decodeFiles() {
        try {
            File populationFile = new File("population.dat");
            if (populationFile.exists() && populationFile.isFile()) {
                try (RandomAccessFile file = new RandomAccessFile(populationFile, "r")) {
                    while (file.getFilePointer() < file.length()) {

                        byte[] nameBytes = new byte[60];
                        file.readFully(nameBytes);
                        String name = new String(nameBytes).trim();

                        int inhabitants = file.readInt();
                        double rt = file.readDouble();

                        byte[] postalCodeBytes = new byte[5];
                        file.readFully(postalCodeBytes);
                        String postalCode = new String(postalCodeBytes).trim();

                        // Display read data
                        System.out.println("Name: " + name);
                        System.out.println("Inhabitants: " + inhabitants);
                        System.out.println("RT: " + rt);
                        System.out.println("Postal Code: " + postalCode);
                        System.out.println("---------------------------");
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            } else {
                System.out.println("File population.dat does not exist.");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // Method to modify the fifth population record
    public static void modifyFifthPopulationRecord() {
        try {
            File populationFile = new File("population.dat");
            if (populationFile.exists() && populationFile.isFile()) {
                try (RandomAccessFile file = new RandomAccessFile(populationFile, "rw")) {
                    // Exact size of each record (without UTF headers)
                    long recordSize = 60 + 4 + 8 + 5;

                    long recordCount = file.length() / recordSize;

                    if (recordCount < 5) {
                        System.out.println("File does not contain enough records (at least 5 required)");
                        return;
                    }

                    file.seek(recordSize * 4); // Position of the fifth record

                    byte[] nameBytes = new byte[60];
                    file.readFully(nameBytes);
                    String name = new String(nameBytes).trim();

                    int inhabitants = file.readInt();
                    double rt = file.readDouble();

                    byte[] postalCodeBytes = new byte[5];
                    file.readFully(postalCodeBytes);
                    String postalCode = new String(postalCodeBytes).trim();

                    // Modify values
                    inhabitants += 100;
                    rt -= 0.05;

                    // Overwrite fifth record with modified values
                    file.seek(recordSize * 4);
                    file.writeBytes(String.format("%-60s", name));
                    file.writeInt(inhabitants);
                    file.writeDouble(rt);
                    file.writeBytes(String.format("%-5s", postalCode));

                    System.out.println("Fifth record updated");

                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    // Method to add a new population or region record
    public static void addPopulationOrRegion() {
        System.out.println("1.population 2.region");
        int option = input.nextInt();

        if (option == 1) {
            try (RandomAccessFile file = new RandomAccessFile("population.dat", "rw")) {
                Scanner scanner = new Scanner(System.in);
                System.out.println("---------Enter a population--------");
                file.seek(file.length());
                System.out.println("Enter name:");
                String name = scanner.nextLine();
                name = String.format("%-60s", name);

                System.out.println("Enter number of inhabitants:");
                int inhabitants = scanner.nextInt();

                System.out.println("Enter rt:");
                double rt = scanner.nextDouble();

                scanner.nextLine();

                System.out.println("Enter postal code:");
                String postalCode = scanner.nextLine();
                postalCode = String.format("%-5s", postalCode);

                file.writeUTF(name);
                file.writeInt(inhabitants);
                file.writeDouble(rt);
                file.writeUTF(postalCode);

            } catch (IOException e) {
                e.printStackTrace();
            }
        } else if (option == 2) {
            try (RandomAccessFile file = new RandomAccessFile("region.dat", "rw")) {
                System.out.println("---------Enter a region--------");
                file.seek(file.length());
                System.out.println("Enter name:");
                String name = input.nextLine();
                name = String.format("%-30s", name);

                System.out.println("Enter number of inhabitants:");
                int inhabitants = input.nextInt();

                System.out.println("Enter rt:");
                double rt = input.nextDouble();

                input.nextLine();

                file.writeUTF(name);
                file.writeInt(inhabitants);
                file.writeDouble(rt);

            } catch (IOException e) {
                e.printStackTrace();
            }
        } else {
            System.out.println("Invalid option");
        }
    }
}

6
  • 3
    Your stack trace does not relate to the code you've posted. For example, the stack trace mentions Catalan class and method names, but the code only has English class and method names. Commented Oct 25, 2024 at 13:36
  • to complement previous comment, posted trace is from starting FitxersAleatorisAlex.main, but posted code shows RandomFileHandlerAlex.main (translated?) -- please post a minimal reproducible example Commented Oct 25, 2024 at 13:49
  • If you'll excuse the code, I've translated it into English to make it easier for readers to understand. Commented Oct 25, 2024 at 13:53
  • @k314159 I have already changed the text, please forgive me, the statement is now correct Commented Oct 25, 2024 at 13:57
  • 2
    fast analysis: using writeUTF() to write, but readFully() to read, should use readUTF() - writeUTF(): "First, two bytes are written to the file, starting at the current file pointer, as if by the writeShort method giving the number of bytes to follow...." and readFully(): "Reads b.length bytes from this file ... EOFException - if this file reaches the end before reading all the bytes." - the file does not have that many (60) bytes -- I would start small first: just one single name. Next maybe one name and one number... Commented Oct 25, 2024 at 14:04

1 Answer 1

1

The problem is caused by two factors:

First, you are using writeUtf:

            file.writeUTF(name);

As the documentation of writeUTF says:

Writes two bytes of length information to the output stream, followed by the modified UTF-8 representation of every character in the string s.

So, although your name is padded to a fixed 60 characters long, it is always stored with at least 62 bytes, possibly more if it contains any non-ASCII characters. This means you are not using fixed-length records.

Then, you have this while loop:

                while (file.getFilePointer() < file.length()) {

Your file pointer is using a multiple of what you think is a fixed size, but file.length is actually slightly larger because of the use of writeUTF. Therefore you enter the loop even when you have already processed the last record, and you try to read another record but there aren't enough bytes left in the file.

Note: what I've written regarding name also applies to all strings used by your application, including post code and region.

You have many options for fixing this problem. Here are a few:

  1. Instead of using a binary file with fixed-size records, use a JSON file, and use one of the many available JSON libraries to write and read it.
  2. Instead of using a RandomAccessFile, use ObjectOutputStream.
  3. If you must use fixed-size records, make your name a fixed number of bytes (after encoding it as UTF-8), instead of a fixed number of chars. That way, you can still use writeUTF but now you know that it will have fixed byte length in the file.

For the third option, to pad name to 60 bytes, instead of this:

        String name = scanner.nextLine();
        name = String.format("%-60s", name); // Set fixed length

you should do something like this:

        String name = scanner.nextLine();
        byte[] nameBytes = name.getBytes();
        byte[] paddedNameBytes = new byte[60];
        System.arraycopy(paddedNameBytes, 0, nameBytes, 0, nameBytes.length);
        ...
        file.write(paddedNameBytes)

This uses your default charset, which could be different for each user of your program. It may be better to be explicit, so instead of name.getBytes() do name.getBytes(StandardCharsets.UTF_8) and when reading the file, use String(nameBytes, StandardCharsets.UTF_8).trim().

Note: I've left out error handling, which you should add. For example, check that the name actually fits into 60 bytes. Also, you should define a constant final static int NAME_LENGTH = 60 instead of using 60 everywhere.

Sign up to request clarification or add additional context in comments.

5 Comments

The third option, how can I specify it because I already thought that my program already did it?
@chamorro see the addition I've just made to my answer.
Note also that it's possible that the name might only be able to contain fewer than NAME_LENGTH characters, depending on what the name contains and the character encoding (UTF-8 is variable-length)
@g00se good point; for clarity, the constant NAME_LENGTH should really be NAME_BYTES_LENGTH.
You should definitely always use getBytes(UTF_8) (or whatever other charset), not getBytes(). You can import static java.nio.charset.StandardCharsets.UTF_8 to make it more terse and less painful.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.