JavaFX 1.0 - Reflections on the JSON Weather Service

In my previous posting on the JSON Weather Service, I hard coded the Pull Parser's population of the Weather object. I don't particularly like to hard-code anything, so I have been investigating how to make this a little more generic. I found an answer using the JavaFX reflection package.

 What I did was create a class called "ParseModel". ParseModel has public instance variables for the "model" object (the Weather Object that will be populated), an optional "map" instance variable that contains maps between the parsed element name and the name of the instance varaible within the model object, a "url" that will point to the GeoNames geographical database, a "documentType" that is either PullParser.XML or PullParser.JSON, and an "onDone" function that is called when all the parsing is complete. I also have 3 private variables that hold the javafx.reflect types for the model object. context holds the javafx.reflect.FXLocal.Context instance, classRef holds the class type of the model object, and fxModel to hold the javafx.reflect.FXObjectValue that will be a mirror of the model object.


public class ParseModel {

    def context:FXLocal.Context = FXLocal.getContext();
    var classRef: FXClassType;
    var fxModel: FXObjectValue;

    public-init var model:Object on replace {
        if(model != null) {
            classRef = context.findClass(model.getClass().getName());
            fxModel = context.mirrorOf(model);
        }else { classRef = null; fxModel = null;}
    };

    public-init var map: Map;
    public-init var url:String;
    public-init var documentType:String;
    public-init var onDone: function(model:Object) : Void;


When the model is set, the java.reflect.FXLocal.Context instance is used to locate the javafx.reflect.ClassRefType  for the model class, and to create a javafx.reflect.FXObjectValue  object that encapsualtes the model Object. The classRef will be used to query the model for instance variables, and the fxModel variable will be used when setting the values for those variables.

Next, I create a function to be used by the PullParser to handle the parse events. This function translates the type from the parsed element to the type within the model object. Notice that when I set the value of the instance variable member object, I have to encapsulate the event value using context.mirrorOf().


function handler(event: Event) : Void {
        if(event.type == PullParser.TEXT or
            event.type == PullParser.INTEGER or
            event.type == PullParser.NUMBER ) {
            var field:String; // holds the name of the instance variable within the model object.
            if(map != null and map.containsKey(event.name)) { // a map entry exists, use it
                field = map.get(event.name) as String;
            } else { // default to the event name
                field = event.name;
            }
            var member = classRef.getVariable(field); // get the instance variable within the model object, for the field.

            var type = "{member.getType()}"; // the type of the instance variable within the model object.
            // try to guess the conversion
            if(event.type == PullParser.TEXT){
                if(type == "Integer") {
member.setValue(fxModel, context.mirrorOf(java.lang.Integer.valueOf(event.text)));
                } else if (type == "Number") {
member.setValue(fxModel, context.mirrorOf(java.lang.Double.valueOf(event.text)));
                } else if (type == "Boolean") {
member.setValue(fxModel, context.mirrorOf(java.lang.Boolean.valueOf(event.text)));
                }else { // assuming string
member.setValue(fxModel, context.mirrorOf(event.text));
                }
            } else if(event.type == PullParser.INTEGER) {
                if(type == "Number") {
member.setValue(fxModel, context.mirrorOf(java.lang.Double.valueOf(event.integerValue)));
                } else {
member.setValue(fxModel, context.mirrorOf(java.lang.Integer.valueOf(event.integerValue)));
                }
            } else if(event.type == PullParser.NUMBER) {
                if(type == "Integer") {
member.setValue(fxModel, context.mirrorOf(java.lang.Integer.valueOf(event.numberValue.intValue())));
                } else {
member.setValue(fxModel, context.mirrorOf(java.lang.Double.valueOf(event.numberValue)));
                }
            } else if(event.type == PullParser.TRUE or event.type == PullParser.FALSE) {
member.setValue(fxModel, context.mirrorOf(event.booleanValue));
            }
        }
    }


Next, I created an init block to create a RemoteTextDocument with a PullParser. The PullParser's onEvent function variable is set to the handler described above. When RemoteTextDocument returns and the PullParser completes, I call the onDone function variable passing the model object. Doing it this way makes ParseModel a once use object. It could have just as easily been done in a function rather than in the init block, and you could change the public instance variables from public-init to public so they can change between invocations.


    init {
       var input:InputStream;
       println("Starting");
       var rtd:RemoteTextDocument = RemoteTextDocument {
            url: bind url
            onDone: function(success:Boolean):Void {
                if(success) {
                    var doc = rtd.document;
                    println(doc);
                    input = new ByteArrayInputStream(doc.getBytes());
var parser: PullParser = PullParser {
                        documentType: documentType
                        input: input
                        onEvent: handler
                    };

                    parser.parse();
                    input.close();
onDone(model);

                }else {
                    println("failure = {rtd.failureText}");
                }
                println("Done");
            }
        };
    }


To initialize this:


    var w = Weather {};
    var m: Map = HashMap{};
m.put("stationName", "station");// It is only necessary to put entries when names do not match

    var modl = ParseModel {
        model: w
        map: m
        documentType: PullParser.JSON
        url: "http://ws.geonames.org/weatherIcaoJSON?ICAO=KMCO"
        onDone: function(obj: Object ) : Void {
var weather = obj as Weather;
            println("MODEL: {weather}");

        }
    };


TODO: This program is still one dimensional, only handling the basic variable types of Boolean, Integer, Number, and String. An improvement would be to recognize complex object trees for the model and do the appropriate recursion during parsing.

A program to test these ideas out can be downloaded from here.


Comments:

Post a Comment:
  • HTML Syntax: NOT allowed
About

jimclarke

Search

Categories
Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today