/*
* JBoss, Home of Professional Open Source.
* Copyright 2006, Red Hat Middleware LLC, and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors. 
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/ 
package org.jboss.aop.util;

import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javassist.bytecode.ClassFile;

/**
 * Class to be used during installation of the JDK 1.4 deployer in JBoss 4.0.x.
 * The AOP 1.5.x JDK 1.4 aspect library annotation interfaces do not extend  
 * org.jboss.lang.Annotation, which is a requirement for the JBoss Retroed
 * JDK 1.4 version of the aop deployer.   
 * 
 * @author <a href="kabir.khan@jboss.com">Kabir Khan</a>
 * @version $Revision: 1.1 $
 */
public class ForceInterfaceToImplementAnnotation
{
   static final String RETRO_ANNOTATION = "org/jboss/lang/Annotation";  
   public static void main(String[] args) throws Exception
   {
      if (args.length == 0 || args.length > 2)
      {
         System.out.println("Usage:\njava org.jboss.aop.util.ForceInterfaceToImplementAnnotation dir-with-classes [file-listing-classes]");
         return;
      }

      String input = (args.length == 1) ? "annotations.txt" : args[1];
      List<String> classes = readClasses(input);
      if (classes != null)
      {
         weaveAnnotations(args[0], classes);
      }
   }
   
   private static List<String> readClasses(String input) 
   {
      File file = new File(input);
      if (!file.exists())
      {
         System.out.println(input + " could not be found");
         return null;
      }
      
      BufferedReader rdr = null;
      try
      {
         rdr = new BufferedReader(new FileReader(file));
         ArrayList<String> classes = new ArrayList<String>();
         String clazz = rdr.readLine();
         while (clazz != null)
         {
            classes.add(clazz);
            clazz = rdr.readLine();
         }
         
         return classes;
      }
      catch (Exception e)
      {
         // AutoGenerated
         throw new RuntimeException(e);
      }
      finally
      {
         if (rdr != null)
         {
            try
            {
               rdr.close();
            }
            catch (IOException e)
            {
               throw new RuntimeException(e);
            }
         }
      }
   }
   
   private static void weaveAnnotations(String directory, List<String> classes)
   {
      File dir = new File(directory);
      if (!dir.exists())
      {
         throw new RuntimeException(directory + " does not exist"); 
      }
      if (!dir.isDirectory())
      {
         throw new RuntimeException(directory + " is not a directory");
      }
      
      for (String clazz : classes)
      {
          File fileToWeave = getFileToWeave(directory, clazz);
          if (!fileToWeave.exists())
          {
             System.out.println("Weaving skipped for " + clazz + ". Could not find file at " + fileToWeave);
          }
          else
          {
             weaveClass(fileToWeave);
          }
      }
      
   }
   
   private static File getFileToWeave(String directory, String clazz)
   {
      directory = directory.replace('/', File.separatorChar);
      directory = directory.replace('\\', File.separatorChar);
      StringBuffer filename = new StringBuffer(directory);
      if (!directory.endsWith(File.separator))
      {
         filename.append(File.separatorChar);
      }

      if (clazz.endsWith(".class"))
      {
         clazz = clazz.substring(0, clazz.length() - ".class".length());
      }
      clazz = clazz.replace('.', File.separatorChar);
      clazz = clazz.replace('/', File.separatorChar);
      clazz = clazz.replace('\\', File.separatorChar);
      filename.append(clazz);
      
      filename.append(".class");
      
      return new File(filename.toString());
   }
   
   public static void weaveClass(File file)
   {
      System.out.println("* Weaving interface in " + file + " to be compatible as an annotation in javassist");
      ClassFile classFile = getClassFile(file);
      
      if (!classFile.isInterface())
      {
         throw new RuntimeException(file + " does not contain an interface");
      }
      
      String[] interfaces = classFile.getInterfaces();
      boolean implementsRetroAnnotation = false;
      for (String intf : interfaces)
      {
         if (intf.equals(RETRO_ANNOTATION))
         {
            implementsRetroAnnotation = true;
            break;
         }
      }
      
      if (!implementsRetroAnnotation)
      {
//         String[] intfs = new String[interfaces.length + 1];
//         System.arraycopy(interfaces, 0, intfs, 0, interfaces.length);
//         intfs[interfaces.length] = RETRO_ANNOTATION;
//         classFile.setInterfaces(interfaces);
         classFile.addInterface(RETRO_ANNOTATION);
         writeClassFile(classFile, file);
      }
   }
   
   private static ClassFile getClassFile(File file)
   {
      DataInputStream in = null;
      try
      {
         in = new DataInputStream(new FileInputStream(file));
         return new ClassFile(in);
      }
      catch(Exception e)
      {
         throw new RuntimeException(e);
      }
      finally
      {
         try
         {
            if (in != null)
            {
               in.close();
            }
         }
         catch(Exception e)
         {
            throw new RuntimeException(e);
         }
      }
   }
   
   private static void writeClassFile(ClassFile cf, File file)
   {
      DataOutputStream out = null;
      try
      {
         out = new DataOutputStream(new FileOutputStream(file));
         cf.write(out);
      }
      catch(Exception e)
      {
         throw new RuntimeException("Problems writing " + file, e);
      }
      finally
      {
         if (out != null)
         {
            try
            {
               out.close();
            }
            catch (IOException e)
            {
               throw new RuntimeException(e);
            }
         }
      }
   }
}
