Patching files

Wednesday, April 7, 2010 5:09 PM Posted by Valer Micle

Hi there,

I've started working on a new project with a lot a lot of JSP:s and I had to do some modifications in a JSP. Now since I'm new on the project, it is quite hard to identify which is the JSP which I need to update even if the modification is very small.

After spending some time looking around for the right JSP to modify I've gave up and I took a shortcut: I've patched all the JSP files in the deployment folder with two HTML comments:
- at the beginning of the file simply add:
<!-- Start FileName: ${fileName} -->
and at the end of the file add:
<!-- END FileName: ${fileName} -->
where ${fileName} is a place holder for the name of the file being patched.

In this way, in any browser you can look at the source code and search for the place where you need to make the modification (like search by a label name or something). Then look up for the first html comment which looks like Start FileName to see which JSP is rendering the part that you need to modify.

One thing that should be taking care of is that the jsp files should not be recreated but instead modified! Some build tools might have problems with re-builds if the files gets recreated because they update files on rebuilds based on the create & modified date of a file. If you recreate the file, there is a chance that your file will never be updatedafterwardsby your build toll and you certainly don't want that.

Now lets do it generic. I need a small program which will take 4 input arguments:
1. the folder containing the files that needs to be processed
2. a file containing the text that needs to be added in the beginning of each file
3. a file containing the text that needs to be added at the end of each file
4. the type of the files that needs to be processed

That being said, the main method calling this program should look something like this:
public static void main(String[] args) {

   long startTime = System.currentTimeMillis();
   PatchFiles patch = new PatchFiles("d:\\myProject\\deploymentDir", "d:\\temp\\header.txt", "d:\\temp\\footer.txt", "*.jsp");
   patch.startPatching();

   System.out.println("patching completed in = "
       + (System.currentTimeMillis() - startTime) + " for " + patch.getProcessedFiles() + " files.");
 }

Here is the PatchFiles class with the basic methods:

public class PatchFiles {

public static final String FILE_NAME_PLACE_HOLDER = "\\$\\{fileName\\}";

private String pathName;

/for example for *.jsp, the matcher should be: .*\.jsp$private String fileMatcher;
 private String headerText = "";
 private String footerText = "";
 private int processedFiles = 0;
 private boolean processCompleted = false;

 public PatchFiles(String pathName, String headerFilePath, String footerFilePath, String fileMatcher) {
   this.pathName = pathName;
   this.fileMatcher = "." + fileMatcher.replaceAll("\\.", "\\\\.") + "$";

   headerText = readFileContent(headerFilePath);
   if(headerText.length() > 0) {
     headerText = headerText + "\n";
   }
   footerText = readFileContent(footerFilePath);
   if(footerText.length() > 0) {
     footerText = "\n" + footerText;
   }
   processCompleted = false;
 }

 public void startPatching() {
   if(processCompleted) {
     return;
   }
   if(headerText.length() + footerText.length() == 0) {
     //if the header and footer texts are empty, simply return
     return;
   }
   File dir = new File(pathName);

   if(dir.exists() && dir.isDirectory()) {
     File tempFile = createTempFileOnDir(dir);

     patchFilesInFolder(dir, tempFile);

     tempFile.delete();
   }
   processCompleted = true;
 }

 private void patchFilesInFolder(File dir, File tempFile) {

   File[] files = dir.listFiles(new FileFilter(){
     public boolean accept(File currentFile) {
       return fileMatcher == null || currentFile.isDirectory() || currentFile.getName().matches(fileMatcher);
     }
   });

   for (File file : files) {
     if(file.isDirectory()) {
       patchFilesInFolder(file, tempFile);
     } else {
       patchFile(file, tempFile);
       processedFiles++;
     }
   }
 }

There are three helper methods used by this small program described here:
/**
     * Read the whole content of a file using UTF-8 encoding and return the file's content as a string
     *
     * @param filePath
     * @return
     */
    private String readFileContent(String filePath) {
        File file = new File(filePath);
        StringBuilder contentBuilder = new StringBuilder();

        if (file.exists() && !file.isDirectory()) {
            try {
                BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file), "UTF-8"));

                String line;
                while ((line = reader.readLine()) != null) {
                    contentBuilder.append(line);
                }

            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return contentBuilder.toString().trim();
    }

    /**
     * Create a new temp file to be used as a tampon file in the
     * patching process in the folder passed in
     *
     * @param dir
     * @return
     */
    private File createTempFileOnDir(File dir) {
        // create a tmp file in this folder
        String fileNameFolderPath = dir.getPath() + "\\";
        File tempFile;
        long randomLong = new Random(System.currentTimeMillis()).nextInt();
        do {
            String fileNamePath = fileNameFolderPath + "_tmp_" + randomLong + ".tmp";
            tempFile = new File(fileNamePath);
            randomLong++;
        } while (tempFile.exists());

        try {
            tempFile.createNewFile();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return tempFile;
    }

/**
     * Copy the whole content of the dest file in the src file
     * overwriting the whole content of the src. The copy is done using a buffer of 1024 bytes.
     *
     * @param src
     * @param dest
     */
    private void copyAllContentOfFileToFile(File src, File dest) {
        InputStream inputStream;
        OutputStream outputStream;
        try {
            outputStream = new FileOutputStream(dest);
            inputStream = new FileInputStream(src);

            byte[] buf = new byte[1024];
            int len;
            while ((len = inputStream.read(buf)) > 0) {
                outputStream.write(buf, 0, len);
            }

            inputStream.close();
            outputStream.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

And the core method which does the actual patching of a file:

private void patchFile(File file, File tempFile) {
        InputStream inputStream;
        OutputStream outputStream;
        try {
            outputStream = new FileOutputStream(tempFile);
            inputStream = new FileInputStream(file);

            if (headerText.length() > 0) {
                byte[] headerBytes = headerText.replaceAll(FILE_NAME_PLACE_HOLDER, file.getName()).getBytes("UTF-8");
                outputStream.write(headerBytes);
            }

            byte[] buf = new byte[1024];
            int len;
            while ((len = inputStream.read(buf)) > 0) {
                outputStream.write(buf, 0, len);
            }

            if (footerText.length() > 0) {
                byte[] footerBytes = footerText.replaceAll(FILE_NAME_PLACE_HOLDER, file.getName()).getBytes("UTF-8");
                outputStream.write(footerBytes);
            }

            inputStream.close();
            outputStream.close();

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        copyAllContentOfFileToFile(tempFile, file);
    }


Thats all. Hope you'll find it useful  :)
Cheers

Comments (2)

It just came to me that this could be very nice integrated with the ant build target for dev environment so that it won't influence your production build.

If you want to do something like this, than you should check if a file is already patched or not when walking the files in order to avoid double patching

That was very useful to me ! thank you very much for your hard work.
I would appreciate if you could put more posts .
Thank you in advance

Post a Comment