/*
 * Copyright (c) 1998-2006 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.ejb.hessian;

import java.io.*;
import java.util.*;
import java.beans.*;
import java.lang.reflect.*;

import org.w3c.dom.*;

import com.caucho.vfs.*;
import com.caucho.java.*;
import com.caucho.util.*;
import com.caucho.xml.*;
import com.caucho.ejb.*;
import com.caucho.loader.DynamicClassLoader;

/**
 * Base class for generating code to marshal and unmarshal sml-rpc calls.
 */
abstract class MarshalGenerator extends AbstractGenerator {
  // Classes which can be safely passed by reference because they're
  // immutable and part of the JDK
  static IntMap immutableClasses;
  
  protected static Class readerClass;
  protected static Class inputStreamClass;
  protected static Class remoteClass;
  protected static Class nodeClass;
  
  static {
    readerClass = java.io.Reader.class;
    inputStreamClass = java.io.InputStream.class;
    remoteClass = java.rmi.Remote.class;
    nodeClass = org.w3c.dom.Node.class;
  }
  
  protected Class _cl;
  String objClass;
  String fullName;
  String pkg;
  String className;

  protected int unique;
  
  protected ArrayList marshallClasses;
  protected ArrayList unmarshallClasses;
  
  protected ArrayList marshallArrays;
  protected ArrayList unmarshallArrays;

  /**
   * Initialize the generated classname.
   *
   * @param beanClass the bean which needs the stub/skeleton
   *
   * @return the path to the file to be generated
   */
  Path initClassNames(Class beanClass, String suffix)
    throws Exception
  {
    ClassLoader parentLoader = getParentLoader();
    
    if (parentLoader instanceof DynamicClassLoader) {
      DynamicClassLoader dcl = (DynamicClassLoader) parentLoader;

      dcl.make();
    }

    Path workPath = CauchoSystem.getWorkPath();

    _cl = beanClass;

    fullName = _cl.getName() + suffix;

    objClass = _cl.getName();
    int p = objClass.lastIndexOf('.');
    if (p > 0)
      objClass = objClass.substring(p + 1);
    
    p = fullName.lastIndexOf('.');
    if (p > 0) {
      pkg = fullName.substring(0, p);
      className = fullName.substring(p + 1);
    }
    else
      className = fullName;

    Path path = workPath.lookup(fullName.replace('.', '/') + ".java");
    path.getParent().mkdirs();

    return path;
  }

  /**
   * Creates a unique mangled method name based on the method name and
   * the method parameters.
   *
   * @param name the base method name
   * @param method the method to mangle
   * @param isFull if true, mangle the full classname
   *
   * @return a mangled string.
   */
  protected String mangleMethodName(String name, Method method, boolean isFull)
  {
    CharBuffer cb = CharBuffer.allocate();
    
    cb.append(name);
    
    Class []params = method.getParameterTypes();
    for (int i = 0; i < params.length; i++) {
      cb.append('_');
      mangleClass(cb, params[i], isFull);
    }

    return cb.close();
  }

  /**
   * Creates a unique mangled method name based on the method name and
   * the method parameters.
   *
   * @param name the base method name
   * @param method the method to mangle
   * @param isFull if true, mangle the full classname
   *
   * @return a mangled string.
   */
  protected String mangleMethodName(String name, Class []param, boolean isFull)
  {
    CharBuffer cb = CharBuffer.allocate();
    
    cb.append(name);
    
    for (int i = 0; i < param.length; i++) {
      cb.append('_');
      mangleClass(cb, param[i], isFull);
    }

    return cb.close();
  }

  /**
   * Mangles a classname.
   */
  private void mangleClass(CharBuffer cb, Class cl, boolean isFull)
  {
    String name = cl.getName();

    if (name.equals("boolean"))
      cb.append("boolean");
    else if (name.equals("int") ||
             name.equals("short") ||
             name.equals("byte"))
      cb.append("int");
    else if (name.equals("long"))
      cb.append("long");
    else if (name.equals("double") || name.equals("float"))
      cb.append("double");
    else if (name.equals("java.lang.String") ||
             name.equals("com.caucho.util.CharBuffer") ||
             name.equals("char") ||
             name.equals("java.io.Reader"))
      cb.append("string");
    else if (name.equals("java.util.Date") ||
             name.equals("com.caucho.util.QDate"))
      cb.append("date");
    else if (inputStreamClass.isAssignableFrom(cl) ||
             name.equals("[B"))
      cb.append("binary");
    else if (cl.isArray()) {
      cb.append("[");
      mangleClass(cb, cl.getComponentType(), isFull);
    }
    else if (name.equals("org.w3c.dom.Node") ||
             name.equals("org.w3c.dom.Element") ||
             name.equals("org.w3c.dom.Document"))
      cb.append("xml");
    else if (isFull)
      cb.append(name);
    else {
      int p = name.lastIndexOf('.');
      if (p > 0)
        cb.append(name.substring(p + 1));
      else
        cb.append(name);
    }
  }

  /**
   * Prints a method declaration with a given name.
   *
   * @param name the name to use for the generated method
   * @param method the method to override
   */
  protected void printMethodDeclaration(String name, Method method)
    throws IOException
  {
    Class ret = method.getReturnType();
    Class []params = method.getParameterTypes();

    Class []exns = method.getExceptionTypes();

    println();
    print("public ");
    printClass(ret);
    print(" " + name + "(");
    for (int i = 0; i < params.length; i++) {
      if (i != 0)
        print(", ");
      printClass(params[i]);
      print(" _arg" + i);
    }
    println(")");
    if (exns.length > 0)
      print("  throws ");
    for (int i = 0; i < exns.length; i++) {
      if (i != 0)
        print(", ");
      printClass(exns[i]);
    }
    if (exns.length > 0)
      println();
  }

  /**
   * Unmarshal the reading of a single variable, knowing the result
   * type.
   *
   * @param var the generated java variable which will receive the data.
   * @param cl the class of the target variable.
   */
  protected void printUnmarshalType(Class cl)
    throws IOException
  {
    String name = cl.getName();

    if (cl.equals(boolean.class)) {
      println("in.readBoolean();");
    }
    else if (cl.equals(int.class)) {
      println("in.readInt();");
    }
    else if (cl.equals(short.class) ||
             cl.equals(char.class) ||
             cl.equals(byte.class)) {
      println("(" + name + ") in.readInt();");
    }
    else if (cl.equals(long.class)) {
      println("in.readLong();");
    }
    else if (cl.equals(double.class)) {
      println("in.readDouble();");
    }
    else if (cl.equals(float.class)) {
      println("(" + name + ") in.readDouble();");
    }
    else if (cl.equals(String.class))
      println("in.readString();");
    else if (cl.equals(java.util.Date.class)) {
      println("new java.util.Date(in.readUTCDate());");
    }
    else if (cl.equals(byte[].class)) {
      println("in.readBytes();");
    }
    else if (org.w3c.dom.Node.class.isAssignableFrom(cl)) {
      println("in.readNode();");
    }
    else if (cl.equals(java.lang.Object.class)) {
      println("in.readObject();");
    }
    else {
      print("(");
      printClass(cl);
      print(") in.readObject(");
      printClass(cl);
      println(".class);");
    }
  }

  protected void printMarshalType(Class cl, String var)
    throws IOException
  {
    String name = cl.getName();

    if (cl.equals(void.class)) {
      println("out.writeNull();");
    }
    else if (cl.equals(boolean.class)) {
      println("out.writeBoolean(" + var + ");");
    }
    else if (cl.equals(int.class) ||
             cl.equals(short.class) ||
             cl.equals(char.class) ||
             cl.equals(byte.class)) {
      println("out.writeInt(" + var + ");");
    }
    else if (cl.equals(long.class)) {
      println("out.writeLong(" + var + ");");
    }
    else if (cl.equals(double.class) || cl.equals(float.class)) {
      println("out.writeDouble(" + var + ");");
    }
    else if (cl.equals(String.class)) {
      println("out.writeString(" + var + ");");
    }
    else if (cl.equals(java.util.Date.class)) {
      println("out.writeUTCDate(" + var + " == null ? 0 : " + var + ".getTime());");
    }
    else if (org.w3c.dom.Node.class.isAssignableFrom(cl)) {
      println("out.writeXml(" + var + ");");
    }
    else if (cl.equals(byte[].class)) {
      println("out.writeBytes(" + var + ");");
    }
    else {
      println("out.writeObject(" + var + ");");
    }
  }

  /**
   * Returns true if the class needs serialization.
   *
   * @param cl the class to test.
   *
   * @return true if the class needs serialization
   */
  boolean needsSerialization(Class cl)
  {
    if (cl.isPrimitive())
      return false;
    else
      return immutableClasses.get(cl) < 0;
  }

  public void printClass(Class cl)
    throws IOException
  {
    if (! cl.isArray())
      print(cl.getName());
    else {
      printClass(cl.getComponentType());
      print("[]");
    }
  }

  protected void printNewArray(Class cl)
    throws IOException
  {
    if (! cl.isArray()) {
      print(cl.getName());
      print("[length]");
    }
    else {
      printNewArray(cl.getComponentType());
      print("[]");
    }
  }

  static {
    immutableClasses = new IntMap();
    immutableClasses.put(String.class, 1);
    immutableClasses.put(Byte.class, 1);
    immutableClasses.put(Character.class, 1);
    immutableClasses.put(Short.class, 1);
    immutableClasses.put(Integer.class, 1);
    immutableClasses.put(Long.class, 1);
    immutableClasses.put(Float.class, 1);
    immutableClasses.put(Double.class, 1);
    immutableClasses.put(Class.class, 1);
  }
}
