Day Log

In order to provide the desired functionality, we need two main data structures:

  • A data structure for the current day, holding the date and the values and times for the day's measurements

  • A data structure storing all the days persistently

Thus, the day log data structure needs a field holding the date and a list of the time and value pairs. The date is stored in the integer variable date, holding the day of the month in the lowest byte, the month in the second-lowest byte, and the year in the two upper bytes. We are using this format instead of the Date or Calendar object for simplified comparison, required for keeping the log entries in the right order. To store the pairs, a local class Entry is used, consisting of two integer values: time and value. Similar to the date field, we use an integer to represent the time, measured in minutes since midnight. Again, the main reason is simpler comparison. We also use the DayLog class to define the minimum and maximum value constants for the graphics display (see Figure 9.2).

Figure 9.2. The DayLog constants.


Our basic DayLog class looks as follows:

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

public class DayLog {

    public static final int MIN_TIME = 6 * 60;
    public static final int MAX_TIME = 22 * 60;
    public static final int MIN_VALUE = 40;
    public static final int MAX_VALUE = 280;
    public static final int MIN_BORDER = 70;
    public static final int MAX_BORDER = 160;

    int date; // yymd per byte
    Vector entries = new Vector ();

    class Entry {
        int time; // minutes since midnight
        int value;
    }
}

For creating new DayLogs, a constructor, initializing the structure with the given date, is required:

public DayLog (int date) {
    this.date = date;
 }

In order to fill the structure with data, a set method, adding a new entry, is needed. The following method adds a new entry with respect to the correct time ordering of the entries. If an entry for the given point of time already exists, it is overwritten:

public void set (int minutes, int value) {
    Entry entry = new Entry ();
    entry.time = minutes;
    entry.value = value;

    for (int i = getCount ()-1; i >= 0; i--) {
        int minutesI = getTime (i);

        if (minutes <= minutesI) {
            if (minutes == minutesI)
                entries.setElementAt (entry, i);
            else
                entries.insertElementAt (entry, i+1);
            return;
        }
    }

    entries.insertElementAt (entry, 0);
}

Storing information in the day log data structure does not make sense if the information cannot be read back. Thus, we add access methods for the date, the number of entries, and the time and value of an entry at a given index:

public int getDate () {
    return date;
}

public int getCount () {
    return entries.size ();
}

public int getTime (int index) {
    return ((Entry) entries.elementAt (index)).time;
}

public int getValue (int index) {
    return ((Entry) entries.elementAt (index)).value;
}

Finally, we add a method for deleting an entry. The method takes a point of time as input and removes the best matching entry:

public void remove (int time) {

    int bestDelta = 24;
    int bestIndex = -1;

    for (int i = 0; i < entries.size (); i++) {
        int delta = Math.abs (time - getTime (i));
        if (delta < bestDelta) {
            bestDelta = delta;
            bestIndex = i;
        }
    }

    if (bestIndex != -1) {
        entries.removeElementAt (bestIndex);
    }
}
				

Serialization and Deserialization for Persistent Storage

Because the DayLog is intended to be stored persistently, conversion methods from and to byte arrays are necessary. As described in Chapter 5, “Data Persistency,” we use a ByteArrayInputStream for deserialization and a ByteArrayOutputStream for serialization:

public DayLog (byte [] data) throws IOException {

    DataInputStream dis = new DataInputStream
        (new ByteArrayInputStream (data));

    date = dis.readInt ();
    int count = dis.readInt ();

    for (int i = 0; i < count; i++) {
        Entry entry = new Entry ();
        entry.time = dis.readInt ();
        entry.value = dis.readInt ();
        entries.addElement (entry);
    }

    dis.close ();
}


public byte [] getByteArray () throws IOException {
    ByteArrayOutputStream bos = new ByteArrayOutputStream ();
    DataOutputStream dos = new DataOutputStream (bos);

    dos.writeInt (date);
    int size = entries.size ();
    dos.writeInt (size);
    for (int i = 0; i < size; i++) {
        dos.writeInt (getTime (i));
        dos.writeInt (getValue (i));
    }

    dos.close ();

    return bos.toByteArray ();
}
					

Helper Methods for the User Interface

Although the user interface itself is specific to the target profile, we can simplify the user interface code by providing some helper methods in the DayLog class. Here, we add a set of methods for two purposes:

  • Converting the date of the DayLog to a readable string

  • Scaling the entries to a given display size for simplified drawing

In order to convert the date to a String, we just extract the year, month, and day of month using the corresponding shift and mask operations. Those parts are concatenated with hyphens:

public String getTitle () {
    return "" + (date >> 16)
        + "-" + ((date >> 8) & 0x0ff)
        + "-" + (date & 0x0ff);
}

In order to scale a value to the size of the screen, we would usually normalize it by subtracting the MIN_VALUE and dividing by the difference of MAX_VALUE and MIN_VALUE, and then scale it up to the space available by multiplying by the size available. Because we do not have floating-point numbers in CLDC, we swap the normalization and scaling steps. Thus, we first multiply by the screen size and then divide by the value range. By first multiplying and then dividing, we make sure that we do not leave the scope of the integer data type:

public static int getY (int value, int size) {
    return - (value - MIN_VALUE) * size / (MAX_VALUE - MIN_VALUE);
}

In the getYPoints() method, we build an array of all values of the DayLog, scaled to the given screen size, utilizing the getY() method:

public int [] getYPoints (int size) {

    int [] y = new int [getCount ()];

    for (int i = 0; i < getCount (); i++) {
        y [i] = getY (getValue (i), size);
    }

    return y;
}

The getXPoints() method is analogous to getYPoints(), except that we do not define a getX() helper method but perform the scaling step immediately in the loop:

public int [] getXPoints (int size) {

    int [] x = new int [getCount ()];

    for (int i = 0; i < getCount (); i++) {
        x [i] = (getTime (i)-MIN_TIME) * size / (MAX_TIME-MIN_TIME);
    }

    return x;
}

Finally we add a parseTime() method in order to convert a time string such as 12:00, consisting of an hour and a minute value separated by a colon, to our internal time format. For MIDP, instead of this conversion we could also use the time selector provided by DateField.

public static int parseTime (String time) {
    int cut = time.indexOf (':'),
    if (cut==-1)
        return Integer.parseInt (time) * 60;

    return Integer.parseInt (time.substring (0, cut)) * 60
        + Integer.parseInt (time.substring (cut+1));
}

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset