/*
 * Copyright (c) 1998-2000 Caucho Technology -- all rights reserved
 *
 * This file is part of Resin(R) Open Source
 *
 * Each copy or derived work must preserve the copyright notice and this
 * notice unmodified.
 *
 * Resin Open Source is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * Resin Open Source 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, or any warranty
 * of NON-INFRINGEMENT.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Resin Open Source; if not, write to the
 *   Free SoftwareFoundation, Inc.
 *   59 Temple Place, Suite 330
 *   Boston, MA 02111-1307  USA
 *
 * @author Scott Ferguson
 */

package com.caucho.iiop;

import com.caucho.java.AbstractGenerator;
import com.caucho.server.util.CauchoSystem;
import com.caucho.vfs.MergePath;
import com.caucho.vfs.Path;

import javax.ejb.EJBHome;
import javax.ejb.EJBObject;
import java.io.IOException;
import java.lang.reflect.Method;

public class IiopStubCompiler extends AbstractGenerator {
  private Class _cl;
  
  public IiopStubCompiler(Class cl)
  {
    _cl = cl;

    String name = cl.getName();

    // XXX:
    //name = "org.omg.stub." + name;
    
    int p = name.lastIndexOf('.');
    if (p > 0) {
      String pkg = name.substring(0, p);
      String tail = name.substring(p + 1);
      setFullClassName(pkg + "._" + tail + "_Stub");
    }
    else
      setFullClassName("_" + name + "_Stub");
  }
    
  /**
   * Starts generation of the Java code
   */
  public void generateJava()
    throws Exception
  {
    printHeader();

    Method []methods = _cl.getMethods();
    for (int i = 0; i < methods.length; i++) {
      Method method = methods[i];
      
      if (method.getDeclaringClass().isAssignableFrom(EJBHome.class) ||
          method.getDeclaringClass().isAssignableFrom(EJBObject.class))
        continue;

      printMethod(method);
    }
    
    printFooter();
  }

  /**
   * Prints the header
   */
  private void printHeader()
    throws IOException
  {
    println("/*");
    println(" * Generated by Resin-EJB IiopStubCompiler");
    println(" */");
    println();
    
    if (getPackageName() != null)
      println("package " + getPackageName() + ";");

    println();
    print("public class " + getClassName());
    if (EJBHome.class.isAssignableFrom(_cl))
      println(" extends com.caucho.iiop.client.IiopHomeStub");
    else
      println(" extends com.caucho.iiop.client.IiopStub");
    println("  implements " + _cl.getName() + " {");
    pushDepth();

    println("private static final String[] _type_ids = {");
    println("  \"RMI:" + _cl.getName() + ":0000000000000000\"");
    println("};");
    
    println();
    println("public String[] _ids()");
    println("{");
    println("  return _type_ids;");
    println("}");
  }

  /**
   * Prints a method
   */
  private void printMethod(Method method)
    throws IOException
  {
    printMethodHeader(method);
    println("{");
    pushDepth();

    println("org.omg.CORBA_2_3.portable.OutputStream os = null;");
    println("try {");
    pushDepth();
    
    println("os = (org.omg.CORBA_2_3.portable.OutputStream) _request(\"" + method.getName() + "\", true);");

    Class []args = method.getParameterTypes();
    for (int i = 0; i < args.length; i++) {
      printSetValue(args[i], "a" + i);
    }
    
    println("org.omg.CORBA_2_3.portable.InputStream is;");
    println("is = (org.omg.CORBA_2_3.portable.InputStream) _invoke(os);");

    Class returnType = method.getReturnType();
    
    if (returnType.equals(void.class)) {
    }
    else {
      print("return ");
      printGetValue(returnType);
      println(";");
    }
    
    popDepth();
    println("} catch (org.omg.CORBA.portable.ApplicationException e) {");
    // XXX: handle types
    println("  org.omg.CORBA_2_3.portable.InputStream in = (org.omg.CORBA_2_3.portable.InputStream) e.getInputStream();");
    println("  String name = in.read_string();");
    println("  Throwable ex = (Throwable) in.read_value();");
    for (Class ex : method.getExceptionTypes()) {
      println("  if (ex instanceof " + ex.getName() + ")");
      println("    throw (" + ex.getName() + ") ex;");
    }
    println("  throw new java.rmi.RemoteException(ex.getMessage(), ex);");
    println("} catch (Exception e) {");
    println("  e.printStackTrace();");
    println("  throw new java.rmi.RemoteException(e.getMessage(), e);");
    println("}");

    popDepth();
    println("}");
  }

  private void printGetValue(Class type)
    throws IOException
  {
    if (type.equals(boolean.class))
      print("is.read_boolean()");
    else if (type.equals(byte.class))
      print("is.read_octet()");
    else if (type.equals(short.class))
      print("is.read_short()");
    else if (type.equals(int.class))
      print("is.read_long()");
    else if (type.equals(long.class))
      print("is.read_longlong()");
    else if (type.equals(float.class))
      print("is.read_float()");
    else if (type.equals(double.class))
      print("is.read_double()");
    else if (type.equals(char.class))
      print("is.read_wchar()");
    else if (javax.ejb.EJBObject.class.isAssignableFrom(type) ||
	     java.rmi.Remote.class.isAssignableFrom(type)) {
      // Note that if sender/receiver have mismatched types, one
      // possible result is a Java OOM
      // ejb/114t
      print("(");
      printJavaClass(type);
      print(") is.read_Object(");
      printJavaClass(type);
      print(".class)");
    }
    else {
      print("(");
      printJavaClass(type);
      print(") is.read_value(");
      printJavaClass(type);
      print(".class)");
    }
  }

  void printJavaClass(Class type)
    throws IOException
  {
    if (type.isArray()) {
      printJavaClass(type.getComponentType());
      print("[]");
    }
    else
      print(type.getName());
  }

  private void printSetValue(Class type, String value)
    throws IOException
  {
    if (type.equals(boolean.class))
      println("os.write_boolean(" + value + ");");
    else if (type.equals(byte.class))
      println("os.write_octet(" + value + ");");
    else if (type.equals(short.class))
      println("os.write_short(" + value + ");");
    else if (type.equals(int.class))
      println("os.write_long(" + value + ");");
    else if (type.equals(long.class))
      println("os.write_longlong(" + value + ");");
    else if (type.equals(float.class))
      println("os.write_float(" + value + ");");
    else if (type.equals(double.class))
      println("os.write_double(" + value + ");");
    else if (type.equals(char.class))
      println("os.write_wchar(" + value + ");");
    else
      println("os.write_value((java.io.Serializable) " + value + ");");
  }
  
  /**
   * Prints the footer
   */
  private void printFooter()
    throws IOException
  {
    popDepth();
    println("}");
  }

  public static Class create(String className)
    throws Exception
  {
    Class cl = CauchoSystem.loadClass(className);

    MergePath mergePath = new MergePath();
    mergePath.addClassPath();

    Path classPath = mergePath.lookup(className.replace('.', '/') + ".class");
    classPath = ((MergePath) classPath).getBestPath();

    Path dir = classPath.getParent();
    int i = 0;
    while ((i = className.indexOf('.', i + 1)) >= 0) {
      dir = dir.getParent();
    }
    
    dir.mkdirs();

    IiopStubCompiler compiler = new IiopStubCompiler(cl);
    compiler.setClassDir(dir);
    Class gen = compiler.preload();
    if (gen != null)
      return gen;
    
    compiler.generate();
    
    return compiler.compile();
  }

  /**
   * Compiles the stub
   */
  public static void main(String []argv)
    throws Exception
  {
    String className = argv[0];

    System.out.println(create(className));
  }
}
