/*
 * Copyright 1999-2001 Sun Microsystems, Inc.  All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Sun designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Sun in the LICENSE file that accompanied this code.
 *
 * This code 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 General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
 * CA 95054 USA or visit www.sun.com if you need additional information or
 * have any questions.
 */
/*
 * COMPONENT_NAME: idl.toJava
 *
 * ORIGINS: 27
 *
 * Licensed Materials - Property of IBM
 * 5639-D57 (C) COPYRIGHT International Business Machines Corp. 1997, 1999
 * RMI-IIOP v1.0
 *
 */

package com.sun.tools.corba.se.idl.toJavaPortable;

// NOTES:
// -F46082.51<daz> Remove -stateful feature; javaStatefulName() obsolete.
// -D57147   <klr> Make _Tie implement org.omg.CORBA.portable.InvokeHandler
// -D58037   <klr> Make _Tie delegate to Operations interface
// -D62739   <klr> no TIE for values that support abstract interfaces, etc.

import java.io.File;
import java.io.PrintWriter;

import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;

import com.sun.tools.corba.se.idl.AttributeEntry;
import com.sun.tools.corba.se.idl.GenFileStream;

import com.sun.tools.corba.se.idl.InterfaceEntry;
import com.sun.tools.corba.se.idl.InterfaceState;
import com.sun.tools.corba.se.idl.MethodEntry;
import com.sun.tools.corba.se.idl.SymtabEntry;
import com.sun.tools.corba.se.idl.TypedefEntry;
import com.sun.tools.corba.se.idl.ValueEntry;

/**
 *
 **/
public class Skeleton implements AuxGen
{
  private NameModifier skeletonNameModifier ;
  private NameModifier tieNameModifier ;

  public Skeleton ()
  {
  }

  public void generate (Hashtable symbolTable, SymtabEntry entry)
  {
    // <d62739-begin>
    // Per Simon, 5-12-99, don't generate TIE or Skeleton for
    //
    // 1) valuetypes supporting abstract interfaces
    // 2) valuetypes with no supports.
    // 3) abstract interfaces
    //
    if (entry instanceof ValueEntry)
    {
      ValueEntry v = (ValueEntry) entry;
      if ((v.supports ().size () == 0) ||
          ((InterfaceEntry) v.supports ().elementAt (0)).isAbstract ()) {
        return;
        }
    }
    if (((InterfaceEntry) entry).isAbstract ()) {
        return;
    }
    // <d62739-end>

    this.symbolTable = symbolTable;

    this.i           = (InterfaceEntry)entry;
    init ();
    openStream ();
    if (stream == null)
      return;
    writeHeading ();
    writeBody ();
    writeClosing ();
    closeStream ();
  } // generate

  /**
   * Initialize members unique to this generator.
   **/
  protected void init ()
  {
    tie = ((Arguments)Compile.compiler.arguments).TIEServer ;
    poa = ((Arguments)Compile.compiler.arguments).POAServer ;

    skeletonNameModifier =
        ((Arguments)Compile.compiler.arguments).skeletonNameModifier ;
    tieNameModifier =
        ((Arguments)Compile.compiler.arguments).tieNameModifier ;

    tieClassName = tieNameModifier.makeName( i.name() ) ;
    skeletonClassName = skeletonNameModifier.makeName( i.name() ) ;

    intfName = Util.javaName (i);
    // for valuetype, get the name of the interface the valuetype supports
    if (i instanceof ValueEntry)
    {
      ValueEntry v = (ValueEntry) i;
      InterfaceEntry intf = (InterfaceEntry) v.supports ().elementAt (0);
      intfName = Util.javaName (intf);
    }
  } // init

  protected void openStream ()
  {
    if (tie)
        stream = Util.stream( i, tieNameModifier, ".java" ) ;
    else
        stream = Util.stream( i, skeletonNameModifier, ".java" ) ;
  } // openStream

  protected void writeHeading ()
  {
    Util.writePackage (stream, i, Util.StubFile);
    Util.writeProlog (stream, ((GenFileStream)stream).name ());
    if (i.comment () != null)
      i.comment ().generate ("", stream);
    writeClassDeclaration ();
    stream.println ('{');
    stream.println ();
  } // writeHeading

  protected void writeClassDeclaration ()
  {
    if (tie){
        stream.println ("public class " + tieClassName +
            " extends " + skeletonClassName ) ;
    } else {
        if (poa) {
            stream.println ("public abstract class " + skeletonClassName +
                            " extends org.omg.PortableServer.Servant");
            stream.print   (" implements " + intfName + "Operations, ");
            stream.println ("org.omg.CORBA.portable.InvokeHandler");
        } else {
            stream.println ("public abstract class " + skeletonClassName +
                            " extends org.omg.CORBA.portable.ObjectImpl");
            stream.print   ("                implements " + intfName + ", ");
            stream.println ("org.omg.CORBA.portable.InvokeHandler");
        }
    }
  } // writeClassDeclaration

  /**
   *
   **/
  protected void writeBody ()
  {
    // <f46082.51> Remove -stateful feature.  ?????
    //if (i.state () != null)
    //  writeState ();
    writeCtors ();
    if (i instanceof ValueEntry)
    {
      // use the interface the valuetype supports to generate the
      // tie class instead of using the valuetype itself
      ValueEntry v = (ValueEntry) i;
      this.i = (InterfaceEntry) v.supports ().elementAt (0);
    }
    buildMethodList ();
    //DispatchMethod and MethodTable
    if (tie){ //Concrete class implementing the remote interface
        //The logic is here for future use
        if (poa) {
            writeMethods ();
            stream.println ("  private " + intfName + "Operations _impl;");
            stream.println ("  private org.omg.PortableServer.POA _poa;");
        } else {
            writeMethods ();
            stream.println ("  private " + intfName + "Operations _impl;");
        }
    } else { //Both POA and ImplBase are abstract InvokeHandler
        //The logic is here for future use
        if (poa) {
            writeMethodTable ();
            writeDispatchMethod ();
            writeCORBAOperations ();
        } else {
            writeMethodTable ();
            writeDispatchMethod ();
            writeCORBAOperations ();
        }
    }
    //legacy !!
    writeOperations ();
  } // writeBody

  /**
   * Close the skeleton class. The singleton ORB member is
   * necessary only for portable skeletons.
   **/
  protected void writeClosing ()
  {
    stream.println ();
    if (tie){
        stream.println ("} // class " + tieClassName);
    } else {
        stream.println ("} // class " + skeletonClassName);
    }
  } // writeClosing

  /**
   * Close the print stream, which flushes the stream to file.
   **/
  protected void closeStream ()
  {
    stream.close ();
  } // closeStream

  protected void writeCtors ()
  {
    stream.println ("  // Constructors");
    // Empty argument constructors
    if (!poa) {
        if (tie){
            stream.println ("  public " + tieClassName + " ()");
            stream.println ("  {");
            stream.println ("  }");
        } else {
            stream.println ("  public " + skeletonClassName + " ()");
            stream.println ("  {");
            stream.println ("  }");
        }
    }
    stream.println ();
    // Argumented constructors
    if (tie){
        if (poa) {
            //Write constructors
            writePOATieCtors();
            //Write state setters and getters
            writePOATieFieldAccessMethods();
        } else {
            stream.println ("  public " + tieClassName +
                            " (" + intfName + "Operations impl)");
            stream.println ("  {");
            // Does it derive from a interface having state, e.g., valuetype?
            if (((InterfaceEntry)i.derivedFrom ().firstElement ()).state () != null)
                stream.println ("    super (impl);");
            else
                stream.println ("    super ();");
            stream.println ("    _impl = impl;");
            stream.println ("  }");
            stream.println ();
        }
    } else { //Skeleton is not Tie so it has no constructors.
        if (poa) {
        } else {
        }
    }

  } // writeCtors


  private void writePOATieCtors(){
    //First constructor
    stream.println ("  public " + tieClassName + " ( " + intfName + "Operations delegate ) {");
    stream.println ("      this._impl = delegate;");
    stream.println ("  }");
    //Second constructor specifying default poa.
    stream.println ("  public " + tieClassName + " ( " + intfName +
                    "Operations delegate , org.omg.PortableServer.POA poa ) {");
    stream.println ("      this._impl = delegate;");
    stream.println ("      this._poa      = poa;");
    stream.println ("  }");
  }

  private void writePOATieFieldAccessMethods(){
    //Getting delegate
    stream.println ("  public " + intfName+ "Operations _delegate() {");
    stream.println ("      return this._impl;");
    stream.println ("  }");
    //Setting delegate
    stream.println ("  public void _delegate (" + intfName + "Operations delegate ) {");
    stream.println ("      this._impl = delegate;");
    stream.println ("  }");
    //Overriding default poa
    stream.println ("  public org.omg.PortableServer.POA _default_POA() {");
    stream.println ("      if(_poa != null) {");
    stream.println ("          return _poa;");
    stream.println ("      }");
    stream.println ("      else {");
    stream.println ("          return super._default_POA();");
    stream.println ("      }");
    stream.println ("  }");
  }

  /**
   * Build a list of all of the methods, keeping out duplicates.
   **/
  protected void buildMethodList ()
  {
    // Start from scratch
    methodList = new Vector ();

    buildMethodList (i);
  } // buildMethodList

  /**
   *
   **/
  private void buildMethodList (InterfaceEntry entry)
  {
    // Add the local methods
    Enumeration locals = entry.methods ().elements ();
    while (locals.hasMoreElements ())
      addMethod ((MethodEntry)locals.nextElement ());

    // Add the inherited methods
    Enumeration parents = entry.derivedFrom ().elements ();
    while (parents.hasMoreElements ())
    {
      InterfaceEntry parent = (InterfaceEntry)parents.nextElement ();
      if (!parent.name ().equals ("Object"))
        buildMethodList (parent);
    }
  } // buildMethodList

  /**
   *
   **/
  private void addMethod (MethodEntry method)
  {
    if (!methodList.contains (method))
      methodList.addElement (method);
  } // addMethod

  /**
   *
   **/
  protected void writeDispatchMethod ()
  {
    String indent = "                                ";
    stream.println ("  public org.omg.CORBA.portable.OutputStream _invoke (String $method,");
    stream.println (indent + "org.omg.CORBA.portable.InputStream in,");
    stream.println (indent + "org.omg.CORBA.portable.ResponseHandler $rh)");
    stream.println ("  {");

    // this is a special case code generation for cases servantLocator and
    // servantActivator, where OMG is taking too long to define them
    // as local objects

    boolean isLocalInterface = false;
    if (i instanceof InterfaceEntry) {
        isLocalInterface = i.isLocalServant();
    }

    if (!isLocalInterface) {
        // Per Simon 8/26/98, create and return reply stream for all methods - KLR
        stream.println ("    org.omg.CORBA.portable.OutputStream out = null;");
        stream.println ("    java.lang.Integer __method = (java.lang.Integer)_methods.get ($method);");
        stream.println ("    if (__method == null)");
        stream.println ("      throw new org.omg.CORBA.BAD_OPERATION (0, org.omg.CORBA.CompletionStatus.COMPLETED_MAYBE);");
        stream.println ();
        if (methodList.size () > 0)
        {
          stream.println ("    switch (__method.intValue ())");
          stream.println ("    {");

          // Write the method case statements
          int realI = 0;
          for (int i = 0; i < methodList.size (); ++i)
          {
            MethodEntry method = (MethodEntry)methodList.elementAt (i);
            ((MethodGen)method.generator ()).dispatchSkeleton (symbolTable, method, stream, realI);
            if (method instanceof AttributeEntry && !((AttributeEntry)method).readOnly ())
              realI += 2;
            else
              ++realI;
          }

          indent = "       ";
          stream.println (indent + "default:");
          stream.println (indent + "  throw new org.omg.CORBA.BAD_OPERATION (0, org.omg.CORBA.CompletionStatus.COMPLETED_MAYBE);");
          stream.println ("    }");
          stream.println ();
        }
        stream.println ("    return out;");
    } else {
        stream.println("    throw new org.omg.CORBA.BAD_OPERATION();");
    }
    stream.println ("  } // _invoke");
    stream.println ();
  } // writeDispatchMethod

  /**
   *
   **/
  protected void writeMethodTable ()
  {
    // Write the methods hashtable
    stream.println ("  private static java.util.Hashtable _methods = new java.util.Hashtable ();");
    stream.println ("  static");
    stream.println ("  {");

    int count = -1;
    Enumeration e = methodList.elements ();
    while (e.hasMoreElements ())
    {
      MethodEntry method = (MethodEntry)e.nextElement ();
      if (method instanceof AttributeEntry)
      {
        stream.println ("    _methods.put (\"_get_" + Util.stripLeadingUnderscores (method.name ()) + "\", new java.lang.Integer (" + (++count) + "));");
        if (!((AttributeEntry)method).readOnly ())
          stream.println ("    _methods.put (\"_set_" + Util.stripLeadingUnderscores (method.name ()) + "\", new java.lang.Integer (" + (++count) + "));");
      }
      else
        stream.println ("    _methods.put (\"" + Util.stripLeadingUnderscores (method.name ()) + "\", new java.lang.Integer (" + (++count) + "));");
    }
    stream.println ("  }");
    stream.println ();
  } // writeMethodTable

  /**
   *
   **/
  protected void writeMethods ()
  {
      int realI = 0;
      for (int i = 0; i < methodList.size (); ++i)
          {
              MethodEntry method = (MethodEntry)methodList.elementAt (i);
              ((MethodGen)method.generator ()).skeleton
                  (symbolTable, method, stream, realI);
              if (method instanceof AttributeEntry &&
                  !((AttributeEntry)method).readOnly ())
                  realI += 2;
              else
                  ++realI;
              stream.println ();
          }
  } // writeMethods

  /**
   *
   **/
  private void writeIDs ()
  {
    Vector list = new Vector ();
    buildIDList (i, list);
    Enumeration e = list.elements ();
    boolean first = true;
    while (e.hasMoreElements ())
    {
      if (first)
        first = false;
      else
        stream.println (", ");
      stream.print ("    \"" + (String)e.nextElement () + '"');
    }
  } // writeIDs

  /**
   *
   **/
  private void buildIDList (InterfaceEntry entry, Vector list)
  {
    if (!entry.fullName ().equals ("org/omg/CORBA/Object"))
    {
      String id = Util.stripLeadingUnderscoresFromID (entry.repositoryID ().ID ());
      if (!list.contains (id))
        list.addElement (id);
      Enumeration e = entry.derivedFrom ().elements ();
      while (e.hasMoreElements ())
        buildIDList ((InterfaceEntry)e.nextElement (), list);
    }
  } // buildIDList

  /**
   *
   **/
  protected void writeCORBAOperations ()
  {
    stream.println ("  // Type-specific CORBA::Object operations");

    stream.println ("  private static String[] __ids = {");
    writeIDs ();
    stream.println ("};");
    stream.println ();
    if (poa)
        writePOACORBAOperations();
    else
        writeNonPOACORBAOperations();

  } // writeCORBAOperations

  protected void writePOACORBAOperations(){
      stream.println ("  public String[] _all_interfaces (org.omg.PortableServer.POA poa, byte[] objectId)");
      //Right now, with our POA implementation, the same
      //implementation of _ids() type methods seem to work for both non-POA
      //as well as POA servers. We need to REVISIT since the equivalent
      //POA interface, i.e. _all_interfaces, has parameters which are not being
      //used in the _ids() implementation.
      stream.println ("  {");
      stream.println ("    return (String[])__ids.clone ();");
      stream.println ("  }");
      stream.println ();
      //_this()
      stream.println ("  public "+ i.name() +" _this() ");
      stream.println ("  {");
      stream.println ("    return "+ i.name() +"Helper.narrow(" );
      stream.println ("    super._this_object());");
      stream.println ("  }");
      stream.println ();
      //_this(org.omg.CORBA.ORB orb)
      stream.println ("  public "+ i.name() +" _this(org.omg.CORBA.ORB orb) ");
      stream.println ("  {");
      stream.println ("    return "+ i.name() +"Helper.narrow(" );
      stream.println ("    super._this_object(orb));");
      stream.println ("  }");
      stream.println ();
  }
  protected void writeNonPOACORBAOperations(){
      stream.println ("  public String[] _ids ()");
      stream.println ("  {");
      stream.println ("    return (String[])__ids.clone ();");
      stream.println ("  }");
      stream.println ();
  }
  /**
   *
   **/
  protected void writeOperations ()
  {
    // _get_ids removed at Simon's request 8/26/98 - KLR
  } // writeOperations

  protected Hashtable      symbolTable = null;
  protected InterfaceEntry i           = null;
  protected PrintWriter    stream      = null;

  // Unique to this generator
  protected String         tieClassName   = null;
  protected String         skeletonClassName   = null;
  protected boolean        tie         = false;
  protected boolean        poa         = false;
  protected Vector         methodList  = null;
  protected String         intfName    = "";
} // class Skeleton
