Gidlet
 
   


Fourteenth example

Purpose: Many applications need a permanent storage area for a large amount of varying information or for saving data that must survive when the program ends. The J2ME Midlet class library provides a storage mechanism for a small amount of data, the Record Management System (RMS). Larger amount of data should be stored as data files on one of the mobile phone's memory chip, which can be a build-in or additional flash memory. To access the file system, the mobile phone must support the File Connection package, as defined by the JSR 75 (Java Specification Request). Most modern devices conform to JSR 75.

As usual, file input-output is a cumbersome matter and cost lot of experience, time and nerves. In Java file-IO uses the stream classes. Because streams allocate system resources, a stream must be "opened", "used" and "closed" again. The class MFile is part of the Gidlet framework and simplifies the IO considerably, because file operations can be done without explicitly using streams. The idea behind is simple (inspired by the class CPPath of the C++/Champ framework): Like for the class File in J2SE each instance of MFile is an abstraction of the fully qualified file path. File manipulations and operations are implemented as methods of the MFile class, but more than this, all stream operations are transparent to the user of the MFile class. As example, the following code creates a text file and appends to lines:

MFile mf = new MFile("c:/Photos/Info.txt");
mf.append("first line\n") ;
mf.append("second line\n") ;

For testing and demonstration purposes the MConsole is very well suited, because lines can be displayed in the System.out.println() manner. The following program creates the file too.txt in the root named root1 and appends 3 lines of text each time it is run.

// FileAppendGidlet.java

import
 ch.aplu.gidlet.*;

public
 class FileAppendGidlet extends Gidlet
{
  
private final String path = "root1/foo.txt";
  
private MConsole c = new MConsole();

  
public void main()
  
{
    MFile mf 
= new MFile(path);

    
StringBuffer data = new StringBuffer();
    c.
println("Appending data to file\n" +
               mf.
getPath() + "...");
    
for (int i = 1; i <= 3; i++)
      data.
append("line #" + i + "\n");
    
boolean rc = mf.append(data.toString());
    
if (rc)
      c.
println("done");
    
else
      c.
println("failed");
  
}
}


Execute FileAppendGidlet (if you have the Sun's Wireless Toolkit (WTK) installed and the JAD extension registered. Learn how to register the JAD extension).

Discussion: The root named root1/ corresponds to the simulated file system root when using the emulator. You find root1 as subdirectory in the following directory:

For WTK 2.5.2: <userhome\>j2mewtk\2.5.2\appdb\DefaultColorPhone\filesystem

For WTK 2.5.1: <wtkroot>\appdb\DefaultColorPhone\filesystem

After running the program, you can look for the file foo.txt in the directory root1 and check its content by opening it with any text editor. Each time the program runs, 3 more lines are appended. If you use write() instead of append(), the current content is cleared before the new data is written.

It's somewhat annoying to get a security message each time the emulator's file system is accessed. To suppress the question that the emulator ask about accessing the file system change in file

c:\Documents and Settings\\j2mewtk\2.5.2\appdb\_policy.txt
(for WTK 2.5.2)

wtkroot\appdb\_policy.txt
(for WTK 2.5.1)

existing lines

blanket(oneshot): read_user_data_access
blanket(oneshot): write_user_data_access

to

allow: read_user_data_access
allow: write_user_data_access

The warning message can be suppressed on the real mobile phone by signing the Midlet or, on same devices, by selecting the Rights property in the Midlets soft button menu accordingly.

When the emulator uses the file system, it creates a tag file in.use in directory DefaultColorPhone. In some circumstances this file in not deleted when the emulator terminates and later file operations fail. If you have problems with file operation, check for this tag file and delete it by hand.

 

Fifteenth example

Purpose: With MFile it's very easy to read the previously written file and display its content.

// FileReadGidlet.java

import ch.aplu.gidlet.*;

public class FileReadGidlet extends Gidlet
{
  private final String path = "root1/foo.txt";
  private MConsole c = new MConsole();

  public void main()
  {
    MFile mf = new MFile(path);
    if (!mf.isFile())
    {
      c.println("File\n" + path + "\nnot found");
      return;
    }

    c.println("Reading data from file\n" +
               mf.getPath() + "...");
    String content = mf.read();
    if (content == null)
    {
      c.println("read() failed");
      return;
    }
    c.println("Content of file:");
    c.println(content);
  }
}

 

Execute FileReadGidlet (if you have the Sun's Wireless Toolkit (WTK) installed and the JAD extension registered. Learn how to register the JAD extension)

Discussion: append(), write() and read() work with text files only. If you want to store some other primitive data types, you must either convert them to strings or use your own DataInputStream and DataOutputStream. There are examples how to do this in the Gidlet distribution.

 

Sixteenth example

Purpose: In some circumstances you want to get a list all files and/or directories in a certain directory. The MFile class uses findFirst() and findNext() to perform this task.

// FileEnumerateGidlet.java

import ch.aplu.gidlet.*;

public class FileEnumerateGidlet extends Gidlet
{
  private final String rootPath = "root1/";
  private MConsole c = new MConsole();

  public void main()
  {
    MFile mf = new MFile(rootPath);
    if (!mf.exists())
    {
      c.println("Root\n" + rootPath + 
                "\nnot found");
      return;
    }

    c.println("Enumerating files in\n" + 
               rootPath + "...");
    boolean rc = mf.findFirst();
    if (rc)
    {
      c.println("first: " + mf.getPath());
      while (mf.findNext())
        c.println("next: " + mf.getPath());
    }

    mf.setPath(rootPath);
    c.println("Used: " + 
               mf.getUsedSpace(true) + " bytes");
    c.println("Free: " + 
               mf.getAvailableSpace() + " bytes");
  }
}

 

Execute FileEnumerateGidlet (if you have the Sun's Wireless Toolkit (WTK) installed and the JAD extension registered. Learn how to register the JAD extension)

Discussion: The procedure for using findFirst() and findNext() is the following:

  1. Set the path to the directory where files and subdirectories will be enumerated
  2. Call findFirst(); if it returns true, the path is set to the first file/subdirectory. Otherwise there is no file/subdirectory and you can't proceed.
  3. Keep calling findNext() until it returns false, meaning there are no more files/directories. After every successful findNext() call, the path is set to the next file/subdirectory

Be aware that findFirst() and findNext() have a side effect by modifying the MForm's path to the file/directory that was found.

We use the emulator's root root1/ again. (For the real device, you must use the root names for your device. You may try with c:/ or enumerate all available roots by using the same technique with findFirstRoot() and findNextRoot() respectively. Execute RootEnumerateGidlet, source included in Gidlet distribution)

At the end we display the space used for of all files in the root directory and its subdirectories. Then we also display the remaining free space in the file system.

Sometimes it is convenient to retrieve a directory path from the following properties:

fileconn.dir.photos
fileconn.dir.videos
fileconn.dir.graphics
fileconn.dir.tones
fileconn.dir.music
fileconn.dir.recordings

For example, to get all files and subdirectories where are the photos, call

String photoDir = System.getProperty("fileconn.dir.photos");

and strip the URL part file:/// before instantiating the MFile:

MFile mf = new MFile(photoDir.substring(8));

Then apply the code in FileEnumerateGidlet. If the specific directory is not available, getProperty() returns null.

 

Seventeenth example

Purpose: With MFile it is straightforward to perform file operations like creating/removing directories and files. The following examples show same of these manipulations.

We first look for a certain directory. If it already exists, we are cautious not to change existing data. So we quit. (To run the program successfully, either delete the directory with a file explorer or select another directory name.) Next we create the directory, and also create an empty file in this directory. Then we try to remove the directory, but this fails, because it is not empty. So we delete the file and finish by removing the directory successfully.

// FileCreateGidlet.java

import
 ch.aplu.gidlet.*;

public
 class FileCreateGidlet extends Gidlet
{
  
private final String dirPath = "root1/aplu/";
  
private MConsole c =
    
new MConsole(0, Gidlet.WHITE, Gidlet.BLUE, 1);

  
public void main()
  
{
    
// Look for existing directory
    MFile mf 
= new MFile(dirPath);
    c.
println("Looking for dir\n" +
               mf.
getPath() + "...");
    
if (mf.isDirectory())
    
{
      c.
println("Directory found.\nNo operation");
      
return;
    
}

    
// Create directory
    c.
println("Not found. Creating it...");
    
if (mf.createDir())
      c.
println("OK");
    
else
    
{
      c.
println("createDir() failed");
      
return;
    
}

    
// Create file
    mf.
setPath(dirPath + "foo.txt");
    c.
println("Creating empty file\n" +
               mf.
getPath() + "...");
    
if (mf.createFile())
      c.
println("OK");
    
else
    
{
      c.
println("createFile() failed");
      
return;
    
}

    
// Try to remove directory
    mf.
setPath(dirPath);
    c.
println("Removing dir\n" +
               mf.
getPath() + "...");
    
if (mf.remove())
      c.
println("OK");
    
else
      c.
println("remove() failed. Dir not empty");

    
// Remove file
    mf.
setPath(dirPath + "foo.txt");
    c.
println("Removing file\n" +
               mf.
getPath() + "...");
    
if (mf.remove())
      c.
println("OK");
    
else
    
{
      c.
println("remove() failed.");
      
return;
    
}

    
// Remove directory
    mf.
setPath(dirPath);
    c.
println("Removing dir\n" +
               mf.
getPath() + "...");
    
if (mf.remove())
      c.
println("OK");
    
else
    
{
      c.
println("remove() failed.");
      
return;
    
}
    c.
println("All done");
  
}
}

Execute FileCreateGidlet (if you have the Sun's Wireless Toolkit (WTK) installed and the JAD extension registered. Learn how to register the JAD extension)

Discussion: Because MFile's methods do not throw exceptions, it is a common technique to check return values for success or failure. Instead of creating a new instance of MFile each time you need another path, use setPath() to modify the current path. We use a special constructor for MConsole to set the background color, the text color and the text size to our preference.

Eighteenth example

Purpose: With MFile's read/write-methods you always transfer the whole content of the file to/from a string. This can exhaust the memory if the file is very big. In this case you better use the traditional stream approach. In addition, you can store the primitive data types without converting them to strings when using the DataInputStream and DataOutputStream. In the following example we store 10 integers and read them back.

// FileStreamGidlet.java

import ch.aplu.gidlet.*;
import javax.microedition.io.*;
import javax.microedition.io.file.*;
import java.io.*;

public class FileStreamGidlet extends Gidlet
{
  private final String path = "root1/foo.dat";
  private MConsole c = new MConsole();

  public void main()
  {
    MFile mf = new MFile(path);
    if (mf.exists())
    {
      c.println("File\n" + path +
                "\found. Clearing it...");
      mf.clear();
    }

    c.println("Creating file\n" + path);
    mf.createFile();

    c.println("Writing integer data...");
    DataOutputStream dos = mf.getDataOutputStream();
    try
    {
      for (int i = 0; i < 10; i++)
        dos.writeInt(i);
    }
    catch (IOException ex)
    {
      c.println("Write failed");
      return;
    }
    mf.releaseConnection();
    c.println("OK");

    c.println("Reading back...");
    DataInputStream dis = mf.getDataInputStream();
    try
    {
      while (true)
        c.print(" " + dis.readInt());
    }
    catch (EOFException ex)
    {
      mf.releaseConnection();
      c.println("\nAll done");
    }
    catch (IOException ex)
    {
      c.println("\nRead failed");
      return;
    }
  }
}

Execute FileStreamGidlet (if you have the Sun's Wireless Toolkit (WTK) installed and the JAD extension registered. Learn how to register the JAD extension)

Discussion:
When calling getDataOutputStream() or getDateInputStream() an internal FileConnection and DataOutputStream/DataInputStream is opened. After the write/read-Operation you should call releaseConnection() to close the FileConnection and the stream. For better performance, file operations should be executed in a separate thread, that is the case here, because main() runs in its own thread.