5/12/2014

Java - Temporary Files in Your Java Application


I spent the 4 last weeks to develop an exportation module in Java. This module, depending of the user selection, generates Microsoft Word and Excel tables.

At first I had the brilliant idea to ask a file name and a preferred location to the users. The idea behind that choice consisted in forcing the user to be somehow tidy and organised :) . In fact it turned out to be really boring in a daily use basis without even talking of the user interface which was overloaded by all theses controls.

Okay that was for the little story, now let's see how I managed my temporary files.

Analyse

  1. First of all, I chose a location for all the temporary files generated by the application:C:/Temp/  The folder may be a bad choice since many other application and sysadmin use it as a temporary folder.
  2. In order to be sure that we won't delete the files generated by our favorite sysadmin, let's specify a file name format. tmp#########.rtf (Word tables) and tmp#########.xlsx (Excel Tables) This file format will help us to recognise our own files.(Of course if your sysadmin generates files finishing by rtf or xlsx, be more specific)
  3. Now that we have some tiny specifications, let's see some code

Code

Generate the Temporary Folder

The first method checks if a directory C:/Temp/ exists. If it exists, the directory is returned if not, the directory is created. If the user who launched the application has no right to create a folder in C:/ a custom exception will be thrown. We catch the exception in our Graphic User Interface layer.

 public static File getTemporaryFileBaseDirectory() throws OpenFile_Error
    {
        String basedirectoryString="C:/Temp/";
        File baseDirectory=new File(basedirectoryString);
        boolean permissiontowrite=true;
        if (!baseDirectory.exists())
            permissiontowrite=baseDirectory.mkdir();
        if (!permissiontowrite)
            throw new OpenFile_Error(String.format("Impossible to create a Temp directory at %s", basedirectoryString),null);
        return baseDirectory;
    } 


Generate the Temporary Files 

The second method will create a temporar y file. This temporary file should have the extension given in the "extension" parameter. Like in the getTemporaryFileBaseDirectory() method, an OpenFile_Error exception can be thrown. This method will get the temporary folder as a file. Then by calling the java.io.File.createTempFile(String prefix, String suffixe, File baseDirectory), we will create a temporaryfile. At the end of the function if no exception has been thrown, the new Temporary file is returned.

    public static File getTemporaryFile(String extension) throws OpenFile_Error
    {
        File baseDirectory= null;
        File TemporaryFile=null;
        try {
            baseDirectory = Util.Files.File_UTIL.getTemporaryFileBaseDirectory();
            TemporaryFile = File.createTempFile("tmp", extension, baseDirectory);
        }catch(OpenFile_Error error1)
        {
            throw error1;
        } catch (IOException e) {
            throw new OpenFile_Error("Error while creating temporary File", e.getStackTrace());
        }
        return TemporaryFile;
    }

Below is a concrete example of how to save my Microsoft Word and Excel files in temporary files.
On the first lines I instantiate my exporters which are java classes managing Excel (static) and Word (entity) POI API.
After creating my exporter, I get my temporary file with the method studied above getTemporaryFile(String extension). Once the change in my temporary files are treated, I open my files with the Util.Files.Command_UTIL.OpenFile(File f) function.

 @Override
    public void actionPerformed(ActionEvent e)
    {
        try {
            if (e.getSource().equals(exportRTF_Button))
            {
                //Export in Word Tenporrary File
                WordExporter_UTIL exporter=new WordExporter_UTIL();
                File temp = File_UTIL.getTemporaryFile(".rtf");
                exporter.createDocument(temp,WordExporter_UTIL.PageOrientation.PORTRAIT);
                exporter.closeDocument();
                Util.Files.Command_UTIL.OpenFile(temp);
                System.out.println("RTF");
            }
            if (e.getSource().equals(exportXLS_Button))
            {
                Util.Exporter.EXCEL.ExcelExporter_UTIL.CreateWorkbook();
                File temp = File_UTIL.getTemporaryFile(".xlsx");
                ExcelExporter_UTIL.AddDatasheet_WithRandomData(this.Title,this.results);
                ExcelExporter_UTIL.saveWorkbook(temp);
                Util.Files.Command_UTIL.OpenFile(temp);
            }
        } catch (OpenFile_Error openFile_error) {
            Error_Message.show(openFile_error);
        }
    }

Opening a File in its own application

This is really important, the file name is not known anymore by the user. Therefore, we need to open the file and show directly where is the dataset | table. My method to open a file in its own application is detailed here. Because we are using some network disk in my office, I implemented two differents function. One for the local files and one for the network files.

package Util.Files;

import ERRORL.Files.OpenFile_Error;

import java.io.File;

public class Command_UTIL {

    public static void  OpenFile(File FileToOpen) throws OpenFile_Error
    {
        try {
            if (FileToOpen.getCanonicalPath().startsWith("\\\\"))
                OpenNetworkFile(FileToOpen.getCanonicalPath());
            else
                OpenLocalFile(FileToOpen.getCanonicalPath());
        }catch (Exception e)
        {
            throw new OpenFile_Error("Error while Opening the file", e.getStackTrace());
        }
    }

    private static void OpenLocalFile(String filename)throws Exception
    {
        Runtime.getRuntime().exec(String.format("cmd /c \"%s\"",filename.replaceAll("\\(","^(").replaceAll("\\)","^)").replaceAll(" ","^ ")));
    }
    private static void OpenNetworkFile(String filename)throws Exception
    {
        Runtime.getRuntime().exec(String.format("cmd /c \"%s\"",filename));
    }
}

Deleting the files 

After some tests, you will notice that the temporary folder is getting bigger and bigger. It needs to be cleaned.
For that I decided that the best moment would be whether at the start of the application process, or at its end. The start may sounds simpler because every java programmer knows where the entry point is. But it also means that the application needs to run a second time before the files are deleted. What if theses documents are confidential and what if the user never uses the application again?For all these reasons, I decided to implement a cleanTemporaryFolder() method when the application process ends.
To catch the process shutdown event, we need to add a hook when the application start. (add the hook after the creation of a file may be a good idea to save resources)




public static void main(String[] args)
    {
        //Add a Shutdown Hook cleaning all the temporary files,
        Runtime.getRuntime().addShutdownHook(new Thread(new TemporaryFilesCleane()));
[.....]
}
The code above, shows us how to add a hook witth the addShutDownHook(Thread T) method of the Runtime object. Our hook is a Runnable (it has to be a runnable in any case) called TemporaryFilesCleaner.


package Util.Files;

public class TemporaryFilesCleaner implements Runnable {

    @Override
    public void run()
    {
        File_UTIL.cleanTemporaryFolder();
    }
}

Voila, I kept it simple because we just need this cleaner as an interface between our File_UTIL static methods library and our application's entry point.
The method cleanTemporaryFolder is quite simple and although it could catch an exception, we will never display the exception or block the application since it is in a CLOSING system thread (if there is an exception, the process will never end and you will use resources uselessly without even knowing it).



public static synchronized void cleanTemporaryFolder()
    {
        File TemporaryFolder=null;
        try {
            TemporaryFolder = getTemporaryFileBaseDirectory();
        }catch(OpenFile_Error error)
        {
            error.printStackTrace();
        }
        List<File> filesInFolder= Arrays.asList(TemporaryFolder.listFiles());
        try {
            filesInFolder=filesInFolder
                    .stream()
                    .filter(File_Predicates.isTemporaryFile().and(File_Predicates.isRtfFile().or(File_Predicates.isExcelFile())))
                    .forEach(f->f.delete());
        }catch(Exception e)
        {e.printStackTrace();}

    }
This method lists all the files in our temporary folder then it filters all the files matching the criteria of my predicates (see the predicate below). The criteria expressed with Windows 8 lambda expression, filter all the files starting with "temp" and finishing by "rtf" or by "xlsx". After what each of the files matching the criteria will be deleted. You probably noticed that my method is synchronized. I added this keyword because we use this method in a Thread and we want our application to be thread safe, especially in this case in a hook.

package Util.Files;

import java.io.File;
import java.util.function.Predicate;

public class File_Predicates
{
    public static Predicate<File> isTemporaryFile()
    {
        return f -> f.getName().startsWith("tmp");
    }
    public static Predicate<File> isExcelFile()
    {
        return p -> p.getName().endsWith(".xlsx");
    }
    public static Predicate<File> isRtfFile()
    {
        return p -> p.getName().endsWith(".rtf");
    }


}
I think, I finished to document this part of the application. :D

Have fun and don't forget to work!