/*
  * JBoss, Home of Professional Open Source
  * Copyright 2005, JBoss Inc., and individual contributors as indicated
  * by the @authors tag. See the copyright.txt 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.eclipsesupport;

import org.jboss.aop.AspectManager;
import org.jboss.aop.AspectXmlLoader;
import org.jboss.aop.standalone.AOPTransformer;

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.net.URL;
import java.security.ProtectionDomain;
import java.util.Vector;

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.CtNewMethod;

/**
 * A transformer to weave the main method of the org.eclipse.jdt.internal.junit.runner.RemoteTestRunner, so that we can deploy the correct
 * -aop.xml files before the test case class itself is loaded when running within Eclipse
 *
 * FIXME SecurityManager - privileged blocks
 * @author <a href="mailto:bill@jboss.org">Bill Burke</a>
 * @version $Revision: 44099 $
 */
public class EclipseTestTransformer implements ClassFileTransformer
{
   
   AOPTransformer aopTransformer = new AOPTransformer();
   
   boolean foundRemoteTestRunner;
   
   /**
    * The main class when running tests from Eclipse 
    */
   final static String MAIN_CLASS = "org/eclipse/jdt/internal/junit/runner/RemoteTestRunner";
   
   /**
    * If running from within Eclipse with the -javaagent:eclipse-agent.jar switch set, the EclipseTestTransformer weaves the 
    * org.eclipse.jdt.internal.junit.runner.RemoteTestRunner class to set this system property to the name of the test class
    * run.
    */
   public final static String CLASSLOADER_DEPLOYED_XML = "jboss.aop.eclipse.test.xml.name";
   

   public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException
   {
      if (!foundRemoteTestRunner && className.equals(MAIN_CLASS))
      {
         System.out.println("Found class " + className);
         return weaveRemoteTestRunner(loader, className, classBeingRedefined, protectionDomain, classfileBuffer);
      }
      return aopTransformer.transform(loader, className, classBeingRedefined, protectionDomain, classfileBuffer);
   }

   private byte[] weaveRemoteTestRunner(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException
   {
      try
      {
         AspectManager manager = AspectManager.instance();
         ClassPool pool = manager.registerClassLoader(loader);
         CtClass remoteTestRunner = pool.get(MAIN_CLASS.replace('/', '.'));
         remoteTestRunner.defrost();

         CtMethod originalMain = remoteTestRunner.getMethod("main", "([Ljava/lang/String;)V");
         System.out.println("-----> Found CtMethod " + (originalMain != null));
         
         CtMethod wrapper = CtNewMethod.copy(originalMain, remoteTestRunner, null);
         originalMain.setName("originalMain");
         
         String body =
            "{" +
            "  org.jboss.aop.eclipsesupport.EclipseTestTransformer.recordClassNameAndDeployXml($$);" +
            "  originalMain($$);" +
            "}";
         
         wrapper.setBody(body);
         remoteTestRunner.addMethod(wrapper);

         remoteTestRunner.debugWriteFile();
         return remoteTestRunner.toBytecode();
      }
      catch (Exception e)
      {
         // AutoGenerated
         System.out.println("====> Exception " + e);
         e.printStackTrace();
         throw new RuntimeException(e);
      }
   }
   
   public static void recordClassNameAndDeployXml(String[] args) throws Exception
   {
      //This code is lifted from RemoteTestRunner
      String[] fTestClassNames = null; 
      for(int i = 0; i < args.length; i++)
      {
          if(args[i].toLowerCase().equals("-classnames") || args[i].toLowerCase().equals("-classname"))
          {
              Vector<String> list = new Vector<String>();
              for(int j = i + 1; j < args.length; j++)
              {
                  if(args[j].startsWith("-"))
                  {
                      break;
                  }
                  list.add(args[j]);
              }

              fTestClassNames = list.toArray(new String[list.size()]);
          } 
      }
      
      if (fTestClassNames == null || fTestClassNames.length == 0)
      {
         throw new RuntimeException("No classnames could be found");
      }
      if (fTestClassNames.length > 1)
      {
         throw new RuntimeException("Only one class name is supported");
      }
      
      deployXmlForEclipse(fTestClassNames[0]);
   }
   
   /**
    * This will be initiated from the woven RemoteTestRunner classs to deploy the xml for the testcase UNLESS
    * the jboss.aop.path system property has been set. 
    */
   public static void deployXmlForEclipse(String testClass) throws Exception
   {
      if (System.getProperty("jboss.aop.path") != null)
      {
         return;
      }
      
      //Determine the url of the -aop.xml file depending on test package name
      String testName = null;
      if (testClass.contains(".regression."))
      {
         //Handle the regresssion test differently
         testName = "regression";
      }
      else
      {
         int dot = testClass.lastIndexOf('.');
         String packageName = testClass.substring(0, dot);
         dot = packageName.lastIndexOf('.');
         testName = packageName.substring(dot + 1);
      }
      
      testName = "test/" + testName + "/jboss-aop.xml";
      URL url = Thread.currentThread().getContextClassLoader().getResource(testName);
      
      if (url != null)
      {
         System.setProperty(CLASSLOADER_DEPLOYED_XML, testName);
         AspectXmlLoader.deployXML(url);
      }
   }
}
