Java Question raiseEvent or raiseEventFromDifferentThread

Discussion in 'Libraries developers questions' started by mindful, Sep 5, 2017.

  1. mindful

    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:

    Code:
    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 !?

    Code:
    @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....
     
  2. Erel

    Erel Administrator Staff Member Licensed User

    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 likes this.
  3. mindful

    mindful Active Member Licensed User

    Even if I am wrapping a non-ui library that will be used in a non-ui application ? will ba.raiseEventFromUI work in a non-ui app ?
     
  4. Erel

    Erel Administrator Staff Member Licensed User

    ba.raiseEventFromUI will work in a non-ui app if StartMessageLoop was called.
     
  5. mindful

    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 ?
     
  6. mindful

    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.
    Code:
    @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 !? :)
     
  7. Erel

    Erel Administrator Staff Member Licensed User

    You should never raise events from other threads directly. The B4J code is expected to run on the main thread (with some exceptions such as server solutions).
    This is true in both release and debug modes.

    It is hard to give you concrete advice as we don't really know what you are trying to implement.
     
  8. mindful

    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:
    Code:
    @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 ...
     
  9. Erel

    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:

    Code:
    @Override
         
    public boolean promptYesNo(String message) {
           prompt = message;
           ba.raiseEventFromDifferentThread(
    nullnull0, 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.
     
    stevel05 and mindful like this.
  10. mindful

    mindful Active Member Licensed User

    Have tried a bunch of things but not the CountDownLatch :( Thank you !
     
  11. mindful

    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 !?
     
  12. Erel

    Erel Administrator Staff Member Licensed User

    It depends on the BA object. How do you set it?
     
  13. mindful

    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 ?
     
  14. Erel

    Erel Administrator Staff Member Licensed User

    Yes.

    If it was initialized in the WebSocket class then it will raise the event there.
     
    mindful likes this.
  15. mindful

    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:
    Code:
    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 ...
     
  16. Erel

    Erel Administrator Staff Member Licensed User

    Why?

    For a comparison of two strings, you can provide two or more implementations (case sensitive or case insensitive) and let the user choose one beforehand.
     
  17. mindful

    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?
     
  18. Erel

    Erel Administrator Staff Member Licensed User

    It is up to you to design your library. As I explained you shouldn't raise events on other threads if you want your library to be usable with B4J.

    Personally I think that the CountDownLatch is a good option but I don't see any point in arguing about it...
     
    mindful likes this.
  19. mindful

    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 ?
     
  20. Erel

    Erel Administrator Staff Member Licensed User

    No such feature is planned.
     
Loading...
  1. This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
    By continuing to use this site, you are consenting to our use of cookies.
    Dismiss Notice