Returning array of user defined objects in JNI.

Returning an array of complex object is not straight forward in JNI, Here I will explain an example doing it.

JAVA part.

StudentRecord.java

package com.test;
public class  StudentRecord {
	public String name;  
	public int rollNumber;  
	public String departement; 
	public float    totalMark;
	boolean hasReservation;

	public  StudentRecord(){
		name           = new String("");
		rollNumber     = 0;
		departement    = new String("");
		totalMark      = 0;
		hasReservation = false;
	}
	public String toString() {   
		return "[" + departement+ ","+ name + "," + rollNumber +"]";   
	}
}

Main class is as shown below
Test.java

package com.test;
public class Test {	
	public native static StudentRecord[] getStudentDetails(); 	
	public static void main(String[] args) {
		System.loadLibrary("Sample");
		int a= 10;
		StudentRecord[] records = getStudentDetails();		
		for(StudentRecord record:records){
			System.out.println("Name:"+record.name);
			System.out.println("Roll Number:"+record.rollNumber);
			System.out.println("Departement:"+record.departement);
			System.out.println("Total Marks:"+record.totalMark);
			System.out.println("Has Reservation:"+record.hasReservation);
		}

	}	
}

Next step would be to generate native method header file from java code.
as you know java has a tool javah for it, following figure shows the command.

The above command generates com_test_Test.h as shwown below.

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_test_Test */

#ifndef _Included_com_test_Test
#define _Included_com_test_Test
#ifdef __cplusplus
extern "C" {
#endif
    /*
    * Class:     com_test_Test
    * Method:    getStudentDetails
    * Signature: ()[Lcom/test/StudentRecord;
    */
    JNIEXPORT jobjectArray JNICALL Java_com_test_Test_getStudentDetails
        (JNIEnv *, jclass);

#ifdef __cplusplus
}
#endif
#endif

Implementation of JNI method is shown below
Sample.cpp

#include<stdafx.h>
#include<string>
#include<vector>
#include "com_test_Test.h"


typedef struct _JNI_POSREC {
    jclass cls;
    jmethodID constructortorID;
    jfieldID nameID;
    jfieldID rollNumberID;
    jfieldID departementID;
    jfieldID totalMarkID;
    jfieldID hasReservationID;
} JNI_POSREC;

/**
*   Return Search class.
*/
struct SearchRecord {
    std::string name;
    int rollNumber;
    std::string departement;
    float    totalMark;
    bool   hasReservation;
};

JNI_POSREC * jniPosRec = NULL;

/**
*   Fills the Student Record Details.
*/
void FillStudentRecordDetails(std::vector<SearchRecord*>* searchRecordResult ){
    SearchRecord *pRecord1 = new SearchRecord();
    pRecord1->name = "Ram";
    pRecord1->rollNumber = 1;
    pRecord1->departement = "Computer Science";
    pRecord1->totalMark = 512.500;
    pRecord1->hasReservation = true;
    searchRecordResult->push_back(pRecord1);

    SearchRecord *pRecord2 = new SearchRecord();
    pRecord2->name = "Raju";
    pRecord2->rollNumber = 2;
    pRecord2->departement = "Electronics";
    pRecord2->totalMark = 572.25;
    pRecord2->hasReservation = false;
    searchRecordResult->push_back(pRecord2);
}

/**
*   Fills JNI details.
*/
void LoadJniPosRec(JNIEnv * env) {

    if (jniPosRec != NULL)
        return;

    jniPosRec = new JNI_POSREC;

    jniPosRec->cls = env->FindClass("com/test/StudentRecord");

    if(jniPosRec->cls != NULL)
        printf("sucessfully created class");

    jniPosRec->constructortorID = env->GetMethodID(jniPosRec->cls, "<init>", "()V");
    if(jniPosRec->constructortorID != NULL){
        printf("sucessfully created ctorID");
    }

    jniPosRec->nameID = env->GetFieldID(jniPosRec->cls, "name", "Ljava/lang/String;");
    jniPosRec->rollNumberID = env->GetFieldID(jniPosRec->cls, "rollNumber", "I");
    jniPosRec->departementID = env->GetFieldID(jniPosRec->cls, "departement", "Ljava/lang/String;");
    jniPosRec->totalMarkID = env->GetFieldID(jniPosRec->cls, "totalMark", "F");
    jniPosRec->hasReservationID = env->GetFieldID(jniPosRec->cls, "hasReservation", "Z");

}

void FillStudentRecValuesToJni(JNIEnv * env, jobject jPosRec, SearchRecord* cPosRec) {

    env->SetObjectField(jPosRec, jniPosRec->nameID, env->NewStringUTF(cPosRec->name.c_str()));
    jint rollNum = (jint)cPosRec->rollNumber;
    env->SetIntField(jPosRec, jniPosRec->rollNumberID, rollNum);
    env->SetObjectField(jPosRec, jniPosRec->departementID, env->NewStringUTF(cPosRec->departement.c_str()));
    jfloat totalMark = (jfloat)cPosRec->totalMark;
    env->SetFloatField( jPosRec, jniPosRec->totalMarkID, totalMark);
    jboolean hasReservation = cPosRec->hasReservation;
    env->SetBooleanField( jPosRec, jniPosRec->hasReservationID, hasReservation);
}

/**
* JNI method calling from JAVA 
*/
JNIEXPORT jobjectArray JNICALL Java_com_test_Test_getStudentDetails( JNIEnv *env, jclass cls)
{
    jniPosRec = NULL;
    LoadJniPosRec(env);
    std::vector<SearchRecord*> searchRecordResult ;
    FillStudentRecordDetails(&searchRecordResult);
    printf("\nsearchRecordResult size is"+searchRecordResult.size());
    jobjectArray jPosRecArray = env->NewObjectArray(searchRecordResult.size(), jniPosRec->cls, NULL);

    for (size_t i = 0; i < searchRecordResult.size(); i++) {
        jobject jPosRec = env->NewObject(jniPosRec->cls, jniPosRec->constructortorID);
        FillStudentRecValuesToJni(env, jPosRec, searchRecordResult[i]);
        env->SetObjectArrayElement(jPosRecArray, i, jPosRec);
    }

    return jPosRecArray;
}

Building MinGW dll for JNI.

I think you are familiar with Java Native Interface (JNI), Simply it is a Java technology with which a Java application can call a library method written with such as C, C++ and assembly.

When using JNI with MSVC dll it is straight forward, but with MinGw there is some compiler option need to be set while building MinGW dll . If we follow the normal steps to create the native dll, while calling from JAVA “java.lang.UnsatisfiedLinkError” will be thrown.Here I will explain the special steps to be followed while building a JNI MinGW dll.
while compiling the MinGW DLL following compiler option should be added.

“-Wall -D_JNI_IMPLEMENTATION_ -Wl,–kill-at”

for eg: suppose I need to compile “sample.cpp” to generate JNI library “sample.dll”

g++ -Wall -D_JNI_IMPLEMENTATION_ -Wl,–kill-at Sample.cpp -shared -o Sample.dll -I.C:/Program Files/Java/jdk1.6.0_10/include -I.C:/Program Files/Java/jdk1.6.0_10/include/win32

Here “C:/Program Files/Java/jdk1.6.0_10” is the installation directory of JAVA.

CSV File Writer Class In JAVA

While saving any table data’s to file, it is better to choose a CSV file.
CSV files are just comma separated plain text files.
In my current project I created a CSV file writer class in java.
Here it is!!!

CsvFileWriter.java


import java.awt.List;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.regex.Pattern;


public class CsvFileWriter extends BufferedWriter
{
	/**
	 * Parameterized constructor
	 * @param fileName
	 * @throws IOException
	 */
	public CsvFileWriter(String fileName) throws IOException{
		super(new FileWriter(fileName));
	}
   
	/**
	 * Writes a single row to a CSV file.
	 * @param row
	 * @throws IOException
	 */
	public void WriteRow(CsvRow row) throws IOException
	{
		StringBuilder builder = new StringBuilder();
		boolean firstColumn = true;
		for(String column : row ){
			if (!firstColumn)
				builder.append(',');
			if(column != null)
			{
				if (StringUtilities.indexOfFirstContainedCharacter(column, "\"+-,") !=-1){
					column = column.replaceAll("\"", "\"\"");
					builder.append(String.format("\"%s\"",column));;
				}
				else
					builder.append(column);
				firstColumn = false;
			} 
		}
		row.lineText = builder.toString();
		write(row.lineText);
		newLine();	
	}
	
	/**
	 * 
	 *  Class to store one CSV row.
	 *
	 */
	public class CsvRow extends  ArrayList<String>
	{
		String lineText;
		
		public String getlineText(){
			return lineText;
		}
		
		public void setLineText(String lineText){
			this.lineText = lineText;
		}

	}
}

StringUtilities.java

import java.util.HashSet;
import java.util.Set;

public class StringUtilities
{	
	public static int indexOfFirstContainedCharacter(String s1, String s2) {
		if (s1 == null || s1.isEmpty())
			return -1;
		Set<Character> set = new HashSet<Character>();
		for (int i=0; i<s2.length(); i++) {
			set.add(s2.charAt(i)); // Build a constant-time lookup table.
		}
		for (int i=0; i<s1.length(); i++) {
			if (set.contains(s1.charAt(i))) {
				return i; // Found a character in s1 also in s2.
			}
		}
		return -1; // No matches.
	}
	
	public static boolean isNumeric(String str)  
	{  
	  try  
	  {  
	    double d = Double.parseDouble(str);  
	  }  
	  catch(NumberFormatException nfe)  
	  {  
	    return false;  
	  }  
	  return true;  
	}
}

CsvFileWriterSample.java


import java.io.IOException;

public class CsvFileWriterSample {
	public static void main(String[] args) throws IOException {
		CsvFileWriter fileWriter = new CsvFileWriter("sample.csv");
		
		CsvFileWriter.CsvRow headerRow = fileWriter.new CsvRow();			
		headerRow.add("No");
		headerRow.add("Name");
		headerRow.add("Age");
		headerRow.add("Sex");
		headerRow.add("Height");
		headerRow.add("Weight");		
		/**
		 * adding header row 
		 */
		fileWriter.WriteRow(headerRow);
		
		CsvFileWriter.CsvRow dataRow1 = fileWriter.new CsvRow();			
		dataRow1.add("1");
		dataRow1.add("Jhon");
		dataRow1.add("20");
		dataRow1.add("Male");
		dataRow1.add("1.72 cm");
		dataRow1.add("60 kg");		
		/**
		 * adding data row  1 
		 */
		fileWriter.WriteRow(dataRow1);
		
		CsvFileWriter.CsvRow dataRow2 = fileWriter.new CsvRow();			
		dataRow2.add("2");
		dataRow2.add("Salam");
		dataRow2.add("25");
		dataRow2.add("Male");
		dataRow2.add("1.76 cm");
		dataRow2.add("70 kg");			
		/**
		 * adding data  row 2
		 */
		fileWriter.WriteRow(dataRow2);
		
		/* always close the csv writer object after use */
		fileWriter.close();
	}
}