What will you build

You will see some way of reading from a file and display the information and writing in a file from a method.

What You'll Learn

What will you need

Java like every programming language allows to read a file and write in a file. Java did evolve a lot since the beginning, several libraries exist to read a file (as to write in it).

In this codelabs, we will not look at all the level of these in details, however, it is important to know that most of them are build on a very low level library called Streams and File. It has been defined in java.io.

The input stream can abstract many different kinds of input: from a disk file, a keyboard, or a network socket. A stream is a sequence of data.

The File class (java.io.File) is an abstract representation of file and directory pathnames. It have been more and more replaced by the java.nio.file package. This class doesn't reflect the content of a file, but the name of the file, or the names of the files in a folder.

Some more information in and here for the file and even here for a nice tutorial

We will see the three first option in this codelabs.

We want to list all the name of the files in a given folder. This folder could be hard-coded or given in the arguments. In this example, we are using a recursion to browse all the hierarchy of the given folder.

import java.io.File;

public class ReadAFile {
    public void getFileNames(File folder){
        File[] list;
        list = folder.listFiles();
        for(File f: list){
            System.out.println(f);
            if(f.isDirectory()){
                getFileNames(f);
            }else{
                System.out.println(f.getName());
            }
        }
    }
}

To test this method, we will invoke it in a main:

package hdr.nsa;

import java.io.File;

public class Main {

    public static void main(String[] args) {
	ReadAFile read = new ReadAFile();
	read.listFile(new File("."));
    }
}

How could we transform it to take the arguments given by a user?

The aim of this little program is to read a file. As I wrote above, you have several methods to be able to read a file, and depending of what you want to do, the size of the file and other variables, you should look at which method is the most appropriate.

For this little application, the aim is to read a file and display it to the user in the console. To use the different library, you need first to import them.

You will use the method we did implement in the previous step to have the path of the file(s) we want to read.
We will refactors it to return all the Files instead of printing it.

public class ReadAFile {
    private List<File> arrayList = new ArrayList<>();
    
    public List<File> getFileNames(File folder){
        File[] list;
        list = folder.listFiles();
        for(File f: list){
            if(f.isDirectory()){
                getFileNames(f);
            }else{
                arrayList.add(f);
            }
        }
        return arrayList;
    }
}

To read the content of the file, we will implement a new method that will take as parameter the File that the method getFileNames is returning. The BufferedReader and the File are throwing exception that we will need to catch. We are using a try catch in this case.

public void readContentFile (File f) {
        try {
            BufferedReader br = new BufferedReader(new FileReader(f));
            String l;
            while ((l = br.readLine()) != null) {
                System.out.println(l);
            }
            br.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

To test these two methods, we are refactoring the main method.

import java.io.File;
import java.util.List;

public class Main {

    public static void main(String[] args) {
	ReadAFile read = new ReadAFile();
        List<File> files = read.getFileNames(new File("."));
        for(File f: files){
            System.out.println(f.getName());
            read.readContentFile(f);
        }
    }
}

One issue with the method above is how to handle the path of the file. Java since version 7 propose the java.nio.File that could help with this, and offer some methods that will help. We will use the Object Path.

You have two types of Path, relative or absolute:

Symbolic links are a special file that serves as a reference to another file. It can be a confusing notion, if you know aliases, they are very similar, but aliases are linked to the file or folder, and if you move them, the aliases will be updated, for the symbolic links, if you move the file or folder the symbolic link refer from, the link is broken.

A Path class represent a path in the file system, the Path object will contain the file name and the directory list used to constructor the path. The Path instance will reflect the structure that the application is running on, which makes it plateform independant.

In this method, we will get the Path of a file, and this method will be plateform independent, however, the file need to exist in the specific path. The level of the hierarchy is indicated with the different arguments.

public void readAFileWithThePath(){
        Path p1 = Paths.get(System.getProperty("user.home"),"Dropbox","travail","CardiffUniversity","Teaching","java_20_21","code_codeLabs_example","How-to-read-a-file","data","text.txt");
        System.out.println(p1.toString());
        try {
            Charset ch = Charset.forName("UTF-8");
            BufferedReader bufferedReader = Files.newBufferedReader(p1,ch);
            String line;
            while((line = bufferedReader.readLine())!=null){
                System.out.println(line);
            }
            bufferedReader.close();
        }catch (IOException e) {
            System.err.format("IO exception "+e.getMessage());
        }
    }

It is always very important to be sure that the BufferedReader or any Stream is closed.

To see more about this

Of course, if we can read in a file, we can also write in a file. Some of the decision the developper will need to take are, do they want to append the text to the existing text, or do they want to re-write on top of the existing text. They can also decide if the file is existing, and throws an exception if the file already exists. Again, depending of what they want to do, they should check the documentation.

In this example, we want to create a new file that will write some random text and throws an exception if the file already exist to add the text to the existing one.

Some of the existing options for the file Open:

For this example, we are creating a small method that could retrieve the path of a file. This method will return a String.

 public String retrieveThePath(){
    Path p1 = 	  Paths.get(System.getProperty("user.home"),"Dropbox","travail","CardiffUniversity","Teaching","java_20_21","code_codeLabs_example","How-to-read-a-file","data","text.txt");
    System.out.println(p1.getNameCount());
    System.out.println("root "+p1.getRoot().toString());
    for(int i = 0; i<p1.getNameCount();i++){
        System.out.println(p1.getName(i));
    }
    String path = p1.subpath(0,p1.getNameCount()-1).toString();
    return p1.getRoot().toString()+path;
}

The second method is taking two arguments, the path (String) of the file and a String that will be written in the file.

public void writeAFile(String path,String toWrite){
    Charset charset = Charset.forName("US-ASCII");
    Path p2 = Paths.get(path);
    try {
        BufferedWriter writer = Files.newBufferedWriter(p2, charset, StandardOpenOption.CREATE);
        writer.write(toWrite);
        writer.close();
    } catch (IOException e) {
        System.out.println("IOException: "+e);
    }
}

The File class, the Stream class, as the Path class are all thwrowing exceptions. These exceptions need to be take into account. In Java, one of the philosophy is to try to catch the error in compile time and not in runtime. We have a really good example of this with the generic. However, it is not always possible to catch the error in compile time, and Java cames with the concept of management of exception.

It is important to understand that an exception is different than a traditional problem or error. For example, exception shouldn't be used to check if a password is correctly written in a login system. A validation should be implemented to check if the password match the pattern that the developper wants to implement.

You saw in this Codelabs one way of catching the exceptions with the try-catch method. You can also throw exception to be catch in another class, and depending of what you want to do, it could be a good solution.

We will see in more details what are exceptions and how to create them in another codelabs about exception.

You can find the code for this example in https://git.cardiff.ac.uk/ASE_GROUP_2020/code_for_codelabs.git