/* -*- Mode: C; c-file-style: "gnu"; indent-tabs-mode:nil -*-
   classpath.c -- stuff for dealing with classpaths.
   Created: Chris Toshok <toshok@hungry.com>, 26-Jul-1997
 */
/*
  This file is part of Japhar, the GNU Virtual Machine for Java Bytecodes.
  Japhar is a project of The Hungry Programmers, GNU, and OryxSoft.

  Copyright (C) 1997, 1998, 1999 The Hungry Programmers

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Library General Public
  License as published by the Free Software Foundation; either
  version 2 of the License, or (at your option) any later version.

  This library 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
  Library General Public License for more details.

  You should have received a copy of the GNU Library General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "runtimeint.h"
#include "compat.h"
#include "arch.h"

static HVMClasspathEntryType
get_entry_type(const char *path)
{
  PRFileInfo info;
  
  if (PR_SUCCESS == PR_GetFileInfo(path, &info))
    {
      if (PR_FILE_DIRECTORY == info.type)
        return eClasspathEntryDirectory;
      else if (PR_FILE_FILE == info.type)
        {
          char *period = PL_strrchr(path, '.');
                  
          if (!period) /* punt */
            return eClasspathEntryUnknown;
          else
            {
              if (!PL_strcasecmp(period, ".jar"))
                return eClasspathEntryJAR;
              else if (!PL_strcasecmp(period, ".zip"))
                return eClasspathEntryZIP;
              else
                return eClasspathEntryUnknown;
            }
        }
      else
        return eClasspathEntryUnknown;
    }
  else
    {
      return eClasspathEntryUnknown;
    }
}

static int
jar_or_zip_select(const PRDirEntry *entry)
{
  /* Linux glibc6 and Solaris is missing struct dirent.d_namlen */
  int namlen = -1;

  PR_ASSERT(NULL != entry);
  namlen = PL_strlen(entry->name);

  if (namlen > 4)
    {
      if (!PL_strcasecmp(entry->name + namlen - 4, ".jar"))
        return 1;
      else if (!PL_strcasecmp(entry->name + namlen - 4, ".zip"))
        return 1;
    }

  return 0;
}

static void
grovel_in_path(HVMClasspath *cp, const char *path)
{
  PRDirEntry **namelist = NULL;
  int num_entries;
  int i;

  num_entries = Scandir(path, &namelist, jar_or_zip_select, Alphasort);

  if (-1 == num_entries) {
    fprintf(stderr, "Warning: directory %s does not exist.\n", path);
    return;
  }

  if (0 > num_entries) {
    fprintf(stderr, "Warning: unable to find any Java classes at %s.\n", path);
    return;
  }

  for (i = 0; i < num_entries; i ++)
    {
      char *cp_entry = PR_smprintf("%s" DIR_SEPARATOR "%s", 
                                   path,
                                   namelist[i]->name);
      if (PR_FAILURE == HVM_ClasspathAddPath(cp, cp_entry))
        {
          free(cp_entry);
          return; /* XXX silently lose */
        }

      free(cp_entry);
    }

  if (NULL != namelist)
    {
      for (i = 0; i < num_entries; i ++) PR_DELETE(namelist[i]);
      PR_DELETE(namelist);
    }
}

PR_IMPLEMENT( HVMClasspath* )
HVM_ClasspathGetSystemClasspath()
{
  HVMClasspath *new_cp = HVM_ClasspathNew();

  if (NULL == new_cp)
    return new_cp;

  grovel_in_path(new_cp, CLASSZIPDIR);

  return new_cp;
}

PR_IMPLEMENT( HVMClasspath* )
HVM_ClasspathGetUserClasspath()
{
  HVMClasspath *new_cp;
  char *classpath_env = PR_GetEnv("CLASSPATH");
  if (NULL == classpath_env)
    return NULL;

  new_cp = HVM_ClasspathNew();
  if (NULL == new_cp)
    return NULL;

  HVM_ClasspathAddPath(new_cp, classpath_env);
  return new_cp;
}

PR_IMPLEMENT( HVMClasspath* )
HVM_ClasspathNew()
{
  HVMClasspath *new_cp = PR_CALLOC(sizeof(HVMClasspath));
  if (NULL == new_cp)
    return NULL;

  new_cp->alloc_entries = 10;
  new_cp->entries = (HVMClasspathEntry*)PR_Calloc(new_cp->alloc_entries, sizeof(HVMClasspathEntry));
  if (NULL == new_cp->entries)
    {
      free (new_cp);
      return NULL;
    }

  return new_cp;
}

static void
add_entry(HVMClasspath *cp, const char *entry)
{
  HVMClasspathEntry *new_entry;

  if (cp->num_entries == cp->alloc_entries - 1)
    {
      /* realloc array bigger */
      cp->alloc_entries *= 2;
      cp->entries = PR_REALLOC(cp->entries, cp->alloc_entries);
    }
  new_entry = &cp->entries[ cp->num_entries++ ];
  new_entry->path = PL_strdup(entry);
  new_entry->type = get_entry_type(entry);

  if (new_entry->type == eClasspathEntryZIP ||
      new_entry->type == eClasspathEntryJAR)
    {
      /* XXX do we still need something like O_BINARY for windows? */
      new_entry->zip.fd = PR_Open(new_entry->path, PR_RDONLY, 0);

      if (NULL != new_entry->zip.fd)
        if (read_zip_archive(&new_entry->zip) != 0)
          {
            PR_Close(new_entry->zip.fd);
            new_entry->zip.fd = NULL;
          }
    }
}

static void
remove_entry(HVMClasspath *cp, const char *entry)
{
  int i;

  for (i = 0; i < cp->num_entries; i ++)
    {
      HVMClasspathEntry *cp_entry = &cp->entries[ i ];
      if (!PL_strcmp(entry, cp_entry->path))
        {
          /* do what we need to do to clean up the entry */
          if (cp_entry->type == eClasspathEntryZIP ||
              cp_entry->type == eClasspathEntryJAR)
            {
              /* XXX more stuff needed here */
              
              PR_Close(cp_entry->zip.fd);
              cp_entry->zip.fd = NULL;
            }
          
          /* then nuke it */
          memmove(cp->entries + i,
                  cp->entries + i + 1,
                  (cp->num_entries - i - 1) * sizeof(HVMClasspathEntry));
          cp->num_entries--;
          
          break;
        }
    }
}

static void
iterate_over_entries(HVMClasspath *cp, const char *spec,
                     void (*func)(HVMClasspath *cp, const char *entry))
{
  char *class_path_entry;
  char *colon;
  int i;

  class_path_entry = PL_strdup(spec);

  i = 0;
  while (class_path_entry && class_path_entry[0] != '\0')
    {
      colon = PL_strstr(class_path_entry, PATH_SEPARATOR);

      if (colon)
        *colon = '\0';
          
      func(cp, class_path_entry);
      
      if (colon)
        {
          *colon = PATH_SEPARATOR[0];
          class_path_entry = colon + PL_strlen(PATH_SEPARATOR);
        }
      else
        {
          class_path_entry = NULL;
        }
      i++;
    }

  free(class_path_entry);
}

PR_IMPLEMENT( PRStatus )
HVM_ClasspathRemovePath(HVMClasspath *cp, const char *spec)
{
  iterate_over_entries(cp, spec, remove_entry);

  return PR_SUCCESS;
}

PR_IMPLEMENT( PRStatus )
HVM_ClasspathAddPath(HVMClasspath *cp, const char *spec)
{
  iterate_over_entries(cp, spec, add_entry);

  return PR_SUCCESS;
}

PR_IMPLEMENT( HVMClasspath* )
HVM_ClasspathConcat(HVMClasspath *cp1, HVMClasspath *cp2)
{
  int i;

  if (cp1 == NULL)
    return cp2;
  if (cp2 == NULL)
    return cp1;

  for (i = 0; i < cp2->num_entries; i ++)
    {
      add_entry(cp1, cp2->entries[i].path);
    }

  HVM_ClasspathDestroy(cp2);

  return cp1;
}

PR_IMPLEMENT( void )
HVM_ClasspathForEachEntry(HVMClasspath *cp,
                          HVMClasspathEntryFunc func,
                          void *arg)
{
  int i;

  for (i = 0; i < cp->num_entries; i ++)
    func(&cp->entries[i], arg);
}

PR_IMPLEMENT( char* )
HVM_ClasspathToString(HVMClasspath *cp)
{
  int i;
  int length = 0;
  char *cp_str;

  for (i = 0; i < cp->num_entries; i ++)
    {
      length += PL_strlen(cp->entries[i].path);
      if (i < cp->num_entries - 1)
        length += PL_strlen(PATH_SEPARATOR);
    }

  cp_str = PR_CALLOC(length);
  for (i = 0; i < cp->num_entries; i ++)
    {
      PL_strcat(cp_str, cp->entries[i].path);
      if (i < cp->num_entries - 1)
        PL_strcat(cp_str, PATH_SEPARATOR);
    }

  return cp_str;
}

PR_IMPLEMENT( PRStatus )
HVM_ClasspathDestroy(HVMClasspath *cp)
{
  int i;

  for (i = 0; i < cp->num_entries; i ++)
    {
      PR_DELETE(cp->entries[i].path);
      if (cp->entries[i].type == eClasspathEntryZIP ||
          cp->entries[i].type == eClasspathEntryJAR)
        {
          if (cp->entries[i].zip.central_directory)
            {
              PR_DELETE(cp->entries[i].zip.central_directory);
              cp->entries[i].zip.central_directory = NULL;
            }
          if (NULL != cp->entries[i].zip.fd)
            {
              PR_Close(cp->entries[i].zip.fd);
              cp->entries[i].zip.fd = NULL;
            }
        }
    }

  PR_DELETE(cp->entries);
  PR_DELETE(cp);
  return PR_SUCCESS;
}
