content: /* The contents of this file are subject to the terms
   * of the Common Development and Distribution License
   * (the License). You may not use this file except in
   * compliance with the License.
   *
   * You can obtain a copy of the License at
   * http://www.sun.com/cddl/cddl.html or
   * install_dir/legal/LICENSE
   * See the License for the specific language governing
   * permission and limitations under the License.
   *
   * When distributing Covered Code, include this CDDL
   * Header Notice in each file and include the License file
   * at install_dir/legal/LICENSE.
   * If applicable, add the following below the CDDL Header,
   * with the fields enclosed by brackets [] replaced by
   * your own identifying information:
   * "Portions Copyrighted [year] [name of copyright owner]"
   *
   * $Id$
   *
   * Copyright 2005-2009 Sun Microsystems Inc. All Rights Reserved
   */
  package com.sun.faban.harness.util;
  
  import com.sun.faban.harness.agent.CmdAgentImpl;
  import com.sun.faban.harness.agent.FileAgent;
  import com.sun.faban.harness.agent.FileService;
  import com.sun.faban.harness.agent.FileServiceException;
  import com.sun.faban.harness.common.Config;
  
  import java.io.*;
  import java.nio.channels.FileChannel;
  import java.util.ArrayList;
  import java.util.Enumeration;
  import java.util.Properties;
  import java.util.StringTokenizer;
  import java.util.jar.*;
  import java.util.logging.Level;
  import java.util.logging.Logger;
  
  /**
   * File utilities. A collection of static methods to deal with file operations.
   */
  public class FileHelper {
      
      private static Logger logger = Logger.getLogger(FileHelper.class.getName());
  
      /**
       * Copies a file.
       * @param srcFile  - the full pathname of the source file
       * @param destFile  - the full pathname of the destination file
       * @param append - should destination file be appended with source file
       * @return Whether the copy succeeded
       */
      public static boolean copyFile(String srcFile, String destFile,
                                     boolean append) {
          try {
              FileChannel src = (new FileInputStream(srcFile)).getChannel();
              FileChannel dest = (new FileOutputStream(destFile)).getChannel();
              if (append)
                  dest.position(dest.size());
              src.transferTo(0, src.size(), dest);
              dest.close();
              src.close();
          } catch (IOException e) {
              logger.log(Level.SEVERE, "Could not copy " + srcFile + " to " +
                                       destFile, e);
              return false;
          }
          return true;
      }
  
      /**
       * This method opens, traverses through the file and
       * finds the properties and replaces the values
       * This updates only the first occurrence of the prop
       * in the file to eliminate cases where it changes
       * props defined like PROP=$PROP:MYPROP
       * We expect only one prop per line in the file.
       * @param fileName The full pathname of the file
       * @param prop Property names and their new values
       * @param backupFileName The file to save a copy before edit, if any
       * @return Whether the edit succeeded
       */
      public static boolean editPropFile(String fileName, Properties prop, String backupFileName) {
  
          String tmpFile = Config.TMP_DIR + ".FileHelper";
          //copy the file
          if(backupFileName == null)
              backupFileName = tmpFile;
              
          if(!FileHelper.copyFile(fileName, backupFileName, false))
              return false; // Failed to backup the file
  
          // Do not modify the passed prop as it may be used by the caller
          // Call by reference pitfalls (:-)
          Properties props = new Properties(prop);
  
          try {
              BufferedReader in = new BufferedReader(new FileReader(backupFileName));
              BufferedWriter out = new BufferedWriter(new FileWriter(fileName));
              String line;
              while((line = in.readLine()) != null) {
                  line = line.trim();
                  if((line.length() > 3) && (Character.isLetterOrDigit(line.charAt(0)))) {
                      Enumeration e = props.propertyNames();
                      while (e.hasMoreElements()) {
                          String propName = (String) e.nextElement();
                          if((line.indexOf(propName) != -1) &&
                             (line.charAt(line.indexOf(propName) + propName.length()) == '=')) {
                              StringBuffer sb = new StringBuffer(propName);
                              sb.append("=").append(props.getProperty(propName));
                              line = sb.toString();
                              // Will update only the first instance of the prop
                              props.remove(propName);
                              // We expect only one prop per line
                              break;
                          }
                      }
                  }
                  // Write the line to file
                  out.write(line, 0, line.length());
                  out.newLine();
              }
              in.close();
              out.close();           
              
              // Delete the temp file
              if(tmpFile == backupFileName) 
                  (new File(tmpFile)).delete();
          }
          catch (Exception e) {
              logger.severe("Failed with " + e);
              logger.log(Level.FINE, "Exception", e);
              return false;
          }
          return true;
      }       
      
      /**
       * This method opens, traverses through the file and
       * finds the token and replaces it with new value
       * This method updates only the first occurrence of
       * the token in the file to eliminate cases where it
       * changes props defined like PROP=$PROP:MYPROP.
       * @param fileName The full pathname of the file
       * @param token Token to find
       * @param replacement The replacement string
       * @param backupFileName If needed pass a backup file name
       * @return Whether there is at least one replacement
       */
      public static boolean tokenReplace(String fileName, String token, String replacement, String backupFileName) {
  
          String tmpFile = System.getProperty("java.io.tmpdir") + File.separator + ".FileHelper";
  
          //copy the file
          if(backupFileName == null)
              backupFileName = tmpFile;
  
          if(!FileHelper.copyFile(fileName, backupFileName, false))
              return false; // Failed to backup the file
  
          logger.finest("Token : " + token);
          logger.finest("Replacement : " + replacement);
  
          try {
              BufferedReader in = new BufferedReader(new FileReader(backupFileName));
              BufferedWriter out = new BufferedWriter(new FileWriter(fileName));
              String line;
              boolean replaced = false;
              while((line = in.readLine()) != null) {
                  // replace only the first occurrence of the token
                  if((!replaced) && (line.indexOf(token) != -1)) {
                      System.err.println("FileHelper.tokenReplace : replacing " + token + " with " + replacement);
                      StringBuffer sb = new StringBuffer();
                      // to escape special chars in token
                      for(int i = 0; i < token.length(); i++) {
                          if(Character.isLetterOrDigit(token.charAt(i)))
                              sb.append(token.charAt(i));
                          else
                              sb.append("\\").append(token.charAt(i));
                      }
  
                      // to escape special chars in replacement string
                      token = sb.toString();
                      sb = new StringBuffer();
                      for(int i = 0; i < replacement.length(); i++) {
                          if(Character.isLetterOrDigit(replacement.charAt(i)))
                              sb.append(replacement.charAt(i));
                          else
                              sb.append("\\").append(replacement.charAt(i));
                      }
                      replacement = sb.toString();
  
                      line = line.replaceAll(token, replacement);
                      replaced = true;
                  }
                  // Write the line to file
                  out.write(line, 0, line.length());
                  out.newLine();
              }
              in.close();
              out.close();
  
              // Delete the temp file
              if(tmpFile == backupFileName)
                  (new File(tmpFile)).delete();
          }
          catch (Exception e) {
              logger.severe("Failed with " + e);
              logger.log(Level.FINE, "Exception", e);
              return false;
          }
          return true;
      }
  
      /**
       * This method opens, traverses through the file and
       * finds the token, it will avoid comments when searching.
       * @param fileName The full pathname of the file
       * @param token Token to search for
       * @return true if found, false otherwise
       */
      public static boolean isInFile(String fileName, String token) {
          boolean found = false;
  
          try {
              BufferedReader in = new BufferedReader(new FileReader(fileName));
              String line;
              while((line = in.readLine()) != null) {
                  // Avoid comments
                  if(line.length() > token.length() &&
                     Character.isLetterOrDigit(line.charAt(0)) && line.indexOf(token) != -1) {
                      found = true;
                      break;
                  }
              }
              in.close();
          }
          catch (Exception e) {
              logger.severe("Failed with " + e);
              logger.log(Level.FINE, "Exception", e);
              return false;
          }
          return found;
      }
  
      /**
       * This  method is used to delete a directory and 
       * recursively delete files and subdirectories within it.
       *
       * @param file The file or directory to delete
       * @return Whether the delete succeeded
       */
      public static boolean recursiveDelete(File file) {
          // Does a post-order traversal of the directory hierarchy and deletes
          // all the nodes in its path.
  	
          // Logging file deletions at fine so we can turn on if there's
          // anything spooky. We use an exception to capture the stack, but
          // do not really throw the exception. After all, nothing is wrong.
          Exception te = new Exception("Deleting all files in " + file.getName());
          logger.log(Level.FINE, te.getMessage(), te);
  
          boolean success = true;
   
  	    try {
              if (file.isDirectory()) {
                  File[] list = file.listFiles();
                  for (int i = 0; i < list.length; i++)
                      if (!recursiveDelete(list[i])) {
                          logger.severe("Delete failed for file " +
                                  file.getPath());
                          success = false;
                      }
              }
              if (!file.delete()) {
                  logger.warning("Delete failed for file " + file.getPath());
                  success = false;
              }
          } catch(Exception e) {
              logger.log(Level.SEVERE, "Delete failed", e);
              success = false;
          }
          return success;
      }
  
      /**
       * Deletes all files matched by the filter in a certain directory.
       * @param dir The directory to look for files to delete
       * @param filter The file name filter
       * @return True if all deletes succeeded, false otherwise
       */
      public static boolean delete(File dir, FileFilter filter) {
          boolean success = true;
  
          try {
              if (!dir.isDirectory())
                  return false;
              File[] list = dir.listFiles(filter);
              for (File file : list)
                  if (!recursiveDelete(file)) {
                      logger.severe("Delete failed for file " + file.getPath());
                      success = false;
                  }
          } catch (Exception e) {
              logger.log(Level.SEVERE, "Delete failed", e);
              success = false;
          }
          return success;
      }
  
      /**
       * This method is used to delete a directory and
       * recursively delete files and subdirectories within it.
       *
       * @param parentDir The file object corresponding to the parent directory
       * @param name Name of the directory to be deleted
       * @return Whether the delete succeeded
       */
      public static boolean recursiveDelete(File parentDir, String name) {
          return recursiveDelete(new File(parentDir, name));
      }
  
      /**
       * Copies a file from source to dest. If src is a directory, the whole
       * directory tree is copied.
       * @param src The source file
       * @param dest The dest file, must not exist before calling method
       * @return true if copy succeeded, false afterwise
       */
      public static boolean recursiveCopy(File src, File dest) {
          if (src.isDirectory()) {
              if (!dest.exists() && !dest.mkdir())
                  return false;
              File[] files = src.listFiles();
              for (File s : files) {
                  File d = new File(dest, s.getName());
                  if (!recursiveCopy(s, d))
                      return false;
              }
          } else {
              return copyFile(src.getAbsolutePath(), dest.getAbsolutePath(),
                              false);
          }
          return true;
      }
  
      /**
       * Jars up a directory to a given Jar file.
       * @param dir The base directory to jar (not included in output)
       * @param fileNames The file names to jar, can be multiple
       * @param jarPath The pathname of the jar file
       * @throws IOException There is a problem jarring up
       */
      public static void jar(String dir, String[] fileNames, String jarPath)
              throws IOException {
  
          logger.fine("Jar'ing up " + dir + " to " + jarPath + '.');
  
          File jarFile = new File(jarPath);
          if (!jarFile.isAbsolute())
              jarFile = new File(dir, jarPath);
  
          Manifest manifest = new Manifest();
          Attributes attributes = manifest.getMainAttributes();
          attributes.put(Attributes.Name.MANIFEST_VERSION, "1.0");
          attributes.putValue("Created-By", Config.HARNESS_NAME + ' ' +
                              Config.HARNESS_VERSION);
          JarOutputStream out = new JarOutputStream(
                  new FileOutputStream(jarFile), manifest);
  
          byte[] buffer = new byte[8192];
          for (String fileName : fileNames) {
              addEntry(out, dir, fileName, buffer);
          }
          out.flush();
          out.close();
      }
  
      private static void addEntry(JarOutputStream out, String baseDir,
                                   String name, byte[] buffer)
              throws IOException {
          File input = new File(baseDir + '/' + name);
          if (input.isDirectory()) {
              JarEntry jarEntry = new JarEntry(name + '/');
              jarEntry.setTime(input.lastModified());
              out.putNextEntry(jarEntry);
              out.closeEntry();
              String[] entries = input.list();
              for (String entry : entries)
                  addEntry(out, baseDir, name + '/' + entry, buffer);
          } else {
              JarEntry jarEntry = new JarEntry(name);
              jarEntry.setTime(input.lastModified());
              FileInputStream in = new FileInputStream(input);
              out.putNextEntry(jarEntry);
              for (;;) {
                  int len = in.read(buffer);
                  if (len == -1)
                      break;
                  out.write(buffer, 0, len);
              }
              out.closeEntry();
              in.close();
          }
      }
  
      /**
       * Unjars a jar file into an output directory.
       * @param jarPath The path to the jar file
       * @param outputDir The output directory
       * @throws IOException If there is an error running unjar
       */
      public static void unjar(String jarPath, String outputDir)
              throws IOException {
  
          logger.fine("Unjar'ing " + jarPath + " to " + outputDir + '.');
  
          File target;
          FileOutputStream out;
  
          JarInputStream in =
                  new JarInputStream(new FileInputStream(jarPath));
  
          Manifest manifest = in.getManifest();
  
          if (manifest != null) {
              target = new File(outputDir + File.separator + "META-INF");
              target.mkdirs();
              out = new FileOutputStream(new File(target, "MANIFEST.MF"));
              manifest.write(out);
              out.close();
          }
  
          byte[] buffer = new byte[8192];
          for (;;) {
              JarEntry entry = in.getNextJarEntry();
              if (entry == null)
                  break;
              target = new File(outputDir + File.separator + entry.getName());
              if (entry.isDirectory()) {
                  target.mkdirs();
              } else {
                  File parent = target.getParentFile();
                  if (!parent.isDirectory())
                      parent.mkdirs();
                  out = new FileOutputStream(target);
                  for (;;) {
                      int size = in.read(buffer);
                      if (size == -1)
                          break;
                      out.write(buffer, 0, size);
                  }
                  target.setLastModified(entry.getTime());
                  out.flush();
                  out.close();
              }
              in.closeEntry();
          }
          in.close();
      }
  
  
      /**
       * Unjars a temporary jar file xxxx.jar under the directory
       * xxxx in the same path.
       * @param tmpJarFile The temporary jar file
       * @return The file reference to the resulting directory
       * @throws IOException If there is an error unjaring
       */
      public static File unjarTmp(File tmpJarFile) throws IOException {
          logger.info("Preparing run from " + tmpJarFile.getAbsolutePath() + '.');
  
          String dirName = tmpJarFile.getName();
          int dotPos = dirName.lastIndexOf('.');
          dirName = dirName.substring(0, dotPos);
          File unjarDir = new File(tmpJarFile.getParent(), dirName);
          unjarDir.mkdir();
  
          unjar(tmpJarFile.getAbsolutePath(), unjarDir.getAbsolutePath());
          return unjarDir;
      }
  
      /**
       * Writes a string to a file. Replaces the file if it already exists.
       * @param string The string to be written
       * @param file The target file
       * @throws IOException If the write fails
       */
      public static void writeStringToFile(String string, File file)
              throws IOException {
          file.delete();
          file.createNewFile();
          FileOutputStream out = new FileOutputStream(file);
          out.write(string.getBytes());
          out.flush();
          out.close();
      }
  
      /**
       * Reads a whole file and obtains the contents as a string.
       * @param file The file to be read
       * @return The string representing the whole content of the file
       * @throws IOException If the read fails
       */
      public static String readStringFromFile(File file) throws IOException {
          String content = null;
          if (file.isFile())
              content = new String(getContent(file.getAbsolutePath()));
          return content;
      }
  
      /**
       * Writes the entire content to file. Replaces the file if it already exists.
       * @param string
       * @param file
       * @throws java.io.IOException
       */
      public static void writeContentToFile(String string, File file)
              throws IOException {
          //StringTokenizer t = new StringTokenizer(string," \n,\t");
          file.delete();
          file.createNewFile();
          RandomAccessFile rf = new RandomAccessFile(file, "rwd");
          long size = rf.length();
          byte[] buffer = new byte[(int) size];
          rf.readFully(buffer);
          rf.seek(rf.length());
          rf.writeBytes(string);
          //while (t.hasMoreTokens()) {
                 // rf.writeBytes(t.nextToken().trim() + "\n");
          //}
          rf.close();
      }
  
      /**
       * Reads a whole file and obtains the contents as a formatted string with
       * "\n" seperated.
       * @param file
       * @return string
       * @throws java.io.IOException
       */
      public static String readContentFromFile(File file) throws IOException {
          String content = null;
          StringBuilder formattedTags = new StringBuilder();
          if (file.isFile()) {
              content = new String(getContent(file.getAbsolutePath()));
          StringTokenizer t = new StringTokenizer(content,"\n");
              while (t.hasMoreTokens()) {
                  formattedTags.append(t.nextToken().trim() + " ");
              }
          }
          return formattedTags.toString();
      }
  
      /**
       * Obtains the content of a file as a strin array.
       * @param file
       * @return string array
       * @throws java.io.IOException
       */
      public static String[] readArrayContentFromFile(File file)
              throws IOException {
          String content = null;
          ArrayList<String> contentList = new ArrayList<String>();
          if (file.isFile()) {
              content = new String(getContent(file.getAbsolutePath()));
              StringTokenizer t = new StringTokenizer(content, "\n");
              while (t.hasMoreTokens()) {
                  contentList.add(t.nextToken().trim());
              }
          }
          return contentList.toArray(new String[contentList.size()]);
      }
      /**
       * Transfers a file from the current host to the Faban master.
       * @param inFile The input file name on the current host
       * @param outFile The output file name on the Faban master
       * @param move Whether to remove the original file or not
       * @return True if the transfer is complete, false otherwise.
       * @deprecated
       */
      @Deprecated public static boolean xferFile(String inFile, String outFile, boolean move) {
          File f = new File(inFile);
          if(!f.exists())
              return false ;
  
          // Use FileAgent on master machine to copy log
          FileAgent fa = null;
           try {
              String s = Config.FILE_AGENT;
              fa = (FileAgent) CmdAgentImpl.getRegistry().getService(s);
           } catch (Exception e) {
              logger.severe("Unable to get File Service");
               return false;
           }
  
          logger.fine("Input File = " + inFile + " Output File = "+ outFile);
  
          try {
              BufferedInputStream in = new BufferedInputStream(new FileInputStream(f));
              byte[] buf = new byte[8192];
              int i = in.read(buf);
  
              if(i > 0) {
                  FileService outfp = fa.open(outFile, FileAgent.WRITE);
                  while (i > 0) {
                      outfp.writeBytes(buf, 0, i);
                      i = in.read(buf);
                  }
                  outfp.close();
              }
              in.close();
          } catch (FileServiceException fe) {
              logger.severe("Error in creating file " + outFile);
              logger.log(Level.FINE, "Exception", fe);
          } catch (IOException e) {
              logger.severe("Error in reading file " + inFile);
              logger.log(Level.FINE, "Exception", e);
          } catch (Exception e) {
              logger.severe("Error Xfering file " + e);
              logger.log(Level.FINE, "Exception", e);
              if(move)
                  f.delete();
          }
          return true;
      }
  
      /**
       * Obtains the whole content of a local file in a byte array.
       * @param file The file name
       * @return The byte content of the file
       * @throws IOException If the file cannot be read.
       */
      public static byte[] getContent(String file) throws IOException {
          return getContent(new File(file));
      }
  
      /**
       * Obtains the whole content of a local file in a byte array.
       * @param file The file name
       * @return The byte content of the file
       * @throws IOException If the file cannot be read.
       */
      public static byte[] getContent(File file) throws IOException {
          long size = file.length();
          if (size == 0)
              throw new IOException("Cannot determine file size.");
          if (size >= Integer.MAX_VALUE)
              throw new IOException("Cannot handle file size >= 2GB.");
          byte[] content = new byte[(int) size];
          FileInputStream in = null;
          try {
              in = new FileInputStream(file);
              int readLength = 0;
              while (readLength < size) {
                  int bytes = in.read(content, readLength,
                              (int) size - readLength);
                  if (bytes == -1) // EOS
                      break;
                  readLength += bytes;
              }
          } finally {
              if (in != null)
                  in.close();
          }
          return content;
      }
  
      /**
       * Checks a file whether it contains the given string.
       * @param file The file
       * @param string The string
       * @return True if the string is found in the file, false otherwise
       * @throws IOException Problem reading the file
       */
      public static boolean hasString(File file, String string)
              throws IOException {
          boolean retVal = false;
          BufferedReader bufR = new BufferedReader(new FileReader(file));
          String s;
          while ((s = bufR.readLine()) != null) {
              if (s.indexOf(string) != -1) {
                  retVal = true;
                  break;
              }
          }
          bufR.close();
          return retVal;
      }
      /**
       * Unit test the functionality.
       * @param args The command line arguments
       */
      public static void main(String[] args) {
          if (args.length < 3) {
              System.out.println("Usage: java FileHelper File token replacement");
              return;
          }
          Properties prop = new Properties();
          prop.setProperty(args[1], args[2]);
  
          //FileHelper.editPropFile(args[0], prop, null);
          // FileHelper.tokenReplace(args[0], args[1], args[2], null);
          //FileHelper.tokenReplace(System.getProperty("java.io.tmpdir") +
          //      "/t", "\"$JAVA_HOME\"/bin/java",
          // "profcmd= \n\\$profcmd \"\\$JAVA_HOME\"/bin/java", null);
          FileHelper.tokenReplace(System.getProperty("java.io.tmpdir") + "/t",
                  "\"$JAVA_HOME\"/bin/java",
                  "profcmd= \n$profcmd \"$JAVA_HOME\"/bin/java", null);
      }
  }
  /*
  // This code will replace properties which is specified anywhere in the line
  // But will messup cases where there are blanks in it.
                          if((line.indexOf(propName)) != -1) {
                              StringBuffer sb = new StringBuffer();
                              StringTokenizer st = new StringTokenizer(line);
                              while (st.hasMoreTokens()) {
                                  String tk = st.nextToken();
                                  if(tk.indexOf(propName) != -1)
                                      sb.append(propName).append("=")
                                        .append(props.getProperty(propName)).append(" ");
                                  else
                                      sb.append(tk).append(" ");
                              }
                              line = sb.toString();
                          }
  
  */
  