Tag Archive: message pump

On every JNIOR, there is a System Message Pump that is constantly circulating any message sent between applications. Each message sent around the system has a unique type that identifies the message. This is done so when an application receives a message, it can determine if the message was meant for it. If the message was for that application, it consumes the message and reacts accordingly for the what the application is doing. If its not meant for it, then it simply re-broadcasts the message so the next application can evaluate the message until the correct application receives it. 

Message Types

As far as the different types of messages that can be sent, there are two groups. The first group is the system messages, which are predefined and reserved only for the OS to use when its performing checks and tasks. Message numbers below 1024 (0x400) are reserved for the system. The most common example of this is the SM_PROBE (0x02) message. This is a message that isn’t supposed to be consumed by other applications and is supposed to make its way back to the OS after its sent. If the message doesn’t come back, then it knows somethings wrong, and an application is consuming messages it shouldn’t be. It fixes this by restarting the OS. Here are other predefined system examples the JNIOR OS uses. The other group of messages are user defined messages. These messages can have any message number at or above 1024 (0x400), and can be given any message a user would wish to define for the message. 

Message Example

Below is an example of two applications communicating with one another using a user defined message. They use the System class to declare a MessagePump and SystemMsg objects. The first application has a separate class that declares a listener interface that will be used to grab messages when they enter the message pump. Inside the first application, it opens the message pump and permanently loops, only pausing the loop when its listening for a message. When it receives a message with type 1600 it prints the string in the message and continues the loop again. 

View on GitHub
View on GitHub

The second application opens the message pump, sends a user defined message containing a string and the message number 1600, and then closes the pump.

View on GitHub

I put the built jar files of these example applications into the JNIOR’s flash folder and ran one from from the Web UI’s console tab and the other from a command line connection. As shown below, one application constantly loops through itself, pausing when its trying to get a message from the message pump and then printing the message out when it does. The other sends the message the first one is listening for.

NOTE: In the picture above, the message only prints out once, while other times it just goes through the loop without printing the message. This is because we only handle one type of message, 1600. If it continues through the loop without printing a message, it means it got a message with a number type that wasn’t 1600. The other message its getting is most likely the SM_PROBE (0x02) message.

This post explains how to send and receive messages from a System Message Pump. There is a previous post showing how to create a System Message Pump. This example uses the additional applications the previous example did and should be included when the project for this application is created. That post can be accessed here. Please look over that post first as this one uses code from and references that post. 

package mqttmessagepump;

import com.integpg.system.JANOS;
import com.integpg.system.MessagePump;
import com.integpg.system.SystemMsg;
import java.util.Json;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;

public class MQTTMessagePump implements Runnable {

    private static final MQTTMessagePump THIS = new MQTTMessagePump();
    private static final MessagePump MESSAGE_PUMP = new MessagePump();
    private static final Vector LISTENERS = new Vector<>();

    private static Thread _thread;

    /**
     * Singleton constructor
     */
    private MQTTMessagePump() {
    }

    /**
     * adds a listener that will be alerted of all received messages. The
     * listener will be responsible for determining if the message has meaning
     * to them
     *
     * @param listener
     */
    public static void addListener(MessagePumpListener listener) {
        synchronized (LISTENERS) {
            LISTENERS.addElement(listener);
        }
    }

    /**
     * starts our message pump engine.
     */
    static void start() {
        if (null == _thread) {
            _thread = new Thread(THIS);
            _thread.setName("message-pump-engine");
            _thread.setDaemon(true);
            _thread.start();
        }
    }
    
    //completes the message 1600 after being sent from the MQTT application
    
    public static void completeMessage1600 (String recievedJsonInfo) {
        
        Json holdinginfo = new Json(recievedJsonInfo);
        String[] arrayofkeys = holdinginfo.keyarray();
        String subscribecheck = holdinginfo.getString(arrayofkeys[0]);
        String topicscheck = holdinginfo.getString(arrayofkeys[1]);
        String infocheck = holdinginfo.getString(arrayofkeys[2]);
        System.out.println(arrayofkeys[0] + ": " + subscribecheck + ", " + arrayofkeys[1] + ": " + topicscheck + ", " + arrayofkeys[2] + ": " + infocheck);
 
    }
    
    //creates the message that will be sent to the other program to complete
    
    public static SystemMsg createMessage (int messageType, byte [] messageInfo) {
        
        SystemMsg returnMsg = new SystemMsg();
        returnMsg.type = messageType;
        returnMsg.msg = messageInfo;
        
        System.out.println(returnMsg);
        return returnMsg;
        
    }
    
    //adds a value to the Json object that we be sent as part of the message to the other program
    
    public static Json addToJson(Json Jsonadd, String valuename, Object jsonvalue) {
    
        Jsonadd.put(valuename, jsonvalue);
        return Jsonadd;
        
    }
    
    @Override
    public void run() {
        System.out.println("open the message pump and start monitoring.\r\n");
        MESSAGE_PUMP.open();

        for (;;) {
            // read all messages from the message pump
            SystemMsg systemMsg = MESSAGE_PUMP.getMessage();

            // we must repost as fast as we can
            MESSAGE_PUMP.postMessage(systemMsg);

            // notify all of our listeners

            if (systemMsg.type == 1600) {

                System.out.println();
                System.out.println("Recieved command 1600.\n");
                String jsoninfo = new String(systemMsg.msg);
                completeMessage1600(jsoninfo);           

            }
            
             Json thirdjson = new Json();
             addToJson(thirdjson, "Message", "publish"); 
             addToJson(thirdjson, "Topic", "jnior/(Your JNIOR's Serial Number)/set/digital/outputs/1/state");
             addToJson(thirdjson, "Payload", "true");
             SystemMsg returnMsg = createMessage(1600, thirdjson.toString().getBytes());
             MESSAGE_PUMP.postMessage(returnMsg);    
            try {
                Thread.sleep(1000);
            } catch (InterruptedException ex) {
                Logger.getLogger(MQTTMessagePump.class.getName()).log(Level.SEVERE, null, ex);
            }

        }

    }

    /**
     * Exposes the postMesssage method of the System MessagePump
     *
     * @param msg the message the will be sent to the system message pump
     */
    public static void postMessage(SystemMsg msg) {
        MESSAGE_PUMP.postMessage(msg);
    }

}

To start, before we can create an application to talk with the MQTT application, we need to configure the MQTT application to hook up with a broker. Here is a download for the MQTT application update project. An online broker is needed in order to use the MQTT protocol. Free ones can be found online, but for this example the free hivemq broker is being used. This broker is public and if you want to use MQTT protocols outside this example, a secure one should be used. The only information you need to have in the MQTT application is the host URL and the port number. Then click connect and make sure the MQTT application is running on the JNIOR.

MQT connection in MQTT Application

Once the MQTT application is properly configured, the MQTT coding can be made. After creating a Message Pump program that could send messages to and from another application, the next step is to create and receive messages between the message pump and the MQTT application using the MQTT protocol. To start, we can create a new program that will interact with the MQTT application. We’ll call this program our MQTTMessage program. This sample program will only send a message to toggle an output, but could be edited to do more.

For this example, only the application containing the message pump from the previous example is needed. In the previous example, the message being sent between the applications are Json Objects. The message that needs to be created for this example is formatted as this:

{
"Message" : "publish",
"Topic" : STRING,
"Payload" : STRING
}

The MQTT application also uses Json Objects to communicate through the messages. Looking at the for loop when creating the message to send, the message has to be “published”, because when you are sending a message to the MQTT application, you are publishing information on a specified topic. The topic is the keyword that other devices are subscribed to. If a device is subscribed to that topic, then the device will receive the payload of the message. The payload is the actual data of the message that any device subscribed to the related topic will receive.

The run function in the previous example’s MessagePumpEngine application is replaced with a new for loop. When creating the message, it needs to be reformatted to match the Json message the MQTT application needs to receive. The message ID number has to be 1600, since that is the message ID the MQTT application is listening to. In this example, the Json object is created as:

{
  "Message" : "publish",
  "Topic" :jnior/"Your JNIOR's IP"/set/digital/outputs/1/state,
  "Payload" : true
}

The topic is to specify the JNIOR, what channel you want to toggle, and if its opened or closed. The payload sets that channels state to true, closing that output.

for (;;) {
            // read all messages from the message pump
            SystemMsg systemMsg = MESSAGE_PUMP.getMessage();

            // we must repost as fast as we can
            MESSAGE_PUMP.postMessage(systemMsg);

            // notify all of our listeners

            if (systemMsg.type == 1600) {

                System.out.println();
                System.out.println("Recieved command 1600.\n");
                String jsoninfo = new String(systemMsg.msg);
                completeMessage1600(jsoninfo);           

            }
            
             Json thirdjson = new Json();
             addToJson(thirdjson, "Message", "publish"); 
             addToJson(thirdjson, "Topic", "jnior/(Your JNIOR's Serial Number)/set/digital/outputs/1/state");
             addToJson(thirdjson, "Payload", "true");
             SystemMsg returnMsg = createMessage(1600, thirdjson.toString().getBytes());
             MESSAGE_PUMP.postMessage(returnMsg);    
            try {
                Thread.sleep(1000);
            } catch (InterruptedException ex) {
                Logger.getLogger(MQTTMessagePump.class.getName()).log(Level.SEVERE, null, ex);
            }

        }

The if statement checking the (systemMsg.type == 1600) is there because the JNIOR being used to publish the message is also listening the for messages on the message pump too, and those messages will have the same message ID 1600.

if (systemMsg.type == 1600) {

                System.out.println();
                System.out.println("Recieved command 1600.\n");
                String jsoninfo = new String(systemMsg.msg);
                completeMessage1600(jsoninfo);           

            }

Inside that if statement is a call to the completeMessage1600() function. That function will grab the data out of the Json object from the message and print it out for you to see.

public static void completeMessage1600 (String recievedJsonInfo) {
        
        Json holdinginfo = new Json(recievedJsonInfo);
        String[] arrayofkeys = holdinginfo.keyarray();
        String subscribecheck = holdinginfo.getString(arrayofkeys[0]);
        String topicscheck = holdinginfo.getString(arrayofkeys[1]);
        String infocheck = holdinginfo.getString(arrayofkeys[2]);
        System.out.println(arrayofkeys[0] + ": " + subscribecheck + ", " + arrayofkeys[1] + ": " + topicscheck + ", " + arrayofkeys[2] + ": " + infocheck);
 
    }

With these changes to the previous message pump application and MQTT application configuration, after running this example program you should be able to create and send messages through the MQTT application using the MQTT protocol.

We can take the Message Pump sample one step further. We can incorporate real-time web page communication. The web server supports WebSockets. WebSockets allow for real-time bidirectional communication. We can leverage this feature and have these messages put on the Message Pump so that our Java application can get them.

Our web page for the example looks like this.

In the code we have a button handler that will send the json formatted message to the web server.

            // register an onclick method so that we can post a message to get a random number from our 
            // sample java application.
            document.getElementById('random-number-button').onclick = function (e) {
                var getUpdates = {Message: "Post Message", Number: 2000, Content: "get-random-number"};
                _comm.sendJson(getUpdates);
            };

A full listing of the web page is as follows

<!DOCTYPE html>
<html lang="en">

    <head>
        <base href="/messagepumpsample/">

        <title>Message Pump Web Interface</title>

        <script src="comm.js" type="text/javascript"></script>
        <script src="md5.js" type="text/javascript"></script>

        <style>
            body { padding: 5px; }
        </style>

    </head>


    <body>

        <div>

            <h1>Message Pump Web Interface</h1>

            <button id="random-number-button">Get Random Number</button>
            <p style="width: 600px;">
                Clicking the button above will cause a message to get sent to the Web Server.  The Web Server 
                then places it in the System Message Pump.  Our sample Java application will then receive it and 
                for the sake of the sample will respond with a random number.  By placing the random number in a 
                message and back on to the System Message Pump with the same message type that it received, the 
                Operating System knows to send it back to the Web Server and in turn back to the sending Web 
                Socket connection.
            </p>

        </div>


        <script type="text/javascript">
            // our websocket communication object
            var _comm;
            var _loggedIn = false;

            // method called when the connection is successfully opened
            var onopen = function () {
                console.log("comm opened");
            };
            
            // method called when our websocket object receives a message from the web server
            var onmessage = function (evt) {
                var json = JSON.parse(evt.data);
                console.log(json);

                if (json.Message === "Monitor") {
                    if (!_loggedIn) {
                    }
                    _loggedIn = true;
                }

                //
                else if (json.Message === "Reply Message") {
                    if (2000 === json.Number) {
                        alert("Your Random Number is " + json.Content);
                    }
                }
            };
            
            // instantiate our websocket communications object and wire up some methods.  lastly call connect!
            _comm = new Comm();
            _comm.onopen = onopen;
            _comm.onmessage = onmessage;
            _comm.connect();


            
            // register an onclick method so that we can post a message to get a random number from our 
            // sample java application.
            document.getElementById('random-number-button').onclick = function (e) {
                var getUpdates = {Message: "Post Message", Number: 2000, Content: "get-random-number"};
                _comm.sendJson(getUpdates);
            };
        </script>
    </body>

</html>

To accomplish this we created a class to handle the get-random-number request. We could have handled this in our Main class but hey I like modularity.

package com.integpg.messagepumpsample;

import com.integpg.system.SystemMsg;
import java.util.Random;

public class RandomNumberMessageHandler implements MessagePumpListener {

    @Override
    public void messageReceived(SystemMsg systemMsg) {
        // this sample uses message type 2000
        if (2000 == systemMsg.type) {
            String content = new String(systemMsg.msg);
            System.out.println("content: " + content);
            
            Random rand = new Random();
            int randomNumber = rand.nextInt();
            
            SystemMsg responseMsg = new SystemMsg();
            responseMsg.type = 2000;
            responseMsg.msg = String.valueOf(randomNumber).getBytes();
            
            MessagePumpEngine.postMessage(responseMsg);
        }
    }

}

We also had to modify our init() method to attach our MessagePumpHandler.

package com.integpg.messagepumpsample;

import com.integpg.system.SystemMsg;
import java.util.Random;

public class RandomNumberMessageHandler implements MessagePumpListener {

    @Override
    public void messageReceived(SystemMsg systemMsg) {
        // this sample uses message type 2000
        if (2000 == systemMsg.type) {
            String content = new String(systemMsg.msg);
            System.out.println("content: " + content);
            
            // get a random number
            Random rand = new Random();
            int randomNumber = rand.nextInt();
            
            // build our response
            SystemMsg responseMsg = new SystemMsg();
            responseMsg.type = 2000;
            responseMsg.msg = String.valueOf(randomNumber).getBytes();
            MessagePumpEngine.postMessage(responseMsg);
        }
    }

}

Almost immediately upon clicking the button we get our response.

As you can see here the web page gives you an amazing ability to communicate with a Java application in real-time.

There are many times that you want to get a message from the system. You may also want to communicate between processes. To do either of these we employ the Message Pump.

In this example  we will monitor the messages that are going around the message pump inside the JNIOR.

We first need a class that will define all the message pump types

package com.integpg.messagepumpsample;

/**
 * A class that contains an enumeration of values for each message type.
 */
public class SystemMessageTypes {

    public static final int SM_SHUTDOWN = 0x01;
    public static final int SM_PROBE = 0x02;
    public static final int SM_GCRUN = 0x10;
    public static final int SM_WATCHDOG = 0x11;
    public static final int SM_SYSLOG = 0x12;
    public static final int SM_REGUPDATE = 0x40;
    public static final int SM_WEBSTARTUP = 0x60;
    public static final int SM_WEBSHUTDOWN = 0x61;
    public static final int SM_PROTCMDMSG = 0x70;
    public static final int SM_PROTCMDRESP = 0x71;
    public static final int SM_PIPEOPEN = 0x80;
    public static final int SM_PIPECLOSE = 0x81;
    public static final int SM_USER = 0x400;



    /**
     * @return a String definition based on the type parameter
     */
    public static String getMessageNameByType(int type) {
        switch (type) {
            case SM_SHUTDOWN:
                return "Shutdown";
            case SM_PROBE:
                return "Probe";
            case SM_GCRUN:
                return "GC Run";
            case SM_WATCHDOG:
                return "Watchdog";
            case SM_SYSLOG:
                return "Syslog";
            case SM_REGUPDATE:
                return "Registry Update";
            case SM_WEBSTARTUP:
                return "Web Server Startup";
            case SM_WEBSHUTDOWN:
                return "Web Server Shutdown";
            case SM_PROTCMDMSG:
                return "Protocol Command Message";
            case SM_PROTCMDRESP:
                return "Protocol Command Reesponse";
            case SM_PIPEOPEN:
                return "Pipe Opened";
            case SM_PIPECLOSE:
                return "Pipe Closed";
            default:
                if (type >= SM_USER) return "User Defined Type " + String.valueOf(type);
                else return "Unknown Type " + String.valueOf(type);
        }
    }
}

Not only does the above class define the types but it gives us a nice helper function to get a nice textual description for the message type.

We will create a class that will monitor the message pump. The biggest note to observe is that we MUST forward the message once we receive it so that other processes can see the message. The message pump message delivery is also time sensitive. If we receive the message and perform a lengthy process before re-posting it the we could cause the system to assert as the message pump timing has been violated.

package com.integpg.messagepumpsample;

import com.integpg.system.MessagePump;
import com.integpg.system.SystemMsg;
import java.util.Vector;

public class MessagePumpEngine implements Runnable {

    private static final MessagePumpEngine THIS = new MessagePumpEngine();
    private static final MessagePump MESSAGE_PUMP = new MessagePump();
    private static final Vector LISTENERS = new Vector<>();

    private static Thread _thread;



    /**
     * Singleton constructor
     */
    private MessagePumpEngine() {
    }



    /**
     * adds a listener that will be alerted of all received messages. The listener will be responsible for determining
     * if the message has meaning to them
     */
    public static void addListener(MessagePumpListener listener) {
        synchronized (LISTENERS) {
            LISTENERS.addElement(listener);
        }
    }



    /**
     * starts our message pump engine.
     */
    static void start() {
        if (null == _thread) {
            _thread = new Thread(THIS);
            _thread.setName("message-pump-engine");
            _thread.setDaemon(true);
            _thread.start();
        }
    }



    @Override
    public void run() {
        System.out.println("open the message pump and start monitoring.\r\n");
        MESSAGE_PUMP.open();

        for (;;) {
            // read all messages from the message pump
            SystemMsg systemMsg = MESSAGE_PUMP.getMessage();

            // we must repost as fast as we can
            MESSAGE_PUMP.postMessage(systemMsg);

            // notify all of our listeners
            synchronized (LISTENERS) {
                for (int i = 0; i < LISTENERS.size(); i++) {
                    MessagePumpListener listener = (MessagePumpListener) LISTENERS.elementAt(i);
                    listener.messageReceived(systemMsg);
                }
            }

        }
    }



    /**
     * Exposes the postMesssage method of the System MessagePump
     *
     * @param msg the message the will be sent to the system message pump
     */
    public static void postMessage(SystemMsg msg) {
        MESSAGE_PUMP.postMessage(msg);
    }
}

When the above class receives a message from the message pump it will want to alert any listeners.  Lets define the listener interface now.

package com.integpg.messagepumpsample;

import com.integpg.system.SystemMsg;

/**
 * An interface that outlines the method or methods that all classes that implement this interface must define.
 */
public interface MessagePumpListener {

    public void messageReceived(SystemMsg systemMsg);
}

Now that we have laid the ground work for the application lets glue the peices together.  Here is our Main class

package com.integpg.messagepumpsample;

/**
 * HexUtils will give us access to the hex dump so that we can show a meaningful representation of the data in each
 * message
 */
import com.integpg.janoslib.utils.HexUtils;
/**
 * System classes pertaining to the message pump itself and the class that will be returned from the getMessage call
 */
import com.integpg.system.SystemMsg;
/**
 * A class that will format a date string quickly
 */
import com.integpg.janoslib.text.QuickDateFormat;

/**
 * A class to demonstrate the Message Pump feature in the JNIOR.
 */
public class MessagePumpSampleMain implements MessagePumpListener {

    /**
     * the QuickDateFormat() class is part of a library of classes and are beyond the scope of this example.
     */
    private final static QuickDateFormat QUICK_DATE_FORMAT = new QuickDateFormat("MM-dd-yy HH:mm:ss.fff");



    public static void main(String[] args) throws InterruptedException {
        // instantiate our main class
        MessagePumpSampleMain msgPumpSample = new MessagePumpSampleMain();
        msgPumpSample.init();

        // the main method has nothing left to do.  let it sleep forever.  Most if not all of our other threads 
        // are daemon threads that would not keep the process from sxiting if this main thread completes.  
        // Sometimes we might do somehting in this thread but for the sake of this example we have nothing to do.
        Thread.sleep(Integer.MAX_VALUE);
    }



    public void init() {
        // the message pump engine is a singleton.  We do not have to instatiate another instance.  We can just 
        // use static methods to interface with it.  
        //
        // first we need to add in any listeners that should be alerted when a new message is received.  It 
        // will be up to those llisteners to determine if it cares about the message.
        MessagePumpEngine.addListener(this);
        MessagePumpEngine.addListener(new RegistryMessageHandler());

        // we are ready.  start listening
        MessagePumpEngine.start();
    }



    @Override
    public void messageReceived(SystemMsg systemMsg) {
        // the QUICK_DATE_FORMAT.format() call is part of a library of classes and are beyond the scope of this 
        // example.
        String dateString = QUICK_DATE_FORMAT.format(System.currentTimeMillis());
        // so we have a message.  what kind is it?
        String messageName = SystemMessageTypes.getMessageNameByType(systemMsg.type);
        // print out what we know about this message
        System.out.println(dateString + " - Process Msg: " + messageName);

        // if there is data in the message then we should dump it so that we can see what it is.
        if (0 < systemMsg.msg.length) {
            // the HexUtils.hexDump() call is part of a library of classes and are beyond the scope of this 
            // example.
            String hexDumpString = HexUtils.hexDump(systemMsg.msg);
            System.out.println(hexDumpString);
        }
    }

}

In the above lines 48 and 49 we add our listener classes.  Line 48 is the this object representing the Main class.  You can see what happens in the messageReceived() method starting at line 58.  The other listener is the RegistryMessageHandler class.  Here is the definition for that class.

package com.integpg.messagepumpsample;

import com.integpg.system.JANOS;
import com.integpg.system.SystemMsg;

public class RegistryMessageHandler implements MessagePumpListener {

    @Override
    public void messageReceived(SystemMsg systemMsg) {
        // we only care about registry key updates
        if (SystemMessageTypes.SM_REGUPDATE == systemMsg.type) {
            // get the key name from the message data
            String keyName = new String(systemMsg.msg);
            // now get the new value
            String keyValue = JANOS.getRegistryString(keyName, "");
            System.out.println("Registry Update: " + keyName + " = " + keyValue);
        }
    }

}

Each time a message is received from the message pump each of our listeners is notified.  Each listener can check the message type to see if it cares about the message.  A sample output from the application shows three interesting events.  First, I opened the web page for the unit.  The web server is NOT always running unless the web server port is accessed.  Once the web server port was hit it caused the web server to spin up and send the Web Server Startup message.  The next two events were registry updates.  The Main class listener shows us that there was a Registry Update along with the HEX dump of the message payload.  Then the RegistryMessageHandler listener used the registry key that was updated to get the new value.  You can download the sample application JAR file below.

  msgpump-monitor.jar [ Jul 25 2018, 23.38 KB, MD5: 00eb2d3b97bbc67c5d1d13a9901e9fbe ]

The two files below need to be added to the project with the example above for it to run properly.

HexUtils.java

/**
 * A Utility class for dealing with Hex
 */
public class HexUtils {

    public static final char[] HexArray = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
    public static final char[] LowerHexArray = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };



    public static String bytesToHex(byte[] bytes) {
        return bytesToHex(bytes, 0, bytes.length);
    }



    public static String bytesToHex(byte[] bytes, int offset, int len) {
        if (null == bytes) return "null";

        char[] hexChars = new char[len * 2];
        int v, i = 0;
        for (int j = offset; j < offset + len; j++) {
            v = bytes[j] & 0xFF;
            hexChars[i++] = HexArray[(v >> 4 & 0x0F)];
            hexChars[i++] = HexArray[v & 0x0F];
        }
        return new String(hexChars);
    }



    public static byte[] hexToBytes(String hexString, int offset, int len) {
        if (null == hexString) return new byte[0];

        byte[] bytes = new byte[len/2];
        int i = 0;
        for (int j = offset; j < len; j += 2) {
            char c = hexString.charAt(j);
            int n = 0;
            if ('0' <= c && '9' >= c) n = c - '0';
            else if ('A' <= c && 'F' >= c) n = c - 55;
            else if ('a' <= c && 'f' >= c) n = c - 87;
            else throw new RuntimeException("error parsing hex");
//            System.out.println(String.format("%s, %d", String.valueOf(c), n));

            int val = n << 4;

            c = hexString.charAt(j + 1);
            n = 0;
            if ('0' <= c && '9' >= c) n = c - '0';
            else if ('A' <= c && 'F' >= c) n = c - 55;
            else if ('a' <= c && 'f' >= c) n = c - 87;
            else throw new RuntimeException("error parsing hex");

            val += n;
//            System.out.println(String.format("%s, %d, %d", String.valueOf(c), n, val));

            bytes[i++] = (byte) val;
        }
        return bytes;
    }



    public static String hexDump(byte[] bytes) {
        return hexDump(bytes, 0, bytes.length);
    }



    public static String hexDump(byte[] bytes, int offset, int length) {
        StringBuilder sb = new StringBuilder((length / 16 + 1) * 83);

        char[] line = new char[81];
        line[0] = '0';
        line[1] = 'x';
        line[line.length - 2] = '\r';
        line[line.length - 1] = '\n';

        int endAddress = offset + length;
        for (int lineAddress = offset - (offset % 16); lineAddress < endAddress; lineAddress += 16) {
            int charPos = 2;
            line[charPos++] = LowerHexArray[(lineAddress >> 12 & 0x0F)];
            line[charPos++] = LowerHexArray[(lineAddress >> 8 & 0x0F)];
            line[charPos++] = LowerHexArray[(lineAddress >> 4 & 0x0F)];
            line[charPos++] = LowerHexArray[lineAddress & 0x0F];
            line[charPos++] = ' ';
            line[charPos++] = ' ';
            line[charPos++] = ' ';

            for (int linePos = 0; linePos < 16; linePos++) {
                int address = lineAddress + linePos;
                if (address < offset) {
                    line[charPos++] = ' ';
                    line[charPos++] = ' ';
                    line[charPos++] = ' ';
                } else if (address < endAddress) {
                    int c = bytes[address];
                    line[charPos++] = LowerHexArray[(c >> 4 & 0x0F)];
                    line[charPos++] = LowerHexArray[c & 0x0F];
                    line[charPos++] = ' ';
                } else {
                    line[charPos++] = ' ';
                    line[charPos++] = ' ';
                    line[charPos++] = ' ';
                }

                if (7 == linePos) {
                    // add 2 spaces between each group of 8 bytes
                    line[charPos++] = ' ';
                    line[charPos++] = ' ';
                }
            }
            // add 3 spaces between the second group of 8 bytes and the ASCII output
            line[charPos++] = ' ';
            line[charPos++] = ' ';
            line[charPos++] = ' ';

            for (int linePos = 0; linePos < 16; linePos++) {
                int address = lineAddress + linePos;

                if (address < offset) line[charPos++] = ' ';
                else if (address < endAddress) {
                    char c = (char) bytes[address];
                    if (0x20 > c || 0x80 <= c) line[charPos++] = '.';
                    else line[charPos++] = c;
                } else line[charPos++] = ' ';
                if (7 == linePos) line[charPos++] = ' ';
            }

            sb.append(line, 0, line.length);
        }

        return sb.toString();
    }



    public static long parseHexAsLong(String s) {
        if (s == null) return 0;
        int len = s.length();
        // get the address in two stages because some addresses can be too great for the parseLong method.
        long l = Long.parseLong(s.substring(0, len - 8), 16);
        l <<= 32;
        l += Long.parseLong(s.substring(len - 8), 16);
        return l;
    }
}

QuickDateFormat.java

import com.integpg.system.ArrayUtils;
import com.integpg.system.Timebase;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

/**
 * This class constructs a date string with a user defined format. it caches date and time locations to help construct
 * the string faster.
 */
public class QuickDateFormat {

    private static final SimpleDateFormat TIMEZON_DATE_FORMAT = new SimpleDateFormat("z");
    private static final long MILLIS_IN_HOUR = 3600000;
    private static final String[] MONTH_NAME_STRINGS = new String[]{ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
    private static final byte[][] AM_PM_STRING_BYTES = new byte[][]{ "am".getBytes(), "pm".getBytes() };

    private final Calendar _calendar = Calendar.getInstance();

    private final byte[] _dateStampBytes;
    public final int DATE_STAMP_LENGTH;
    public final int DATE_STAMP_LENGTH_WITH_COMMA;
    private final String _pattern;

    int _weekOfYearPos = -1;
    int _monthPos = -1;
    int _monthNamePos = -1;
    int _dayPos = -1;
    int _2yearPos = -1;
    int _4yearPos = -1;
    int _hourPos = -1;
    int _hourOfDayPos = -1;
    int _minutePos = -1;
    int _secondPos = -1;
    int _millisPos = -1;
    int _timezonePos = -1;
    int _ampmPos = -1;

    private final byte[] _buf = new byte[8];
    private long _lastTimeRef = 0;

    private long _nextHour = System.currentTimeMillis() - System.currentTimeMillis() % MILLIS_IN_HOUR;
    private long _nextMinute = 0;



    public QuickDateFormat() {
        this("MM-dd-yyyy HH:mm:ss.fff zzz");
    }



    public QuickDateFormat(String pattern) {
        _pattern = pattern;

        _dateStampBytes = new byte[pattern.length() + 2];
        ArrayUtils.arraycopy(pattern.getBytes(), 0, _dateStampBytes, 0, pattern.length());
        _dateStampBytes[_dateStampBytes.length - 2] = ',';
        _dateStampBytes[_dateStampBytes.length - 1] = ' ';

        DATE_STAMP_LENGTH = _dateStampBytes.length - 2;
        DATE_STAMP_LENGTH_WITH_COMMA = _dateStampBytes.length;

        _weekOfYearPos = pattern.indexOf("WY");
        _monthNamePos = pattern.indexOf("MMM");
        if (-1 == _monthNamePos) {
            _monthPos = pattern.indexOf("MM");
        }
        _dayPos = pattern.indexOf("dd");
        if (-1 == _dayPos) _dayPos = pattern.indexOf("DD");
        _2yearPos = pattern.indexOf("yy");
        if (-1 == _2yearPos) _2yearPos = pattern.indexOf("YY");
        _4yearPos = pattern.indexOf("yyyy");
        if (-1 == _4yearPos) _4yearPos = pattern.indexOf("YYYY");
        _hourPos = pattern.indexOf("hh");
        _hourOfDayPos = pattern.indexOf("HH");
        _minutePos = pattern.indexOf("mm");
        _secondPos = pattern.indexOf("ss");
        _millisPos = pattern.indexOf("fff");
        if (-1 == _millisPos) _millisPos = pattern.indexOf("SSS");
        _timezonePos = pattern.indexOf("zzz");
        _ampmPos = pattern.indexOf("aa");

        updateTimezone();
    }



    public String getDateFormatString() {
        return _pattern;
    }



    private void updateTimezone() {
        if (-1 != _timezonePos) {
            String timezoneString = Timebase.getTimeZone(); //TIMEZON_DATE_FORMAT.format(new Date());
//            System.out.println("timezoneString: " + timezoneString);
//            System.out.println("_dateStampBytes.legnth: " + _dateStampBytes.length);
//            System.out.println("_timezonePos: " + _timezonePos);
            ArrayUtils.arraycopy(timezoneString.getBytes(), 0, _dateStampBytes, _timezonePos, timezoneString.length());
        }
    }



    // 2.0
    public String format(Date date) {
        return format(date.getTime());
    }



    public String format() {
        return format(System.currentTimeMillis());
    }



    public String format(long time) {
        synchronized (_dateStampBytes) {
            if (time <= 0) time = System.currentTimeMillis();

            if (_nextHour <= time) {
                _nextHour += MILLIS_IN_HOUR;
                updateTimezone();
            }

            if (_lastTimeRef > time || time >= _nextMinute) {
                initRefBytes(time);
            }

            int delta = (int) (time - _lastTimeRef);
            int rSecond = delta / 1000;

            if (-1 != _secondPos) {
                stuffInt(_buf, rSecond + 100, 0, false);
                ArrayUtils.arraycopy(_buf, 1, _dateStampBytes, _secondPos, 2);
            }

            if (-1 != _millisPos) {
                int rMillis = delta - 1000 * rSecond;
                stuffInt(_buf, rMillis + 1000, 0, false);
                ArrayUtils.arraycopy(_buf, 1, _dateStampBytes, _millisPos, 3);
            }

            return new String(_dateStampBytes, 0, DATE_STAMP_LENGTH);
        }
    }



    // 2.0
    public byte[] toByteArray() {
        return _dateStampBytes;
    }



    private void initRefBytes(long time) {
        _calendar.setTimeInMillis(time);

        int second = _calendar.get(Calendar.SECOND);
        int millis = _calendar.get(Calendar.MILLISECOND);

        _lastTimeRef = time - (1000 * second + millis);
        _nextMinute = time + 60000 - (time % 60000);

        synchronized (_dateStampBytes) {
            if (-1 != _weekOfYearPos) {
                int weekOfYear = _calendar.get(Calendar.WEEK_OF_YEAR);
//        System.out.println("woy: " + weekOfYear);
                stuffInt(_buf, weekOfYear + 100, 0, false);
                ArrayUtils.arraycopy(_buf, 1, _dateStampBytes, _weekOfYearPos, 2);
            }

            if (-1 != _monthPos) {
                int month = _calendar.get(Calendar.MONTH);
                stuffInt(_buf, month + 101, 0, false);
                ArrayUtils.arraycopy(_buf, 1, _dateStampBytes, _monthPos, 2);
            }

            if (-1 != _monthNamePos) {
                int month = _calendar.get(Calendar.MONTH);
                String monthName = MONTH_NAME_STRINGS[month];
                ArrayUtils.arraycopy(monthName.getBytes(), 0, _dateStampBytes, _monthNamePos, 3);
            }

            if (-1 != _dayPos) {
                int day = _calendar.get(Calendar.DAY_OF_MONTH);
                stuffInt(_buf, day + 100, 0, false);
                ArrayUtils.arraycopy(_buf, 1, _dateStampBytes, _dayPos, 2);
            }

            if (-1 != _2yearPos || -1 != _4yearPos) {
                int year = _calendar.get(Calendar.YEAR);
                stuffInt(_buf, year, 0, false);
                if (-1 != _2yearPos) ArrayUtils.arraycopy(_buf, 2, _dateStampBytes, _2yearPos, 2);
                if (-1 != _4yearPos) ArrayUtils.arraycopy(_buf, 0, _dateStampBytes, _4yearPos, 4);
            }

            if (-1 != _hourPos) {
                int rHour = _calendar.get(Calendar.HOUR);
                stuffInt(_buf, rHour + 100, 0, false);
                ArrayUtils.arraycopy(_buf, 1, _dateStampBytes, _hourPos, 2);
            }

            if (-1 != _hourOfDayPos) {
                int rHour = _calendar.get(Calendar.HOUR_OF_DAY);
                stuffInt(_buf, rHour + 100, 0, false);
                ArrayUtils.arraycopy(_buf, 1, _dateStampBytes, _hourOfDayPos, 2);
            }

            if (-1 != _minutePos) {
                int minute = _calendar.get(Calendar.MINUTE);
                stuffInt(_buf, minute + 100, 0, false);
                ArrayUtils.arraycopy(_buf, 1, _dateStampBytes, _minutePos, 2);
            }

            if (-1 != _ampmPos) {
                byte[] amPmBytes = AM_PM_STRING_BYTES[_calendar.get(Calendar.AM_PM)];
                ArrayUtils.arraycopy(amPmBytes, 0, _dateStampBytes, _ampmPos, 2);
            }
        }
    }



    private void stuffInt(byte[] array, int value, int dec, boolean rjustified) {
        ArrayUtils.arrayFill(array, 0, array.length, (byte) ' ');

        if (value == 0) {
            array[0] = (byte) '0';
            return;
        }

        int dig;
        int offset = 0;
        boolean leading = true;

        if (value < 0) {
            array[offset++] = (byte) '-';
            value = -value;
            rjustified = false;
        }

        for (dig = 1000000; dig >= 1; dig /= 10) {
            if (leading && value < dig) {
                if (rjustified) {
                    offset++;
                }
                continue;
            }

            array[offset] = (byte) '0';
            while (value >= dig) {
                array[offset]++;
                value -= dig;
            }
            offset++;
            leading = false;
        }

        for (dig = dec; dig > 0; dig--) {
            array[offset] = array[offset - 1];
            if (array[offset] == ' ') {
                array[offset] = '0';
            }
            offset--;

            if (dig == 1) {
                array[offset] = (byte) '.';
                if (array[offset - 1] == ' ') {
                    array[offset - 1] = '0';
                }
            }
        }
    }
}

JANOS supplies a Message Pump wherein messages of various types may circulate between processes.

 

JANOS supplies a Message Pump wherein messages of various types may be circulated between processes or application. These messages may be user defined.
Message numbers below 1024 (0x400) are RESERVED by the system.
The following are the system defined message types.

SM_SHUTDOWN (0x01)

This message is generated by the system prior to shutdown. When received applications MUST forward the message by returning it to the pump and exit in an expeditious fashion. The JNIOR is about to reboot.

SM_PROBE (0x02)

This message is generated by the system periodically. When receive applications MUST forward the message by returning it to the pump. This is used to detect listeners that are no longer responding or that are not properly forwarding messages. The system expects to see this message return to it in a prompt fashion.

SM_GCRUN (0x10)

This message indicates that the Garbage Collection (GC) has completed. When received applications MUST forward the message by returning it to the pump.

SM_WATCHDOG (0x11)

This message is generated by a application watchdog configured to send then message on timer expiration.

SM_SYSLOGMSG (0x12)

System log messages can be sent to an external Syslog Server. This message also passes the log information to listening applications.

SM_PWRLOST (0x20)

When Ride-Thru Power support is available this indicates the lost of external power.

SM_PWRGOOD (0x21)

When Ride-Thru Power support is available this indicates that external power has been restored.

SM_PWRREADY (0x22)

When Ride-Thru Power support is available this indicates that the supply is fully charged and ready to provide maximum holding capacity.

SM_REGUPDATE (0x40)

This message is generated whenever a registry entry is updated or removed. When received application MUST forward the message by returning it to the pump.

SM_WEBSTARTUP (0x60)

Message sent when the Web Server process is activated.

SM_WEBSHUTDOWN (0x61)

Message sent when the Web Server process is terminated.

SM_PROTCMDMSG (0x70)

This message is generated when the JNIOR Protocol receives a custom command message. When received an application MUST either forward the message or provide a SM_PROTCMDRESP response.

SM_PROTCMDRESP (0x71)

This message is generated by an application in response to a SM_PROTCMDMSG command message. It is intended for the JNIOR Protocol server. When received applications MUST forward the message by returning it to the pump.

SM_PIPEOPEN (0x80)

This message is sent by the Web Server when a piped websocket connection has been established. The message contains the client IP Address and Port as well as the target message number.

SM_PIPECLOSE (0x81)

This message is sent by the Web Server when a piped websocket connection has terminated. The message contains the client IP Address and Port as well as the original targeted message number.

SM_USER (0x400)

Lowest allowed user defined message number. Applications that intend to exchange messages SHOULD attempt to define globally unique message identifiers. These must be values from 1024 and up. Message numbers below SM_USER are RESERVED by the system.