// ----------------------------------------------------------------------------------
// Variant code handling (conditional compiling) for Java - simple aproach.
//
// Java does not support conditional compiling and it is very rare that you might 
// need this. However, we did need it when migrating an application from JDK 1.4
// to JDK 1.1.8 because of a PDA implementation. 
// ----------------------------------------------------------------------------------
// USAGE:
//
// This tool enables you to write one source file and include conditional compiling.
// For example, when you need to support different Java versions with one source code.
// 
// For each version, make a subdirectory (e.g. c:\mysource\1_4 and c:\mysource\1_1_8). Then
// start develop in one of those directories (let's assume in c:\mysource\1_4). Whenever
// you have some code which is only available on 1_1_8 then include it in special remarks
// (see below). After finishing the version 1_4, run this utility
//
// e.g. java srcVariants c:\mysource\1_4 c:\mysource\1_1_8 JDK_1_1_8
// 
// Maybe later you have modified someting in c:\mysource\1_1_8, then you can run
// the utility to copy files back:
//
// e.g. java srcVariants c:\mysource\1_1_8 c:\mysource\1_4 JDK_1_4
//
// The utility runs recursivley deeper throught the given source path (1st argument)
// and checks, if new or update files exist and processes them. Each file processed
// creates a backup file in the destination path with the extension .bak.
//
// This only works on java sourcefiles with the extension .java.
//
// Your source file might look like this:
//
//    +-----------------------------------------------------------+
//    |  // This is a code example                                |
//    |  public class mysort                                      |
//    |  {                                                        |
//    |     // do some sorting, implementation now depends        |
//    |     // on the JDK version                                 |
//    |     public static void sort( String s[] )                 |
//    |     {                                                     |
//    |        if (s != null)                                     |
//    |        {                                                  |
//    |           //IFDEF JDK_1_4                                 |
//    |           Arrays.sort(s);                                 |
//    |           //END_IFDEF JDK_1_4                             |
//    |                                                           |
//    |           /*IFDEF JDK_1_1_8                               |
//    |           for( int i = 0; i < s.length - 1; i++ )         |
//    |              for( int j = i + 1; j < s.length; i++ )      |
//    |                 if (s[i].compareTo(s[j]) > 0)             |
//    |                 {                                         |
//    |                    /..* swap *../                         |
//    |                    String h = s[i];                       |
//    |                    s[i] = s[j];                           |
//    |                    s[j] = h;                              |
//    |                 }                                         |
//    |           END_IFDEF JDK_1_1_8*/                           |
//    |        }                                                  |
//    |     }                                                     |
//    | }                                                         |
//    +-----------------------------------------------------------+
//
// Now, this utility reads your source file and "turns around" the
// remarks, based on this rules:
// 
//   A line looks like "//IFDEF xy" then "xy" is compared to the 3rd 
//   argument given to the program. If this matches, then the line
//   is copies to the new created output file. If it is not matching
//   a remark block is opened, starting with "/*IFDEF xy".
//
//   A line looks like "//END_IFDEF xy" then "xy" is compared to the 3rd 
//   argument given to the program. If this matches, then the line
//   is copies to the new created output file. If it is not matching
//   a remark block is closed, starting with "END_IFDEF xy*/".
//    
//   During a remark block, normal remarks are switched to "/..* remark *../"
//   to avoid nested remarks. 
//
//   A line looks like "/*IFDEF xy" then "xy" is compared to the 3rd 
//   argument given to the program. If this matches, then the line 
//   will be changed to "//IFDEF xy" and a subsequent remark is modified
//   from "/..* remark *../" entries to "/* remark */" entries.
//   
//   A line looks like "END_IFDEF xy*/" then "xy" is compared to the 3rd 
//   argument given to the program. If this matches, then the line 
//   will be changed to "//END_IFDEF xy".
//
// Note, that before or after these "//IFDEF xy" or "/*IFDEF xy" you can use
// tabs or blanks, but not inside (means between "IFDEF" and "xy"). Only one
// tag per line. 
//
// You also can use more arguments rather than one version identifier following
// the 3rd argument.
//
// Call the program without parameters to see how it is used.
//
// Before you use this utility, make sure to have a valid and current backup of your
// sourcefiles!
// 
// ----------------------------------------------------------------------------------
// SUPPORT:
// 
// door2solution is not able to give you any support on this utility, but feel free 
// to let us know your opinion about: office@door2solution.at
//
// ----------------------------------------------------------------------------------
// DISCLAIMER:
//
// THIS UTILITY IS FREEWARE AND FREE ADVICE IS PROVIDED "AS IS" AND WITHOUT 
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED 
// WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. IF YOUR 
// LOCALITY DOES NOT ALLOW THESE WARRANTY CONDITIONS, YOU ARE NOT GRANTED 
// PERMISSION TO USE THIS UTILITY. DOOR2SOLUTION WILL NOT TAKE ANY RESPONSIBILITY
// OR LIABILITY FOR FAILURES, CORRECT, MODIFIED OR INCORRECT USAGE.
// 
// (C)2004 door2solution software gmbh
// Max-Kahrer Gasse 5
// 3400 Klosterneuburg
// AUSTRIA
// 
// www.door2solution.com
// office@door2solution.com
// ----------------------------------------------------------------------------------

import java.io.*;
import java.util.*;
import java.text.*;

class srcVariants
{
   private static Hashtable m_variant = null;

   private static String IFDEF = "IFDEF";
   private static String ENDIF = "END_IFDEF";
   private static String IFNDEF = "IFNDEF";

   public final static String VERSION = "1.02";
   
   
   public srcVariants()
   {
   }
   
   public srcVariants( String sourcepath, String destpath )
   {
      processDir( sourcepath, destpath );
   }

   
   private void processDir( String sourcepath, String destpath )
   {
      if ((! sourcepath.endsWith("\\")) && (! sourcepath.endsWith("/")))
         sourcepath += "/";
      if ((! destpath.endsWith("\\")) && (! destpath.endsWith("/")))
         destpath += "/";

      System.out.println(sourcepath);
      
      File mkdir = new File(destpath);
      mkdir.mkdirs();
      
      File f = new File(sourcepath);
      
      File dir[] = f.listFiles();
      
      if (dir != null)
      {
         for( int i = 0; i < dir.length; i++ )
         { 
            String fname = dir[i].getName();
            
            if ( dir[i].isDirectory() )
               processDir(sourcepath + fname + "/", destpath + fname + "/");   
            else if (fname.toLowerCase().endsWith(".java"))
               processFile(sourcepath + fname,destpath + fname);
         }
      }
   }
   
   
   private void processFile( String sourcefile, String destfile )
   {
      try
      {
         File sFile = new File(sourcefile);
         File dFile = new File(destfile);
         
         /* if (dFile.exists() && (sFile.lastModified() < dFile.lastModified()))
            processFile(dFile,sFile);
         else 
         */ 
         if ((! dFile.exists()) || (sFile.lastModified() > dFile.lastModified()))
            processFile(sFile,dFile);
      }
      catch( IOException ioe )
      {
         System.out.println(ioe);
      }
   }
   
   
   private void processFile( File sFile, File dFile )
   throws IOException      
   {
      int linecount = -1;
      
      try
      {
         if (dFile.exists())
            backupFile(dFile);
         
         BufferedReader reader = new BufferedReader(new FileReader(sFile));
   
         FileOutputStream sOut = new FileOutputStream(dFile);
         PrintStream writer = new PrintStream(sOut);
   
         System.out.println( " " + sFile.getName() );
         
         String line = null;
         String lineTrUc = null;
         String block = null;
         boolean active = true;
      
         linecount = 0;
         while ((line = reader.readLine()) != null)
         {
            linecount++;
            
            lineTrUc = line.trim().toUpperCase();
            
            if (lineTrUc.startsWith("//" + IFDEF))
            {
               // active remark, starting
               
               if (block == null)
               {
                  block = lineTrUc.substring(2 + IFDEF.length()).trim();
                  
                  if (isIncluded(block))
                  {
                     active = true;
                     writer.println(line);
                  }
                  else
                  {
                     active = false;
                     writer.println(getSpaces(line) + "/*" + IFDEF + " " + block);
                  }
               }
               else
               {
                  Error(writer,linecount,"nested variant comments not allowed");                        
                  active = true;
                  writer.println(line);
               }
            }
            else if (lineTrUc.startsWith("//" + IFNDEF))
            {
               // active remark, starting
               
               if (block == null)
               {
                  block = lineTrUc.substring(2 + IFNDEF.length()).trim();
               
                  if (! isIncluded(block))
                  {
                     active = true;
                     writer.println(line);
                  }
                  else
                  {
                     active = false;
                     writer.println(getSpaces(line) + "/*" + IFNDEF + " " + block);
                  }
               }
               else
               {
                  Error(writer,linecount,"nested variant comments not allowed");                        
                  active = true;
                  writer.println(line);
               }
            }
            else if (lineTrUc.startsWith("/*" + IFDEF))
            {
               // inactive remark, starting
               
               if (block == null)
               {
                  block = lineTrUc.substring(2 + IFDEF.length()).trim();
      
                  if (! isIncluded(block))
                  {
                     active = false;
                     writer.println(line);
                  }
                  else
                  {
                     active = true;
                     writer.println(getSpaces(line) + "//" + IFDEF + " " + block);
                  }
               }
               else
               {
                  Error(writer,linecount,"nested variant comments not allowed");  
                  active = true;
                  writer.println(line);
               }
            }
            else if ((block != null) && lineTrUc.equals("//" + ENDIF + " " + block))
            {
               // active remark, ending
   
               if (isIncluded(block))
                  writer.println(line);
               else
                  writer.println(getSpaces(line) + ENDIF + " " + block + "*/");
               
               block = null;
               active = true;
            }
            else if ((block != null) && lineTrUc.equals("//" + ENDIF))
            {
               Error(writer,linecount,"invalid " + ENDIF + " mark");
               writer.println(line);
            }
            else if ((block != null) && lineTrUc.equals(ENDIF + " " + block + "*/"))
            {
               // inactive remark, ending
               
               if (isIncluded(block))
                  writer.println(getSpaces(line) + "//" + ENDIF + " " + block);
               else
                  writer.println(line);
   
               block = null;
               active = true;
            }
            else
            {
               if (! active)
               {
                  if (line.indexOf("/*") >= 0)
                     line = line.replaceAll("/\\*","/..\\*");
                  if (line.indexOf("*/") >= 0)
                     line = line.replaceAll("\\*/","\\*../");
               }
               else
               {
                  if (line.indexOf("/..*") >= 0)
                     line = line.replaceAll("/..\\*","/\\*");
                  if (line.indexOf("*../") >= 0)
                     line = line.replaceAll("\\*../","\\*/");
               }
               
               writer.println(line);   
            }
         }
         
         linecount = -1;
         
         reader.close();
         writer.close();
         
         dFile.setLastModified(sFile.lastModified());
      }
      catch( IOException ioe )
      {
         throw ioe;   
      }
      catch( Throwable t )
      {
         System.out.println("Error processing file " + sFile.getAbsolutePath() + (linecount == -1 ? "" : ", line " + linecount));
         System.out.println(t);
      }
   }

   
   private void Error( PrintStream writer, int linecount, String msg )
   {
      System.out.println("###ERROR: " + msg + " in line " + linecount);
      writer.println("### " + getClass().getName() + ":ERROR: " + msg);
   }

   
   private String getSpaces( String s )
   {
      String res = null;
  
      if (s != null)
      {
         res = "";
         
         int i = 0;
         while ((i < s.length()) && ((s.charAt(i) == ' ') || (s.charAt(i) == '\t')))
         {
            res += s.charAt(i);
            i++;
         }
      }
      
      return res;
   }


   private void backupFile( File f )
   throws IOException
   {
      String backup = f.getAbsolutePath();
      
      if (backup.toLowerCase().endsWith(".java"))
         backup = backup.substring(0,backup.length() - 5) + ".bak";
      else
         backup += ".bak";
      
      File fBackup = new File(backup);

      if (fBackup.exists())
         fBackup.delete();
      
      BufferedReader reader = new BufferedReader(new FileReader(f));
      PrintStream writer = new PrintStream(new FileOutputStream(fBackup));
      
      String line = null;

      while ((line = reader.readLine()) != null)
      {
         writer.println(line);
      }
      
      writer.close();
      reader.close();
      
      fBackup.setLastModified(f.lastModified());
   }
 

   private static boolean isIncluded( String block )
   {
      boolean res = false;
      
      if ((m_variant != null) && (block != null))
      {
         if (m_variant.get(block.toUpperCase()) != null)
            res = true;
      }
      
      return res;
   }


   public static void main(String args[])
   {
      System.out.println("Variant-Preprocessor for Java " + VERSION);
      System.out.println("(C)door2solution software gmbh");
      System.out.println("http://www.door2solution.com");
      System.out.println();
 
      System.out.println("DISCLAIMER:");
      System.out.println("THIS UTILITY  IS  FREEWARE AND FREE  ADVICE IS  PROVIDED \"AS IS\"  AND  WITHOUT");
      System.out.println("ANY EXPRESS OR  IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED");
      System.out.println("WARRANTIES  OF  MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.  IF YOUR");
      System.out.println("LOCALITY DOES  NOT  ALLOW  THESE  WARRANTY  CONDITIONS,  YOU  ARE  NOT GRANTED");
      System.out.println("PERMISSION TO USE THIS UTILITY. DOOR2SOLUTION WILL NOT TAKE ANY RESPONSIBILITY");
      System.out.println("OR LIABILITY FOR FAILURES, CORRECT, MODIFIED OR INCORRECT USAGE.");
      System.out.println();
      
      if ((args == null) || (args.length < 2))      
      {
         System.out.println("Usage: java srcVariants srcpath destpath [version..]");
      }
      else
      {
         m_variant = new Hashtable();
         
         for( int i = 2; i < args.length; i++ )
            m_variant.put(args[i].toUpperCase(),new Long(1));

         srcVariants start = new srcVariants(args[0],args[1]);
      }
   }
}

