/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

#include <stddef.h>
#include <kstat.h>

#include "jkstat.h"

/*
 * Class descriptors
 */
#define	DOUBLE_CLASS_DESC	"java/lang/Double"
#define	LONG_CLASS_DESC		"java/lang/Long"
#define	UI64_CLASS_DESC		"com/sun/solaris/service/pools/UnsignedInt64"
#define	HRTIME_CLASS_DESC	"com/sun/solaris/service/pools/HRTime"
#define	KSTAT_CLASS_DESC	"com/sun/solaris/service/kstat/Kstat"
#define	KSTATCTL_CLASS_DESC	"com/sun/solaris/service/kstat/KstatCtl"
#define	KSTAT_READ_EX_CLASS_DESC \
	"com/sun/solaris/service/kstat/KstatReadException"
#define	KSTAT_TNS_EX_CLASS_DESC	\
	"com/sun/solaris/service/kstat/KstatTypeNotSupportedException"
#define	THROWABLE_CLASS_DESC	"java/lang/Throwable"

#define	CLASS_FIELD_DESC(class_desc)	"L" class_desc ";"

/*
 * Cached class, method, and field IDs.
 */
static jclass doubleclass;
static jclass hrtimeclass;
static jclass kstatclass;
static jclass kstatctlclass;
static jclass longclass;
static jclass ui64class;
static jfieldID kstat_kctl_fieldid;
static jfieldID kstat_ksp_fieldid;
static jfieldID kstatctl_kctl_fieldid;
static jmethodID doublecons_mid;
static jmethodID hrtimecons_mid;
static jmethodID kstatcons_mid;
static jmethodID longcons_mid;
static jmethodID ui64cons_mid;

static jobject
makeUnsignedInt64(JNIEnv *env, uint64_t value)
{
	jobject valueObj;
	jobject byteArray;
	jbyte *bytes;
	int i;

	if (!(byteArray = (*env)->NewByteArray(env, 9)))
		return (NULL); /* OutOfMemoryError thrown */
	if (!(bytes = (*env)->GetByteArrayElements(env, byteArray, NULL)))
		return (NULL); /* OutOfMemoryError thrown */

	/*
	 * Interpret the uint64_t as a 9-byte big-endian signed quantity
	 * suitable for constructing an UnsignedInt64 or BigInteger.
	 */
	for (i = 8; i >= 1; i--) {
		bytes[i] = value & 0xff;
		value >>= 8;
	}
	bytes[0] = 0;
	(*env)->ReleaseByteArrayElements(env, byteArray, bytes, 0);

	if (!(valueObj = (*env)->NewObject(env, ui64class, ui64cons_mid,
	    byteArray)))
		return (NULL); /* exception thrown */

	return (valueObj);
}

/*
 * Return a Long object with the given value.
 */
static jobject
makeLong(JNIEnv *env, jlong value)
{
	jobject valueObj;

	if (!(valueObj = (*env)->NewObject(env, longclass, longcons_mid,
	    value)))
		return (NULL); /* exception thrown */

	return (valueObj);
}

/*
 * Return a Double object with the given value.
 */
static jobject
makeDouble(JNIEnv *env, jdouble value)
{
	jobject valueObj;

	if (!(valueObj = (*env)->NewObject(env, doubleclass, doublecons_mid,
	    value)))
		return (NULL); /* exception thrown */

	return (valueObj);
}

/*
 * Returns the kctl_t * from kstat_open(3kstat).
 */
/*ARGSUSED*/
JNIEXPORT jlong JNICALL
Java_com_sun_solaris_service_kstat_KstatCtl_open(JNIEnv *env, jobject obj)
{
	return ((jlong)(uintptr_t)kstat_open());
}

/*
 * Invokes kstat_close(3kstat).
 */
/*ARGSUSED*/
JNIEXPORT jint JNICALL
Java_com_sun_solaris_service_kstat_KstatCtl_close(JNIEnv *env, jobject obj,
    jlong kctl)
{
	if (kctl)
		return (kstat_close((kstat_ctl_t *)(uintptr_t)kctl));
	else
		return (0);
}

/*
 * Invoke kstat_read(3kstat) for the given Kstat object.
 */
JNIEXPORT void JNICALL Java_com_sun_solaris_service_kstat_Kstat_read(
    JNIEnv *env, jobject obj)
{
	kstat_ctl_t *kctl =
	    ((kstat_ctl_t *)(uintptr_t)(*env)->GetLongField(env, obj,
	    kstat_kctl_fieldid));
	kstat_t *ksp = ((kstat_t *)(uintptr_t)(*env)->GetLongField(env, obj,
	    kstat_ksp_fieldid));
	kid_t kid;

	if (!ksp || !kctl)
		return; /* exception thronw */

	kid = kstat_read((kstat_ctl_t *)kctl, (kstat_t *)ksp, NULL);
	if (kid == -1) {
		jclass e;
		if (!(e = (*env)->FindClass(env, KSTAT_READ_EX_CLASS_DESC)))
			return; /* exception thrown */

		(*env)->Throw(env, (*env)->NewObject(env, e,
		    (*env)->GetStaticMethodID(env, e, "<init>",
		    "()" CLASS_FIELD_DESC(THROWABLE_CLASS_DESC))));
	}
}

/*
 * Return a Kstat object corresponding to the result of
 * kstat_lookup(3kstat).
 */
JNIEXPORT jobject JNICALL
Java_com_sun_solaris_service_kstat_KstatCtl_lookup(JNIEnv *env, jobject obj,
    jstring moduleObj, jint instance, jstring nameObj)
{
	const char *module = NULL;
	const char *name = NULL;
	kstat_ctl_t *kctl;
	kstat_t *ksp;
	jobject kstatObject = NULL;

	if (moduleObj == NULL || nameObj == NULL)
		return (NULL);

	if (!(module = (*env)->GetStringUTFChars(env, moduleObj, NULL)))
		goto done; /* exception thrown */
	if (!(name = (*env)->GetStringUTFChars(env, nameObj, NULL)))
		goto done; /* exception thrown */

	kctl = (kstat_ctl_t *)(uintptr_t)(*env)->GetLongField(env, obj,
	    kstatctl_kctl_fieldid);
	ksp = kstat_lookup(kctl, (char *)module, instance, (char *)name);
	if (ksp)
		kstatObject = (*env)->NewObject(env, kstatclass, kstatcons_mid,
		    (jlong)(uintptr_t)kctl, (jlong)(uintptr_t)ksp);

done:
	if (name)
		(*env)->ReleaseStringUTFChars(env, nameObj, name);
	if (module)
		(*env)->ReleaseStringUTFChars(env, moduleObj, module);

	return (kstatObject);
}

/*
 * Returns the named value -- the value of the named kstat, or field in
 * a raw kstat, as applicable, and available.  Returns <i>null</i> if no
 * such named kstat or field is available.
 *
 * Throws KstatTypeNotSupportedException if the raw kstat is not
 * understood.  (Presently, none are.)
 */
JNIEXPORT jobject JNICALL
Java_com_sun_solaris_service_kstat_Kstat_getValue(JNIEnv *env, jobject obj,
    jstring nameObj)
{
	kstat_t *ksp = ((kstat_t *)(uintptr_t)(*env)->GetLongField(env, obj,
	    kstat_ksp_fieldid));
	jobject valueObj = NULL;
	kstat_named_t *ksnp;
	const char *name;
	jclass exceptionClass;

	if (!nameObj)
		return (NULL);

	if (!(name = (*env)->GetStringUTFChars(env, nameObj, NULL)))
		return (NULL); /* exception thrown */

	if (!(exceptionClass = (*env)->FindClass(env,
	    KSTAT_TNS_EX_CLASS_DESC))) {
		(*env)->ReleaseStringUTFChars(env, nameObj, name);
		return (NULL); /* exception thrown */
	}

	switch (ksp->ks_type) {
	case KSTAT_TYPE_NAMED:
		ksnp = kstat_data_lookup(ksp, (char *)name);
		if (ksnp == NULL)
			break;
		switch (ksnp->data_type) {
		case KSTAT_DATA_CHAR:
			valueObj = makeLong(env, ksnp->value.c[0]);
			break;
		case KSTAT_DATA_INT32:
			valueObj = makeLong(env, ksnp->value.i32);
			break;
		case KSTAT_DATA_UINT32:
			valueObj = makeLong(env, ksnp->value.ui32);
			break;
		case KSTAT_DATA_INT64:
			valueObj = makeLong(env, ksnp->value.i64);
			break;
		case KSTAT_DATA_UINT64:
			valueObj = makeUnsignedInt64(env, ksnp->value.ui64);
			break;
		case KSTAT_DATA_STRING:
			valueObj = (*env)->NewStringUTF(env,
			    KSTAT_NAMED_STR_PTR(ksnp));
			break;
		case KSTAT_DATA_FLOAT:
			valueObj = makeDouble(env, ksnp->value.f);
			break;
		case KSTAT_DATA_DOUBLE:
			valueObj = makeDouble(env, ksnp->value.d);
			break;
		default:
			goto fail;
		}
		break;
	default:
		goto fail;
	}

	(*env)->ReleaseStringUTFChars(env, nameObj, name);
	return (valueObj);

fail:
	(*env)->ReleaseStringUTFChars(env, nameObj, name);
	(*env)->Throw(env, (*env)->NewObject(env, exceptionClass,
	    (*env)->GetStaticMethodID(env, exceptionClass, "<init>",
	    "()" CLASS_FIELD_DESC(THROWABLE_CLASS_DESC))));

	return (valueObj);
}

/*
 * Given a Kstat object, return, as an HRTime object, its kstat_t's
 * field at the given offset.
 */
static jobject
ksobj_get_hrtime(JNIEnv *env, jobject obj, offset_t ksfieldoff)
{
	kstat_t *ksp = ((kstat_t *)(uintptr_t)(*env)->GetLongField(env, obj,
	    kstat_ksp_fieldid));

	if (!ksp)
		return (NULL); /* exception thrown */

	return ((*env)->NewObject(env, hrtimeclass, hrtimecons_mid,
	    makeUnsignedInt64(env, *((hrtime_t *)ksp + ksfieldoff *
	    sizeof (hrtime_t)))));
}

/*
 * Given a Kstat object, return as an HRTime object its ks_snaptime
 * field.
 */
JNIEXPORT jobject JNICALL
Java_com_sun_solaris_service_kstat_Kstat_getSnapTime(JNIEnv *env, jobject obj)
{
	return (ksobj_get_hrtime(env, obj, offsetof(kstat_t, ks_snaptime)));
}

/*
 * Given a Kstat object, return as an HRTime object its ks_crtime
 * field.
 */
JNIEXPORT jobject JNICALL
Java_com_sun_solaris_service_kstat_Kstat_getCreationTime(JNIEnv *env,
    jobject obj)
{
	return (ksobj_get_hrtime(env, obj, offsetof(kstat_t, ks_crtime)));
}

/*
 * Invoke kstat_chain_update(3kstat) for the kstat chain corresponding
 * to the given KstatCtl object.
 */
JNIEXPORT void JNICALL
Java_com_sun_solaris_service_kstat_KstatCtl_chainUpdate(JNIEnv *env,
    jobject obj)
{
	kstat_ctl_t *kctl;

	kctl = (kstat_ctl_t *)(uintptr_t)(*env)->GetLongField(env, obj,
	    kstatctl_kctl_fieldid);

	(void) kstat_chain_update(kctl);
}

/*
 * Cache class, method, and field IDs.
 */
/*ARGSUSED*/
JNIEXPORT void JNICALL
Java_com_sun_solaris_service_kstat_KstatCtl_init(JNIEnv *env, jclass clazz)
{
	jclass doubleclass_lref;
	jclass hrtimeclass_lref;
	jclass kstatclass_lref;
	jclass kstatctlclass_lref;
	jclass longclass_lref;
	jclass ui64class_lref;

	if (!(doubleclass_lref = (*env)->FindClass(env, DOUBLE_CLASS_DESC)))
		return; /* exception thrown */
	if (!(doubleclass = (*env)->NewGlobalRef(env, doubleclass_lref)))
		return; /* exception thrown */
	if (!(doublecons_mid = (*env)->GetMethodID(env, doubleclass, "<init>",
	    "(D)V")))
		return; /* exception thrown */

	if (!(hrtimeclass_lref = (*env)->FindClass(env, HRTIME_CLASS_DESC)))
		return; /* exception thrown */
	if (!(hrtimeclass = (*env)->NewGlobalRef(env, hrtimeclass_lref)))
		return; /* exception thrown */
	if (!(hrtimecons_mid = (*env)->GetMethodID(env, hrtimeclass, "<init>",
	    "(" CLASS_FIELD_DESC(UI64_CLASS_DESC) ")V")))
		return; /* exception thrown */

	if (!(kstatclass_lref = (*env)->FindClass(env, KSTAT_CLASS_DESC)))
		return; /* exception thrown */
	if (!(kstatclass = (*env)->NewGlobalRef(env, kstatclass_lref)))
		return; /* exception thrown */
	if (!(kstatcons_mid = (*env)->GetMethodID(env, kstatclass, "<init>",
	    "(JJ)V")))
		return; /* exception thrown */
	if (!(kstat_kctl_fieldid = (*env)->GetFieldID(env, kstatclass, "kctl",
	    "J")))
		return; /* exception thrown */
	if (!(kstat_ksp_fieldid = (*env)->GetFieldID(env, kstatclass, "ksp",
	    "J")))
		return; /* exception thrown */

	if (!(kstatctlclass_lref = (*env)->FindClass(env, KSTATCTL_CLASS_DESC)))
		return; /* exception thrown */
	if (!(kstatctlclass = (*env)->NewGlobalRef(env, kstatctlclass_lref)))
		return; /* exception thrown */
	if (!(kstatctl_kctl_fieldid = (*env)->GetFieldID(env, kstatctlclass,
	    "kctl", "J")))
		return; /* exception thrown */

	if (!(longclass_lref = (*env)->FindClass(env, LONG_CLASS_DESC)))
		return; /* exception thrown */
	if (!(longclass = (*env)->NewGlobalRef(env, longclass_lref)))
		return; /* exception thrown */
	if (!(longcons_mid = (*env)->GetMethodID(env, longclass, "<init>",
	    "(J)V")))
		return; /* exception thrown */

	if (!(ui64class_lref = (*env)->FindClass(env, UI64_CLASS_DESC)))
		return; /* exception thrown */
	if (!(ui64class = (*env)->NewGlobalRef(env, ui64class_lref)))
		return; /* exception thrown */
	ui64cons_mid = (*env)->GetMethodID(env, ui64class, "<init>", "([B)V");
}