Java Question raiseEvent or raiseEventFromDifferentThread

mindful

Active Member
Licensed User
Hello, I am trying to wrap my first library for B4J and I do not get when to use ba.raiseEvent or ba.raiseEventFromDifferentThread...

I am trying to raise some events from different Listeners and also in some interfaces something like:

B4X:
import java.util.Comparator;

import anywheresoftware.b4a.BA;
import anywheresoftware.b4a.BA.CheckForReinitialize;
import anywheresoftware.b4a.BA.Events;
import anywheresoftware.b4a.BA.Hide;
import anywheresoftware.b4a.BA.ShortName;

@ShortName("ComparatorObject")
@Events(values={"Compare(Object1 As Object, Object2 As Object) As Int"})
public class ComparatorObjectWrapper implements Comparator<Object>, CheckForReinitialize {

    private String eventName;
    private BA ba;
   
    public void Initialize(BA ba, String EventName) {
        this.ba = ba;
        this.eventName = EventName.toLowerCase(BA.cul) + "_compare";
    }
   
    @Override
    public boolean IsInitialized() {
        return (this.ba != null);
    }

    @Hide
    @Override
    //raised on another thread than the OwnersThread
    public int compare(Object o1, Object o2) {
        return (int) ba.raiseEvent(this, eventName, new Object[] {o1, o2});
    }
}
In the above example I am calling a sub name from b4j which is exposed as an "event" and all the logic (comparing) is made there. As I need it to return a value I am using raiseEvent ... also tryed with relfection (same good result) but I think it is best to use b4j internal methods (as I am writing a library for b4j) when calling a sub that does the processing and returns a result - are they any other advantages using ba.raiseEvent instead of reflection !?

B4X:
@ShortName("MessageListener")
@Events(values={"OnMessage(Message As Object)"})
public class MessageListenerWrapper implements MessageListener<Object>, CheckForReinitialize {

    private String eventName;
    private BA ba;
   
    public void Initialize(BA ba, String EventName) {
        this.ba = ba;
        this.eventName = EventName.toLowerCase(BA.cul) + "_onmessage";
    }
   
    @Override
    public boolean IsInitialized() {
        return (this.ba != null);
    }

    @Hide
    @Override
    //raised on another thread than the OwnersThread
    public void onMessage(Message<Object> message) {
        ba.raiseEventFromDifferentThread(this, null, 0, eventName.toLowerCase(BA.cul), false, new Object[] {message.getMessageObject()});
    }
}
In the second example I am using raiseEventFromDifferentThread because raiseEvent throws some error (NullPointerException). Using raiseEventFromDifferentThread works great.


So as far as I figured it out it's all about what the user does in the b4j sub this leads me to another question is it safe to change all my code to use ba.raiseEvent and let the user deal with it (he can use CallSubDelayed in the event sub if he encounters errors) ?

I just don't get (from mine perspective - library wrapper writer) which method to use....
 

Erel

Administrator
Staff member
Licensed User
are they any other advantages using ba.raiseEvent instead of reflection !?
You shouldn't use reflection to access B4J methods. It will fail in some cases.

If the callback is called from a different thread then you need to use ba.raiseEventFromDifferentThread. In most other cases you should use ba.raiseEventFromUI.
 

mindful

Active Member
Licensed User
I am in the need of calling a b4j method from a method in the library i am wrapping but it needs to return a value so I am using ba.raiseEvent. In release raiseEvent works synchronous but in debug It returns null and after some time the sub from b4j is call. The method from which raiseEvent is called runs on another thread (a thread created by the library i am wrapping). So my question is how can I wait for the return of raiseEvent or how to call a method from b4j synchronous in debug mode ?
 

mindful

Active Member
Licensed User
This is the code from the class that implements an interface so I am overriding the loadAll() so I need to provide the user ability provide his own result.
B4X:
    @Hide
    @Override
    public synchronized Map<Object, Object> loadAll(Collection<Object> keys) {
        System.err.println("la i : " + (ba != null));
        List l1 = new List();
        l1.setObject(new ArrayList<Object>(keys));
        Object r = ba.raiseEvent(this, eventName + "_loadall", new Object[] {l1});
        if (r == null) {
            return null;
        } else {
            anywheresoftware.b4a.objects.collections.Map m1 = (anywheresoftware.b4a.objects.collections.Map) r; 
            if (m1.IsInitialized()) {
                return m1.getObject();
            } else {
                return null;
            }
        }
    }
I know that in debug all things run on the main thread and I think that raiseEvent is creating the event and putting it in the event queue, which will run when the main thread is free ... so what to do !? :)
 

mindful

Active Member
Licensed User
I am trying to wrap a piece of Hazelcast - IMap (a distributed map). So I am creating a class which implements MapStore Interface:
B4X:
@ShortName("HazelcastMapStore")
@Events(values={"Load(Key As Object) As Object",
                "LoadAll(Keys As List) As Map",
                "LoadAllKeys() As List",
                "Delete(Key As Object)",
                "DeleteAll(Keys As List)",
                "Store(Key As Object, Value As Object)",
                "StoreAll(KeyValue As Map)"})
public class MapStoreWrapper implements MapStore<Object, Object>, CheckForReinitialize {

    private String eventName;
    private BA ba;
  
    public void Initialize(BA ba, String EventName) {
        this.ba = ba;
        this.eventName = EventName.toLowerCase(BA.cul);
    }
  
    @Hide
    public synchronized Object load(Object key) {
        return ba.raiseEvent(this, eventName + "_load", new Object[] {key});
    }
  
    @Hide
    public synchronized Map<Object, Object> loadAll(Collection<Object> keys) {
        List l1 = new List();
        l1.setObject(new ArrayList<Object>(keys));
        Object r = ba.raiseEvent(this, eventName + "_loadall", new Object[] {l1});
        if (r == null) {
            return null;
        } else {
            anywheresoftware.b4a.objects.collections.Map m1 = (anywheresoftware.b4a.objects.collections.Map) r;
            if (m1.IsInitialized()) {
                return m1.getObject();
            } else {
                return null;
            }
        }
    }

    @Hide
    public synchronized Iterable<Object> loadAllKeys() {
        Object r = ba.raiseEvent(this, eventName + "_loadallkeys", new Object[] {});
        if (r == null) {
            return null;
        } else {
            List l1 = (List) r;
            if (l1.IsInitialized()) {
                return l1.getObject();
            } else {
                return null;
            }
        }
    }

    @Hide
    public synchronized void delete(Object key) {
        ba.raiseEvent(this, eventName + "_delete", new Object[] {key});
    }

    @Hide
    public synchronized void deleteAll(Collection<Object> keys) {
        List l1 = new List();
        l1.setObject(new ArrayList<Object>(keys));
        ba.raiseEvent(this, eventName + "_deleteall", new Object[] {l1});
    }

    @Hide
    public synchronized void store(Object key, Object value) {
        ba.raiseEvent(this, eventName + "_store", new Object[] {key, value});
    }

    @Hide
    public synchronized void storeAll(Map<Object, Object> map) {
        anywheresoftware.b4a.objects.collections.Map m1 = new anywheresoftware.b4a.objects.collections.Map();
        m1.Initialize();
        m1.getObject().putAll(map);
        ba.raiseEvent(this, eventName + "_store", new Object[] {m1});
    }

    @Override
    public boolean IsInitialized() {
        return (this.ba != null);
    }

}
So I am passing an instance of this class to a hazelcast Imap and during the creation of a map the IMap calls the method loadAllKeys() in which i want to provide the logic (a list of keys from my sql db) from b4j. The loadAllKeys is called from a hazelcast library thread. So when loadAllKeys (internally from hazelcast) i need to call a sub from b4j in which i do the logic and return a result back to the loadAllKeys ...
 

Erel

Administrator
Staff member
Licensed User
As I wrote you must delegate it to the main thread.

A simple solution is to provide the map beforehand. If it is not possible then you will need to do something a bit more complicated.

This code is from the SFTP library:

B4X:
@Override
     public boolean promptYesNo(String message) {
       prompt = message;
       ba.raiseEventFromDifferentThread(null, null, 0, eventName + "_promptyesno", true,
           new Object[] {prompt});
       cdl = new CountDownLatch(1);
       try {
         if (!cdl.await(60, TimeUnit.SECONDS)) {
           result = false;
         }

         prompt = null;
       } catch (InterruptedException e) {
         e.printStackTrace();
       }
       return result;
}
public void cont(boolean res) {
       result = res;
       if (cdl != null)
         cdl.countDown();
       prompt = null;
     }
It blocks the background thread, with a timeout, until the user calls the 'cont' method with the result.
 

mindful

Active Member
Licensed User
In case of a server solution (using websocket classes) raiseEventFromDifferentThread will delegate to the websocket class thread and not the main thread correct !?
 

mindful

Active Member
Licensed User
In the Initialize method. the class is initialized from the websocket class.

LE: This last question was not about the MapStore wrapper, only for my further knowledge ;) so the raiseEventFromDifferentThread will delegate to the thread of the BA object ?
 

mindful

Active Member
Licensed User
Unfortunetly CountDownLatch isn't an option :(

Let me put it this way so that it's easyer to understand what i am trying to acomplish... how should I create a class that implements a Comparator interface which will be passed to a 3rd party library that runs on it's own threads and the b4j user needs to provide the compare logic.

In my way of thinking in the class where I override the compare method i need to call a sub from b4j where the b4j user provided its logic and needs to return an int (-1, 0, 1)

I did it like this:
B4X:
import java.util.Comparator;

import anywheresoftware.b4a.BA;
import anywheresoftware.b4a.BA.CheckForReinitialize;
import anywheresoftware.b4a.BA.Events;
import anywheresoftware.b4a.BA.Hide;
import anywheresoftware.b4a.BA.ShortName;

@ShortName("ComparatorObject")
@Events(values={"Compare(Object1 As Object, Object2 As Object) As Int"})
public class ComparatorObjectWrapper implements Comparator<Object>, CheckForReinitialize {

 private String eventName;
 private BA ba;
 
 public void Initialize(BA ba, String EventName) {
 this.ba = ba;
 this.eventName = EventName.toLowerCase(BA.cul) + "_compare";
 }
 
 @Override
 public boolean IsInitialized() {
 return (this.ba != null);
 }

 @Hide
 @Override
 //raised on another thread than the OwnersThread
 public int compare(Object o1, Object o2) {
 return (int) ba.raiseEvent(this, eventName, new Object[] {o1, o2});
 }
}
What other alternatives are where the b4j user is required to provide the compare logic (take the comparing of 2 strings .... one user could compare them by the first letter, one by the lenght, etc...) ... as i said the library runs on it's own threads. The above method works in release ... but it calls the b4j sub from another thread and as you said i should not do this ...
 

mindful

Active Member
Licensed User
CountDownLatch isn't an option because the user will need to call the sub that counts down (releases) the latch in the event sub(the b4j sub that I want to call from another thread) and I want it to be more simple for the user.

In the case of the MapStore class i don't get how i can provide the logic of the sub beforehand as it works something like this:
1. The b4j user asks for a value of the IMap (imap.Get(key, value))
2. If the isn't in the map then the imap will call the mapstore loadAll(Collection<Value> keys) where it hands out what keys i need to pull from my persisted store (db)
3. In the loadAll method I need to create a map with the key value pair that i have got from my db and return it.

So how can i know what keys to pull from db beforehand ?

Also the Comparator of strings was an example ... take the comparator of Objects ... where I don't know what type of Object will be stored in the IMap as value. The Comparator class will be provided to the IMap for sorting so i hand the class that implementes Comparator interfeace to the IMap (so IMap cand return a sorted list of values) and the IMap calls the overrided compare method for each entry. I store b4j types in my hazelcast IMaps as values and want to do the comparing based on multiple fields from the b4j type. So how can I provide this logic before hand?
 

mindful

Active Member
Licensed User
Thank you for all your guidance.

But it seems I was on the wrong path with this Comparator interface because the class that implements it needs to be Serializable as Hazelcast will send this class to other members of the cluster over the wire so I can't "save" a reference of ba object in the class.

It seems to me this will only work if the user will provide the java code. I came across javassit library where one could inject code at runtime and the b4j user will need to provide the java code of the overrided compare method when he initializes the comparator class - still testing but it seems to work.

Are there any plans in the future where b4j will support classes that implement interfaces so the b4j user could provide the logic (in b4j code) for the overrided methods ?
 
Top