/**
  * File: ToAssertions.java
  * Content: Separate program to convert the Debug.assert lines into JDK1.4 assertions
  * 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 ToAssertions implements FileFilter
{
  public ToAssertions(boolean recursive, boolean quiet)
  {
    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 (isImportDebugDirective(line))
        {
          changes++;
        }
        else
        {
          int index=isAssertDirective(line);
          if (index==-1)
            output.println(line);
          else
          {
            ++changes;
            output.println(translateAssertDirective(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 ASSERT DIRECTIVE ************************************//
//*************************************************************************************//

  /**
    * Returns different of -1 if it's an assert directive. Otherwise, returns the
    * position where it starts
    **/
  public int isAssertDirective(String line)
  {
    int index=-1;
    String trimed = line.trim();
    if (trimed.startsWith("Debug.assert"))
      index=line.indexOf("D");
    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));
  }

//*************************************************************************************//
//**************************** TRANSLATE ASSERT DIRECTIVE *****************************//
//*************************************************************************************//

  /**
    * Translates a Debug.assert directive into a JDK 1.4 assertion from a line.
    * It must be completely included on the same line!
    **/
  public String translateAssertDirective(String line, int where)
  {
    int openPar = line.indexOf("(",where);
    if (openPar==-1) errorOnLine(line);
    String firstParam =getFirstParam(line, openPar+1);
    if (firstParam==null) errorOnLine(line);
    return line.substring(0,where)+"assert " + firstParam + ";";
  }

  private void errorOnLine(String line)
  {
    System.out.println(line + " is not a valid DEBUG assertion");
    System.exit(0);
  }

  private String getFirstParam(String read, int start){
    int pos=start, len=read.length();
    int openPar=0;
    while(pos<len) {
      char c=read.charAt(pos);
      if (c=='\\') {
        return null; //error!
      }
      else if (c=='\'') {
        pos=passCharacter(read, pos, '\'', len);
        if (pos==-1) {
          return null;
        }
      }
      else if (c=='\"') {
        pos=passCharacter(read, pos, '\"', len);
        if (pos==-1) {
          return null;
        }
      }
      else if (c=='(') {
        openPar++;
      }
      else if (c==')') {
        if (--openPar<0)
          return null;
      }
      else if (c==',' && openPar==0) {
        return read.substring(start,pos);
      }
      else if ((c=='/') && ((pos+1)<len)){
        c=read.charAt(pos+1);
        if (c=='/') {
          return null; //comment??
        }
        else if (c=='*') {
          if ((pos=passComment(read, pos+2, len))==-1) {
            return null;
          }
        }
      }
      ++pos;
    }
    return null;
  }

  private int passCharacter(String read, int pos, char pass, int len){
    while(++pos<len) {
      char c=read.charAt(pos);
      if (c=='\\') {
        ++pos; //do not read next character
      }
      else if (c==pass) {
        return pos;
      }
    }
    return -1;
  }

  private int passComment(String read, int pos, int len){
    while(pos<len) {
      char c=read.charAt(pos++);
      if (c=='*' && pos<len && read.charAt(pos)=='/') {
        return pos;
      }
    }
    return -1;
  }


//*************************************************************************************//
//**************************** 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 ToAssertions class
    **/
  public static void main(String args[])
  {
    try
    {
      Vector validParams=new Vector();
      validParams.add(RecursiveParameter);
      validParams.add(NoBackupParameter);
      validParams.add(QuietParameter);
      Parameters param=new Parameters(args, validParams, new Vector(), 1, Integer.MAX_VALUE);

      ToAssertions prep = new ToAssertions(
                  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] [-nobackup] [-quiet] file/directory [files/directories]");
    }
  }

  boolean recursive, nobackup, quiet;
  HashMap processed;
  static final String RecursiveParameter="recursive";
  static final String NoBackupParameter="nobackup";
  static final String QuietParameter="quiet";
  static final String SecExtension=".noprep";
}