Friday, June 7, 2013

Moving, Renaming Directories,Files or Packages in Java

It is often needed to move and/or rename directories using java programs. One particular case where i needed this feature was after following some tutorial on the web ending up with a heavily configured Eclipse project and where i needed to rename the hole project and sometimes even package names to fit my desired parameters in stead of starting the configuration all over again for a new project. (You may be interested if you followed this JEE tutorial)
Eclipse on its side proposes Project re-factoring but for packages renaming you'll need to do it by hand.
Another scenario where this feature maybe useful is when you do some code generation and where you need to create a new project instance starting from some skeleton.

Classes are grouped in an Eclipse Java Project illustrated by this tree:



1. Package file.util.files:

This package contains an abstract class that exposes useful operations for files.

FileUtils.java:


package file.util.files;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public abstract class FileUtils
{
 public static String LINE_SEPARATOR = System.getProperty("line.separator");
 public static String FILE_SEPARATOR = System.getProperty("file.separator");
 
 public static String toPath(String packageName)
 {
  return(packageName.replaceAll("\\.", FILE_SEPARATOR));
 }
 
 public static String getExtension(File file)
 {
  String result = "";
  
  if (file.isFile())
  {
   String fileName = file.getName();
   int lastDotIndex = fileName.lastIndexOf(".");
   
   if ((lastDotIndex != -1) && (lastDotIndex != (fileName.length()-1)))
   {
    result = fileName.substring(lastDotIndex + 1);
   }
  }
  
  return(result);
 }
 
 public static void string2File(String s, String path) throws IOException 
 {
  FileWriter fw = new FileWriter(path);
  BufferedWriter bw = new BufferedWriter(fw);
  bw.write(s);
  bw.close();
  fw.close();
 }
 
 public static String file2String(String filename) throws IOException 
 {
  FileReader fr = new FileReader(filename);
  BufferedReader br = new BufferedReader(fr);
  StringBuilder result = new StringBuilder();
  String line;
  
  while ((line = br.readLine()) != null) 
  {
   result.append(line);
   result.append(LINE_SEPARATOR);
  }
  
  br.close();
  fr.close();
  
  return result.toString();
 }
 
 public static void copyFolder(File src, File dest) throws IOException
 {
  if(src.isDirectory())
  {
   if(!dest.exists())
   {
    dest.mkdir();
   }
   String files[] = src.list();
    
   for (String file : files)
   {
    File srcFile = new File(src, file);
    File destFile = new File(dest, file);
    copyFolder(srcFile,destFile);
   }
  }
  else
  {
   InputStream in = new FileInputStream(src);
   OutputStream out = new FileOutputStream(dest); 
        
   byte[] buffer = new byte[1024];
    
   int length;
   while ((length = in.read(buffer)) > 0)
   {
    out.write(buffer, 0, length);
   }
    
   in.close();
   out.close();
  }
 }
 
 public static void removeEmptySubDirs(File source)
 {
  if (source.isDirectory())
  {
   if (!source.delete())
   {
    for (File sub : source.listFiles())
    {
     removeEmptySubDirs(sub);
    }
   }
  }
 }
}

public static String toPath(String packageName) in lines 19-22:
Replaces a package name to its corresponding path. Example: file.util.files will return file/util/files for Linux and file\util\files for Windows.

public static String getExtension(File file) in lines 24-40:
Returns the extension (the sub-string of its name starting from the last . occurrence excluding the .) of the File file.

public static void string2File(String s, String path) throws IOException in lines 42-49:
This static method takes as parameters a String s and a String representing a file system valid path path. It stores the content of s in the file at the path path.

public static String file2String(String filename) throws IOException in lines 551-68:
This static method returns the content of the file at the path filename as a String.

public static void copyFolder(File src, File dest) throws IOException in lines 70-103:
Copy the directory (and all its sub-directories) or the file described by the File src to the the directory or file described by the File dest.

public static void removeEmptySubDirs(File source) in lines 105-118:
Removes all the empty sub-directories of the directory described by the File source.

2. Package file.util.files.refactor:

This package contains the master class that will expose he re-factoring feature and a configuration class holding the re-factoring parameters.

RefactorConfiguration.java:


package file.util.files.refactor;

import java.util.ArrayList;
import java.util.Arrays;

public class RefactorConfiguration
{
 public static String[] DEFAULT_ECLIPSE_EXTENSIONS_2_SKIP = {"jpeg","jpg","bmp","png","gif","zip","jar","war","class","pdf","ttf","css"};
 
 public static RefactorConfiguration DEFAULT_ECLIPSE_REFACTOR_CONFIGURATION = 
   new RefactorConfiguration
   (
     true,
     false,
     true,
     false,
     true,
     true,
     new ArrayList<String>(Arrays.asList(DEFAULT_ECLIPSE_EXTENSIONS_2_SKIP)),
     false,
     new ArrayList<String>(),
     true
   );
 
 private boolean inDirNames;
 public boolean isInDirNames() {
  return inDirNames;
 }
 public void setInDirNames(boolean inDirNames) {
  this.inDirNames = inDirNames;
 }
 
 private boolean exactMatchInDirNames;
 public boolean isExactMatchInDirNames() {
  return exactMatchInDirNames;
 }
 public void setExactMatchInDirNames(boolean exactMatchInDirNames) {
  this.exactMatchInDirNames = exactMatchInDirNames;
 }

 private boolean inFileNames;
 public boolean isInFileNames() {
  return inFileNames;
 }
 public void setInFileNames(boolean inFileNames) {
  this.inFileNames = inFileNames;
 }
 
 private boolean exactMatchInFileNames;
 public boolean isExactMatchInFileNames() {
  return exactMatchInFileNames;
 }
 public void setExactMatchInFileNames(boolean exactMatchInFileNames) {
  this.exactMatchInFileNames = exactMatchInFileNames;
 }

 private boolean inFiles;
 public boolean isInFiles() {
  return inFiles;
 }
 public void setInFiles(boolean inFiles) {
  this.inFiles = inFiles;
 }
 
 private boolean skipExtensions;
 public boolean isSkipExtensions() {
  return skipExtensions;
 }
 public void setSkipExtensions(boolean skipExtensions) {
  this.skipExtensions = skipExtensions;
 }
 
 private ArrayList<String> extensions2Skip;
 public ArrayList<String> getExtensions2Skip() {
  return extensions2Skip;
 }
 public void setExtensions2Skip(ArrayList<String> extensions2Skip) {
  this.extensions2Skip = extensions2Skip;
 }
 
 private boolean visitExtensions;
 public boolean isVisitExtensions() {
  return visitExtensions;
 }
 public void setVisitExtensions(boolean visitExtensions) {
  this.visitExtensions = visitExtensions;
 }
 
 private ArrayList<String> extensions2Visit;
 public ArrayList<String> getExtensions2Visit() {
  return extensions2Visit;
 }
 public void setExtensions2Visit(ArrayList<String> extensions2Visit) {
  this.extensions2Visit = extensions2Visit;
 }
 
 private boolean packageNamesAware;
 public boolean isPackageNamesAware() {
  return packageNamesAware;
 }
 public void setPackageNamesAware(boolean packageNamesAware) {
  this.packageNamesAware = packageNamesAware;
 }
 
 public RefactorConfiguration
  (
   boolean inDirNames,
   boolean exactMatchInDirNames,
   boolean inFileNames,
   boolean exactMatchInFileNames,
   boolean inFiles,
   boolean skipExtensions,
   ArrayList<String> extensions2Skip,
   boolean visitExtensions,
   ArrayList<String> extensions2Visit,
   boolean packageNamesAware
  )
 {
  this.inDirNames = inDirNames;
  this.exactMatchInDirNames = exactMatchInDirNames;
  this.inFileNames = inFileNames;
  this.exactMatchInFileNames = exactMatchInFileNames;
  this.inFiles = inFiles;
  this.skipExtensions = skipExtensions;
  this.extensions2Skip = extensions2Skip;
  this.visitExtensions = visitExtensions;
  this.extensions2Visit = extensions2Visit;
  this.packageNamesAware = packageNamesAware;
 }
}

The class defines several boolean flags having the following meanings:
- inDirNames: if set to true re-factoring will also address the directories' names.
- exactMatchInDirNames: if set to true (and if inDirNames is set to true) re-factoring will address directories with names matching exactly the pattern to be re-factored.
inFileNames: if set to true re-factoring will also address the files' names.
exactMatchInFileNames: if set to true (and if inFileNames is set to true) re-factoring will address files' names matching exactly the pattern to be re-factored.
- inFiles: if set to true re-factoring will also address the content of files.
- skipExtensions: if set to true all the files having extensions in extensions2Skip will be skipped.
- visitExtensions: if set to true only files having extension in extensions2Visit will be addressed by re-factoring.
Using skipExtensions and visitExtensions is mutually exclusive, only one should be set to true otherwise all files will not be handled.
- packageNamesAware: is set to true and if for example we are replacing each occurrence of file.util.files.refactor by test.refactor then each occurrence of the former will be replaced by the latter in the content of every visited file but also every directory having in its path the sub-path expression file/util/files/refactor, lets say the directory is $Prefix/file/util/files/refactor/$Suffix will be moved to the path $Prefix/test/refactor/$Suffix.

In lines 8-23, a good configuration for re-factoring an eclipse project is provided.

RefactorAgent.java:



package file.util.files.refactor;

import java.io.File;
import java.io.IOException;
import file.util.files.FileUtils;

public class RefactorAgent
{
 private RefactorConfiguration configuration;
 public RefactorConfiguration getConfiguration() {
  return configuration;
 }
 public void setConfiguration(RefactorConfiguration configuration) {
  this.configuration = configuration;
 }
 
 public RefactorAgent(RefactorConfiguration configuration)
 {
  this.configuration = configuration;
 }
 
 public RefactorAgent ()
 {
  this.configuration = RefactorConfiguration.DEFAULT_ECLIPSE_REFACTOR_CONFIGURATION;
 }
 
 public void refactor(File mySource, String myFrom, String myTo) throws IOException
 {
  if (mySource.exists())
  {
   File newSource = mySource;
   
   if (mySource.isDirectory() && getConfiguration().isInDirNames())
   {
    if (
      getConfiguration().isPackageNamesAware()
      &&
      (
       (getConfiguration().isExactMatchInDirNames() && mySource.getAbsolutePath().endsWith(FileUtils.toPath(myFrom)))
       ||
       (!getConfiguration().isExactMatchInDirNames() && (mySource.getAbsolutePath().indexOf(FileUtils.toPath(myFrom)) != -1))
      )
     )
    {
     newSource = new File (mySource.getAbsolutePath().replaceAll(FileUtils.toPath(myFrom), FileUtils.toPath(myTo)));
     newSource.mkdirs();
     mySource.renameTo(newSource);
    }
    
    if (
      !getConfiguration().isPackageNamesAware()
      &&
      (
       (getConfiguration().isExactMatchInDirNames() && mySource.getName().equals(myFrom))
       ||
       (!getConfiguration().isExactMatchInDirNames() && (mySource.getName().indexOf(myFrom) != -1))
      )
     )
    {
     newSource = new File (mySource.getParent() + FileUtils.FILE_SEPARATOR + mySource.getName().replaceAll(myFrom, myTo));
     newSource.mkdirs();
     mySource.renameTo(newSource);
    }
   }
   
   if (mySource.isFile() && getConfiguration().isInFileNames())
   {
    if (
      (getConfiguration().isExactMatchInFileNames() && mySource.getName().equals(myFrom))
      ||
      (!getConfiguration().isExactMatchInFileNames() && (mySource.getName().indexOf(myFrom) != -1))
     )
    {
     newSource = new File (mySource.getParent() + FileUtils.FILE_SEPARATOR + mySource.getName().replaceAll(myFrom, myTo));
     mySource.renameTo(newSource);
    }
   }
   
   if (newSource.isDirectory())
   {
    for (File sub : newSource.listFiles())
    {
     refactor(sub, myFrom, myTo);
    }
   }
   else
   {
    if (getConfiguration().isInFiles())
    {
     String extension = FileUtils.getExtension(newSource);
     
     if (
       (!getConfiguration().isSkipExtensions() && getConfiguration().isVisitExtensions() && getConfiguration().getExtensions2Visit().contains(extension)) 
       || 
       (!getConfiguration().isVisitExtensions() && getConfiguration().isSkipExtensions() && !getConfiguration().getExtensions2Skip().contains(extension))
      )
     {
      FileUtils.string2File(FileUtils.file2String(newSource.getPath()).replaceAll(myFrom, myTo).replaceAll(FileUtils.toPath(myFrom), FileUtils.toPath(myTo)),newSource.getPath());
     }
    }
   }
  }
 }
 
 public static void main(String[] args) throws IOException
 {
  new RefactorAgent().refactor(new File("/some_path/Test"),"Test","NewName");
  new RefactorAgent().refactor(new File("/some_path/newName"),"mshj.tutorial","pckgname.spckgname1.spckgname2");
  FileUtils.removeEmptySubDirs(new File("/some_path/newName"));
 }
}

This is the master class.
public void refactor(File mySource, String myFrom, String myTo) throws IOException in lines 27-103:
Performs the re-factoring on the file or directory described by the File mySource replacing occurrences of myFrom by myTo.

The main method is provided with the code to rename the project (and its packages) obtained in this JEE tutorial to custom values.


No comments:

Post a Comment