Open Source Repository

Home /mail/mail-1.4.1 | Repository Home



com/sun/mail/mbox/MboxFolder.java
/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License. You can obtain
 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
 * or glassfish/bootstrap/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
 * Sun designates this particular file as subject to the "Classpath" exception
 * as provided by Sun in the GPL Version 2 section of the License file that
 * accompanied this code.  If applicable, add the following below the License
 * Header, with the fields enclosed by brackets [] replaced by your own
 * identifying information: "Portions Copyrighted [year]
 * [name of copyright owner]"
 *
 * Contributor(s):
 *
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */

/*
 * %W% %E%
 */

package com.sun.mail.mbox;

import javax.mail.*;
import javax.mail.event.*;
import javax.mail.internet.*;
import java.io.*;
import java.util.*;

/**
 * This class represents a mailbox file containing RFC822 style email messages. 
 *
 @author John Mani
 @author Bill Shannon
 */

public class MboxFolder extends Folder {

    private String name;  // null => the default folder
    private boolean is_inbox = false;
    private int total;    // total number of messages in mailbox
    private boolean opened = false;
    private Vector message_cache;
    private MboxStore mstore;
    private MailFile folder;
    private long file_size;  // the size the last time we read or wrote it
    private long saved_file_size; // size at the last open, close, or expunge
    private MboxMessage special_imap_message;

    public MboxFolder(MboxStore store, String name) {
  super(store);
  this.mstore = store;
  this.name = name;

  if (name != null && name.equalsIgnoreCase("INBOX"))
      is_inbox = true;

  folder = mstore.getMailFile(name == null "~" : name);
  if (folder.exists())
      saved_file_size = folder.length();
  else
      saved_file_size = -1;
    }

    public char getSeparator() {
  return File.separatorChar;
    }

    public Folder[] list(String patternthrows MessagingException {
  if (!folder.isDirectory())
      throw new MessagingException("not a directory");

  if (name == null)
      return list(null, pattern, true);
  else
      return list(name + File.separator, pattern, false);
    }

    /*
     * Version of list shared by MboxStore and MboxFolder.
     */
    protected Folder[] list(String ref, String pattern, boolean fromStore)
          throws MessagingException {
  if (ref != null && ref.length() == 0)
      ref = null;
  int i;
  String refdir = null;
  String realdir = null;

  pattern = canonicalize(ref, pattern);
  if ((i = indexOfAny(pattern, "%*")) >= 0) {
      refdir = pattern.substring(0, i);
  else {
      refdir = pattern;
  }
  if ((i = refdir.lastIndexOf(File.separatorChar)) >= 0) {
      // get rid of anything after directory name
      refdir = refdir.substring(0, i + 1);
      realdir = mstore.mb.filename(mstore.user, refdir);
  else if (refdir.length() == || refdir.charAt(0!= '~') {
      // no separator and doesn't start with "~" => home dir
      refdir = null;
      realdir = mstore.home;
  else {
      realdir = mstore.mb.filename(mstore.user, refdir);
  }
  Vector flist = new Vector();
  listWork(realdir, refdir, pattern, fromStore ? 1, flist);
  if (match.path("INBOX", pattern, '\0'))
      flist.addElement("INBOX");

  Folder fl[] new Folder[flist.size()];
  for (i = 0; i < fl.length; i++) {
      fl[i= createFolder(mstore, (String)flist.elementAt(i));
  }
  return fl;
    }

    public String getName() {
  if (name == null)
      return "";
  else if (is_inbox)
      return "INBOX";
  else
      return folder.getName();
    }

    public String getFullName() {
  if (name == null)
      return "";
  else
      return name;
    }

    public Folder getParent() {
  if (name == null)
      return null;
  else if (is_inbox)
      return createFolder(mstore, null);
  else
      // XXX - have to recognize other folders under default folder
      return createFolder(mstore, folder.getParent());
    }

    public boolean exists() {
  return folder.exists();
    }

    public int getType() {
  if (folder.isDirectory())
      return HOLDS_FOLDERS;
  else
      return HOLDS_MESSAGES;
    }

    public Flags getPermanentFlags() {
  return mstore.permFlags;
    }

    public synchronized boolean hasNewMessages() {
  if (folder instanceof UNIXFile) {
      UNIXFile f = (UNIXFile)folder;
      if (f.length() 0) {
    long atime = f.lastAccessed();
    long mtime = f.lastModified();
//System.out.println(name + " atime " + atime + " mtime " + mtime);
    return atime < mtime;
      }
      return false;
  }
  long current_size;
  if (folder.exists())
      current_size = folder.length();
  else
      current_size = -1;
  // if we've never opened the folder, remember the size now
  // (will cause us to return false the first time)
  if (saved_file_size < 0)
      saved_file_size = current_size;
  return current_size > saved_file_size;
    }

    public synchronized Folder getFolder(String name)
          throws MessagingException {
  if (folder.exists() && !folder.isDirectory())
      throw new MessagingException("not a directory");
  Folder f;
  if (this.name != null)
      f = createFolder(mstore, this.name + File.separator + name);
  else
      f = createFolder(mstore, name);
  return f;
    }

    public synchronized boolean create(int typethrows MessagingException {
  switch (type) {
  case HOLDS_FOLDERS:
      if (!folder.mkdirs()) {
    return false;
      }
      break;

  case HOLDS_MESSAGES:
      if (folder.exists()) {
    return false;
      }
      try {
    (new FileOutputStream((File)folder)).close();
      catch (FileNotFoundException fe) {
    File parent = new File(folder.getParent());
    if (!parent.mkdirs())
        throw new
      MessagingException("can't create folder: " + name);
    try {
        (new FileOutputStream((File)folder)).close();
    catch (IOException ex3) {
        throw new
      MessagingException("can't create folder: " + name, ex3);
    }
      catch (IOException e) {
    throw new
        MessagingException("can't create folder: " + name, e);
      }
      break;

  default:
      throw new MessagingException("type not supported");
  }
  notifyFolderListeners(FolderEvent.CREATED);
  return true;
    }

    public synchronized boolean delete(boolean recurse)
          throws MessagingException {
  // XXX - implement recurse
  checkClosed();
  if (name == null)
      throw new MessagingException("can't delete default folder");
  if (folder.delete()) {
      notifyFolderListeners(FolderEvent.DELETED);
      return true;
  }
  return false;
    }

    public synchronized boolean renameTo(Folder f)
        throws MessagingException {
  checkClosed();
  if (name == null)
      throw new MessagingException("can't rename default folder");
  if (!(instanceof MboxFolder))
      throw new MessagingException("can't rename to: " + f.getName());
  String newname = ((MboxFolder)f).folder.getPath();
  if (folder.renameTo(new File(folder.getPath(), newname))) {
      notifyFolderRenamedListeners(f);
      return true;
  }
  return false;
    }

    /* Ensure the folder is open */
    void checkOpen() throws IllegalStateException {
  if (!opened
      throw new IllegalStateException("Folder is not Open");
    }

    /* Ensure the folder is not open */
    void checkClosed() throws IllegalStateException {
  if (opened
      throw new IllegalStateException("Folder is Open");
    }

    /* Ensure the folder is open & readable */
    void checkReadable() throws IllegalStateException {
  if (!opened || (mode != READ_ONLY && mode != READ_WRITE))
      throw new IllegalStateException("Folder is not Readable");
    }

    /* Ensure the folder is open & writable */
    void checkWritable() throws IllegalStateException {
  if (!opened || mode != READ_WRITE)
      throw new IllegalStateException("Folder is not Writable");
    }

    public boolean isOpen() {
        return opened;
    }

    /*
     * Open the folder in the specified mode.
     */
    public synchronized void open(int modethrows MessagingException {
  if (opened)
      throw new IllegalStateException("Folder is already Open");

  this.mode = mode;
  switch (mode) {
  case READ_WRITE:
  default:
      if (!folder.canWrite())
    throw new MessagingException("Open Failure, can't write");
      // fall through...

  case READ_ONLY:
      if (!folder.canRead())
    throw new MessagingException("Open Failure, can't read");
      break;
  }

  if (is_inbox && folder instanceof InboxFile) {
      InboxFile inf = (InboxFile)folder;
      if (!inf.openLock(mode == READ_WRITE ? "rw" "r"))
    throw new MessagingException("Failed to lock INBOX");
  }
  if (!folder.lock("r"))
      throw new MessagingException("Failed to lock folder: " + name);
  message_cache = new Vector();
  total = 0;
  Message[] msglist = null;
  try {
      saved_file_size = folder.length();
      msglist = load(0Lfalse);
  catch (IOException e) {
      throw new MessagingException("IOException", e);
  finally {
      folder.unlock();
  }
  notifyConnectionListeners(ConnectionEvent.OPENED);
  if (msglist != null)
      notifyMessageAddedListeners(msglist);
  opened = true;
    }

    public synchronized void close(boolean expungethrows MessagingException {
  checkOpen();

  try {
      if (mode == READ_WRITE) {
    try {
        writeFolder(true, expunge);
    catch (IOException e) {
        throw new MessagingException("I/O Exception", e);
    }
      }
      message_cache = null;
  finally {
      opened = false;
      if (is_inbox && folder instanceof InboxFile) {
    InboxFile inf = (InboxFile)folder;
    inf.closeLock();
      }
      notifyConnectionListeners(ConnectionEvent.CLOSED);
  }
    }

    /**
     * Re-write the folder with the current contents of the messages.
     * If closing is true, turn off the RECENT flag.  If expunge is
     * true, don't write out deleted messages (only used from close()
     * when the message cache won't be accessed again).
     *
     * Return the number of messages written.
     */
    protected int writeFolder(boolean closing, boolean expunge)
      throws IOException, MessagingException {

  /*
   * First, see if there have been any changes.
   */
  int modified = 0, deleted = 0, recent = 0;
  for (int msgno = 1; msgno <= total; msgno++) {
      MboxMessage msg =
    (MboxMessage)message_cache.elementAt(msgno - 1);
      Flags flags = msg.getFlags();
      if (msg.isModified() || !msg.origFlags.equals(flags))
    modified++;
      if (flags.contains(Flags.Flag.DELETED))
    deleted++;
      if (flags.contains(Flags.Flag.RECENT))
          recent++;
  }
  if ((!closing || recent == 0&& (!expunge || deleted == 0&&
    modified == 0)
      return 0;

  /*
   * Have to save any new mail that's been appended to the
   * folder since we last loaded it.
   * XXX - Should do this without actually loading the messages in.
   */
  if (!folder.lock("rw"))
      throw new MessagingException("Failed to lock folder: " + name);
  int oldtotal = total;  // XXX
  Message[] msglist = null;
  if (folder.length() != file_size)
      msglist = load(file_size, !closing);
  // don't use the folder's FD, need to re-open in order to trunc the file
  OutputStream os =
    new BufferedOutputStream(new FileOutputStream((File)folder));
  int wr = 0;
  boolean keep = true;
  try {
      if (special_imap_message != null)
    writeMboxMessage(special_imap_message, os);
      for (int msgno = 1; msgno <= total; msgno++) {
    MboxMessage msg =
        (MboxMessage)message_cache.elementAt(msgno - 1);
    if (expunge && msg.isSet(Flags.Flag.DELETED))
        continue;  // skip it;
    if (closing && msgno <= oldtotal &&
            msg.isSet(Flags.Flag.RECENT))
        msg.setFlag(Flags.Flag.RECENT, false);
    writeMboxMessage(msg, os);
    folder.touchlock();
    wr++;
      }
      file_size = saved_file_size = folder.length();
      // If no messages in the mailbox, and we're closing,
      // maybe we should remove the mailbox.
      if (wr == && closing) {
    String skeep = ((MboxStore)store).getSession().
          getProperty("mail.mbox.deleteEmpty");
    if (skeep != null && skeep.equalsIgnoreCase("true"))
        keep = false;
      }
  catch (IOException e) {
      throw e;
  catch (MessagingException e) {
      throw e;
  catch (Exception e) {
e.printStackTrace();
      throw new MessagingException("unexpected exception " + e);
  finally {
      // close the folder, flushing out the data
      try {
    os.close();
    if (!keep) {
        folder.delete();
        file_size = 0;
    }
      catch (IOException ex) {}

      if (keep) {
    // make sure the access time is greater than the mod time
    // XXX - would be nice to have utime()
    try {
        Thread.sleep(1000);    // sleep for a second
    catch (InterruptedException ex) {}
    InputStream is = null;
    try {
        is = new FileInputStream((File)folder);
        is.read();  // read a byte
    catch (IOException ex) {}  // ignore errors
    try {
        if (is != null)
      is.close();
        is = null;
    catch (IOException ex) {}  // ignore errors
      }

      folder.unlock();
      if (msglist != null)
    notifyMessageAddedListeners(msglist);
  }
  return wr;
    }

    /**
     * Write a MimeMessage to the specified OutputStream in a
     * format suitable for a UNIX mailbox, i.e., including a correct
     * Content-Length header and with the local platform's line
     * terminating convention. <p>
     *
     * If the message is really a MboxMessage, use its writeToFile
     * method, which has access to the UNIX From line.  Otherwise, do
     * all the work here, creating an appropriate UNIX From line.
     */
    public static void writeMboxMessage(MimeMessage msg, OutputStream os)
        throws IOException, MessagingException {
  try {
      if (msg instanceof MboxMessage) {
    ((MboxMessage)msg).writeToFile(os);
      else {
    ContentLengthCounter cos = new ContentLengthCounter();
    NewlineOutputStream nos = new NewlineOutputStream(cos);
    msg.writeTo(nos);
    nos.flush();
    os = new NewlineOutputStream(os);
    os = new ContentLengthUpdater(os, cos.getSize());
    PrintStream pos = new PrintStream(os);
    pos.println(getUnixFrom(msg));
    msg.writeTo(pos);
    pos.println();  // make sure there's a blank line at the end
    pos.flush();
      }
  catch (MessagingException me) {
      throw me;
  catch (IOException ioe) {
      throw ioe;
  catch (Exception e) {
  }
    }

    /**
     * Construct an appropriately formatted UNIX From line using
     * the sender address and the date in the message.
     */
    protected static String getUnixFrom(MimeMessage msg) {
  Address[] afrom;
  String from;
  Date ddate;
  String date;
  try {
      if ((afrom = msg.getFrom()) == null ||
        !(afrom[0instanceof InternetAddress||
        (from = ((InternetAddress)afrom[0]).getAddress()) == null)
    from = "UNKNOWN";
      if ((ddate = msg.getReceivedDate()) == null ||
        (ddate = msg.getSentDate()) == null)
    ddate = new Date();
  catch (MessagingException e) {
      from = "UNKNOWN";
      ddate = new Date();
  }
  date = ddate.toString();
  // date is of the form "Sat Aug 12 02:30:00 PDT 1995"
  // need to strip out the timezone
  return "From " + from + " " +
    date.substring(020+ date.substring(24);
    }

    public synchronized int getMessageCount() throws MessagingException {
  if (!opened)
      return -1;

  boolean locked = false;
  Message[] msglist = null;
  try {
      if (folder.length() != file_size) {
    if (!folder.lock("r"))
        throw new MessagingException("Failed to lock folder: " +
              name);
    locked = true;
    msglist = load(file_size, true);
      }
  catch (IOException e) {
      throw new MessagingException("I/O Exception", e);
  finally {
      if (locked) {
    folder.unlock();
    if (msglist != null)
        notifyMessageAddedListeners(msglist);
      }
  }
  return total;
    }

    /**
     * Get the specified message.  Note that messages are numbered
     * from 1.
     */
    public synchronized Message getMessage(int msgno)
        throws MessagingException {
  checkReadable();

  MboxMessage m = null;

  if (msgno <= total)
      m = (MboxMessage)message_cache.elementAt(msgno - 1);
  return m;
    }

    public synchronized void appendMessages(Message[] msgs)
        throws MessagingException {
  if (!folder.lock("rw"))
      throw new MessagingException("Failed to lock folder: " + name);

  OutputStream os = null;
  boolean err = false;
  try {
      os = new BufferedOutputStream(
    new FileOutputStream(((File)folder).getPath()true));
    // XXX - should use getAbsolutePath()?
      for (int i = 0; i < msgs.length; i++) {
    if (msgs[iinstanceof MimeMessage) {
        writeMboxMessage((MimeMessage)msgs[i], os);
    else {
        err = true;
        continue;
    }
    folder.touchlock();
      }
  catch (IOException e) {
      throw new MessagingException("I/O Exception", e);
  catch (MessagingException e) {
      throw e;
  catch (Exception e) {
e.printStackTrace();
      throw new MessagingException("unexpected exception " + e);
  finally {
      if (os != null)
    try {
        os.close();
    catch (IOException e) {}
      folder.unlock();
  }
  if (opened)
      getMessageCount();  // loads new messages as a side effect
  if (err)
      throw new MessagingException("Can't append non-Mime message");
    }

    public synchronized Message[] expunge() throws MessagingException {
  checkWritable();

  /*
   * First, write out the folder to make sure we have permission,
   * disk space, etc.
   */
  int wr = total;    // number of messages written out
  try {
      wr = writeFolder(false, true);
  catch (IOException e) {
      throw new MessagingException("expunge failed", e);
  }
  if (wr == 0)
      return new Message[0];

  /*
   * Now, actually get rid of the expunged messages.
   */
  int del = 0;
  Message[] msglist = new Message[total - wr];
  int msgno = 1;
  while (msgno <= total) {
      MboxMessage msg =
    (MboxMessage)message_cache.elementAt(msgno - 1);
      if (msg.isSet(Flags.Flag.DELETED)) {
    msglist[del= msg;
    del++;
    message_cache.removeElementAt(msgno - 1);
    total--;
      else {
    msg.setMessageNumber(msgno);  // update message number
    msgno++;
      }
  }
  if (del != msglist.length)    // this is really an assert
      throw new MessagingException("expunge delete count wrong");
  notifyMessageRemovedListeners(true, msglist);
  return msglist;
    }

    /*
     * Load more messages from the folder starting at the specified offset.
     */
    private Message[] load(long offset, boolean notify)
        throws MessagingException, IOException {
  int oldtotal = total;
  try {
      boolean first = offset == 0;
      BufferedInputStream in = new BufferedInputStream(
        new FileInputStream(folder.getFD())8192);
      skipFully(in, offset);

      /*
       * Keep constructing new messages based on the InputStream
       * until we get an EOFException indicating the end of the mailbox.
       */
      for (;;) {
    MboxMessage msg = loadMessage(in, total, mode == READ_WRITE);
    if (first) {
        first = false;
        /*
         * If the first message is the special message that the
         * IMAP server adds to the mailbox, hide it away in a
         * special place and don't let the user see it.
         */
        if (msg.getHeader("X-IMAP"!= null) {
      special_imap_message = msg;
      continue;
        }
        /*
         * Following only works with Sun server, don't use it...
        String subj = msg.getSubject();
        if (subj != null &&
          subj.equals("IMAP4 Server Data-DO NOT DELETE")) {
      special_imap_message = msg;
      continue;
        }
         */
    }
    total++;
    msg.setMessageNumber(total);
    message_cache.addElement((Object)msg);
      }
  catch (EOFException e) {
      // done
      file_size = folder.length();
  }
  if (notify) {
      Message[] msglist = new Message[total - oldtotal];
      for (int i = oldtotal, j = 0; i < total; i++, j++)
    msglist[j(Message)message_cache.elementAt(i);
      return msglist;
  else
      return null;
    }

    /**
     * Parse the input stream and return an appropriate message object.
     */
    private MboxMessage loadMessage(BufferedInputStream is, int msgno,
    boolean writablethrows MessagingException, IOException {
  DataInputStream in = new DataInputStream(is);

  /*
   * Read lines until a UNIX From line,
   * skipping blank lines.
   * XXX - rewrite this to not need a DataInputStream.
   */
  String line;
  String unix_from = null;
  while ((line = in.readLine()) != null) {
      if (line.trim().length() == 0)
    continue;
      if (line.startsWith("From ")) {
    /*
     * A UNIX From line looks like:
     * From address Day Mon DD HH:MM:SS YYYY
     */
    unix_from = line;
    int i;
    // find the space after the address, before the date
    i = unix_from.indexOf(' '5);
    if (i < 0)
        continue;  // not a valid UNIX From line
    break;
      }
      throw new MessagingException("Garbage in mailbox: " + line);
  }

  if (unix_from == null)
      throw new EOFException("end of mailbox");

  /*
   * Now load the RFC822 headers into an InternetHeaders object.
   */
  InternetHeaders hdrs = new InternetHeaders(is);
  byte[] content = null;

  try {
      int len;
      if ((len = contentLength(hdrs)) >= 0) {
    content = new byte[len];
    in.readFully(content);
      else {
    ByteArrayOutputStream buf = new ByteArrayOutputStream();
    int b;

    /*
     * Read bytes until we see "\nFrom ", then
     * back up to the beginning of that string.
     */

    while ((b = is.read()) >= 0) {
        if (b == '\r' || b == '\n') {
      is.mark(6);
      if (b == '\r' && is.read() != '\n') {
          is.reset();
          is.mark(5);
      }
      if (is.read() == 'F' &&
          is.read() == 'r' &&
          is.read() == 'o' &&
          is.read() == 'm' &&
          is.read() == ' ') {
          is.reset();
          break;
      }
      is.reset();
        }
        buf.write(b);
    }
    content = buf.toByteArray();
    //env.setContentSize(content.length);
      }
  catch (EOFException e) {
      /*
       * We're done with this message.
       * Next attempt to read a message
       * will throw EOFException (see above).
       */
  }

  return new MboxMessage(this, hdrs, content, msgno, unix_from, writable);
    }

    /**
     * Extract the value of the Content-Length header, if present.
     */
    private int contentLength(InternetHeaders hdrs) {
  int len = -1;
  String cl[] = hdrs.getHeader("Content-Length");
  try {
      if (cl != null && cl[0!= null)
    len = Integer.parseInt(cl[0]);
  catch (NumberFormatException e) {}
  return len;
    }

    /**
     * Skip the specified number of bytes, repeatedly calling
     * the skip method as necessary.
     */
    private void skipFully(InputStream in, long offsetthrows IOException {
  while (offset > 0) {
      long cur = in.skip(offset);
      if (cur <= 0)
    throw new EOFException("can't skip");
      offset -= cur;
  }
    }

    /*
     * Only here to make accessible to MboxMessage.
     */
    protected void notifyMessageChangedListeners(int type, Message m) {
  super.notifyMessageChangedListeners(type, m);
    }


    /**
     * this is an exact duplicate of the Folder.getURL except it doesn't
     * add a beginning '/' to the URLName.
     */
    public URLName getURLName() {
  // XXX - note:  this should not be done this way with the
  // new javax.mail apis.

  URLName storeURL = getStore().getURLName();
  if (name == null)
      return storeURL;

  char separator = getSeparator();
  String fullname = getFullName();
  StringBuffer encodedName = new StringBuffer();

  // We need to encode each of the folder's names, and replace
  // the store's separator char with the URL char '/'.
  StringTokenizer tok = new StringTokenizer(
      fullname, Character.toString(separator)true);

  while (tok.hasMoreTokens()) {
      String s = tok.nextToken();
      if (s.charAt(0== separator)
    encodedName.append("/");
      else
    // XXX - should encode, but since there's no decoder...
    //encodedName.append(java.net.URLEncoder.encode(s));
    encodedName.append(s);
  }

  return new URLName(storeURL.getProtocol(), storeURL.getHost(),
          storeURL.getPort(), encodedName.toString(),
          storeURL.getUsername(),
          null /* no password */);
    }

    /**
     * Create an MboxFolder object, or a subclass thereof.
     * Can be overridden by subclasses of MboxFolder so that
     * the appropriate subclass is created by the list method.
     */
    protected Folder createFolder(MboxStore store, String name) {
  return new MboxFolder(store, name);
    }

    /*
     * Support routines for list().
     */

    /**
     * Return a canonicalized pattern given a reference name and a pattern.
     */
    private static String canonicalize(String ref, String pat) {
  if (ref == null)
      return pat;
  try {
      if (pat.length() == 0) {
    return ref;
      else if (pat.charAt(0== File.separatorChar) {
    return ref.substring(0, ref.indexOf(File.separatorChar)) + pat;
      else {
    return ref + pat;
      }
  catch (StringIndexOutOfBoundsException e) {
      return pat;
  }
    }

    /**
     * Return the first index of any of the characters in "any" in "s",
     * or -1 if none are found.
     *
     * This should be a method on String.
     */
    private static int indexOfAny(String s, String any) {
  try {
      int len = s.length();
      for (int i = 0; i < len; i++) {
    if (any.indexOf(s.charAt(i)) >= 0)
        return i;
      }
      return -1;
  catch (StringIndexOutOfBoundsException e) {
      return -1;
  }
    }

    /**
     * The recursive part of generating the list of mailboxes.
     * realdir is the full pathname to the directory to search.
     * dir is the name the user uses, often a relative name that's
     * relative to the user's home directory.  dir (if not null) always
     * has a trailing file separator character.
     *
     @param realdir  real pathname of directory to start looking in
     @param dir  user's name for realdir
     @param pat  pattern to match against
     @param level  level of the directory hierarchy we're in
     @param flist  vector to which to add folder names that match
     */
    // Derived from the c-client listWork() function.
    private void listWork(String realdir, String dir, String pat,
          int level, Vector flist) {
  String sl[];
  File fdir = new File(realdir);
  try {
      sl = fdir.list();
  catch (SecurityException e) {
      return;  // can't read it, ignore it
  }

  if (level == && dir != null &&
    match.path(dir, pat, File.separatorChar))
      flist.addElement(dir);

  if (sl == null)
      return;  // nothing return, we're done

  if (realdir.charAt(realdir.length() 1!= File.separatorChar)
      realdir += File.separator;

  for (int i = 0; i < sl.length; i++) {
      if (sl[i].charAt(0== '.')
    continue;  // ignore all "dot" files for now
      String md = realdir + sl[i];
      File mf = new File(md);
      if (!mf.exists())
    continue;
      String name;
      if (dir != null)
    name = dir + sl[i];
      else
    name = sl[i];
      if (mf.isDirectory()) {
    if (match.path(name, pat, File.separatorChar)) {
        flist.addElement(name);
        name += File.separator;
    else {
        name += File.separator;
        if (match.path(name, pat, File.separatorChar))
      flist.addElement(name);
    }
    if (match.dir(name, pat, File.separatorChar))
        listWork(md, name, pat, level + 1, flist);
      else {
    if (match.path(name, pat, File.separatorChar))
        flist.addElement(name);
      }
  }
    }
}

/**
 * Pattern matching support class for list().
 * Should probably be more public.
 */
// Translated from the c-client functions pmatch_full() and dmatch().
class match {
    /**
     * Pathname pattern match
     *
     @param s    base string
     @param pat  pattern string
     @param delim  delimiter character
     @return    true if base matches pattern
     */
    static public boolean path(String s, String pat, char delim) {
  try {
      return path(s, 0, s.length(), pat, 0, pat.length(), delim);
  catch (StringIndexOutOfBoundsException e) {
      return false;
  }
    }

    static private boolean path(String s, int s_index, int s_len,
  String pat, int p_index, int p_len, char delim)
      throws StringIndexOutOfBoundsException {

  while (p_index < p_len) {
      char c = pat.charAt(p_index);
      switch (c) {
      case '%':
    if (++p_index >= p_len)    // % at end of pattern
            // ok if no delimiters
        return delim == || s.indexOf(delim, s_index0;
    // scan remainder until delimiter
    do {
        if (path(s, s_index, s_len, pat, p_index, p_len, delim))
      return true;
    while (s.charAt(s_index!= delim && ++s_index < s_len);
    // ran into a delimiter or ran out of string without a match
    return false;

      case '*':
    if (++p_index >= p_len)    // end of pattern?
        return true;    // unconditional match
    do {
        if (path(s, s_index, s_len, pat, p_index, p_len, delim))
      return true;
    while (++s_index < s_len);
    // ran out of string without a match
    return false;

      default:
    // if ran out of string or no match, fail
    if (s_index >= s_len || c != s.charAt(s_index))
        return false;

    // try the next string and pattern characters
    s_index++;
    p_index++;
      }
  }
  return s_index >= s_len;
    }

    /**
     * Directory pattern match
     *
     @param s    base string
     @param pat  pattern string
     @return    true if base is a matching directory of pattern
     */
    static public boolean dir(String s, String pat, char delim) {
  try {
      return dir(s, 0, s.length(), pat, 0, pat.length(), delim);
  catch (StringIndexOutOfBoundsException e) {
      return false;
  }
    }

    static private boolean dir(String s, int s_index, int s_len,
  String pat, int p_index, int p_len, char delim)
      throws StringIndexOutOfBoundsException {

  while (p_index < p_len) {
      char c = pat.charAt(p_index);
      switch (c) {
      case '%':
    if (s_index >= s_len)    // end of base?
        return true;    // subset match
    if (++p_index >= p_len)    // % at end of pattern?
        return false;    // no inferiors permitted
    do {
        if (dir(s, s_index, s_len, pat, p_index, p_len, delim))
      return true;
    while (s.charAt(s_index!= delim && ++s_index < s_len);

    if (s_index + == s_len)  // s ends with a delimiter
        return true;    // must be a subset of pattern
    return dir(s, s_index, s_len, pat, p_index, p_len, delim);

      case '*':
    return true;      // unconditional match

      default:
    if (s_index >= s_len)    // end of base?
        return c == delim;    // matched if at delimiter

    if (c != s.charAt(s_index))
        return false;

    // try the next string and pattern characters
    s_index++;
    p_index++;
      }
  }
  return s_index >= s_len;
    }
}