/**
  * File: PreprocessDebug.java
  * Content: Separate program to add/remove debug directives
  * Author: LuisM Pena
  * Date: 6th February 2001
  * Version: 0.60.00
  * Last change:
  * Comments:
  *  Lines with preprocess directives are translated into ;//@Prep->
  *  Are considered preprocess directives those starting with Debug. or Trace.
  *  It also removes import sensei.util.Debug
  *  This method is obviously not generic, but works perfectly in sensei
  *
  *
  **/

package preprocessors;

import sensei.util.Parameters;
import sensei.util.ParameterException;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileFilter;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;
import java.util.Iterator;
import java.util.Vector;

/**
  * Separate program to add/remove debug directives
  **/
public class PreprocessDebug implements FileFilter
{
  public PreprocessDebug(boolean todebug, boolean recursive, boolean quiet)
  {
    this.todebug = todebug;
    this.recursive = recursive;
    this.quiet=quiet;
    processed = new HashMap();
  }

//*************************************************************************************//
//**************************** PREPROCESS *********************************************//
//*************************************************************************************//

  /**
    * Handles the arguments as a file or directory and add/removes the debug directives
    * Returns true if the processing is all right
    **/
  public boolean preprocess(File arg)
  {
    boolean ret;
    if (arg.isDirectory())
      ret=preprocessDirectory(arg);
    else if (arg.isFile())
      ret=preprocessFile(arg);
    else
      ret=error(arg, "is not a valid directory or file");
    return ret;
  }

//*************************************************************************************//
//**************************** PREPROCESS DIRECTORY ***********************************//
//*************************************************************************************//

  /**
    * Handles the arguments as a directory and add/removes the debug directives on every
    * java file.
    * Returns true if the processing is all right
    **/
  public boolean preprocessDirectory(File directory)
  {
    boolean ret=true;
    File files[]=directory.listFiles(this);
    for (int i=0; ret && i<files.length ; i++)
      ret=preprocess(files[i]);
    return ret;
  }

//*************************************************************************************//
//**************************** PREPROCESS FILE ****************************************//
//*************************************************************************************//

  /**
    * Handles the arguments as a file and add/removes the debug directives
    * Returns true if the processing is all right
    **/
  public boolean preprocessFile(File arg)
  {
    boolean ret=true;
    if (accept(arg))
    {
      //rename it
      File renamed=new File(arg.getAbsoluteFile()+SecExtension);
      if (!arg.renameTo(renamed))
        ret=error(arg,"can not be renamed into " + renamed.toString());
      else
      {
        processed.put(renamed, arg);

        ret=preprocessFile(renamed, arg);
      }
    }
    return ret;
  }

//*************************************************************************************//
//**************************** PREPROCESS FILE ****************************************//
//*************************************************************************************//

  /**
    * Preprocess the file specified into the target given
    **/
  public boolean preprocessFile(File source, File target)
  {
    BufferedReader input = null;
    PrintWriter output = null;
    boolean ret=true;
    try
    {
      if (!quiet)
        System.out.print(target.getAbsoluteFile());

      input = new BufferedReader(new FileReader(source));
      output= new PrintWriter(new FileOutputStream(target));
      int changes=0;

      String line = input.readLine();
      while(line!=null)
      {
        if (todebug)
          output.println(recoverDebugDirectives(line));
        else
        {
          if (isImportDebugDirective(line))
          {
            changes++;
            output.println(PreprocessImportMark+line);
          }
          else
          {
            int index=isDebugDirective(line);
            if (index==-1)
              output.println(line);
            else
            {
              ++changes;
              output.println(removeDebugDirectives(line, index));
            }
          }
        }
        line=input.readLine();
      }
      if (!quiet)
        if (changes>0)
          System.out.println(" : " + changes + " lines changed");
        else
          System.out.println();
    }
    catch(IOException ex)
    {
      ret=error(target,ex.getMessage());
    }
    finally
    {
      if (input!=null) try{input.close();}catch(IOException ex){}
      if (output!=null) output.close();
    }
    return ret;
  }

//*************************************************************************************//
//**************************** IS DEBUG DIRECTIVES ************************************//
//*************************************************************************************//

  /**
    * Returns different of -1 if it's a debug directive. Otherwise, returns the
    * position where it starts
    **/
  public int isDebugDirective(String line)
  {
    int index=-1;
    String trimed = line.trim();
    if (trimed.startsWith("Debug."))
      index=line.indexOf("D");
    else if (trimed.startsWith("Trace."))
      index=line.indexOf("T");
    return index;
  }

//*************************************************************************************//
//**************************** IS IMPORT DEBUG DIRECTIVE ******************************//
//*************************************************************************************//

  /**
    * Returns true if the line is an import sensei.util.Debug directive
    **/
  public boolean isImportDebugDirective(String line)
  {
    return (line.trim().startsWith("import") && (line.indexOf("sensei.util.Debug")!=-1));
  }

//*************************************************************************************//
//**************************** REMOVE DEBUG DIRECTIVES ********************************//
//*************************************************************************************//

  /**
    * Removes any debug directives from a line
    **/
  public String removeDebugDirectives(String line, int where)
  {
    return line.substring(0,where)+PreprocessMark+line.substring(where);
  }

//*************************************************************************************//
//**************************** RECOVER DEBUG DIRECTIVES *******************************//
//*************************************************************************************//

  /**
    * Recover any debug directives from a line
    **/
  public String recoverDebugDirectives(String line)
  {
    int where=-1;
    String trimed = line.trim();
    if (trimed.startsWith(PreprocessMark))
    {
      where=line.indexOf(PreprocessMark);
      return line.substring(0,where)+line.substring(where+lenMark);
    }
    else if (trimed.startsWith(PreprocessImportMark))
    {
      where=line.indexOf(PreprocessImportMark);
      return line.substring(0,where)+line.substring(where+lenImportMark);
    }
    return line;
  }

//*************************************************************************************//
//**************************** ACCEPT *************************************************//
//*************************************************************************************//

  /**
    * FileFilter method: only accept java files
    **/
  public boolean accept(File file)
  {
    return (recursive && file.isDirectory()) || (file.isFile() && file.getName().toLowerCase().endsWith(".java"));
  }

//*************************************************************************************//
//**************************** ERROR **************************************************//
//*************************************************************************************//

  /**
    * Displays an error on the error stream. returns false
    **/
  boolean error(File file, String errorMessage)
  {
    System.out.println("\nError on " + file.toString() + " : " + errorMessage);
    recoverBackups();
    return false;
  }

//*************************************************************************************//
//**************************** RECOVER BACKUPS ****************************************//
//*************************************************************************************//

  /**
    * Recover the backups
    **/
  void recoverBackups()
  {
    Iterator entries=processed.entrySet().iterator();
    while(entries.hasNext())
    {
      Map.Entry entry = (Map.Entry) (entries.next());
      File backup=(File)(entry.getKey());
      File original=(File)(entry.getValue());
      original.delete();
      if (!backup.renameTo(original))
        System.out.println("I cannot rename back " + backup.toString() + " into " + original.toString());
    }
    processed.clear();
  }

//*************************************************************************************//
//**************************** REMOVE BACKUPS *****************************************//
//*************************************************************************************//

  /**
    * Removes the backups
    **/
  public void removeBackups()
  {
    Iterator entries=processed.keySet().iterator();
    while(entries.hasNext())
    {
      File backup = (File) (entries.next());
      if (!backup.delete())
        System.out.println("I cannot delete the backup " + backup.toString());
    }
  }

//*************************************************************************************//
//**************************** MAIN ***************************************************//
//*************************************************************************************//

  /**
    * Main function, just checks the arguments and passes it to the PreprocessDebug class
    **/
  public static void main(String args[])
  {
    try
    {
      Vector validParams=new Vector();
      validParams.add(RecursiveParameter);
      validParams.add(ToDebugParameter);
      validParams.add(NoBackupParameter);
      validParams.add(QuietParameter);
      Parameters param=new Parameters(args, validParams, new Vector(), 1, Integer.MAX_VALUE);

      PreprocessDebug prep = new PreprocessDebug(
                  param.hasParameter(ToDebugParameter),
                  param.hasParameter(RecursiveParameter),
                  param.hasParameter(QuietParameter)
                  );

      boolean ok=true;

      for (int i=0; ok && i < param.getArgumentsLength() ; i++)
        ok = prep.preprocess(new File(param.getArgument(i)));

      if (ok && param.hasParameter(NoBackupParameter))
        prep.removeBackups();

    }
    catch(ParameterException ex)
    {
      System.out.println(ex.getMessage());
      System.out.println("Use: [-recursive] [-todebug] [-nobackup] [-quiet] file/directory [files/directories]");
    }
  }

  boolean todebug, recursive, nobackup, quiet;
  HashMap processed;
  static final String RecursiveParameter="recursive";
  static final String ToDebugParameter="todebug";
  static final String NoBackupParameter="nobackup";
  static final String QuietParameter="quiet";
  static final String SecExtension=".noprep";
  static final String PreprocessMark=";//@prep->";
  static final String PreprocessImportMark="//@import_prep->";
  static final int lenMark = PreprocessMark.length();
  static final int lenImportMark = PreprocessImportMark.length();
}