//package oracle.aurora.jaccelerator.server;
package oracle.jaccelerator.server;

import java.io.*;
import java.util.Hashtable;
import java.util.Enumeration;
import java.sql.SQLException;

import oracle.aurora.rdbms.Handle;
import oracle.aurora.rdbms.ClassHandle;
import oracle.aurora.rdbms.ResourceHandle;
import oracle.aurora.rdbms.Schema;
import oracle.aurora.rdbms.ObjectTypeChangedException;

import oracle.sql.BLOB;

/**
 * load me to the server this way:
 * 
 *    loadjava -verbose -oci8 -resolve -user scott/tiger TransitiveClosureDumper.java
 * 
 * call me from SQL this way:
 * 
 *    connect $1/$2
 *    create or replace package jaccelerator as
 *      function DUMP_TRANSITIVE_CLOSURE (name varchar2, schema varchar2) 
 *        return VARCHAR2;
 *      pragma restrict_references(DUMP_TRANSITIVE_CLOSURE, wnds);
 *    end;
 *    /
 *    show errors;
 *    create or replace package body jaccelerator as
 *      function DUMP_TRANSITIVE_CLOSURE (name varchar2, schema varchar2) 
 *        return VARCHAR2 as
 *        language java name 
 *        'oracle.jaccelerator.server.TransitiveClosureDumper.traverseReferences(java.lang.String, java.lang.String) return java.lang.String';
 *    end;
 *    /
 *    show errors;
 *    select JACCELERATOR.DUMP_TRANSITIVE_CLOSURE('java/lang/String', 'SYS') from dual;
 *          
 * 
 */
public class TransitiveClosureDumper extends Dumper {

  public TransitiveClosureDumper (String serverSideFileName) 
    throws IOException
  {
    needServerSideDump = true;
    classAttributesWriter = openFileWriter(serverSideFileName);
  }
    
  public TransitiveClosureDumper () 
    throws IOException
  {
    needServerSideDump = true;
    classAttributesWriter = new PrintWriter(System.out);
  }
    
  public TransitiveClosureDumper (BLOB classAttributesListBlob) 
    throws SQLException, IOException
  {
    needServerSideDump = false;
    classAttributesWriter = new PrintWriter(classAttributesListBlob.setBinaryStream(0L));
    System.out.println("-- classAttributesListBlob: " + classAttributesListBlob);
  }

  public static String traverseReferences (String root, String schema) 
    throws SQLException, IOException
  {
    String[] roots = { root };
    TransitiveClosureDumper visitor = 
      new TransitiveClosureDumper();
    return visitor.traverseReferences(roots, schema);
  }

  // public String traverseReferences (String[] roots, String schema, BLOB attributes) 
  //   throws SQLException, IOException
  // {
  //   String result = traverseReferences(roots, schema);
  //   FileInputStream classAttributesListFile = 
  //     new FileInputStream(classAttributesListFileName);
  //   copy(classAttributesListFile, attributes.getBinaryOutputStream());
  //   return result;
  // }

  protected boolean needServerSideDump = true;

  public String traverseReferences (String[] roots, String schemaName) 
    throws SQLException, IOException
  {
    Schema schema = Schema.lookup(schemaName);
    for (int i = 0; i < roots.length; i++) {
      String name = roots[i];
      ClassHandle h = Handle.lookupClass(name, schema);
      if (h == null) throw new SQLException("class to translate not found: " + name + " in " + schema);
      if (h.status() == Handle.INVALID) throw new SQLException("class to translate is invalid: " + name + " in " + schema);
      rootSet.put(name, h);
      traverseReferences(h);
    }
    return dumpClasses();
  }

  // String offset = "trace4";
  int systemClassesCount = 0;

  public void traverseReferences (ClassHandle handle) {
    // System.out.println("//" + handle);
    
    // *** handle.status() on the server is extremely slow. Let's just
    // *** assume that TC of valid classes is always valid.
    // if (handle.status() == Handle.INVALID) return;

    String name = handle.name();
    String schema = handle.schema().toString();
    
    boolean alreadyVisited = visit(handle, name, schema);

    if (alreadyVisited) ;
    else {
      boolean sysClass = schema.equals("SYS");
      // boolean sysTieBraker = sysClass 
      //   ? (name.startsWith("oracle/aurora") ||
      //      name.startsWith("java/security") ||
      //      name.startsWith("sun/security") ||
      //      name.startsWith("java/net") ||
      //      name.startsWith("java/awt"))
      //   : false;
      //if (sysTieBraker) ;
      //else 
      {
        if (sysClass) systemClassesCount++;

        visitedSchemas.put(schema, schema);
      
        // System.out.println("//" + offset + name);
        try {
          ClassHandle[] references = handle.referencedClassHandles();
          // String offset_saved = offset;
          // offset = offset + " ";
          for (int i = 0; i < references.length; i++) {
            traverseReferences(references[i]);
          }
          // offset = offset_saved;
        
        } catch (ObjectTypeChangedException e) {
          System.out.println("-- TransitiveClosureDumper.traverseReferences: " + e);
        }
      }
    }
  }

  protected Hashtable visitedClasses = new Hashtable();
  protected Hashtable visitedSchemas = new Hashtable();
  protected Hashtable rootSet = new Hashtable();

  public boolean visit (ClassHandle handle, String name, String schema) {
    boolean alreadyVisited = false;
    Object prev = visitedClasses.get(name);

    if (prev == null) {
      visitedClasses.put(name, handle);
    }
    if (prev instanceof ClassHandle) {
      ClassHandle prevHandle = (ClassHandle)prev;
      if (prevHandle == handle) {
        alreadyVisited = true;
      }
      else {
        Hashtable bag = new Hashtable();
        bag.put(prevHandle, prevHandle);
        bag.put(handle, handle);
        visitedClasses.put(name, bag);
      }
    }
    if (prev instanceof Hashtable) {
      Hashtable conflicts = ((Hashtable)prev);
      Object val = conflicts.get(handle);
      if (conflicts != null)
        alreadyVisited = true;
      else 
        conflicts.put(handle, handle);
    }
    return alreadyVisited;
  }

  String classAttributesListFileName = "class_attributes.txt";

  protected PrintWriter classAttributesWriter;

  protected void dumpClass (ClassHandle h)
       throws SQLException, IOException
  {
    dumpClassAttributes(h);        
    if (needServerSideDump) dumpClass(h, h.name(), h.schema().toString());
  }

  protected void dumpClassAttributes (ClassHandle h)
       throws SQLException, IOException
  {
    String digest = ClassProperties.digest(h);

    try {
      String definers = h.definers() ? "true" : "false";
      if (classAttributesWriter!= null) {
        String name = h.name();
        classAttributesWriter.println(name + " " + 
                                      (rootSet.get(name) != null ? "ROOT" : "REF") + " " + 
                                      h.schema() + " " + 
                                      "D_" + digest + " " + 
                                      definers);
      }
    } catch (ObjectTypeChangedException e) {
      System.out.println("-- TransitiveClosureDumper.dumpClassAttributes: " + e);
    }
  }
  
  public String dumpClasses () 
    throws SQLException, IOException
  {
    dumpClasses_before();    
    
    int totalCount = 0;
    int multidefNames = 0;

    if (visitedClasses.size() > 0) {
      classAttributesWriter.println("# Attributes of classes related to modules that must be re-ncomped ");
      Enumeration e = visitedClasses.elements();    
      while(e.hasMoreElements()) {
        Object elt = e.nextElement();
        if (elt instanceof ClassHandle) {
          ClassHandle h = (ClassHandle)elt;
          ++totalCount;
          dumpClass(h);
        }
        if (elt instanceof Hashtable) {
          ++multidefNames;
          Enumeration ee = ((Hashtable)elt).elements();
          while (ee.hasMoreElements()) {
            ClassHandle hh = (ClassHandle)ee.nextElement();
            ++totalCount;
            dumpClass(hh);
          }
        }
      }
    }

    dumpClasses_after();

    classAttributesWriter.close();

    StringBuffer schemas = new StringBuffer("schemas:");
    Enumeration e = visitedSchemas.keys();
    while (e.hasMoreElements()) schemas.append(" " + e.nextElement());
    return "total: " + totalCount + " multidefs: " + multidefNames + " " + schemas;
  }

  protected void dumpClasses_after  () 
    throws SQLException, IOException
  {}
  protected void dumpClasses_before () 
    throws SQLException, IOException
  {}

}


