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:
Create the FileSelector
class
file.
Import the required classes.
import java.io.*; import java.util.*; import javax.microedition.io.*; import javax.microedition.io.file.*; import javax.microedition.lcdui.*;
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;
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)); }
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; }
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()); } }
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(); } } } }
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(); } } } }
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); } } }
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); } }
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(); } } } } }
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:
Create the OperationsQueue
class
file.
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.*;
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 } } } } }