
com.sun.mail.mbox.MboxFolder Maven / Gradle / Ivy
Show all versions of catalog-test Show documentation
/*
* 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 pattern) throws 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() == 0 || 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 ? 0 : 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 type) throws 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 (!(f 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 mode) throws 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(0L, false);
} 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 expunge) throws 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 == 0 && 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.
*
* 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[0] instanceof 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(0, 20) + 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[i] instanceof 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 writable) throws 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 offset) throws 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 == 0 && 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 == 0 || s.indexOf(delim, s_index) < 0;
// 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 + 1 == 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;
}
}