/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */

/*
 *  Engrampa
 *
 *  Copyright (C) 2006 The Free Software Foundation, Inc.
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program 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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Street #330, Boston, MA 02110-1301, USA.
 */
 
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <glib.h>
#include "java-utils.h"


/* 
 * The following code conforms to the JVM specification.(Java 2 Platform)
 * For further changes to the classfile structure, please update the 
 * following macros.
 */


/* Tags that identify structures */

#define CONST_CLASS				 7
#define CONST_FIELDREF				 9
#define CONST_METHODREF				10
#define CONST_INTERFACEMETHODREF		11
#define CONST_STRING				 8
#define CONST_INTEGER				 3
#define CONST_FLOAT				 4
#define CONST_LONG				 5
#define CONST_DOUBLE				 6
#define CONST_NAMEANDTYPE			12
#define CONST_UTF8				 1

/* Sizes of structures */

#define CONST_CLASS_INFO			 2
#define CONST_FIELDREF_INFO			 4
#define CONST_METHODREF_INFO	 		 4
#define CONST_INTERFACEMETHODREF_INFO		 4
#define CONST_STRING_INFO			 2
#define CONST_INTEGER_INFO			 4
#define CONST_FLOAT_INFO			 4
#define CONST_LONG_INFO				 8
#define CONST_DOUBLE_INFO			 8
#define CONST_NAMEANDTYPE_INFO			 4


/* represents the utf8 strings in class file */
struct utf_string	
{
	guint16  index;
	guint16  length;
	char    *str;
};

/* structure that holds class information in a class file */
struct class_info
{
	guint16 index;
	guint16 name_index; /* index into the utf_strings */
};

typedef struct {
	int     fd;
	
	guint32 magic_no;		/* 0xCAFEBABE (JVM Specification) :) */

	guint16 major;			/* versions */
	guint16 minor;

	guint16 const_pool_count;
	GSList *const_pool_class;	/* (const_pool_count - 1) elements of tye 'CONST_class_info' */
	GSList *const_pool_utf;		/* (const_pool_count - 1) elements of type 'utf_strings' */

	guint16 access_flags;
	guint16 this_class;		/* the index of the class the file is named after. */
	
#if 0 /* not needed */
	guint16         super_class;
	guint16         interfaces_count;
	guint16        *interfaces;
	guint16         fields_count;
	field_info     *fields;
	guint16         methods_count;
	method_info    *methods;
	guint16         attributes_count;
	attribute_info *attributes;
#endif
} JavaClassFile;


static JavaClassFile*
java_class_file_new (void)
{
	JavaClassFile *cfile;
	
	cfile = g_new0 (JavaClassFile, 1);
	cfile->fd = -1;
	
	return cfile;
}


static void
java_class_file_free (JavaClassFile *cfile)
{
	GSList *scan;

	if (cfile->const_pool_class != NULL) {
		g_slist_foreach (cfile->const_pool_class, (GFunc)g_free, NULL);
		g_slist_free (cfile->const_pool_class);
	}

	for (scan = cfile->const_pool_utf; scan ; scan = scan->next) {
		struct utf_string *string = scan->data;
		g_free (string->str);
	}
	
	if (cfile->const_pool_utf != NULL) {
		g_slist_foreach (cfile->const_pool_utf, (GFunc)g_free, NULL);
		g_slist_free (cfile->const_pool_utf);
	}

	if (cfile->fd != -1)
		close (cfile->fd);

	g_free (cfile);
}


/* The following function loads the utf8 strings and class structures from the 
 * class file. */
static void
load_constant_pool_utfs (JavaClassFile *cfile)
{
	guint8  tag;
	guint16 i = 0;		/* should be comparable with const_pool_count */
	
	while ((i < cfile->const_pool_count - 1) && (read (cfile->fd, &tag, 1) != -1)) {
		struct utf_string *txt = NULL;
		struct class_info *class = NULL;
		
		switch (tag) {
		case CONST_CLASS:
			class = g_new0 (struct class_info, 1);
			class->index = i + 1;
			if (read (cfile->fd, &class->name_index, 2) != 2) {
				g_free (class);
				return;	/* error reading */
			}
			class->name_index = GUINT16_FROM_BE (class->name_index);
			cfile->const_pool_class = g_slist_append (cfile->const_pool_class, class);
			break;
		
		case CONST_FIELDREF:
			lseek (cfile->fd, CONST_FIELDREF_INFO, SEEK_CUR);
			break;
		
		case CONST_METHODREF:
			lseek (cfile->fd, CONST_METHODREF_INFO, SEEK_CUR);
			break;
		
		case CONST_INTERFACEMETHODREF:
			lseek (cfile->fd, CONST_INTERFACEMETHODREF_INFO, SEEK_CUR);
			break;
		
		case CONST_STRING:
			lseek (cfile->fd, CONST_STRING_INFO, SEEK_CUR);
			break;
		
		case CONST_INTEGER:
			lseek (cfile->fd, CONST_INTEGER_INFO, SEEK_CUR);
			break;
		
		case CONST_FLOAT:
			lseek (cfile->fd, CONST_FLOAT_INFO, SEEK_CUR);
			break;
		
		case CONST_LONG:
			lseek (cfile->fd, CONST_LONG_INFO, SEEK_CUR);
			break;
		
		case CONST_DOUBLE:
			lseek (cfile->fd, CONST_DOUBLE_INFO, SEEK_CUR);
			break;
		
		case CONST_NAMEANDTYPE:
			lseek (cfile->fd, CONST_NAMEANDTYPE_INFO, SEEK_CUR);
			break;
		
		case CONST_UTF8:
			txt = g_new0 (struct utf_string, 1);
			txt->index = i + 1;
			if (read (cfile->fd, &(txt->length), 2) == -1) {
				g_free (txt);
				return;	/* error while reading */
			}
			txt->length = GUINT16_FROM_BE (txt->length);
			txt->str = g_new0 (char, txt->length);
			if (read (cfile->fd, txt->str, txt->length) == -1) {
				g_free (txt);
				return;	/* error while reading */
			}
			cfile->const_pool_utf = g_slist_append (cfile->const_pool_utf, txt);
			break;
		
		default:
			return;	/* error - unknown tag in class file */
			break;
		}
		i++;
	}
	
#ifdef DEBUG
	g_print( "Number of Entries: %d\n", i );
#endif
}


static char*
close_and_exit (JavaClassFile *cfile) 
{
	java_class_file_free (cfile);
	return NULL;
}


/* This function extracts the package name from a class file */
char*
get_package_name_from_class_file (char *fname)
{
	char          *package = NULL;
	JavaClassFile *cfile;
	guint16        length = 0, end = 0, utf_index = 0;
	guint32        magic;
	guint16        major, minor, count;
	int            i = 0;
	
	if (! g_file_test (fname, G_FILE_TEST_EXISTS))
		return NULL;

	cfile = java_class_file_new ();
	cfile->fd = open (fname, O_RDONLY);
	if (cfile->fd == -1)
		return close_and_exit (cfile);

	if ((i = read (cfile->fd, &magic, 4)) != 4)
		return close_and_exit (cfile);
	cfile->magic_no = GUINT32_FROM_BE (magic);

	if (read (cfile->fd, &major, 2 ) != 2)
		return close_and_exit (cfile);
	cfile->major = GUINT16_FROM_BE (major);

	if (read (cfile->fd, &minor, 2) != 2)
		return close_and_exit (cfile);
	cfile->minor = GUINT16_FROM_BE (minor);

	if (read (cfile->fd, &count, 2) != 2)
		return close_and_exit (cfile);
	cfile->const_pool_count = GUINT16_FROM_BE(count);
	load_constant_pool_utfs (cfile);

	if (read (cfile->fd, &cfile->access_flags, 2) != 2)
		return close_and_exit (cfile);
	cfile->access_flags = GUINT16_FROM_BE (cfile->access_flags);

	if (read (cfile->fd, &cfile->this_class, 2) != 2)
		return close_and_exit (cfile);
	cfile->this_class = GUINT16_FROM_BE(cfile->this_class);

	/* now search for the class structure with index = cfile->this_class */
	
	for (i = 0; (i < g_slist_length (cfile->const_pool_class)) && (utf_index == 0); i++ ) {
		struct class_info *class = g_slist_nth_data (cfile->const_pool_class, i);
		if (class->index == cfile->this_class)
			utf_index = class->name_index; /* terminates loop */
	}

	/* now search for the utf8 string with index = utf_index */
	
	for (i = 0; i < g_slist_length (cfile->const_pool_utf); i++) {
		struct utf_string *data = g_slist_nth_data (cfile->const_pool_utf, i);
		if (data->index == utf_index) {
			package = g_strndup (data->str, data->length);
			length = data->length;
			break;
		}
	}

	if (package != NULL) {
		for (i = length; (i >= 0) && (end == 0); i-- )
			if (package[i] == '/')
				end = i;
		package = g_strndup (package, end);
	}

	java_class_file_free (cfile);
	
	return package;
}


/* This function consumes a comment from the java file 
 * multiline = TRUE implies that comment is multiline */
static void
consume_comment (int      fdesc,
		 gboolean multiline)
{
	gboolean escaped = FALSE;
	gboolean star = FALSE;
	char     ch;
		
	while (read (fdesc, &ch, 1) == 1) {
		switch (ch) {
		case '/':
			if (escaped)
				break;
			else if (star)
				return;
			break;
			
		case '\n':
			if (! multiline)
				return;
			break;
			
		case '*':
			escaped = FALSE;
			star = TRUE;
			break;
			
		case '\\':
			escaped = ! escaped;
			break;
			
		default:
			escaped = FALSE;
			star = FALSE;
			break;
		}
	}
}


/* This function extracts package name from a java file */
char*
get_package_name_from_java_file (char *fname)
{
	char          *package = NULL;
	JavaClassFile *cfile;
	gboolean       prev_char_is_bslash = FALSE;
	gboolean       valid_char_found = FALSE;
	char           ch;
	
	if (! g_file_test (fname, G_FILE_TEST_EXISTS))
		return NULL;

	cfile = java_class_file_new ();
	cfile->fd = open (fname, O_RDONLY);
	if (cfile->fd == -1)
		return close_and_exit (cfile);
	
	while (! valid_char_found && (read (cfile->fd, &ch, 1) == 1)) {
		switch (ch) {
		case '/':
			if (prev_char_is_bslash == TRUE) { 
				consume_comment (cfile->fd, FALSE);
				prev_char_is_bslash = FALSE;
			}
			else
				prev_char_is_bslash = TRUE;
			break;
				
		case '*':
			if (prev_char_is_bslash == TRUE)
				consume_comment (cfile->fd, TRUE);
			prev_char_is_bslash = FALSE;
			break;
			
		case ' ':
		case '\t':
		case '\r':
		case '\n':
			prev_char_is_bslash = FALSE;
			break;
				
		default:
			prev_char_is_bslash = FALSE;
			valid_char_found = TRUE;
			break;
		}
	}
	
	if (ch == 'p')	{
		char first_valid_word[8] = "";
		
		first_valid_word[0] = 'p';
		if (read (cfile->fd, &first_valid_word[1], 6) != 6) 
			return close_and_exit (cfile);
			
		first_valid_word[7] = 0;
		if (g_ascii_strcasecmp (first_valid_word, "package") == 0) {
			char buffer[500];
			int  index = 0;
			
			while (read (cfile->fd, &ch, 1) == 1) {
				if (ch == ';')
					break;
				if (ch == '.')
					buffer[index++] = '/';
				else 
					buffer[index++] = ch;
			}
			buffer[index] = 0;
			package = g_strdup (buffer);
		}
	}

	java_class_file_free (cfile);

	return package;
}