Chapter 6: Run-Time Variabilities

Summary

Run-time variabilities provide the most flexibility at the latest binding time. A variety of techniques, such as property files, resource files, configuration files, XML files, and databases, can be used as input sources. If persistence is needed, then these can also be used as output. The run-time mechanisms described in this chapter can also be used to implement decisions made at other times. To increase flexibility and decrease maintenance difficulties, separate data input concerns from data usage concerns, as shown in Figure 6-5.



Chapter 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13

Example 6-2: Config.java
Example 6-2X: Config.java (complete example)
Example 6-3: MyFrame1.java
Example 6-7: MyFrame2.java
Example 6-10: XML Configuration File
Example 6-11: MyFrame3.java
Example 6-12: PlayData1.java
Example 6-13: PlayRT.java
Example 6-14: PlayRT.java, a Program Working Directly from XML Data Structures

Example 6-2: Config.java

class Config extends Properties {
     
    public Config(String fname) {
        loadFile(fname);
    }

    public  String get(String key, String defaultValue) {
        String r = getProperty(key);
        if (r==null || r.equals(""))
            return defaultValue;
        return r;
    }

    public String get(String key) {
        String r = getProperty(key);
        if (r==null)
            return "";
        return r;
    }
    
    public boolean loadFile(String filename) {
        FileInputStream f = null;
        try {
            f = new FileInputStream(filename);
            load(f);
            f.close();
        } catch (Exception e) {
            error("Unable to load file "+filename+" "+e.getMessage());
            return false;
        }
        return true;
    }

    /** Gets all keys with a given prefix and suffix */
    public Vector getKeys(String prefix, String suffix) {
        Vector r = new Vector();
        Enumeration e = propertyNames();
        while (e.hasMoreElements()) {
            String key = (String) e.nextElement();
            if (prefix!=null && !key.startsWith(prefix))
                continue;
            if (suffix!=null && !key.endsWith(suffix))
                continue;
            r.addElement(key);
        }
        return r;
    }

    /** Get the value as a list of strings (separated by white space) */
    public String[] getList(String key) {
        return parseList(getProperty(key));
    }

    int getInt(String key, int defaultValue) {
        return parseInt(get(key), defaultValue);
    }

    /** Get integer list */
    int[] getIntList(String key) {
        String[] d = getList(key);
        int[] r = new int[d.length];
        for (int j=0; j<d.length; ++j) {
            r[j] = parseInt(d[j], 1);
        }
        return r;
    }

    // more
}

Example 6-2X: Config.java (complete example)


import java.util.*;
import java.io.*;

/**
 * The Config class implements run-time variabilities as a properties file
 */
class Config extends Properties {
     
    public Config(String fname) {
        loadFile(fname);
    }

    /** Get property for a key, if no value return default */
    public  String get(String key, String defaultValue) {
        String r = getProperty(key);
        if (r==null || r.equals(""))
            return defaultValue;
        return r;
    }

    /** Get property for key */
    public String get(String key) {
        String r = getProperty(key);
        if (r==null)
            return "";
        return r;
    }
    
    /** Load the configuration from a file */
    public boolean loadFile(String filename) {
        FileInputStream f = null;
        try {
            f = new FileInputStream(filename);
            load(f);
            f.close();
        } catch (Exception e) {
            error("Unable to load file "+filename+" "+e.getMessage());
            return false;
        }
        return true;
    }
    
    /** Get the value as a list of strings (separated by white space) */
    public String[] getList(String key) {
        return parseList(getProperty(key));
    }
    
    /** Convert list of items represented as a string */
    static public String[] parseList(String v) {
        if (v==null)
            return new String[0];
        Vector list = new Vector();
        int start = 0;
        int j;
        for (j=0; j<v.length(); ++j) {
            char c = v.charAt(j);
            switch (c) {
            case ' ':
            case '\t':
            case '\n':
            case '\r':
                if (start!=j) {
                    list.addElement(v.substring(start, j));
                }
                start = j+1;
                break;
            case '"':
                if (start!=j) {
                    list.addElement(v.substring(start, j));
                }
                ++j;
                String s = "";
                while (true) {
                    if (j==v.length()) {
                        error("mismatched quotes: value="+v);
                        break;
                    }
                    char c2 = v.charAt(j);
                    if (c2=='\\') {
                        ++j;
                        if (j==v.length()) {
                            error("value ends with escape: value="+v);
                            break;
                        }
                        c2 = v.charAt(j);
                    } else if (c2=='"') {
                        break;
                    }
                    s += c2;
                    ++j;
                }
                list.addElement(s);
                start = j+1;
                break;
            }
        }
        if (start<j)  // grab last element if there is one
            list.addElement(v.substring(start, j));

        String[] r = new String[list.size()];
        for (int j2=0; j2<r.length; ++j2) {
            r[j2] = (String) list.elementAt(j2);
        }
        return r;
    }

    /** Get an integer property */
    int getInt(String key, int defaultValue) {
        return parseInt(get(key), defaultValue);
    }
    
    /** Convert a string to an integer */
    static int parseInt(String r, int defaultValue) {
        int result = defaultValue;
        if (r==null || r.equals(""))
            return result;
        try {
            result = Integer.parseInt(r);
            return result;
        } catch (Exception e) {
            error("**** Invalid integer value="+r);
            return defaultValue;
        }
    }

    /** Get integer list */
    int[] getIntList(String key) {
        String[] d = getList(key);
        int[] r = new int[d.length];
        for (int j=0; j<d.length; ++j) {
            r[j] = parseInt(d[j], 1);
        }
        return r;
    }

    /** Gets all keys with a given prefix and suffix */
    public Vector getKeys(String prefix, String suffix) {
        Vector r = new Vector();
        Enumeration e = propertyNames();
        while (e.hasMoreElements()) {
            String key = (String) e.nextElement();
            if (prefix!=null && !key.startsWith(prefix))
                continue;
            if (suffix!=null && !key.endsWith(suffix))
                continue;
            r.addElement(key);
        }
        return r;
    }
    
    /** save the configuration file */
    boolean saveFile(String fname) {
        PrintStream f;
        try {
            f = new PrintStream(new BufferedOutputStream(new FileOutputStream(fname), 5000));
        } catch (Exception e) {
            error("Unable to open file "+fname+" to save Config file");
            return false;
        }
        save(f, "Config");
        f.close();
        return true;
    }
    
    /** converts a list of objects to a string */
    static public String list2string(Object[] list) {
        String r = "";
        for (int j=0; j<list.length; ++j) {
            if (j!=0) {
                // separate all items with a blank
                r += " ";
            }
            String s = quoteIfNeeded(list[j].toString());
            r += s;
        }
        return r;
    }
        
    /** The string s is quoted if it has blanks in it */
    static public String quoteIfNeeded(String s) {
        boolean hasSpace = s.length()==0 || s.indexOf(' ')>=0 || s.indexOf('\t')>=0 || s.indexOf('\n')>=0 || s.indexOf('\r')>=0 || s.indexOf('"')>=0;
        if (hasSpace) {
            return quote(s);
        }
        return s;
    }
    
    /** Quote the string and escape embedded quotes or escapes */
    static public String quote(String s) {
        String s2 = "\"";
        for (int k=0; k<s.length(); ++k) {
            char c = s.charAt(k);
            if (c=='"') {
                s2 += "\\\"";
            } else if (c=='\\') {
                s2 += "\\\\";
            } else {
                s2 += c;
            }
        }
        return s2+'"';
    }
    
    /** converts a list of ints to a string */
    static public String intlist2string(int[] list) {
        String r = "";
        for (int j=0; j<list.length; ++j) {
            if (j!=0) r += " ";
            r += list[j];
        }
        return r;
    }
    
    /** returns a new object of an arbitrary type */
    static public Object parseClass(String className) {
        Object s;
        Class c = null;
        try {
            c = Class.forName(className);
        } catch (Exception e) {
            error("Unable to download "+className+" class.");
            return null;
        }
        try {
            s = (Object) c.newInstance();
        } catch (Exception e) {
            error("Unable to instantiate "+className+" class.");
            return null;
        }
        return s;
    }
    
    static void error(String message) {
        System.out.println("Config error: "+message);
    }
    
    /** Test program */
    static void main(String[] args) {
        System.out.println("main called");
        for (int j=0; j<args.length; ++j) {
            System.out.println("arg "+j+" = "+args[j]);
        }
        if (args.length>0) {
            Config c = new Config(args[0]);
            System.out.println("one is "+c.get("one"));
        }
    }
}

Example 6-3: MyFrame1.java

package com.craigc.progen;
import java.awt.*;

class MyFrame1 extends Frame {
    
    public MyFrame1(Config c) {
        super(c.get("title", "MyFrame1 Example"));
        Button myButton = new Button(c.get("label", "exit"));
        myButton.setBackground(Color.decode(
            c.get("background", "#000000")));
        myButton.setForeground(Color.decode(
            c.get("foreground", "#ffffff")));
        add(myButton);
        setSize(c.getInt("width", 100), c.getInt("height", 100));
        show();
    }
        
    public static void main(String args[]) {
        if (args.length>0) {
            Config c = new Config(args[0]);
            new MyFrame1(c);
        } else {
            System.out.println("No config file specified");
        }
    }
}

Example 6-7: MyFrame2.java

import java.awt.*;

/**
 * Chapter 6 Example illustrating hierarchy and lists.
 */
class MyFrame2 extends Frame {
    
    public MyFrame2(Config c) {
        super(c.get("title", "MyFrame Example"));
        String[] buttons = c.getList("buttons");
        setLayout(new FlowLayout());
        for (int j=0; j<buttons.length; ++j) {
            Button b = new Button(c.get(buttons[j]+".label", "?"));
            b.setBackground(Color.decode(
                c.get(buttons[j]+".background", "#000000")));
            b.setForeground(Color.decode(
                c.get(buttons[j]+".foreground", "#ffffff")));
            add(b);
        }
        setSize(c.getInt("width", 100), c.getInt("height", 100));
        show();
    }
        
    public static void main(String args[]) {
        if (args.length>0) {
            Config c = new Config(args[0]);
            new MyFrame2(c);
        } else {
            System.out.println("No config file specified");
        }
    }
}

Example 6-10: XML Configuration File

<?xml version="1.0"?>

<mydoc>
<title>Chapter 6, Third Example</title>
<buttons>one two three</buttons>

<button>
<name>one</name>
<label>here's the first sample label ...</label>
<foreground>#002200</foreground>
<background>#ddffff</background>
</button>

<button>
<name>two</name>
<label>Button Two</label>
<foreground>#220000</foreground>
<background>#ffffdd</background>
</button>

<button>
<name>three</name>
<label>Third button</label>
<foreground>#000022</foreground>
<background>#ffddff</background>
</button>

<width>300</width>
<height>120</height>

</mydoc>

Example 6-11: MyFrame3.java

import org.w3c.dom.*;
import java.awt.*;

/**
 * Chapter 6 Example using XML configuration file.
 */
class MyFrame3 extends Frame {
    
    public MyFrame3(Document c) {
        super(DOM_Util.get(c, "title", "MyFrame3 Example"));
        setLayout(new FlowLayout());
        NodeList blist = c.getElementsByTagName("button");
        for (int j=0; j<blist.getLength(); ++j) {
            Node bnode = blist.item(j);
            Button but = new Button(DOM_Util.get(bnode, "label", "?"));
            but.setBackground(Color.decode(
                DOM_Util.get(bnode, "background", "#000000")));
            but.setForeground(Color.decode(
                DOM_Util.get(bnode, "foreground", "#ffffff")));
            add(but);
        }
        setSize(DOM_Util.getInt(c, "width", 100),
                DOM_Util.getInt(c, "height", 100));
        show();
    }
        
    public static void main(String args[]) {
        if (args.length>0) {
            try {
                Document c = DOM_Util.readDocument(args[0]);
                new MyFrame3(c);
            } catch (Exception e) {
                System.out.println("Invalid config file: "+args[0]);
            }
        } else {
            System.out.println("No config file specified");
        }
    }
}

Example 6-12: PlayData1.java

import org.w3c.dom.*;

class PlayData1 {
    public String name;
    public String title;
    public int width;
    public int height;
    public SceneData startScene;
    
    public PropData[] props;
    public SceneData[] scenes;
    
    public class PropData {
        public String name;
        public String label;      // initial value
        public ScriptData script; // only action script in Phase I
    }
    
    class SceneData {
        public String name;
        public String color;
        public PropData[] addprops;
    }
    
    class ScriptData {
        public SceneData nextScene;
        public TraitData[] traits;
    }
    
    class TraitData {
        public PropData prop;
        public String newValue;
    }
    
    public PlayData1(Document d) {
        convertDocument(d);
    }
    
    /** Convert XML data to PlayData1 */
    public void convertDocument(Document d) {
           details in Chapter 9
    }
}

Example 6-13: PlayRT.java

import org.w3c.dom.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;

/**
 * Play Domain with all Run-time variabilities using PlayData1
 */
class PlayRT1 extends Frame implements ActionListener {
    Hashtable prop2button = new Hashtable();// props to buttons
    Hashtable button2prop = new Hashtable();// buttons to props
    PlayData1 pd; // the internal representation of the Play Data
    
    /** Create and display a play based on PlayData1 */
    public PlayRT1(PlayData1 pd) {
        super(pd.title);
        this.pd = pd;
        setLayout(new FlowLayout());
        
        // create buttons and hash tables
        for (int j=0; j<pd.props.length; ++j) {
            PlayData1.PropData p = pd.props[j];
            Button b = new Button(p.label);
            b.addActionListener(this);
            prop2button.put(p, b);
            button2prop.put(b, p);
        }
        setSize(pd.width, pd.height);
        enterNewScene(pd.startScene);
        show();
    }
    
    /** Exit scene and enter a new one */
    void enterNewScene(PlayData1.SceneData scene) {
        removeAll();
        setBackground(Color.decode(scene.color));
        for (int j=0; j<scene.addprops.length; ++j) {
            Button b = (Button) prop2button.get(scene.addprops[j]);
            if (b!=null) {
                add(b);
            } else {
                error("No button for "+scene.addprops[j].name);
            }
        }
        show();
    }
    
    /** perform action when button is clicked */
    public void actionPerformed(ActionEvent e) {
        PlayData1.PropData p = (PlayData1.PropData)
                    button2prop.get(e.getSource());
        if (p==null) {
            error("No prop named "+e.getSource());
            return;
        }
        if (p.script==null) {
            return;
        }
        for (int j=0; j<p.script.traits.length; ++j) {
            PlayData1.TraitData td = p.script.traits[j];
            Button b = (Button) prop2button.get(td.prop);
            b.setLabel(td.newValue);
        }
        if (p.script.nextScene!=null) {
            enterNewScene(p.script.nextScene);
        }
    }
    
    static void error(String msg) {
        System.out.println("Error: "+msg);
    }
        
    public static void main(String args[]) {
        if (args.length>0) {
            try {
                // Read XML document
                Document c = DOM_Util.readDocument(args[0]);
                // Convert XML to PlayData1
                PlayData1 pd = new PlayData1(c);
                // Execute program
                new PlayRT1(pd);
            } catch (Exception e) {
                error("Invalid config file: "+args[0]);
            }
        } else {
            error("No config file specified");
        }
    }
}

Example 6-14: PlayRT.java, a Program Working Directly from XML Data Structures

package com.craigc.progen;
import org.w3c.dom.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;

/**
 * Play Domain with all Run-time variabilities, Phase I
 */
class PlayRT extends Frame implements ActionListener {
    Hashtable name2button = new Hashtable();// prop names to buttons
    Hashtable button2name = new Hashtable();// buttons to prop names
    Hashtable name2prop = new Hashtable();  // prop names to prop nodes
    Document doc; // the internal representation of the XML document
    
    public PlayRT(Document c) {
        super(DOM_Util.get(c, "title", "Play Example"));
        doc = c;
        setLayout(new FlowLayout());
        Node playNode = c.getDocumentElement();
        
        // initialize props and hash tables
        NodeList e = c.getElementsByTagName("prop");
        for (int j=0; j<e.getLength(); ++j) {
            Node bnode = e.item(j);
            String name = DOM_Util.getAttr(bnode, "name", "?");
            name2prop.put(name, bnode);
            Button b = new Button();
            setupProp(b, bnode);
            b.addActionListener(this);
            name2button.put(name, b);
            button2name.put(b, name);
        }
        setSize(DOM_Util.getIntAttr(playNode, "width", 500),
                DOM_Util.getIntAttr(playNode, "height", 250));
        enterNewScene(DOM_Util.getAttr(playNode, "start", "?"));
        show();
    }
    
    void enterNewScene(String sceneName) {
        removeAll();
        NodeList e = doc.getElementsByTagName("scene");
        for (int j=0; j<e.getLength(); ++j) {
            Element snode = (Element) e.item(j);
            String name = DOM_Util.getAttr(snode, "name", "");
            if (name.equals(sceneName)) {
                // found the right scene, now add the props
                setBackground(Color.decode(DOM_Util.getAttr(snode, 
                                                "color", "#ff0000")));
                NodeList e2 = snode.getElementsByTagName("addprop");
                for (int j2=0; j2<e2.getLength(); ++j2) {
                    Node pnode = e2.item(j2);
                    String pname = DOM_Util.getAttr(pnode, "name", "");
                    Button b = (Button) name2button.get(pname);
                    if (b!=null) {
                        add(b);
                    } else {
                        error("No prop named "+pname);
                    }
                }
                show();
                return;
            }
        }
    }
    
    /** perform action when button is clicked */
    public void actionPerformed(ActionEvent e) {
        String pname = (String) button2name.get(e.getSource());
        if (pname==null || pname.equals("")) {
            error("Invalid button prop");
            return;
        }
        Node bnode = (Node) name2prop.get(pname);
        if (bnode==null) {
            error("No prop named "+pname);
            return;
        }
        NodeList e2 = ((Element)bnode).getElementsByTagName("script");
        // there should only be one action script for a prop
        for (int j=0; j<e2.getLength(); ++j) {
            Node snode = e2.item(j);
            // in phase 1, all scripts are Action scripts
            setupProp((Button)name2button.get(pname), snode);
            String nextScene = DOM_Util.getAttr(snode, "goto", "");
            if (nextScene!=null && !nextScene.equals("")) {
                enterNewScene(nextScene);
            }
            return; // only execute first script
        }
    }
    
    void setupProp(Button b, Node bnode) {
        // can't use getElementsByTagName here since there are nested
        // trait elements in script elements that shouldn't be used.
        for (Node n = bnode.getFirstChild(); n!=null;
                                        n = n.getNextSibling()) {
            if (n instanceof Element) {
                if (n.getNodeName().equals("trait")
                  && DOM_Util.getAttr(n, "name", "?").equals("Label")) {
                    String pname = DOM_Util.getAttr(n, "prop", "");
                    if (pname!=null && !pname.equals("")) {
                        b = (Button) name2button.get(pname);
                    }
                    if (b!=null) {
                        b.setLabel(DOM_Util.getContent(n));
                    } else {
                        error("Configuration error");
                    }
                } // ignore all other traits since this is only Phase 1
            }
        }
        
    }
    
    static void error(String msg) {
        System.out.println("Error: "+msg);
    }
        
    public static void main(String args[]) {
        if (args.length>0) {
            try {
                // read XML file and start up display
                Document c = DOM_Util.readDocument(args[0]);
                new PlayRT(c);
            } catch (Exception e) {
                error("Invalid config file: "+args[0]);
            }
        } else {
            error("No config file specified");
        }
    }
}