/* -*- Mode: C; c-file-style: "gnu" -*-
   common.c -- common methods for java/io/File...
   Created: Petter Reinholdtsen <pere@td.org.uit.no>, 1998-02-21
 */
/*
  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) 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 "jni.h"
#include "objects.h"
#include "array-class.h"
#include "exceptions.h" /* For throw_Exception() */
#include "file.h"
#include "common.h"

#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#ifdef HAVE_UNISTD_H
#  include <unistd.h>
#endif
#ifdef HAVE_IO_H
#  include <io.h>
#endif

char *
get_file_path(JNIEnv *env,
	      jobject obj)
{
  jfieldID path_id;
  jstring path;
  const jbyte *filename;
  char *result;
  jclass clazz = (*env)->GetObjectClass(env, obj);

  path_id = (*env)->GetFieldID(env, clazz, "path", "Ljava/lang/String;");
  if (!path_id) return NULL;

  path = (*env)->GetObjectField(env, obj, path_id);
  if (!path) return NULL;

  filename = (*env)->GetStringUTFChars(env, path, NULL);

  result = strdup((char*)filename);

  (*env)->ReleaseStringUTFChars(env, path, filename);

  return result;
}

int
stat_file(JNIEnv *env,
	  jobject obj,
	  struct stat *buf)
{
  int result;
  char *path = get_file_path(env, obj);

  result = stat((char*)path, buf);

  free(path);

  return result;
}

int
get_file_descriptor(JNIEnv *env, jobject input_stream)
{
  jfieldID fd_field;
  jobject fd;
  jclass strm_cls = (*env)->GetObjectClass(env, input_stream);
  jclass fd_cls;

  fd_field = (*env)->GetFieldID(env, strm_cls, "fd", "Ljava/io/FileDescriptor;");
  fd = (*env)->GetObjectField(env, input_stream, fd_field);

  fd_cls = (*env)->GetObjectClass(env, fd);

  fd_field = (*env)->GetFieldID(env, fd_cls, "fd", "I");

  return (*env)->GetIntField(env, fd, fd_field);
}

void
set_file_descriptor(JNIEnv *env, jobject input_stream, int fd_int)
{
  jfieldID fd_field;
  jobject fd;
  jclass fd_cls, strm_cls;

  strm_cls = (*env)->GetObjectClass(env, input_stream);

  fd_field = (*env)->GetFieldID(env, strm_cls, "fd", "Ljava/io/FileDescriptor;");
  fd = (*env)->GetObjectField(env, input_stream, fd_field);

  fd_cls = (*env)->GetObjectClass(env, fd);
  fd_field = (*env)->GetFieldID(env, fd_cls, "fd", "I");

  (*env)->SetIntField(env, fd, fd_field, fd_int);
}

void
file_open(JNIEnv *env,
	  jobject obj,
	  jstring name,
	  int flags)
{
  int new_fd = -1;
  const jbyte *filename = (*env)->GetStringUTFChars(env, name, NULL);

  new_fd = open( (char *)filename, flags, 0666);

  if (new_fd == -1)
    {
      char buf[200];

      snprintf(buf, sizeof(buf), "open of %s failed", filename);

      throw_Exception(env, "java/io/IOException", buf);
    }
  else
    {
      set_file_descriptor(env, obj, new_fd);
    }

  (*env)->ReleaseStringUTFChars(env, obj, filename);
}

void
file_close(JNIEnv *env,
	   jobject obj)
{
  int fd = get_file_descriptor(env, obj);

  if (-1 == fd)
    return;

  while ( -1 == close(fd) ) {
    switch (errno) {
    case EBADF:
      (*env)->FatalError(env, "java.io.*.close bad fd detected.");
      break;
    case EINTR: /* Try again */
      break;
#if 0 /* XXX ETIMEOUT missing on linux? */
    case ETIMEOUT: /* NFS timeout ? */
      return;
      break;
#endif
    default:
      return; /* XXX Unknown error, should maybe throw something? */
      break;
    }
  }
  set_file_descriptor(env, obj, -1);
}

/*
 * Generic read function used by RandomAccessFile.read() and
 * FileInputStream.read()
 */
jint
file_read(JNIEnv *env,
	  jobject obj)
{
  int fd = get_file_descriptor(env, obj);
  jboolean next_byte; /* jboolean is used as an 8-bit unsigned value */
  ssize_t ret_val;

  if (fd == -1)
    {
      throw_Exception(env, "java/io/IOException", "read() called on unopened file.");
      return -1;
    }

  ret_val = read(fd, &next_byte, sizeof(jboolean));
  
  if (-1 == ret_val)
    {
      throw_Exception(env, "java/io/IOException", "read() failed");
      return -1;
    }
  else if (0 == ret_val)
    {
      return -1;
    }
  else
    {
      return next_byte;
    }
}

/*
 * Generic readBytes function used by RandomAccessFile.read() and
 * FileInputStream.read()
 */
jint
file_readBytes(JNIEnv *env,
	       jobject obj,
	       jbyteArray array,
	       jint offset,
	       jint length)
{
  int fd = get_file_descriptor(env, obj);
  jclass array_cls = (*env)->GetObjectClass(env, array);
  ClazzFile *array_type = jclass_to_clazzfile(env, array_cls);
  jvalue array_body;
  ssize_t ret_val;

  if (fd == -1)
    {
      throw_Exception(env, "java/io/IOException", "readBytes() called on unopened file.");
      return -1;
    }

  get_instance_field(array,
		     array_type->fields[ARRAY_BODY_INDEX],
		     &array_body);

  ret_val = read(fd, (char*)array_body.l + offset, length);

  if (-1 == ret_val)
    {
      throw_Exception(env, "java/io/IOException", "read() failed");
      return -1;
    }
  else if (0 == ret_val)
    {
      return -1;
    }
  else
    {
      return ret_val;
    }
}

void
file_write(JNIEnv *env, jobject obj, jint byte)
{
  int fd = get_file_descriptor(env, obj);
  ssize_t res;
  jbyte byte_write = (jbyte) byte; /* downcast to an 8-bit signed value */

  if (fd == -1)
    {
      throw_Exception(env, "java/io/IOException", "write() called on unopened file.");
      return;
    }

  res = write(fd, &byte_write, sizeof(jbyte));

  if (res == -1)
    {
      throw_Exception(env, "java/io/IOException", "write() failed");
    }
}

void
file_writeBytes(JNIEnv *env,
		jobject obj,
		jbyteArray array,
		jint offset,
		jint length)
{
  int fd = get_file_descriptor(env, obj);
  jbyte *bytes = NULL;
  ssize_t res = -1;

  if (fd == -1)
    {
      throw_Exception(env, "java/io/IOException", "writeBytes() called on unopened file.");
      return;
    }

  bytes = (*env)->GetByteArrayElements(env, array, NULL);

  res = write(fd, bytes + offset, length);

  (*env)->ReleaseByteArrayElements(env, array, bytes, 0);

  if (0 > res)
    {
      throw_Exception(env, "java/io/IOException", "write() failed");
    }
}
