Implementing MIDlet functionality

FileSelector class

This is the main class of the application. It consists of the file browser user interface, and it additionally invokes all the file operations using the FileConnection API. FileSelector includes a list that is filled by the contents of the current directory being explored. The content is presented using icons to denote directories and files. When starting, the class checks whether the images directory is available and starts navigating from there. Otherwise it will present a list of all available roots. The current directory is stored in the currentRoot field, and a null value indicates pointing to the roots set. On opening a directory, its contents are searched for other directories, and PNG and JPG files that are displayed as the directory's content.

File-related operations are enclosed in try/catch blocks to detect both IOExceptions and SecurityExceptions.

To create this class:

  1. Create the FileSelector class file.

  2. Import the required classes.

    import java.io.*;
    import java.util.*;
    import javax.microedition.io.*;
    import javax.microedition.io.file.*;
    import javax.microedition.lcdui.*;
  3. Set the FileSelector class to extend List and to implement CommandListener and FileSystemListener. Create the methods used later to manage the file system.

    When starting, the class checks whether the images directory is available and starts navigating from there. Otherwise it will present a list of all available roots.

    // Simple file selector class.
    // It navigates the file system and shows images currently available
    class FileSelector
      extends List
      implements CommandListener, FileSystemListener
    {
      private final static Image ROOT_IMAGE =
        ImageViewerMIDlet.makeImage("/root.png");
      private final static Image FOLDER_IMAGE =
        ImageViewerMIDlet.makeImage("/folder.png");
      private final static Image FILE_IMAGE =
        ImageViewerMIDlet.makeImage("/file.png");
      private final OperationsQueue queue = new OperationsQueue();
    
      private final static String FILE_SEPARATOR =
        (System.getProperty("file.separator")!=null)?
          System.getProperty("file.separator"):
          "/";
    
      private final static String UPPER_DIR = "..";
      private final ImageViewerMIDlet midlet;
      private final Command openCommand =
        new Command("Open", Command.ITEM, 1);
      private final Command createDirCommand =
        new Command("Create new directory", Command.ITEM, 2);
      private final Command deleteCommand =
        new Command("Delete", Command.ITEM, 3);
      private final Command renameCommand =
        new Command("Rename", Command.ITEM, 4);
      private final Command exitCommand =
        new Command("Exit", Command.EXIT, 1);
      private final static int RENAME_OP = 0;
      private final static int MKDIR_OP = 1;
      private final static int INIT_OP = 2;
      private final static int OPEN_OP = 3;
      private final static int DELETE_OP = 4;
  4. The current directory is stored in the currentRoot field, and a null value indicates pointing to the roots set. Here, the class also registers itself in the FileSystemRegistry in order to listen to new file systems being added or removed, and in that case it will restart itself. For more information on file system listeners and FileSystemRegistry, see the FileSystemListener interface and the FileSystemRegistry class.

      private Vector rootsList = new Vector();
      // Stores the current root, if null we are showing all the roots
      private FileConnection currentRoot = null;
      // Stores a suggested title in case it is available
      private String suggestedTitle = null;
      FileSelector(ImageViewerMIDlet midlet)
      {
        super("Image Viewer", List.IMPLICIT);
        this.midlet = midlet;
        addCommand(openCommand);
        addCommand(createDirCommand);
        addCommand(deleteCommand);
        addCommand(renameCommand);
        addCommand(exitCommand);
        setSelectCommand(openCommand);
        setCommandListener(this);
        queue.enqueueOperation(new ImageViewerOperations(INIT_OP));
        FileSystemRegistry.addFileSystemListener(FileSelector.this);
      }
      void stop()
      {
        if (currentRoot != null)
        {
          try
          {
            currentRoot.close();
          }
          catch (IOException e)
          {
          }
        }
        queue.abort();
        FileSystemRegistry.removeFileSystemListener(this);
      }
      void inputReceived(String input, int code)
      {
        switch (code)
        {
          case RENAME_OP:
            queue.enqueueOperation(new ImageViewerOperations(
              input,
              RENAME_OP));
            break;
          case MKDIR_OP:
            queue.enqueueOperation(new ImageViewerOperations(
              input,
              MKDIR_OP));
            break;
        }
      }
      public void commandAction(Command c, Displayable d)
      {
        if (c == openCommand)
        {
          queue.enqueueOperation(new ImageViewerOperations(OPEN_OP));
        }
        else if (c == renameCommand)
        {
          queue.enqueueOperation(new ImageViewerOperations(RENAME_OP));
        }
        else if (c == deleteCommand)
        {
          queue.enqueueOperation(new ImageViewerOperations(DELETE_OP));
        }
        else if (c == createDirCommand)
        {
          queue.enqueueOperation(new ImageViewerOperations(MKDIR_OP));
        }
        else if (c == exitCommand)
        {
          midlet.fileSelectorExit();
        }
      }
      // Listen for changes in the roots
      public void rootChanged(int state, String rootName)
      {
        queue.enqueueOperation(new ImageViewerOperations(INIT_OP));
      }
  5. Use the method displayAllRoots() to display the list of all roots available.

      private void displayAllRoots()
      {
        setTitle("Image Viewer - [Roots]");
        deleteAll();
        Enumeration roots = rootsList.elements();
        while (roots.hasMoreElements())
        {
          String root = (String) roots.nextElement();
          append(root.substring(1), ROOT_IMAGE);
        }
        currentRoot = null;
      }
  6. Use the method createNewDir(), created earlier, to create a new directory under the current directory. Note that FileConnection is used here to access the file system.

    The FileSystemRegistry class provides the listRoots() utility method that returns an enumeration of the roots on the file system. This includes both logical and virtual roots.

      private void createNewDir()
      {
        if (currentRoot == null)
        {
          midlet.showMsg("Is not possible to create a new root");
        }
        else
        {
          midlet.requestInput("New dir name", "", MKDIR_OP);
        }
      }
      private void createNewDir(String newDirURL)
      {
        if (currentRoot != null)
        {
          try
          {
            FileConnection newDir =
              (FileConnection) Connector.open(
                currentRoot.getURL() + newDirURL,
                Connector.WRITE);
            newDir.mkdir();
            newDir.close();
          }
          catch (IOException e)
          {
            midlet.showError(e);
          }
          catch (SecurityException e)
          {
            midlet.showMsg(e.getMessage());
          }
          displayCurrentRoot();
        }
      }
      private void loadRoots()
      {
        if (!rootsList.isEmpty())
        {
          rootsList.removeAllElements();
        }
        try
        {
          Enumeration roots = FileSystemRegistry.listRoots();
          while (roots.hasMoreElements())
          {
            rootsList.addElement(FILE_SEPARATOR +
              (String) roots.nextElement());
          }
        }
        catch (SecurityException e)
        {
          midlet.showMsg(e.getMessage());
        }
      }
  7. Use the method deleteCurrent(), created earlier, to delete the currently selected file. Note that FileConnection is used here to access the file system.

      private void deleteCurrent()
      {
        if (currentRoot == null)
        {
          midlet.showMsg("Is not possible to delete a root");
        }
        else
        {
          int selectedIndex = getSelectedIndex();
          if (selectedIndex >= 0)
          {
            String selectedFile = getString(selectedIndex);
            if (selectedFile.equals(UPPER_DIR))
            {
              midlet.showMsg("Is not possible to delete an upper dir");
            }
            else
            {
              try
              {
                FileConnection fileToDelete =
                  (FileConnection) Connector.open(
                    currentRoot.getURL() + selectedFile,
                    Connector.WRITE);
                if (fileToDelete.exists())
                {
                  fileToDelete.delete();
                }
                else
                {
                  midlet.showMsg("File "
                    + fileToDelete.getName() + " does not exists");
                }
                fileToDelete.close();
              }
              catch (IOException e)
              {
                midlet.showError(e);
              }
              catch (SecurityException e)
              {
                midlet.showError(e);
              }
              displayCurrentRoot();
            }
          }
        }
      }
  8. Use the method renameCurrent(), created earlier, to change the name of the currently selected file. Note that FileConnection is used here to access the file system.

      private void renameCurrent()
      {
        if (currentRoot == null)
        {
          midlet.showMsg("Is not possible to rename a root");
        }
        else
        {
          int selectedIndex = getSelectedIndex();
          if (selectedIndex >= 0)
          {
            String selectedFile = getString(selectedIndex);
            if (selectedFile.equals(UPPER_DIR))
            {
              midlet.showMsg("Is not possible to rename the upper dir");
            }
            else
            {
              midlet.requestInput("New name", selectedFile, RENAME_OP);
            }
          }
        }
      }
      private void renameCurrent(String newName)
      {
        if (currentRoot == null)
        {
          midlet.showMsg("Is not possible to rename a root");
        }
        else
        {
          int selectedIndex = getSelectedIndex();
          if (selectedIndex >= 0)
          {
            String selectedFile = getString(selectedIndex);
            if (selectedFile.equals(UPPER_DIR))
            {
              midlet.showMsg("Is not possible to rename the upper dir");
            }
            else
            {
              try
              {
                FileConnection fileToRename =
                  (FileConnection) Connector.open(
                    currentRoot.getURL() + selectedFile,
                    Connector.WRITE);
                if (fileToRename.exists())
                {
                  fileToRename.rename(newName);
                }
                else
                {
                  midlet.showMsg("File "
                    + fileToRename.getName() + " does not exists");
                }
                fileToRename.close();
              }
              catch (IOException e)
              {
                midlet.showError(e);
              }
              catch (SecurityException e)
              {
                midlet.showError(e);
              }
              displayCurrentRoot();
            }
          }
        }
      }
  9. Use the method openSelected(), created earlier, to update the list of files displayed on screen. Note that FileConnection is used here to access the file system. The setFileConnection() method resets the FileConnection object to another file or directory, therefore allowing the reuse of the FileConnection object for directory traversal. This allows that the FileConnection instance object can remain open, referring now to the new file or directory.

      private void openSelected()
      {
        int selectedIndex = getSelectedIndex();
        if (selectedIndex >= 0)
        {
          String selectedFile = getString(selectedIndex);
          if (selectedFile.endsWith(FILE_SEPARATOR))
          {
            try
            {
              if (currentRoot == null)
              {
                currentRoot = (FileConnection) Connector.open(
                  "file:///" + selectedFile, Connector.READ);
              }
              else
              {
                currentRoot.setFileConnection(selectedFile);
              }
              displayCurrentRoot();
            }
            catch (IOException e)
            {
              midlet.showError(e);
            }
            catch (SecurityException e)
            {
              midlet.showError(e);
            }
          }
          else if (selectedFile.equals(UPPER_DIR))
          {
            if(rootsList.contains(currentRoot.getPath()
                +currentRoot.getName()))
            {
              displayAllRoots();
            }
            else
            {
              try
              {
                currentRoot.setFileConnection(UPPER_DIR);
                displayCurrentRoot();
              }
              catch (IOException e)
              {
                midlet.showError(e);
              }
              catch (SecurityException e)
              {
                midlet.showMsg(e.getMessage());
              }
            }
          }
          else
          {
            String url = currentRoot.getURL() + selectedFile;
            midlet.displayImage(url);
          }
        }
      }
  10. Use the method displayCurrentRoot() to handle reading the contents of the directory and display the contents on the screen. When a directory is opened, its contents are searched for subdirectories and PNG or JPG files. The found files and subdirectories are then displayed as the directory's content.

      private void displayCurrentRoot()
      {
        try
        {
          setTitle("Image Viewer - ["
            + ((suggestedTitle!=null)?suggestedTitle:currentRoot.getURL())
            + "]");
          // open the root
          deleteAll();
          append(UPPER_DIR, FOLDER_IMAGE);
          // list all dirs
          Enumeration listOfDirs = currentRoot.list("*", false);
          while (listOfDirs.hasMoreElements())
          {
            String currentDir = (String) listOfDirs.nextElement();
            if (currentDir.endsWith(FILE_SEPARATOR))
            {
              append(currentDir, FOLDER_IMAGE);
            }
          }
          // list all png files and don't show hidden files
          Enumeration listOfFiles = currentRoot.list("*.png", false);
          while (listOfFiles.hasMoreElements())
          {
            String currentFile = (String) listOfFiles.nextElement();
            if (currentFile.endsWith(FILE_SEPARATOR))
            {
              append(currentFile, FOLDER_IMAGE);
            }
            else
            {
              append(currentFile, FILE_IMAGE);
            }
          }
          // also list the jpg files
          listOfFiles = currentRoot.list("*.jpg", false);
          while (listOfFiles.hasMoreElements())
          {
            String currentFile = (String) listOfFiles.nextElement();
            if (currentFile.endsWith(FILE_SEPARATOR))
            {
              append(currentFile, FOLDER_IMAGE);
            }
            else
            {
              append(currentFile, FILE_IMAGE);
            }
          }
        }
        catch (IOException e)
        {
          midlet.showError(e);
        }
        catch (SecurityException e)
        {
          midlet.showError(e);
        }
      }
  11. Set up a new class to implement the Operation interface. In most cases this new class is used to invoke the private methods of FileSelector.

      private class ImageViewerOperations implements Operation
      {
        private final String parameter;
        private final int operationCode;
        ImageViewerOperations(int operationCode)
        {
          this.parameter = null;
          this.operationCode = operationCode;
        }
        ImageViewerOperations(String parameter, int operationCode)
        {
          this.parameter = parameter;
          this.operationCode = operationCode;
        }
        public void execute()
        {
          switch (operationCode)
          {
            case INIT_OP:
              String initDir = System.getProperty("fileconn.dir.photos");
              suggestedTitle =          
                System.getProperty("fileconn.dir.photos.name");
              loadRoots();
              if (initDir != null)
              {
                try
                {
                  currentRoot =
                    (FileConnection) Connector.open(
                      initDir,
                      Connector.READ);
                  displayCurrentRoot();
                }
                catch (IOException e)
                {
                  midlet.showError(e);
                  displayAllRoots();
                }
                catch (SecurityException e)
                {
                  midlet.showError(e);
                }
              }
              else
              {
                displayAllRoots();
              }
              break;
            case OPEN_OP:
              openSelected();
              break;
            case DELETE_OP:
              deleteCurrent();
              break;
            case RENAME_OP:
              if (parameter != null)
              {
                renameCurrent(parameter);
              }
              else
              {
                renameCurrent();
              }
              break;
            case MKDIR_OP:
              if (parameter != null)
              {
                createNewDir(parameter);
              }
              else
              {
                createNewDir();
              }
          }
        }
      }
    }

OperationsQueue class

The operations queue is a simple utility class that runs in a separate thread executing operations serially. The OperationsQueue class takes objects implementing the Operation interface, making it independent of the kind of operations executed. Operation implementers are expected to handle their exceptions locally since OperationsQueue will discard any exceptions thrown.

To create the OperationsQueue class:

  1. Create the OperationsQueue class file.

  2. Import the required classes and implement the interface.

    // Defines the interface for a single operation executed by
    // the commands queue
    interface Operation
    {
      // Implement here the operation to be executed
      void execute();
    }
    import java.util.*;
    
    
  3. Create the operations queue and use the run() method to execute the operations on the queue.

    // Simple Operations Queue
    // It runs in an independent thread and executes Operations serially
    class OperationsQueue implements Runnable
    {
    
      private volatile boolean running = true;
      private final Vector operations = new Vector();
    
      OperationsQueue()
      {
        // Notice that all operations will be done in another
        // thread to avoid deadlocks with GUI thread
        new Thread(this).start();
      }
    
      void enqueueOperation(Operation nextOperation)
      {
        operations.addElement(nextOperation);
        synchronized (this)
        {
          notify();
        }
      }
    
      // stop the thread
      void abort()
      {
        running = false;
        synchronized (this)
        {
          notify();
        }
      }
      public void run()
      {
        while (running)
        {
          while (operations.size() > 0)
          {
            try
            {
              // execute the first operation on the queue
              ((Operation) operations.firstElement()).execute();
            }
            catch (Exception e)
            {
              // Nothing to do. It is expected that each operation handles
              // its own exception locally but this block is to ensure
              // that the queue continues to operate
            }
            operations.removeElementAt(0);
          }
          synchronized (this)
          {
            try
            {
              wait();
            }
            catch (InterruptedException e)
            {
              // it doesn't matter
            }
          }
        }
      }
    }

Operation interface

  1. Create the Operation class file.

  2. Define the interface.

    // Defines the interface for a single operation executed by the commands queue
    interface Operation
    {
      // Implement here the operation to be executed
      void execute();
    }