/* -*- Mode: C; c-file-style: "gnu" -*-
   jnistr.c -- Java Native Interface string operations.
   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 "jni.h"
#include "jniint.h"
#include "log.h"

#include <stdlib.h>
#include <string.h>

#define MYLOG "JNI"

/*
  UTF encoding description.
  It is way to encode unicode characters (16-bit) into 8-bit character stream.
  Idea is to leave most often used characters (1-127) the same, at cost of 
  increased length of high end chars.
  01-7f              0[0-6bit]
  0 and 80-7ff       110[6-10bit]   10[0-5bit]
  800-ffff           1110[12-15bit] 10[6-11bit] 10[0-5bit]
*/

/*
  JDK String internal variable description.

  - value (Char[]) The content of the string
  - count (int)    The length of this string
  - offset (int)   How far into the content array this string starts

  The content of a JDK string runs from value[offset] to value[offset+count].
*/

/*
 None of this functions are tested as I cannot get japhar running.
 This file needs a testsuite.  
   Artur Biesiadowski <abies@pg.gda.pl>
*/

#define UTF8_1BYTE_TRESHOLD	0x0080
#define UTF8_2BYTE_TRESHOLD	0x07ff
 
jstring
JNIFUNC(NewString)(JNIEnv *env,
		   const jchar *unicodeChars,
		   jsize len)
{
  static jclass jstring_class = NULL;
  jstring new_string;
  jmethodID constructor;
  jcharArray char_array;
  jchar* array_elements;
  int i;

  if (!jstring_class)
    {
      jstring_class = (*env)->FindClass(env, "java/lang/String");
    }

  char_array = (*env)->NewCharArray(env, len);
  if (NULL == char_array)
    return NULL;

  /* Is this needed ? */
  char_array = (*env)->NewGlobalRef(env, char_array);

  array_elements = (*env)->GetCharArrayElements(env, char_array, NULL);
  
  for (i = 0; i < len; i ++)
    array_elements[i] = unicodeChars[i];

  (*env)->ReleaseCharArrayElements(env, char_array, array_elements, 0);

  constructor = (*env)->GetMethodID(env, jstring_class, "<init>", "([C)V");

  new_string = (*env)->NewObject(env, jstring_class, constructor, char_array, 0, len);
  
  /*
    MEMORY LEAK ????
    I'm not sure but maybe we should free char_array - String constructor copy it,
    so we are left with our version GlobalRef'erred
    (*env)->DeleteGlobalRef(env, char_array);
    BTW aren't objects returned by methods like NewCharArray supposed to live at
    least to end of native method ? In this case it might be possible to not allocate
    GlobalRef at all
  */

  return new_string;

}

jsize
JNIFUNC(GetStringLength)(JNIEnv *env,
			 jstring string)
{
  jsize length;
  jclass string_class = (*env)->FindClass(env, "java/lang/String");
  jfieldID length_field = (*env)->GetFieldID(env,
					     string_class, "count", "I");
  length = (*env)->GetIntField(env, string, length_field);

  return length;
}

jchar*
JNIFUNC(GetStringChars)(JNIEnv *env,
			jstring string,
			jboolean *isCopy)
{
  jclass string_class = (*env)->FindClass(env, "java/lang/String");
  jfieldID value_field = (*env)->GetFieldID(env, string_class, "value", "[C");
  jfieldID offset_field = (*env)->GetFieldID(env, string_class, "offset", "I");
  jint offset = 0;
  jcharArray char_array = (*env)->GetObjectField(env, string, value_field);
  jboolean char_copy;
  jchar* chars = (*env)->GetCharArrayElements(env, char_array, &char_copy);
  jfieldID length_field = (*env)->GetFieldID(env,
					     string_class, "count", "I");  
  int i;
  int length = (*env)->GetIntField(env, string, length_field);
  jchar* copyChars = calloc(length, sizeof(*copyChars));
  
  if (NULL != offset_field) /* Classpath has no offset field */
    offset = (*env)->GetIntField(env, string, offset_field);

  JAVARLOG3(MYLOG, 1, "GetStringChars() value=%s offset=%d length=%d\n",
	    char_array ? "(non-null)" : "(null)", offset, length);

  if (isCopy)
    *isCopy = JNI_TRUE;
    

  for ( i =0; i < length; i++ )
    {
      copyChars[i] = chars[i+offset];
    }


  (*env)->ReleaseCharArrayElements(env, char_array, chars, 0);

  return copyChars;


}

void
JNIFUNC(ReleaseStringChars)(JNIEnv *env,
			    jstring string,
			    const jchar *chars)
{
  /* do we need to copy it back */
  free((jchar*)chars);
}

/*
  This one can also be speed up by using alloc instead of malloc/free
 */
 
jstring
JNIFUNC(NewStringUTF)(JNIEnv *env,
		      const char *bytes)
{
  jclass jstring_class;
  jstring new_string;
  jmethodID constructor;
  jcharArray char_array;
  jchar* array_elements; 
  int java_at, utf_at;
  unsigned char atchar;
  jchar* tmparray;

  jstring_class = (*env)->FindClass(env, "java/lang/String");

  tmparray = calloc(strlen(bytes), sizeof(*tmparray));
  utf_at = 0;
  java_at =0;
  while(1)
    {
      atchar = (unsigned char) bytes[utf_at++];
      if( !atchar )
	{
	  break;
	}
      else if ( !(atchar & 0x80) )
	{
	  tmparray[java_at]= atchar;
	}
      else if ( !(atchar & 0x20) )
	{
	  tmparray[java_at] = ((atchar & 0x1f)<<6) + (bytes[utf_at++] & 0x3f);
	}
      else
	{
	  tmparray[java_at] = ((atchar & 0x0f)<<12);
	  tmparray[java_at] |= ((bytes[utf_at++] & 0x3f)<<6);
	  tmparray[java_at] |= ((bytes[utf_at++] & 0x3f));
	}
      java_at++;
    }  

  char_array = (*env)->NewCharArray(env, java_at);

  if (NULL == char_array)
    return NULL;

  /* See note in NewString */
  char_array = (*env)->NewGlobalRef(env, char_array);

  array_elements = (*env)->GetCharArrayElements(env, char_array, NULL);
  
  memcpy(array_elements, tmparray, java_at*sizeof(jchar));

  (*env)->ReleaseCharArrayElements(env, char_array, array_elements, 0);

  constructor = (*env)->GetMethodID(env, jstring_class, "<init>", "([C)V");

  new_string = (*env)->NewObject(env, jstring_class, constructor, char_array, 0, java_at);
  
  free(tmparray);
  
  /* 
     See note in NewString 
     (*env)->DeleteGlobalRef(env, char_array);  
  */

  return new_string;
}

jsize
JNIFUNC(GetStringUTFLength)(JNIEnv *env,
			    jstring string)
{
  jsize length;
  jsize utflength;
  int i;
  jchar atchar;
  jclass string_class = (*env)->FindClass(env, "java/lang/String");
  jfieldID length_field = (*env)->GetFieldID(env,
					     string_class, "count", "I");
  jfieldID value_field = (*env)->GetFieldID(env, string_class, "value", "[C");
  jfieldID offset_field = (*env)->GetFieldID(env, string_class, "offset", "I");
  jint offset = 0;
  jcharArray char_array = (*env)->GetObjectField(env, string, value_field);
  jboolean char_copy;
  jchar* chars = (*env)->GetCharArrayElements(env, char_array, &char_copy);
  
  if (NULL != offset_field) /* Classpath has no offset field */
    offset = (*env)->GetIntField(env, string, offset_field);

  length = (*env)->GetIntField(env, string, length_field);
  utflength = 0;
  for ( i=0; i < length; i++ )
    {
      atchar = chars[i+offset];
      if ( atchar == 0 )
  	{
	  /* Zero is encoded by 2 bytes in java utf8 */
	  utflength += 2;
  	}
      else if ( atchar <= UTF8_1BYTE_TRESHOLD )
  	{
	  utflength += 1;
  	}
      else if ( atchar <= UTF8_2BYTE_TRESHOLD )
  	{
	  utflength += 2;
  	}
      else
  	{
	  utflength += 3;
  	}  	
    }
  (*env)->ReleaseCharArrayElements(env, char_array, chars, 0);
  
  return utflength;
}

/* 
  Idea for speedup - length should be not calculated for relatively short
  strings. Instead 3*string length space should be alloc()ated, buffer would
  be malloc()ated at end and data memcopied.
 */

const jbyte*
JNIFUNC(GetStringUTFChars)(JNIEnv *env,
			   jstring string,
			   jboolean *isCopy)
{
  jsize utflength = (*env)->GetStringUTFLength(env, string);
  /* XXX +1 to get zero termination.  Is this needed */
  jbyte *bytes = calloc(utflength+1, sizeof(*bytes));
  jclass string_class = (*env)->FindClass(env, "java/lang/String");
  jfieldID value_field = (*env)->GetFieldID(env, string_class, "value", "[C");
  jfieldID offset_field = (*env)->GetFieldID(env, string_class, "offset", "I");
  jint offset = 0;
  jcharArray char_array = (*env)->GetObjectField(env, string, value_field);
  jchar* chars = (*env)->GetCharArrayElements(env, char_array, NULL);
  jfieldID length_field = (*env)->GetFieldID(env,
					     string_class, "count", "I");  
  int java_at, utf_at;
  int length = (*env)->GetIntField(env, string, length_field);
  
  if (NULL != offset_field) /* Classpath has no offset field */
    offset = (*env)->GetIntField(env, string, offset_field);

  JAVARLOG3(MYLOG, 1, "GetStringUTFChars() value=%s offset=%d length=%d\n",
	    char_array ? "(non-null)" : "(null)", offset, length);

  if (isCopy)
    *isCopy = JNI_TRUE;
    
  utf_at =0;


  for ( java_at=offset; java_at < (length+offset); java_at++ )
    {
      jchar atchar = chars[java_at];
      if ( atchar == 0 )
  	{
	  /* Zero is encoded by 2 bytes in java utf8 */
	  bytes[utf_at++] = (jbyte)0xC0;
	  bytes[utf_at++] = (jbyte)0x80;
  	}
      else if ( atchar <= UTF8_1BYTE_TRESHOLD )
  	{
	  bytes[utf_at++] = (jbyte) atchar;
  	}
      else if ( atchar <= UTF8_2BYTE_TRESHOLD )
  	{
	  bytes[utf_at++] = 0xC0 | ((atchar & 0x07C0)>>6);
	  bytes[utf_at++] = 0x80 | ((atchar & 0x003f));
  	}
      else
  	{
	  bytes[utf_at++] = 0xE0| ((atchar & 0xF000)>>12);
	  bytes[utf_at++] = 0x80| ((atchar & 0x0FC0)>>6);
	  bytes[utf_at++] = 0x80| ((atchar & 0x003f));
  	}  	
    }


  (*env)->ReleaseCharArrayElements(env, char_array, chars, 0);

  /* XXX Zero terminate C-string.  Is this needed? */
  bytes[utf_at] = 0;

  return bytes;
}

void
JNIFUNC(ReleaseStringUTFChars)(JNIEnv *env,
			       jstring string,
			       const jbyte *utf)
{
  /* do we need to copy it back ? */
  free((jbyte*)utf);
}

/* New JDK1.2 JNI functions */

void
JNIFUNC(GetStringRegion)(JNIEnv* env, jstring string, jsize start,
			 jsize len, jchar * buf )
{
  jclass string_class = (*env)->FindClass(env, "java/lang/String");
  jfieldID value_field = (*env)->GetFieldID(env, string_class, "value", "[C");
  jfieldID offset_field = (*env)->GetFieldID(env, string_class, "offset", "I");
  jint offset = 0;
  jcharArray char_array = (*env)->GetObjectField(env, string, value_field);
  jboolean char_copy;
  jchar* chars = (*env)->GetCharArrayElements(env, char_array, &char_copy);
  jfieldID length_field = (*env)->GetFieldID(env,
					     string_class, "count", "I");  
  int i;
  int length = (*env)->GetIntField(env, string, length_field);
  
  if (NULL != offset_field) /* Classpath has no offset field */
    offset = (*env)->GetIntField(env, string, offset_field);

  if ( length < start + len )
    {
      /*
	JNI was not supposed to catch errors !!!! Sun is inconsistent
	(*env)->ThrowNew(env, 
	(*env)->FindClass(env,"java/lang/StringIndexOutOfBoundsException"), "");
      */
    }    

  for ( i =start; i < len+start; i++ )
    {
      buf[i] = chars[i+offset];
    }


  (*env)->ReleaseCharArrayElements(env, char_array, chars, 0);

}

void 
JNIFUNC(GetStringUTFRegion)( JNIEnv *env, jstring string, jsize start,
			     jsize len, char * buf )
{

  jclass string_class = (*env)->FindClass(env, "java/lang/String");
  jfieldID value_field = (*env)->GetFieldID(env, string_class, "value", "[C");
  jfieldID offset_field = (*env)->GetFieldID(env, string_class, "offset", "I");
  jint offset = 0;
  jcharArray char_array = (*env)->GetObjectField(env, string, value_field);
  jboolean char_copy;
  jchar* chars = (*env)->GetCharArrayElements(env, char_array, &char_copy);
  jfieldID length_field = (*env)->GetFieldID(env,
					     string_class, "count", "I");  
  int java_at, utf_at;
  int length = (*env)->GetIntField(env, string, length_field);
    
  if (NULL != offset_field) /* Classpath has no offset field */
    offset = (*env)->GetIntField(env, string, offset_field);

  utf_at =0;
  
  if ( length < start + len )
    {
      /*
	JNI was not supposed to catch errors !!!! Sun is inconsistent
	(*env)->ThrowNew(env, 
	(*env)->FindClass(env,"java/lang/StringIndexOutOfBoundsException"), "");
      */
    }

  for ( java_at=offset+start; java_at < (len+start+offset); java_at++ )
    {
      jchar atchar = chars[java_at];
      if ( atchar == 0 )
  	{
	  /* Zero is encoded by 2 bytes in java utf8 */
	  buf[utf_at++] = (jbyte)0xC0;
	  buf[utf_at++] = (jbyte)0x80;
  	}
      else if ( atchar <= UTF8_1BYTE_TRESHOLD )
  	{
	  buf[utf_at++] = (jbyte) atchar;
  	}
      else if ( atchar <= UTF8_2BYTE_TRESHOLD )
  	{
	  buf[utf_at++] = 0xC0 | ((atchar & 0x07C0)>>6);
	  buf[utf_at++] = 0x80 | ((atchar & 0x003f));
  	}
      else
  	{
	  buf[utf_at++] = 0xE0| ((atchar & 0xF000)>>12);
	  buf[utf_at++] = 0x80| ((atchar & 0x0FC0)>>6);
	  buf[utf_at++] = 0x80| ((atchar & 0x003f));
  	}  	
    }


  (*env)->ReleaseCharArrayElements(env, char_array, chars, 0);

}

/* 
  for now just map to normal functions - later we may want to give direct
  access to string array
*/
const jchar * 
JNIFUNC(GetStringCritical)( JNIEnv *env, jstring string, 
			    jboolean *isCopy )
{
  return (*env)->GetStringChars(env, string, isCopy);
}

void
JNIFUNC(ReleaseStringCritical)(JNIEnv *env, jstring string,
			       const jchar *carray )
{
  free((jchar*)carray);
}
