//===-- JnjvmClassLoader.cpp - Jnjvm representation of a class loader ------===//
//
//                          The VMKit project
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#define DEBUG_VERBOSE_CLASS_LOADER_UNLOADING		1

#include <iostream>
#include <climits>
#include <cstdlib>

// for strrchr
#include <cstring>

// for dlopen and dlsym
#include <dlfcn.h> 

// for stat, S_IFMT and S_IFDIR
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

#include <string>

#include "debug.h"
#include "vmkit/Allocator.h"

#include "Classpath.h"
#include "ClasspathReflect.h"
#include "JavaClass.h"
#include "j3/JavaCompiler.h"
#include "JavaConstantPool.h"
#include "JavaString.h"
#include "JavaThread.h"
#include "JavaTypes.h"
#include "JavaUpcalls.h"
#include "Jnjvm.h"
#include "JnjvmClassLoader.h"
#include "LockedMap.h"
#include "Reader.h"
#include "Zip.h"


using namespace j3;
using namespace std;

typedef void (*static_init_t)(JnjvmClassLoader*);

const UTF8* JavaCompiler::InlinePragma = 0;
const UTF8* JavaCompiler::NoInlinePragma = 0;


JnjvmBootstrapLoader::JnjvmBootstrapLoader(vmkit::BumpPtrAllocator& Alloc,
                                           JavaCompiler* Comp, 
                                           bool dlLoad) : 
    JnjvmClassLoader(Alloc) {
  
	TheCompiler = Comp;
  
  javaTypes = new(allocator, "TypeMap") TypeMap(); 
  javaSignatures = new(allocator, "SignMap") SignMap();
  strings = new(allocator, "StringList") StringList();
  
  bootClasspathEnv = ClasslibBootEnv;
  libClasspathEnv = ClasslibLibEnv;
   
  upcalls = new(allocator, "Classpath") Classpath();
  bootstrapLoader = this;
   
  // Try to find if we have a pre-compiled rt.jar
  bool bootLoaded = false;
  if (dlLoad) {
    bootLoaded = Precompiled::Init(this);
  }
 
  if (!bootLoaded) {
    classes = new(allocator, "ClassMap") ClassMap();
    hashUTF8 = new(allocator, "UTF8Map") UTF8Map(allocator);
    // Analyze the boot classpath, we know bootstrap classes are not in the
    // executable.
    analyseClasspathEnv(bootClasspathEnv);

    // Allocate the interfaces array for array classes, so that VT construction
    // can use it right away.
    ClassArray::InterfacesArray =
      (Class**)allocator.Allocate(2 * sizeof(UserClass*), "Interface array");

    // Create the primitive classes.
    upcalls->OfChar = UPCALL_PRIMITIVE_CLASS(this, "char", 1);
    upcalls->OfBool = UPCALL_PRIMITIVE_CLASS(this, "boolean", 0);
    upcalls->OfShort = UPCALL_PRIMITIVE_CLASS(this, "short", 1);
    upcalls->OfInt = UPCALL_PRIMITIVE_CLASS(this, "int", 2);
    upcalls->OfLong = UPCALL_PRIMITIVE_CLASS(this, "long", 3);
    upcalls->OfFloat = UPCALL_PRIMITIVE_CLASS(this, "float", 2);
    upcalls->OfDouble = UPCALL_PRIMITIVE_CLASS(this, "double", 3);
    upcalls->OfVoid = UPCALL_PRIMITIVE_CLASS(this, "void", 0);
    upcalls->OfByte = UPCALL_PRIMITIVE_CLASS(this, "byte", 0);
  }
  
  // Create the primitive arrays.
  upcalls->ArrayOfChar = constructArray(asciizConstructUTF8("[C"),
                                        upcalls->OfChar);

  upcalls->ArrayOfByte = constructArray(asciizConstructUTF8("[B"),
                                        upcalls->OfByte);
  
  upcalls->ArrayOfInt = constructArray(asciizConstructUTF8("[I"),
                                       upcalls->OfInt);
  
  upcalls->ArrayOfBool = constructArray(asciizConstructUTF8("[Z"),
                                        upcalls->OfBool);
  
  upcalls->ArrayOfLong = constructArray(asciizConstructUTF8("[J"),
                                        upcalls->OfLong);
  
  upcalls->ArrayOfFloat = constructArray(asciizConstructUTF8("[F"),
                                         upcalls->OfFloat);
  
  upcalls->ArrayOfDouble = constructArray(asciizConstructUTF8("[D"),
                                          upcalls->OfDouble);
  
  upcalls->ArrayOfShort = constructArray(asciizConstructUTF8("[S"),
                                         upcalls->OfShort);
  
  // Fill the maps.
  primitiveMap[I_VOID] = upcalls->OfVoid;
  primitiveMap[I_BOOL] = upcalls->OfBool;
  primitiveMap[I_BYTE] = upcalls->OfByte;
  primitiveMap[I_CHAR] = upcalls->OfChar;
  primitiveMap[I_SHORT] = upcalls->OfShort;
  primitiveMap[I_INT] = upcalls->OfInt;
  primitiveMap[I_FLOAT] = upcalls->OfFloat;
  primitiveMap[I_LONG] = upcalls->OfLong;
  primitiveMap[I_DOUBLE] = upcalls->OfDouble;

  arrayTable[JavaArray::T_BOOLEAN - 4] = upcalls->ArrayOfBool;
  arrayTable[JavaArray::T_BYTE - 4] = upcalls->ArrayOfByte;
  arrayTable[JavaArray::T_CHAR - 4] = upcalls->ArrayOfChar;
  arrayTable[JavaArray::T_SHORT - 4] = upcalls->ArrayOfShort;
  arrayTable[JavaArray::T_INT - 4] = upcalls->ArrayOfInt;
  arrayTable[JavaArray::T_FLOAT - 4] = upcalls->ArrayOfFloat;
  arrayTable[JavaArray::T_LONG - 4] = upcalls->ArrayOfLong;
  arrayTable[JavaArray::T_DOUBLE - 4] = upcalls->ArrayOfDouble;
  
  JavaAttribute::annotationsAttribute =
    asciizConstructUTF8("RuntimeVisibleAnnotations");
  JavaAttribute::codeAttribute = asciizConstructUTF8("Code");
  JavaAttribute::exceptionsAttribute = asciizConstructUTF8("Exceptions");
  JavaAttribute::constantAttribute = asciizConstructUTF8("ConstantValue");
  JavaAttribute::lineNumberTableAttribute = asciizConstructUTF8("LineNumberTable");
  JavaAttribute::innerClassesAttribute = asciizConstructUTF8("InnerClasses");
  JavaAttribute::sourceFileAttribute = asciizConstructUTF8("SourceFile");
  JavaAttribute::signatureAttribute = asciizConstructUTF8("Signature");
  JavaAttribute::enclosingMethodAttribute = asciizConstructUTF8("EnclosingMethod");
  JavaAttribute::paramAnnotationsAttribute = asciizConstructUTF8("RuntimeVisibleParameterAnnotations");
  JavaAttribute::annotationDefaultAttribute = asciizConstructUTF8("AnnotationDefault");
 
  JavaCompiler::InlinePragma =
    asciizConstructUTF8("Lorg/vmmagic/pragma/Inline;");
  JavaCompiler::NoInlinePragma =
    asciizConstructUTF8("Lorg/vmmagic/pragma/NoInline;");

  initName = asciizConstructUTF8("<init>");
  initExceptionSig = asciizConstructUTF8("(Ljava/lang/String;)V");
  clinitName = asciizConstructUTF8("<clinit>");
  clinitType = asciizConstructUTF8("()V");
  runName = asciizConstructUTF8("run");
  prelib = asciizConstructUTF8("lib");
  postlib = asciizConstructUTF8(vmkit::System::GetDyLibExtension());
  mathName = asciizConstructUTF8("java/lang/Math");
  VMFloatName = asciizConstructUTF8("java/lang/VMFloat");
  VMDoubleName = asciizConstructUTF8("java/lang/VMDouble");
  stackWalkerName = asciizConstructUTF8("gnu/classpath/VMStackWalker");
  NoClassDefFoundError = asciizConstructUTF8("java/lang/NoClassDefFoundError");

#define DEF_UTF8(var) \
  var = asciizConstructUTF8(#var)
  
  DEF_UTF8(abs);
  DEF_UTF8(sqrt);
  DEF_UTF8(sin);
  DEF_UTF8(cos);
  DEF_UTF8(tan);
  DEF_UTF8(asin);
  DEF_UTF8(acos);
  DEF_UTF8(atan);
  DEF_UTF8(atan2);
  DEF_UTF8(exp);
  DEF_UTF8(log);
  DEF_UTF8(pow);
  DEF_UTF8(ceil);
  DEF_UTF8(floor);
  DEF_UTF8(rint);
  DEF_UTF8(cbrt);
  DEF_UTF8(cosh);
  DEF_UTF8(expm1);
  DEF_UTF8(hypot);
  DEF_UTF8(log10);
  DEF_UTF8(log1p);
  DEF_UTF8(sinh);
  DEF_UTF8(tanh);
  DEF_UTF8(finalize);
  DEF_UTF8(floatToRawIntBits);
  DEF_UTF8(doubleToRawLongBits);
  DEF_UTF8(intBitsToFloat);
  DEF_UTF8(longBitsToDouble);

#undef DEF_UTF8 
}

JnjvmClassLoader::JnjvmClassLoader(vmkit::BumpPtrAllocator& Alloc) :
	allocator(Alloc)
{
}

JnjvmClassLoader::JnjvmClassLoader(
	vmkit::BumpPtrAllocator& Alloc, JnjvmClassLoader& JCL, JavaObject* loader,
	VMClassLoader* vmdata, Jnjvm* VM) :
	allocator(Alloc)
{
  llvm_gcroot(loader, 0);
  llvm_gcroot(vmdata, 0);
  bootstrapLoader = JCL.bootstrapLoader;
  TheCompiler = bootstrapLoader->getCompiler()->Create(
	"Applicative loader",
	bootstrapLoader->getCompiler()->isCompilingGarbageCollector());
  
  hashUTF8 = new(allocator, "UTF8Map") UTF8Map(allocator);
  classes = new(allocator, "ClassMap") ClassMap();
  javaTypes = new(allocator, "TypeMap") TypeMap();
  javaSignatures = new(allocator, "SignMap") SignMap();
  strings = new(allocator, "StringList") StringList();

  vmdata->JCL = this;
  vmkit::Collector::objectReferenceNonHeapWriteBarrier(
      (gc**)&javaLoader, (gc*)loader);
  vm = VM;

  JavaMethod* meth = bootstrapLoader->upcalls->loadInClassLoader;
  loadClassMethod = 
    JavaObject::getClass(loader)->asClass()->lookupMethodDontThrow(
        meth->name, meth->type, false, true, &loadClass);
  assert(loadClass && "Loader does not have a loadClass function");
}

void JnjvmClassLoader::setCompiler(JavaCompiler* Comp) {
  // Set the new compiler.
  TheCompiler = Comp;
}

ClassBytes* JnjvmBootstrapLoader::openName(const UTF8* utf8) {
  ClassBytes* res = reinterpret_cast<ClassBytes*>(dlsym(vmkit::System::GetSelfHandle(),
      UTF8Buffer(utf8).toCompileName("_bytes")->cString()));
  if (res != NULL) return res;

  vmkit::ThreadAllocator threadAllocator;

  char* asciiz = (char*)threadAllocator.Allocate(utf8->size + 1);
  for (sint32 i = 0; i < utf8->size; ++i) 
    asciiz[i] = utf8->elements[i];
  asciiz[utf8->size] = 0;
  
  uint32 alen = utf8->size;
  
  for (std::vector<const char*>::iterator i = bootClasspath.begin(),
       e = bootClasspath.end(); i != e; ++i) {
    const char* str = *i;
    unsigned int strLen = strlen(str);
    char* buf = (char*)threadAllocator.Allocate(strLen + alen + 7);

    sprintf(buf, "%s%s.class", str, asciiz);
    res = Reader::openFile(this, buf);
    if (res != NULL) return res;
  }

  for (std::vector<ZipArchive*>::iterator i = bootArchives.begin(),
       e = bootArchives.end(); i != e; ++i) {
    
    ZipArchive* archive = *i;
    char* buf = (char*)threadAllocator.Allocate(alen + 7);
    sprintf(buf, "%s.class", asciiz);
    res = Reader::openZip(this, archive, buf);
    if (res != NULL) return res;
  }

  return NULL;
}


UserClass* JnjvmBootstrapLoader::internalLoad(const UTF8* name,
                                              bool doResolve,
                                              JavaString* strName) {
  ClassBytes* bytes = NULL;
  llvm_gcroot(strName, 0);

  UserCommonClass* cl = lookupClass(name);
  
  if (!cl) {
    bytes = openName(name);
    if (bytes != NULL) {
      cl = constructClass(name, bytes);
    }
  }
  
  if (cl) {
    assert(!cl->isArray());
    if (doResolve) cl->asClass()->resolveClass();
  }

  if (cl) {
    // If we don't have knowledge of the package containing this class,
    // add it to the set of packages defined in this classLoader
    UTF8Buffer buffer(name);
    const char * cname = buffer.cString();
    const char * slash = strrchr(cname, '/');
    if (slash) {
      int packagelen = slash - cname;
      const UTF8 * package = name->extract(hashUTF8, 0, packagelen);
      //classes->lock.lock();
      lock.lock();
      packages.insert(package);
      lock.unlock();
      //classes->lock.unlock();
    }
  }



  return (UserClass*)cl;
}

UserCommonClass* JnjvmClassLoader::internalLoadCreateClass(const UTF8* name, JavaString* strName)
{
  JavaObjectClass* jcl = 0;
  JavaObject* obj = 0;
  llvm_gcroot(strName, 0);
  llvm_gcroot(obj, 0);
  llvm_gcroot(jcl, 0);

  UserClass* forCtp = loadClass;
  if (strName == NULL) {
    strName = JavaString::internalToJava(name, vm);
  }

  obj = loadClassMethod->invokeJavaObjectVirtual(vm, forCtp, javaLoader, &strName);
  return JavaObjectClass::getClass(jcl = (JavaObjectClass*)obj);
}

UserClass* JnjvmClassLoader::internalLoad(const UTF8* name, bool doResolve,
                                          JavaString* strName) {
  llvm_gcroot(strName, 0);
  
  UserCommonClass* cl = lookupClass(name);
  
  if (!cl) {
    cl = internalLoadCreateClass(name, strName);
  }
  
  if (cl && doResolve && cl->isClass()) {
    cl->asClass()->resolveClass();
  }

  return (UserClass*)cl;
}

UserClass* JnjvmClassLoader::loadName(const UTF8* name, bool doResolve,
                                      bool doThrow, JavaString* strName) {
  
  llvm_gcroot(strName, 0);

  UserClass* cl = internalLoad(name, doResolve, strName);

  if (!cl && doThrow) {
    Jnjvm* vm = JavaThread::get()->getJVM();
    if (name->equals(bootstrapLoader->NoClassDefFoundError)) {
      fprintf(stderr, "Unable to load NoClassDefFoundError");
      abort();
    }
    if (TheCompiler->isStaticCompiling()) {
      fprintf(stderr, "Could not find %s, needed for static compiling\n",
              UTF8Buffer(name).cString());
      abort();
    }
    vm->noClassDefFoundError(name);
  }

  ensureCached(cl);

  return cl;
}

void JnjvmClassLoader::ensureCached(UserCommonClass* cl) {
  if (cl && cl->classLoader != this) {
    classes->lock.lock();
    ClassMap::iterator End = classes->map.end();
    ClassMap::iterator I = classes->map.find(cl->name);
    if (I == End)
      classes->map.insert(std::make_pair(cl->name, cl));
    classes->lock.unlock();
  }
}


const UTF8* JnjvmClassLoader::lookupComponentName(const UTF8* name,
                                                  UTF8* holder,
                                                  bool& prim) {
  uint32 len = name->size;
  uint32 start = 0;
  uint32 origLen = len;
  
  while (true) {
    --len;
    if (len == 0) {
      return 0;
    } else {
      ++start;
      if (name->elements[start] != I_TAB) {
        if (name->elements[start] == I_REF) {
          uint32 size = (uint32)name->size;
          if ((size == (start + 1)) || (size == (start + 2)) ||
              (name->elements[start + 1] == I_TAB) ||
              (name->elements[origLen - 1] != I_END_REF)) {
            return 0;
          } else {
            const uint16* buf = &(name->elements[start + 1]);
            uint32 bufLen = len - 2;
            const UTF8* componentName = hashUTF8->lookupReader(buf, bufLen);
            if (!componentName && holder) {
              holder->size = len - 2;
              for (uint32 i = 0; i < len - 2; ++i) {
                holder->elements[i] = name->elements[start + 1 + i];
              }
              componentName = holder;
            }
            return componentName;
          }
        } else {
          uint16 cur = name->elements[start];
          if ((cur == I_BOOL || cur == I_BYTE ||
               cur == I_CHAR || cur == I_SHORT ||
               cur == I_INT || cur == I_FLOAT || 
               cur == I_DOUBLE || cur == I_LONG)
              && ((uint32)name->size) == start + 1) {
            prim = true;
          }
          return 0;
        }
      }
    }
  }

  return 0;
}

UserCommonClass* JnjvmClassLoader::lookupClassOrArray(const UTF8* name) {
  UserCommonClass* temp = lookupClass(name);
  if (temp) return temp;

  if (this != bootstrapLoader) {
    temp = bootstrapLoader->lookupClassOrArray(name);
    ensureCached(temp);
    if (temp) return temp;
  }
  
  
  if (name->elements[0] == I_TAB) {
    bool prim = false;
    const UTF8* componentName = lookupComponentName(name, 0, prim);
    if (prim) return constructArray(name);
    if (componentName) {
      UserCommonClass* temp = lookupClass(componentName);
      if (temp) return constructArray(name);
    }
  }

  return 0;
}

UserCommonClass* JnjvmClassLoader::loadClassFromUserUTF8(const UTF8* name,
                                                         bool doResolve,
                                                         bool doThrow,
                                                         JavaString* strName) {
  llvm_gcroot(strName, 0);
  
  if (name->size == 0) {
    return 0;
  } else if (name->elements[0] == I_TAB) {
    vmkit::ThreadAllocator threadAllocator;
    bool prim = false;
    UTF8* holder = (UTF8*)threadAllocator.Allocate(
        sizeof(UTF8) + name->size * sizeof(uint16));
    if (!holder) return 0;
    
    const UTF8* componentName = lookupComponentName(name, holder, prim);
    if (prim) return constructArray(name);
    if (componentName) {
      UserCommonClass* temp = loadName(componentName, doResolve, doThrow, NULL);
      ensureCached(temp);
      if (temp) return constructArray(name);
    }
  } else {
    return loadName(name, doResolve, doThrow, strName);
  }

  return NULL;
}

UserCommonClass* JnjvmClassLoader::loadClassFromAsciiz(const char* asciiz,
                                                       bool doResolve,
                                                       bool doThrow) {
  const UTF8* name = hashUTF8->lookupAsciiz(asciiz);
  vmkit::ThreadAllocator threadAllocator;
  if (!name) name = bootstrapLoader->hashUTF8->lookupAsciiz(asciiz);
  if (!name) {
    uint32 size = strlen(asciiz);
    UTF8* temp = (UTF8*)threadAllocator.Allocate(
        sizeof(UTF8) + size * sizeof(uint16));
    temp->size = size;

    for (uint32 i = 0; i < size; ++i) {
      temp->elements[i] = asciiz[i];
    }
    name = temp;
  }
  
 
  return loadClassFromUserUTF8(name, doResolve, doThrow, NULL);
}


UserCommonClass* 
JnjvmClassLoader::loadClassFromJavaString(JavaString* str, bool doResolve,
                                          bool doThrow) {

  llvm_gcroot(str, 0);
  vmkit::ThreadAllocator allocator; 
  UTF8* name = (UTF8*)allocator.Allocate(sizeof(UTF8) + str->count * sizeof(uint16));

  name->size = str->count;
  for (sint32 i = 0; i < str->count; ++i) {
    uint16 cur = ArrayUInt16::getElement(JavaString::getValue(str), str->offset + i);
    if (cur == '.') name->elements[i] = '/';
    else if (cur == '/') {
      return 0;
    }
    else name->elements[i] = cur;
  }

  return loadClassFromUserUTF8(name, doResolve, doThrow, str);
}

UserCommonClass* JnjvmClassLoader::lookupClassFromJavaString(JavaString* str) {
  
  const ArrayUInt16* value = NULL;
  llvm_gcroot(str, 0);
  llvm_gcroot(value, 0);
  value = JavaString::getValue(str);
  vmkit::ThreadAllocator allocator; 
  
  UTF8* name = (UTF8*)allocator.Allocate(sizeof(UTF8) + str->count * sizeof(uint16));
  name->size = str->count;
  for (sint32 i = 0; i < str->count; ++i) {
    uint16 cur = ArrayUInt16::getElement(value, str->offset + i);
    if (cur == '.') name->elements[i] = '/';
    else name->elements[i] = cur;
  }
  UserCommonClass* cls = lookupClass(name);
  return cls;
}

UserCommonClass* JnjvmClassLoader::lookupClass(const UTF8* utf8) {
  classes->lock.lock();
  UserCommonClass* cl = classes->map.lookup(utf8);
  classes->lock.unlock();
  return cl;
}

UserCommonClass* JnjvmClassLoader::loadBaseClass(const UTF8* name,
                                                 uint32 start, uint32 len) {
  
  if (name->elements[start] == I_TAB) {
    UserCommonClass* baseClass = loadBaseClass(name, start + 1, len - 1);
    JnjvmClassLoader* loader = baseClass->classLoader;
    const UTF8* arrayName = name->extract(loader->hashUTF8, start, start + len);
    return loader->constructArray(arrayName, baseClass);
  } else if (name->elements[start] == I_REF) {
    const UTF8* componentName = name->extract(hashUTF8,
                                              start + 1, start + len - 1);
    UserCommonClass* cl = loadName(componentName, false, true, NULL);
    return cl;
  } else {
    Classpath* upcalls = bootstrapLoader->upcalls;
    UserClassPrimitive* prim = 
      UserClassPrimitive::byteIdToPrimitive(name->elements[start], upcalls);
    assert(prim && "No primitive found");
    return prim;
  }
}


UserClassArray* JnjvmClassLoader::constructArray(const UTF8* name) {
  ClassArray* res = (ClassArray*)lookupClass(name);
  if (res) return res;

  UserCommonClass* cl = loadBaseClass(name, 1, name->size - 1);
  assert(cl && "no base class for an array");
  JnjvmClassLoader* ld = cl->classLoader;
  res = ld->constructArray(name, cl);

  ensureCached(res);
  return res;
}

UserClass* JnjvmClassLoader::constructClass(const UTF8* name,
                                            ClassBytes* bytes) {
  JavaObject* excp = NULL;
  llvm_gcroot(excp, 0);
  UserClass* res = NULL;
  lock2.lock();
  classes->lock.lock();
  res = (UserClass*) classes->map.lookup(name);
  classes->lock.unlock();
  if (res == NULL) {
    TRY {
      const UTF8* internalName = readerConstructUTF8(name->elements, name->size);
      res = new(allocator, "Class") UserClass(this, internalName, bytes);
      res->readClass();
      res->makeVT();
      getCompiler()->resolveVirtualClass(res);
      getCompiler()->resolveStaticClass(res);
      classes->lock.lock();
      assert(res->getDelegatee() == NULL);
      assert(res->getStaticInstance() == NULL);
      assert(classes->map.lookup(internalName) == NULL);
      classes->map[internalName] = res;
      classes->lock.unlock();
    } CATCH {
      excp = JavaThread::get()->pendingException;
      JavaThread::get()->clearException();    
    } END_CATCH;
  }
  lock2.unlock();
  if (excp != NULL) {
    JavaThread::get()->throwException(excp);
  }

  if (res->super == NULL) {
    // java.lang.Object just got created, initialise VTs of arrays.
    ClassArray::initialiseVT(res);
  }
  return res;
}

UserClassArray* JnjvmClassLoader::constructArray(const UTF8* name,
                                                 UserCommonClass* baseClass) {
  assert(baseClass && "constructing an array class without a base class");
  assert(baseClass->classLoader == this && 
         "constructing an array with wrong loader");
  UserClassArray* res = 0;
  classes->lock.lock();
  res = (UserClassArray*) classes->map.lookup(name);
  if (res == NULL) {
    const UTF8* internalName = readerConstructUTF8(name->elements, name->size);
    res = new(allocator, "Array class") UserClassArray(this, internalName,
                                                       baseClass);
    classes->map.insert(std::make_pair(internalName, res));

  }
  classes->lock.unlock();
  return res;
}

Typedef* JnjvmClassLoader::internalConstructType(const UTF8* name) {
  short int cur = name->elements[0];
  Typedef* res = 0;
  switch (cur) {
    case I_TAB :
      res = new(allocator, "ArrayTypedef") ArrayTypedef(name);
      break;
    case I_REF :
      res = new(allocator, "ObjectTypedef") ObjectTypedef(name, hashUTF8);
      break;
    default :
      UserClassPrimitive* cl = 
        bootstrapLoader->getPrimitiveClass((char)name->elements[0]);
      assert(cl && "No primitive");
      bool unsign = (cl == bootstrapLoader->upcalls->OfChar || 
                     cl == bootstrapLoader->upcalls->OfBool);
      res = new(allocator, "PrimitiveTypedef") PrimitiveTypedef(name, cl,
                                                                unsign, cur);
  }
  return res;
}


Typedef* JnjvmClassLoader::constructType(const UTF8* name) {
  javaTypes->lock.lock();
  Typedef* res = javaTypes->map.lookup(name);
  if (res == 0) {
    res = internalConstructType(name);
    javaTypes->map[name] = res;
  }
  javaTypes->lock.unlock();
  return res;
}

static void typeError(const UTF8* name, short int l) {
  if (l != 0) {
    fprintf(stderr, "wrong type %d in %s", l, UTF8Buffer(name).cString());
  } else {
    fprintf(stderr, "wrong type %s", UTF8Buffer(name).cString());
  }
  abort();
}


static bool analyseIntern(const UTF8* name, uint32 pos, uint32 meth,
                          uint32& ret) {
  short int cur = name->elements[pos];
  switch (cur) {
    case I_PARD :
      ret = pos + 1;
      return true;
    case I_BOOL :
      ret = pos + 1;
      return false;
    case I_BYTE :
      ret = pos + 1;
      return false;
    case I_CHAR :
      ret = pos + 1;
      return false;
    case I_SHORT :
      ret = pos + 1;
      return false;
    case I_INT :
      ret = pos + 1;
      return false;
    case I_FLOAT :
      ret = pos + 1;
      return false;
    case I_DOUBLE :
      ret = pos + 1;
      return false;
    case I_LONG :
      ret = pos + 1;
      return false;
    case I_VOID :
      ret = pos + 1;
      return false;
    case I_TAB :
      if (meth == 1) {
        pos++;
      } else {
        while (name->elements[++pos] == I_TAB) {}
        analyseIntern(name, pos, 1, pos);
      }
      ret = pos;
      return false;
    case I_REF :
      if (meth != 2) {
        while (name->elements[++pos] != I_END_REF) {}
      }
      ret = pos + 1;
      return false;
    default :
      typeError(name, cur);
  }
  return false;
}

Signdef* JnjvmClassLoader::constructSign(const UTF8* name) {
  javaSignatures->lock.lock();
  Signdef* res = javaSignatures->map.lookup(name);
  if (res == 0) {
    std::vector<Typedef*> buf;
    uint32 len = (uint32)name->size;
    uint32 pos = 1;
    uint32 pred = 0;

    while (pos < len) {
      pred = pos;
      bool end = analyseIntern(name, pos, 0, pos);
      if (end) break;
      else {
        buf.push_back(constructType(name->extract(hashUTF8, pred, pos)));
      } 
    }
  
    if (pos == len) {
      typeError(name, 0);
    }
  
    analyseIntern(name, pos, 0, pred);

    if (pred != len) {
      typeError(name, 0);
    }
    
    Typedef* ret = constructType(name->extract(hashUTF8, pos, pred));
    
    res = new(allocator, buf.size()) Signdef(name, this, buf, ret);

    javaSignatures->map[name] = res;
  }
  javaSignatures->lock.unlock();
  return res;
}

JnjvmClassLoader* JnjvmClassLoader::createForJavaObject(
  Jnjvm* vm, JavaObject* loader, VMClassLoader**vmdata)
{
	llvm_gcroot(loader, 0);

    *vmdata = VMClassLoader::allocate();
    vmkit::BumpPtrAllocator* A = new vmkit::BumpPtrAllocator();

    JnjvmClassLoader* JCL = new(*A, "Class loader")
    	JnjvmClassLoader(*A, *vm->bootstrapLoader, loader, *vmdata, vm);

    Classpath* upcalls = vm->bootstrapLoader->upcalls;
    upcalls->vmdataClassLoader->setInstanceObjectField(loader, *vmdata);
    return JCL;
}

JnjvmClassLoader*
JnjvmClassLoader::getJnjvmLoaderFromJavaObject(JavaObject* loader, Jnjvm* vm) {
  
  VMClassLoader* vmdata = 0;
  
  llvm_gcroot(loader, 0);
  llvm_gcroot(vmdata, 0);
  
  if (loader == NULL)
	  return vm->bootstrapLoader;
 
  JnjvmClassLoader* JCL = 0;
  Classpath* upcalls = vm->bootstrapLoader->upcalls;
  vmdata = 
    (VMClassLoader*)(upcalls->vmdataClassLoader->getInstanceObjectField(loader));
  
  // If the vmdata field isn't set yet, or it's set to something other than
  // a VMClassLoader (which happens in the OpenJDK port), then initialize
  // the field now.
  if (vmdata == NULL) {
    JavaObject::acquire(loader);
    vmdata = 
      (VMClassLoader*)(upcalls->vmdataClassLoader->getInstanceObjectField(loader));
    if (!vmdata || !VMClassLoader::isVMClassLoader(vmdata)) {
      JCL = JnjvmClassLoader::createForJavaObject(vm, loader, &vmdata);
    }
    JavaObject::release(loader);
  }
  else if (!VMClassLoader::isVMClassLoader(vmdata)) {
	JavaObject::acquire(loader);
	JCL = JnjvmClassLoader::createForJavaObject(vm, loader, &vmdata);
	JavaObject::release(loader);
  } else {
    JCL = vmdata->getClassLoader();
    assert(JCL->javaLoader == loader);
  }

  if (!JCL) {
	  assert( vmdata->getClassLoader() == JCL && "Loader is not equal to stored value");
	  fprintf(stderr, "Error in method %s, %d because condition 1: %d\n",
		__FILE__, __LINE__, (vmdata == NULL || !VMClassLoader::isVMClassLoader(vmdata)));
  }
  return JCL;
}

const UTF8* JnjvmClassLoader::asciizConstructUTF8(const char* asciiz) {
  return hashUTF8->lookupOrCreateAsciiz(asciiz);
}

const UTF8* JnjvmClassLoader::readerConstructUTF8(const uint16* buf,
                                                  uint32 size) {
  return hashUTF8->lookupOrCreateReader(buf, size);
}

JnjvmClassLoader::~JnjvmClassLoader() {

  if (vm) {
    vm->removeFrameInfos(TheCompiler);
  }

  if (classes) {
    classes->~ClassMap();
    allocator.Deallocate(classes);
    classes = NULL;
  }

  if (hashUTF8) {
    hashUTF8->~UTF8Map();
    allocator.Deallocate(hashUTF8);
    hashUTF8 = NULL;
  }

  if (javaTypes) {
    javaTypes->~TypeMap();
    allocator.Deallocate(javaTypes);
    javaTypes = NULL;
  }

  if (javaSignatures) {
    javaSignatures->~SignMap();
    allocator.Deallocate(javaSignatures);
    javaSignatures = NULL;
  }

  for (std::vector<void*>::iterator
    i = nativeLibs.begin(), e = nativeLibs.end(); i != e; ++i)
  {
    dlclose(*i);
  }
  nativeLibs.clear();

  vm = NULL;

  delete TheCompiler;
  TheCompiler = NULL;

  // Don't delete the allocator. The caller of this method must
  // delete it after the current object is deleted.
}


JnjvmBootstrapLoader::~JnjvmBootstrapLoader() {
}

JavaString** JnjvmClassLoader::UTF8ToStr(const UTF8* val) {
  JavaString* res = NULL;
  llvm_gcroot(res, 0);
  res = vm->internalUTF8ToStr(val);
  return strings->addString(this, res);
}

JavaString** JnjvmBootstrapLoader::UTF8ToStr(const UTF8* val) {
  JavaString* res = NULL;
  llvm_gcroot(res, 0);
  Jnjvm* vm = JavaThread::get()->getJVM();
  res = vm->internalUTF8ToStr(val);
  return strings->addString(this, res);
}

void JnjvmBootstrapLoader::analyseClasspathEnv(const char* str) {
  ClassBytes* bytes = NULL;
  vmkit::ThreadAllocator threadAllocator;
  if (str != 0) {
    unsigned int len = strlen(str);
    char* buf = (char*)threadAllocator.Allocate((len + 1) * sizeof(char));
    const char* cur = str;
    int top = 0;
    char c = 1;
    while (c != 0) {
      while (((c = cur[top]) != 0) && c != Jnjvm::envSeparator[0]) {
        top++;
      }
      if (top != 0) {
        memcpy(buf, cur, top);
        buf[top] = 0;
        char* rp = (char*)threadAllocator.Allocate(PATH_MAX);
        memset(rp, 0, PATH_MAX);
        rp = realpath(buf, rp);
        if (rp && rp[PATH_MAX - 1] == 0 && strlen(rp) != 0) {
          struct stat st;
          stat(rp, &st);
          if ((st.st_mode & S_IFMT) == S_IFDIR) {
            unsigned int len = strlen(rp);
            char* temp = (char*)allocator.Allocate(len + 2, "Boot classpath");
            memcpy(temp, rp, len);
            temp[len] = Jnjvm::dirSeparator[0];
            temp[len + 1] = 0;
            bootClasspath.push_back(temp);
          } else {
            bytes = Reader::openFile(this, rp);
            if (bytes) {
              ZipArchive *archive = new(allocator, "ZipArchive")
                ZipArchive(bytes, allocator);
              if (archive) {
                bootArchives.push_back(archive);
              }
            }
          }
        } 
      }
      cur = cur + top + 1;
      top = 0;
    }
  }
}

// constructArrayName can allocate the UTF8 directly in the classloader
// memory because it is called by safe places, ie only valid names are
// created.
const UTF8* JnjvmClassLoader::constructArrayName(uint32 steps,
                                                 const UTF8* className) {
  uint32 len = className->size;
  uint32 pos = steps;
  bool isTab = (className->elements[0] == I_TAB ? true : false);
  uint32 n = steps + len + (isTab ? 0 : 2);
  vmkit::ThreadAllocator allocator;
  uint16* buf = (uint16*)allocator.Allocate(n * sizeof(uint16));
    
  for (uint32 i = 0; i < steps; i++) {
    buf[i] = I_TAB;
  }

  if (!isTab) {
    ++pos;
    buf[steps] = I_REF;
  }

  for (uint32 i = 0; i < len; i++) {
    buf[pos + i] = className->elements[i];
  }

  if (!isTab) {
    buf[n - 1] = I_END_REF;
  }

  const UTF8* res = readerConstructUTF8(buf, n);
  return res;
}

word_t JnjvmClassLoader::loadInLib(const char* buf, bool& j3) {
  // Check 'self'.  Should only check our process, however it's possible native
  // code dlopen'd something itself (with RTLD_GLOBAL; OpenJDK does this).
  // To handle this, we search both ourselves and the libraries we loaded.
  word_t sym =
    (word_t)TheCompiler->loadMethod(vmkit::System::GetSelfHandle(), buf);

  // Search loaded libraries as well, both as fallback and to determine
  // whether or not the symbol in question is defined by vmkit.
  word_t symFromLib = 0;
  for (std::vector<void*>::iterator i = nativeLibs.begin(),
      e = nativeLibs.end(); i!= e; ++i) {
    symFromLib = (word_t)TheCompiler->loadMethod((*i), buf);
    if (symFromLib) break;
  }

  if (sym) {
    // Always use the definition from 'self', if it exists.
    // Furthermore, claim it's defined in j3 iff it wasn't found in one of our
    // libraries.  This might be wrong if we do a lookup on a symbol that's
    // neither in vmkit nor a VM-loaded library (but /is/ in a different library
    // that has been dlopen'd by native code), but that should never
    // be called from java code anyway.
    j3 = (sym != symFromLib);
    return sym;
  }

  // Otherwise return what we found in the libraries, if anything
  if (symFromLib) return symFromLib;

  if (this != bootstrapLoader)
    return bootstrapLoader->loadInLib(buf, j3);

  return 0;
}

void* JnjvmClassLoader::loadLib(const char* buf) {
  void* handle = dlopen(buf, RTLD_LAZY | RTLD_LOCAL);
  if (handle) nativeLibs.push_back(handle);
  return handle;
}

char* JnjvmClassLoader::getErrorMessage() {
  return dlerror();
}

word_t JnjvmClassLoader::loadInLib(const char* name, void* handle) {
  return (word_t)TheCompiler->loadMethod(handle, name);
}

word_t JnjvmClassLoader::nativeLookup(JavaMethod* meth, bool& j3,
                                        char* buf) {

  // Is this method defined via registerNatives()?
  // If so, use that definition.
  word_t res = getRegisteredNative(meth);
  if (res != 0) return res;

  // Otherwise, try to resolve the method with a symbol lookup
  // First as the base method
  meth->jniConsFromMeth(buf);
  res = loadInLib(buf, j3);
  if (!res) {
    // Failing that, try the overloaded symbol name
    meth->jniConsFromMethOverloaded(buf);
    res = loadInLib(buf, j3);
  }
  return res;
}


JavaString** StringList::addString(JnjvmClassLoader* JCL, JavaString* obj, bool hasTheLock) {
  llvm_gcroot(obj, 0);
  if (!hasTheLock)
	  JCL->lockForStrings.lock();
  if (length == MAXIMUM_STRINGS) {
    StringList* next = new(JCL->allocator, "StringList") StringList();
    next->prev = this;
    JCL->strings = next;
    JavaString** res = next->addString(JCL, obj, true);
    if (!hasTheLock)
    	JCL->lockForStrings.unlock();
    return res;
  } else {
    vmkit::Collector::objectReferenceNonHeapWriteBarrier(
        (gc**)&(strings[length]), (gc*)obj);
    JavaString** res = &strings[length++];
    if (!hasTheLock)
    	JCL->lockForStrings.unlock();
    return res;
  }
}

void JnjvmClassLoader::registerNative(JavaMethod * meth, word_t fnPtr) {
  nativesLock.lock();
  // Don't support multiple levels of registerNatives
  assert(registeredNatives.find(meth) == registeredNatives.end());

  registeredNatives[meth] = fnPtr;
  nativesLock.unlock();
}

word_t JnjvmClassLoader::getRegisteredNative(const JavaMethod * meth) {
  nativesLock.lock();
  word_t res = registeredNatives[meth];
  nativesLock.unlock();
  return res;
}

ArrayObject* JnjvmBootstrapLoader::getBootPackages(Jnjvm * vm) {
  ArrayObject* res = 0;
  llvm_gcroot(res, 0);

  lock.lock();
  res = (ArrayObject*)vm->upcalls->ArrayOfString->doNew(packages.size(), vm);
  std::set<const UTF8*>::const_iterator I = packages.begin(),
                                        E = packages.end();
  for (int i = 0; I != E; ++I, ++i) {
    ArrayObject::setElement(res, *UTF8ToStr(*I), i);
  }

  lock.unlock();

  return res;
}
