B4J Question [SOLVED] How to Build Custom Jar For use with B4X JavaObject?

cjpryor

Active Member
Licensed User
So I built a simple java program with dependencies to call from within B4J using the JavaObject. The jar files I generated (I tried several different options) all worked in standalone mode (java -jar ReportWrapper.jar) but failed in B4J. I was hoping maybe a "Fat" jar (where all dependencies are copied into the jar - not the one were dependencies are extracted into the jar file ... but I did try that as well) but that did not work.

I can get one of them to work in B4j in Debug mode but I cannot get it to work in Release mode. So, I am looking for advice on how to configure and deploy my simple java program (with dependencies on other jars) so that it will work with a deployed B4J app. As you look at my example, please note that I know there is a jJasperReports Library but I did encounter some problems with it (that are still being worked) and I will need to write custom java code for some future features that I plan to deploy so this will be a good learning experience for me.

Here is more information. What I am hoping for is some guidance and/or requirements for what it takes to build a B4X compatible Jar file when there are dependencies on other jar files. I will be happy to provide even more on request. I am not really asking anybody to troubleshoot my attempt but that would be welcome if it will help me understand how to do this properly.

First, my custom Java Code:

Java:
package net.nmcollector.reportwrapper;

import net.sf.jasperreports.engine.*;
import net.sf.jasperreports.view.JasperViewer;

import java.sql.Connection;
import java.sql.DriverManager;

import java.util.HashMap;

public class ReportWrapper {
    public static void main(String[] args) {
        System.out.println("Hello from net.nmcollector.cpReports main");

        for (int i = 0; i < args.length; i++) {
            System.out.println(("main - value of args( " + i + "): " + args[i]) );
        }

        String[] str = new String[5];

        if (args.length==0){

            str[0] = "C:\\Users\\cjpry\\AppData\\Roaming\\nmcollector\\nmcswDB.sqlite";
            str[1] = "C:\\Users\\cjpry\\AppData\\Roaming\\nmcollector\\Reports\\";
            str[2] = "Inventory.jasper";
            str[3] = "1";
            str[4] = "1";

        } else {

            str = args;
        }

        int returnCode = openReport(str);

        System.out.println("call to openReport returnCode = " + returnCode);

    }

    public static int openReport(String[] args) {

        int returnCode = -1;

        System.out.println("Hello from net.nmcollector.cpReports openReport");

        for (int i = 0; i < args.length; i++) {
            System.out.println(("openReport value of args( " + i + "): " + args[i]) );
        }

        String jasperReport = args[1] + args[2];
        System.out.println("net.nmcollector.cpReports openReport jasperReport = " + jasperReport);
        String datasourceConnectionString = args[0];
        System.out.println("net.nmcollector.cpReports openReport datasourceConnectionString = " + datasourceConnectionString);

        // Parameters for report
        //Map<String, Object> parameters = new HashMap<>();
        HashMap parameters = new HashMap();

        parameters.put("collectionId",Integer.parseInt(args[3]));
        parameters.put("selectedItemId",Integer.parseInt(args[4]));
        parameters.put("SUBREPORT_DIR",args[1]);

        System.out.println("net.nmcollector.cpReports openReport parameters loaded");

        // DataSource
        Connection conn = null;
        JasperPrint jasperPrint;

        try {

            String url = "jdbc:sqlite:" + datasourceConnectionString;

            System.out.println("net.nmcollector.cpReports openReport url = " + url);

            conn = DriverManager.getConnection(url);

            System.out.println("net.nmcollector.cpReports openReport conn loaded");

        } catch (Exception e) {

            System.out.println("SQL connection error:" + e.getMessage());

        }

        try {

            System.out.println("net.nmcollector.cpReports openReport ********* B4J Release Fails Here ********** call JasperPrint");
            jasperPrint = JasperFillManager.fillReport(jasperReport, parameters, conn);

            System.out.println("net.nmcollector.cpReports openReport call JasperViewer");
            JasperViewer.viewReport( jasperPrint, false );

            returnCode=0;

        } catch (Exception e) {

            System.out.println("Jasper Reports error:" + e.getMessage());

        }

        return returnCode;
    }
}

Next, my B4X call:

B4X:
Private Sub Button_Open_Report_Click

    Dim returnCode As Int
   
    Try
   
        Dim reportName As String = reportFileNamesList.Get(selectedReportIndex)
        Dim subReportDir As String = File.DirData("nmcollector/Reports") & MainPage.pathSeparator
        Dim sqlConnectionString As String = File.Combine(File.DirData("nmcollector"),"nmcswDB.sqlite")
       
        'setup link to Report Wrapper
        Dim jo As JavaObject
        jo.InitializeStatic("net.nmcollector.reportwrapper.ReportWrapper")
        'jo.InitializeNewInstance("net.nmcollector.reportwrapper.ReportWrapper", Null)
        returnCode = jo.RunMethod("openReport", Array(Array As String(sqlConnectionString, subReportDir, reportName, selectedCollectionId, selectedItemId)))
   
        Log("call to reports returnCode = " & returnCode)
   
    Catch
       
        Log("Open Report, error = [" & LastException & "]")
        'Wait For (xui.MsgboxAsync(LastException, "Error")) Msgbox_Result (Result As Int)
               
        Dim sf As Object = xui.Msgbox2Async(LastException, "Error", "okay", "", "", Null)
        Wait For (sf) Msgbox_Result (Result As Int)
       
    End Try
   
End Sub

Now, the error I encounter in B4J Release mode (Debug mode works just fine!):

B4X:
net.nmcollector.cpReports openReport ********* B4J Release Fails Here ********** call JasperPrint
reportspage$ResumableSub_Button_Open_Report_Click.resume (java line: 302)
java.lang.ClassCastException: class java.lang.NoClassDefFoundError cannot be cast to class java.lang.Exception (java.lang.NoClassDefFoundError and java.lang.Exception are in module java.base of loader 'bootstrap')
    at anywheresoftware.b4a.BA.setLastException(BA.java:377)
    at net.nmcollector.nmCollectorCP.reportspage$ResumableSub_Button_Open_Report_Click.resume(reportspage.java:302)

I can post more of the stack trace if you need it.
 
Last edited:

cjpryor

Active Member
Licensed User
For clarification, the key to understanding where things are going wrong is the following line in the custom java code:

System.out.println("net.nmcollector.cpReports openReport ********* B4J Release Fails Here ********** call JasperPrint");

The next line in the custom java app is where it fails in B4J release mode.
 
Upvote 0

cjpryor

Active Member
Licensed User
I still need help with the proper way to create a jar file that has embedded jar files for use in B4X. I am going to go through the B4A Library creation tutorial next to see if that helps.

So, Eclipse warned me that I was accessing a Static method in a non-static way. IntelliJ did not warn me of that. When I addressed that issue the error above went away! I updated the java code above to reflect the correct approach.

Now the only problem I have is that my jar file in Release mode cannot access an embedded jar file. There is no error in Debug mode. Here is the new result from the B4J logs:

B4X:
Hello from net.nmcollector.cpReports openReport

...

net.nmcollector.cpReports openReport ********* B4J Release Fails Here ********** call JasperPrint
Jasper Reports error:Font "DejaVu Sans" is not available to the JVM. See the Javadoc for more details.
call to reports returnCode = -1

This is the same result I get with the jJasperReport library.
 
Last edited:
Upvote 0

cjpryor

Active Member
Licensed User
I should add, that the missing Font is part of the jasperreports-fonts-6.17.0.jar which is referenced in my custom jar (and in the jJasperReport library). Both work fine in B4X Debug mode and both fail to find the font in B4X Release mode. It should also be noted that when my custom code jar file is run (java -jar ReportWrapper.jar) directly in the B4X additionallibraries folder (in my case, C:\data\B4X\additionallibraries\B4X), it sees the font.
 
Upvote 0

cjpryor

Active Member
Licensed User
So, switching to an installed font to avoid the font error, all is well on Windows and Mac but still failing in Linux. I can deploy and SUCESSFULLY run my ReportWrapper.jar on Linux (java -jar ReportWrapper.jar). However, if I try to run the same jar from within the B4X deployed jar I get the following result (again, on Linux only):

Hello from net.nmcollector.cpReports openReport
openReport value of args( 0): /home/nmcollector/tempjars/temp/build/bin/nmcswDB.sqlite
openReport value of args( 1): /home/nmcollector/tempjars/temp/build/bin/
openReport value of args( 2): Collections.jasper
openReport value of args( 3): 1
openReport value of args( 4): 0
net.nmcollector.cpReports openReport jasperReport = /home/nmcollector/tempjars/temp/build/bin/Collections.jasper
net.nmcollector.cpReports openReport datasourceConnectionString = /home/nmcollector/tempjars/temp/build/bin/nmcswDB.sqlite
net.nmcollector.cpReports openReport parameters loaded
net.nmcollector.cpReports openReport url = jdbc:sqlite:/home/nmcollector/tempjars/temp/build/bin/nmcswDB.sqlite
net.nmcollector.cpReports openReport conn loaded
net.nmcollector.cpReports openReport ********* B4J Release Fails Here ********** call JasperPrint
reportspage$ResumableSub_Button_Open_Report_Click.resume (java line: -1)
java.lang.ClassCastException: class java.lang.IllegalAccessError cannot be cast to class java.lang.Exception (java.lang.IllegalAccessError and java.lang.Exception are in module java.base of loader 'bootstrap')
at b4j/anywheresoftware.b4a.BA.setLastException(Unknown Source)
at b4j/net.nmcollector.nmCollectorCP.reportspage$ResumableSub_Button_Open_Report_Click.resume(Unknown Source)
at b4j/net.nmcollector.nmCollectorCP.reportspage._button_open_report_click(Unknown Source)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.base/java.lang.reflect.Method.invoke(Unknown Source)
at b4j/anywheresoftware.b4a.BA.raiseEvent2(Unknown Source)
at b4j/anywheresoftware.b4a.BA$1.run(Unknown Source)
at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runLater$10(Unknown Source)
at java.base/java.security.AccessController.doPrivileged(Unknown Source)
at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runLater$11(Unknown Source)
at javafx.graphics/com.sun.glass.ui.InvokeLaterDispatcher$Future.run(Unknown Source)
at javafx.graphics/com.sun.glass.ui.gtk.GtkApplication._runLoop(Native Method)
at javafx.graphics/com.sun.glass.ui.gtk.GtkApplication.lambda$runLoop$11(Unknown Source)
at java.base/java.lang.Thread.run(Unknown Source)
 
Upvote 0

cjpryor

Active Member
Licensed User
As much as I hate to have a separate program for reports, I may have to deploy one for Linux users - only until we can figure out how to get it to work in a B4X application deployed to Linux.
 
Upvote 0
Top