2 * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v. 2.0, which is available at
6 * http://www.eclipse.org/legal/epl-2.0.
8 * This Source Code may also be made available under the following Secondary
9 * Licenses when the conditions for such availability set forth in the
10 * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
11 * version 2 with the GNU Classpath Exception, which is available at
12 * https://www.gnu.org/software/classpath/license.html.
14 * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
17 package com
.sun
.mail
.mbox
;
20 import javax
.mail
.event
.*;
21 import javax
.mail
.internet
.*;
22 import javax
.mail
.util
.*;
25 import com
.sun
.mail
.util
.LineInputStream
;
28 * This class represents a mailbox file containing RFC822 style email messages.
31 * @author Bill Shannon
34 public class MboxFolder
extends Folder
{
36 private String name
; // null => the default folder
37 private boolean is_inbox
= false;
38 private int total
; // total number of messages in mailbox
39 private volatile boolean opened
= false;
40 private List
<MessageMetadata
> messages
;
41 private TempFile temp
;
42 private MboxStore mstore
;
43 private MailFile folder
;
44 private long file_size
; // the size the last time we read or wrote it
45 private long saved_file_size
; // size at the last open, close, or expunge
46 private boolean special_imap_message
;
48 private static final boolean homeRelative
=
49 Boolean
.getBoolean("mail.mbox.homerelative");
52 * Metadata for each message, to avoid instantiating MboxMessage
53 * objects for messages we're not going to look at. <p>
55 * MboxFolder keeps an array of these objects corresponding to
56 * each message in the folder. Access to the array elements is
57 * synchronized, but access to contents of this object is not.
58 * The metadata stored here is only accessed if the message field
59 * is null; otherwise the MboxMessage object contains the metadata.
61 static final class MessageMetadata
{
62 public long start
; // offset in temp file of start of this message
63 // public long end; // offset in temp file of end of this message
64 public long dataend
; // offset of end of message data, <= "end"
65 public MboxMessage message
; // the message itself
66 public boolean recent
; // message is recent?
67 public boolean deleted
; // message is marked deleted?
68 public boolean imap
; // special imap message?
71 public MboxFolder(MboxStore store
, String name
) {
76 if (name
!= null && name
.equalsIgnoreCase("INBOX"))
79 folder
= mstore
.getMailFile(name
== null ?
"~" : name
);
81 saved_file_size
= folder
.length();
86 public char getSeparator() {
87 return File
.separatorChar
;
90 public Folder
[] list(String pattern
) throws MessagingException
{
91 if (!folder
.isDirectory())
92 throw new MessagingException("not a directory");
95 return list(null, pattern
, true);
97 return list(name
+ File
.separator
, pattern
, false);
101 * Version of list shared by MboxStore and MboxFolder.
103 protected Folder
[] list(String ref
, String pattern
, boolean fromStore
)
104 throws MessagingException
{
105 if (ref
!= null && ref
.length() == 0)
108 String refdir
= null;
109 String realdir
= null;
111 pattern
= canonicalize(ref
, pattern
);
112 if ((i
= indexOfAny(pattern
, "%*")) >= 0) {
113 refdir
= pattern
.substring(0, i
);
117 if ((i
= refdir
.lastIndexOf(File
.separatorChar
)) >= 0) {
118 // get rid of anything after directory name
119 refdir
= refdir
.substring(0, i
+ 1);
120 realdir
= mstore
.mb
.filename(mstore
.user
, refdir
);
121 } else if (refdir
.length() == 0 || refdir
.charAt(0) != '~') {
122 // no separator and doesn't start with "~" => home or cwd
125 realdir
= mstore
.home
;
129 realdir
= mstore
.mb
.filename(mstore
.user
, refdir
);
131 List
<String
> flist
= new ArrayList
<String
>();
132 listWork(realdir
, refdir
, pattern
, fromStore ?
0 : 1, flist
);
133 if (Match
.path("INBOX", pattern
, '\0'))
136 Folder fl
[] = new Folder
[flist
.size()];
137 for (i
= 0; i
< fl
.length
; i
++) {
138 fl
[i
] = createFolder(mstore
, flist
.get(i
));
143 public String
getName() {
149 return folder
.getName();
152 public String
getFullName() {
159 public Folder
getParent() {
163 return createFolder(mstore
, null);
165 // XXX - have to recognize other folders under default folder
166 return createFolder(mstore
, folder
.getParent());
169 public boolean exists() {
170 return folder
.exists();
173 public int getType() {
174 if (folder
.isDirectory())
175 return HOLDS_FOLDERS
;
177 return HOLDS_MESSAGES
;
180 public Flags
getPermanentFlags() {
181 return MboxStore
.permFlags
;
184 public synchronized boolean hasNewMessages() {
185 if (folder
instanceof UNIXFile
) {
186 UNIXFile f
= (UNIXFile
)folder
;
187 if (f
.length() > 0) {
188 long atime
= f
.lastAccessed();
189 long mtime
= f
.lastModified();
190 //System.out.println(name + " atime " + atime + " mtime " + mtime);
191 return atime
< mtime
;
197 current_size
= folder
.length();
200 // if we've never opened the folder, remember the size now
201 // (will cause us to return false the first time)
202 if (saved_file_size
< 0)
203 saved_file_size
= current_size
;
204 return current_size
> saved_file_size
;
207 public synchronized Folder
getFolder(String name
)
208 throws MessagingException
{
209 if (folder
.exists() && !folder
.isDirectory())
210 throw new MessagingException("not a directory");
211 return createFolder(mstore
,
212 (this.name
== null ?
"~" : this.name
) + File
.separator
+ name
);
215 public synchronized boolean create(int type
) throws MessagingException
{
218 if (!folder
.mkdirs()) {
224 if (folder
.exists()) {
228 (new FileOutputStream((File
)folder
)).close();
229 } catch (FileNotFoundException fe
) {
230 File parent
= new File(folder
.getParent());
231 if (!parent
.mkdirs())
233 MessagingException("can't create folder: " + name
);
235 (new FileOutputStream((File
)folder
)).close();
236 } catch (IOException ex3
) {
238 MessagingException("can't create folder: " + name
, ex3
);
240 } catch (IOException e
) {
242 MessagingException("can't create folder: " + name
, e
);
247 throw new MessagingException("type not supported");
249 notifyFolderListeners(FolderEvent
.CREATED
);
253 public synchronized boolean delete(boolean recurse
)
254 throws MessagingException
{
257 throw new MessagingException("can't delete default folder");
259 if (recurse
&& folder
.isDirectory())
260 ret
= delete(new File(folder
.getPath()));
261 if (ret
&& folder
.delete()) {
262 notifyFolderListeners(FolderEvent
.DELETED
);
269 * Recursively delete the specified file/directory.
271 private boolean delete(File f
) {
272 File
[] files
= f
.listFiles();
274 for (int i
= 0; ret
&& i
< files
.length
; i
++) {
275 if (files
[i
].isDirectory())
276 ret
= delete(files
[i
]);
278 ret
= files
[i
].delete();
283 public synchronized boolean renameTo(Folder f
)
284 throws MessagingException
{
287 throw new MessagingException("can't rename default folder");
288 if (!(f
instanceof MboxFolder
))
289 throw new MessagingException("can't rename to: " + f
.getName());
290 String newname
= ((MboxFolder
)f
).folder
.getPath();
291 if (folder
.renameTo(new File(newname
))) {
292 notifyFolderRenamedListeners(f
);
298 /* Ensure the folder is open */
299 void checkOpen() throws IllegalStateException
{
301 throw new IllegalStateException("Folder is not Open");
304 /* Ensure the folder is not open */
305 private void checkClosed() throws IllegalStateException
{
307 throw new IllegalStateException("Folder is Open");
311 * Check that the given message number is within the range
312 * of messages present in this folder. If the message
313 * number is out of range, we check to see if new messages
316 private void checkRange(int msgno
) throws MessagingException
{
317 if (msgno
< 1) // message-numbers start at 1
318 throw new IndexOutOfBoundsException("message number < 1");
323 // Out of range, let's check if there are any new messages.
326 if (msgno
> total
) // Still out of range ? Throw up ...
327 throw new IndexOutOfBoundsException(msgno
+ " > " + total
);
330 /* Ensure the folder is open & readable */
331 private void checkReadable() throws IllegalStateException
{
332 if (!opened
|| (mode
!= READ_ONLY
&& mode
!= READ_WRITE
))
333 throw new IllegalStateException("Folder is not Readable");
336 /* Ensure the folder is open & writable */
337 private void checkWritable() throws IllegalStateException
{
338 if (!opened
|| mode
!= READ_WRITE
)
339 throw new IllegalStateException("Folder is not Writable");
342 public boolean isOpen() {
347 * Open the folder in the specified mode.
349 public synchronized void open(int mode
) throws MessagingException
{
351 throw new IllegalStateException("Folder is already Open");
353 if (!folder
.exists())
354 throw new FolderNotFoundException(this, "Folder doesn't exist: " +
360 if (!folder
.canWrite())
361 throw new MessagingException("Open Failure, can't write: " +
366 if (!folder
.canRead())
367 throw new MessagingException("Open Failure, can't read: " +
372 if (is_inbox
&& folder
instanceof InboxFile
) {
373 InboxFile inf
= (InboxFile
)folder
;
374 if (!inf
.openLock(mode
== READ_WRITE ?
"rw" : "r"))
375 throw new MessagingException("Failed to lock INBOX");
377 if (!folder
.lock("r"))
378 throw new MessagingException("Failed to lock folder: " + name
);
379 messages
= new ArrayList
<MessageMetadata
>();
381 Message
[] msglist
= null;
383 temp
= new TempFile(null);
384 saved_file_size
= folder
.length();
385 msglist
= load(0L, false);
386 } catch (IOException e
) {
387 throw new MessagingException("IOException", e
);
391 notifyConnectionListeners(ConnectionEvent
.OPENED
);
393 notifyMessageAddedListeners(msglist
);
394 opened
= true; // now really opened
397 public synchronized void close(boolean expunge
) throws MessagingException
{
401 if (mode
== READ_WRITE
) {
403 writeFolder(true, expunge
);
404 } catch (IOException e
) {
405 throw new MessagingException("I/O Exception", e
);
411 if (is_inbox
&& folder
instanceof InboxFile
) {
412 InboxFile inf
= (InboxFile
)folder
;
417 notifyConnectionListeners(ConnectionEvent
.CLOSED
);
422 * Re-write the folder with the current contents of the messages.
423 * If closing is true, turn off the RECENT flag. If expunge is
424 * true, don't write out deleted messages (only used from close()
425 * when the message cache won't be accessed again).
427 * Return the number of messages written.
429 protected int writeFolder(boolean closing
, boolean expunge
)
430 throws IOException
, MessagingException
{
433 * First, see if there have been any changes.
435 int modified
= 0, deleted
= 0, recent
= 0;
436 for (int msgno
= 1; msgno
<= total
; msgno
++) {
437 MessageMetadata md
= messages
.get(messageIndexOf(msgno
));
438 MboxMessage msg
= md
.message
;
440 Flags flags
= msg
.getFlags();
441 if (msg
.isModified() || !msg
.origFlags
.equals(flags
))
443 if (flags
.contains(Flags
.Flag
.DELETED
))
445 if (flags
.contains(Flags
.Flag
.RECENT
))
454 if ((!closing
|| recent
== 0) && (!expunge
|| deleted
== 0) &&
459 * Have to save any new mail that's been appended to the
460 * folder since we last loaded it.
462 if (!folder
.lock("rw"))
463 throw new MessagingException("Failed to lock folder: " + name
);
464 int oldtotal
= total
; // XXX
465 Message
[] msglist
= null;
466 if (folder
.length() != file_size
)
467 msglist
= load(file_size
, !closing
);
468 // don't use the folder's FD, need to re-open in order to trunc the file
470 new BufferedOutputStream(new FileOutputStream((File
)folder
));
474 if (special_imap_message
)
475 appendStream(getMessageStream(0), os
);
476 for (int msgno
= 1; msgno
<= total
; msgno
++) {
477 MessageMetadata md
= messages
.get(messageIndexOf(msgno
));
478 MboxMessage msg
= md
.message
;
480 if (expunge
&& msg
.isSet(Flags
.Flag
.DELETED
))
481 continue; // skip it;
482 if (closing
&& msgno
<= oldtotal
&&
483 msg
.isSet(Flags
.Flag
.RECENT
))
484 msg
.setFlag(Flags
.Flag
.RECENT
, false);
485 writeMboxMessage(msg
, os
);
487 if (expunge
&& md
.deleted
)
488 continue; // skip it;
489 if (closing
&& msgno
<= oldtotal
&& md
.recent
) {
490 // have to instantiate message so that we can
491 // clear the recent flag
492 msg
= (MboxMessage
)getMessage(msgno
);
493 msg
.setFlag(Flags
.Flag
.RECENT
, false);
494 writeMboxMessage(msg
, os
);
496 appendStream(getMessageStream(msgno
), os
);
502 // If no messages in the mailbox, and we're closing,
503 // maybe we should remove the mailbox.
504 if (wr
== 0 && closing
) {
505 String skeep
= ((MboxStore
)store
).getSession().
506 getProperty("mail.mbox.deleteEmpty");
507 if (skeep
!= null && skeep
.equalsIgnoreCase("true"))
510 } catch (IOException e
) {
512 } catch (MessagingException e
) {
514 } catch (Exception e
) {
516 throw new MessagingException("unexpected exception " + e
);
518 // close the folder, flushing out the data
521 file_size
= saved_file_size
= folder
.length();
526 } catch (IOException ex
) {}
529 // make sure the access time is greater than the mod time
530 // XXX - would be nice to have utime()
532 Thread
.sleep(1000); // sleep for a second
533 } catch (InterruptedException ex
) {}
534 InputStream is
= null;
536 is
= new FileInputStream((File
)folder
);
537 is
.read(); // read a byte
538 } catch (IOException ex
) {} // ignore errors
543 } catch (IOException ex
) {} // ignore errors
548 notifyMessageAddedListeners(msglist
);
554 * Append the input stream to the output stream, closing the
555 * input stream when done.
557 private static final void appendStream(InputStream is
, OutputStream os
)
560 byte[] buf
= new byte[64 * 1024];
562 while ((len
= is
.read(buf
)) > 0)
563 os
.write(buf
, 0, len
);
570 * Write a MimeMessage to the specified OutputStream in a
571 * format suitable for a UNIX mailbox, i.e., including a correct
572 * Content-Length header and with the local platform's line
573 * terminating convention. <p>
575 * If the message is really a MboxMessage, use its writeToFile
576 * method, which has access to the UNIX From line. Otherwise, do
577 * all the work here, creating an appropriate UNIX From line.
579 public static void writeMboxMessage(MimeMessage msg
, OutputStream os
)
580 throws IOException
, MessagingException
{
582 if (msg
instanceof MboxMessage
) {
583 ((MboxMessage
)msg
).writeToFile(os
);
585 // XXX - modify the message to preserve the flags in headers
586 MboxMessage
.setHeadersFromFlags(msg
);
587 ContentLengthCounter cos
= new ContentLengthCounter();
588 NewlineOutputStream nos
= new NewlineOutputStream(cos
);
591 os
= new NewlineOutputStream(os
, true);
592 os
= new ContentLengthUpdater(os
, cos
.getSize());
593 PrintStream pos
= new PrintStream(os
, false, "iso-8859-1");
594 pos
.println(getUnixFrom(msg
));
598 } catch (MessagingException me
) {
600 } catch (IOException ioe
) {
606 * Construct an appropriately formatted UNIX From line using
607 * the sender address and the date in the message.
609 protected static String
getUnixFrom(MimeMessage msg
) {
615 if ((afrom
= msg
.getFrom()) == null ||
616 !(afrom
[0] instanceof InternetAddress
) ||
617 (from
= ((InternetAddress
)afrom
[0]).getAddress()) == null)
619 if ((ddate
= msg
.getReceivedDate()) == null ||
620 (ddate
= msg
.getSentDate()) == null)
622 } catch (MessagingException e
) {
626 date
= ddate
.toString();
627 // date is of the form "Sat Aug 12 02:30:00 PDT 1995"
628 // need to strip out the timezone
629 return "From " + from
+ " " +
630 date
.substring(0, 20) + date
.substring(24);
633 public synchronized int getMessageCount() throws MessagingException
{
637 boolean locked
= false;
638 Message
[] msglist
= null;
640 if (folder
.length() != file_size
) {
641 if (!folder
.lock("r"))
642 throw new MessagingException("Failed to lock folder: " +
645 msglist
= load(file_size
, true);
647 } catch (IOException e
) {
648 throw new MessagingException("I/O Exception", e
);
653 notifyMessageAddedListeners(msglist
);
660 * Get the specified message. Note that messages are numbered
663 public synchronized Message
getMessage(int msgno
)
664 throws MessagingException
{
668 MessageMetadata md
= messages
.get(messageIndexOf(msgno
));
669 MboxMessage m
= md
.message
;
671 InputStream is
= getMessageStream(msgno
);
673 m
= loadMessage(is
, msgno
, mode
== READ_WRITE
);
674 } catch (IOException ex
) {
675 MessagingException mex
=
676 new MessageRemovedException("mbox message gone", ex
);
684 private final int messageIndexOf(int msgno
) {
685 return special_imap_message ? msgno
: msgno
- 1;
688 private InputStream
getMessageStream(int msgno
) {
689 int index
= messageIndexOf(msgno
);
690 MessageMetadata md
= messages
.get(index
);
691 return temp
.newStream(md
.start
, md
.dataend
);
694 public synchronized void appendMessages(Message
[] msgs
)
695 throws MessagingException
{
696 if (!folder
.lock("rw"))
697 throw new MessagingException("Failed to lock folder: " + name
);
699 OutputStream os
= null;
702 os
= new BufferedOutputStream(
703 new FileOutputStream(((File
)folder
).getPath(), true));
704 // XXX - should use getAbsolutePath()?
705 for (int i
= 0; i
< msgs
.length
; i
++) {
706 if (msgs
[i
] instanceof MimeMessage
) {
707 writeMboxMessage((MimeMessage
)msgs
[i
], os
);
714 } catch (IOException e
) {
715 throw new MessagingException("I/O Exception", e
);
716 } catch (MessagingException e
) {
718 } catch (Exception e
) {
720 throw new MessagingException("unexpected exception " + e
);
725 } catch (IOException e
) {
731 getMessageCount(); // loads new messages as a side effect
733 throw new MessagingException("Can't append non-Mime message");
736 public synchronized Message
[] expunge() throws MessagingException
{
740 * First, write out the folder to make sure we have permission,
743 int wr
= total
; // number of messages written out
745 wr
= writeFolder(false, true);
746 } catch (IOException e
) {
747 throw new MessagingException("expunge failed", e
);
749 if (wr
== total
) // wrote them all => nothing expunged
750 return new Message
[0];
753 * Now, actually get rid of the expunged messages.
756 Message
[] msglist
= new Message
[total
- wr
];
758 while (msgno
<= total
) {
759 MessageMetadata md
= messages
.get(messageIndexOf(msgno
));
760 MboxMessage msg
= md
.message
;
762 if (msg
.isSet(Flags
.Flag
.DELETED
)) {
763 msg
.setExpunged(true);
766 messages
.remove(messageIndexOf(msgno
));
769 msg
.setMessageNumber(msgno
); // update message number
774 // have to instantiate it for the notification
775 msg
= (MboxMessage
)getMessage(msgno
);
776 msg
.setExpunged(true);
779 messages
.remove(messageIndexOf(msgno
));
786 if (del
!= msglist
.length
) // this is really an assert
787 throw new MessagingException("expunge delete count wrong");
788 notifyMessageRemovedListeners(true, msglist
);
793 * Load more messages from the folder starting at the specified offset.
795 private Message
[] load(long offset
, boolean notify
)
796 throws MessagingException
, IOException
{
797 int oldtotal
= total
;
798 MessageLoader loader
= new MessageLoader(temp
);
799 int loaded
= loader
.load(folder
.getFD(), offset
, messages
);
801 file_size
= folder
.length();
803 if (offset
== 0 && loaded
> 0) {
805 * If the first message is the special message that the
806 * IMAP server adds to the mailbox, remember that we've
807 * seen it so it won't be shown to the user.
809 MessageMetadata md
= messages
.get(0);
811 special_imap_message
= true;
816 Message
[] msglist
= new Message
[total
- oldtotal
];
817 for (int i
= oldtotal
, j
= 0; i
< total
; i
++, j
++)
818 msglist
[j
] = getMessage(i
+ 1);
825 * Parse the input stream and return an appropriate message object.
826 * The InputStream must be a SharedInputStream.
828 private MboxMessage
loadMessage(InputStream is
, int msgno
,
829 boolean writable
) throws MessagingException
, IOException
{
830 LineInputStream in
= new LineInputStream(is
);
833 * Read lines until a UNIX From line,
834 * skipping blank lines.
837 String unix_from
= null;
838 while ((line
= in
.readLine()) != null) {
839 if (line
.trim().length() == 0)
841 if (line
.startsWith("From ")) {
843 * A UNIX From line looks like:
844 * From address Day Mon DD HH:MM:SS YYYY
848 // find the space after the address, before the date
849 i
= unix_from
.indexOf(' ', 5);
851 continue; // not a valid UNIX From line
854 throw new MessagingException("Garbage in mailbox: " + line
);
857 if (unix_from
== null)
858 throw new EOFException("end of mailbox");
861 * Now load the RFC822 headers into an InternetHeaders object.
863 InternetHeaders hdrs
= new InternetHeaders(is
);
865 // the rest is the message content
866 SharedInputStream sis
= (SharedInputStream
)is
;
867 InputStream stream
= sis
.newStream(sis
.getPosition(), -1);
868 return new MboxMessage(this, hdrs
, stream
, msgno
, unix_from
, writable
);
872 * Only here to make accessible to MboxMessage.
874 protected void notifyMessageChangedListeners(int type
, Message m
) {
875 super.notifyMessageChangedListeners(type
, m
);
880 * this is an exact duplicate of the Folder.getURL except it doesn't
881 * add a beginning '/' to the URLName.
883 public URLName
getURLName() {
884 // XXX - note: this should not be done this way with the
885 // new javax.mail apis.
887 URLName storeURL
= getStore().getURLName();
891 char separator
= getSeparator();
892 String fullname
= getFullName();
893 StringBuilder encodedName
= new StringBuilder();
895 // We need to encode each of the folder's names, and replace
896 // the store's separator char with the URL char '/'.
897 StringTokenizer tok
= new StringTokenizer(
898 fullname
, Character
.toString(separator
), true);
900 while (tok
.hasMoreTokens()) {
901 String s
= tok
.nextToken();
902 if (s
.charAt(0) == separator
)
903 encodedName
.append("/");
905 // XXX - should encode, but since there's no decoder...
906 //encodedName.append(java.net.URLEncoder.encode(s));
907 encodedName
.append(s
);
910 return new URLName(storeURL
.getProtocol(), storeURL
.getHost(),
911 storeURL
.getPort(), encodedName
.toString(),
912 storeURL
.getUsername(),
913 null /* no password */);
917 * Create an MboxFolder object, or a subclass thereof.
918 * Can be overridden by subclasses of MboxFolder so that
919 * the appropriate subclass is created by the list method.
921 protected Folder
createFolder(MboxStore store
, String name
) {
922 return new MboxFolder(store
, name
);
926 * Support routines for list().
930 * Return a canonicalized pattern given a reference name and a pattern.
932 private static String
canonicalize(String ref
, String pat
) {
936 if (pat
.length() == 0) {
938 } else if (pat
.charAt(0) == File
.separatorChar
) {
939 return ref
.substring(0, ref
.indexOf(File
.separatorChar
)) + pat
;
943 } catch (StringIndexOutOfBoundsException e
) {
949 * Return the first index of any of the characters in "any" in "s",
950 * or -1 if none are found.
952 * This should be a method on String.
954 private static int indexOfAny(String s
, String any
) {
956 int len
= s
.length();
957 for (int i
= 0; i
< len
; i
++) {
958 if (any
.indexOf(s
.charAt(i
)) >= 0)
962 } catch (StringIndexOutOfBoundsException e
) {
968 * The recursive part of generating the list of mailboxes.
969 * realdir is the full pathname to the directory to search.
970 * dir is the name the user uses, often a relative name that's
971 * relative to the user's home directory. dir (if not null) always
972 * has a trailing file separator character.
974 * @param realdir real pathname of directory to start looking in
975 * @param dir user's name for realdir
976 * @param pat pattern to match against
977 * @param level level of the directory hierarchy we're in
978 * @param flist list to which to add folder names that match
980 // Derived from the c-client listWork() function.
981 private void listWork(String realdir
, String dir
, String pat
,
982 int level
, List
<String
> flist
) {
984 File fdir
= new File(realdir
);
987 } catch (SecurityException e
) {
988 return; // can't read it, ignore it
991 if (level
== 0 && dir
!= null &&
992 Match
.path(dir
, pat
, File
.separatorChar
))
996 return; // nothing return, we're done
998 if (realdir
.charAt(realdir
.length() - 1) != File
.separatorChar
)
999 realdir
+= File
.separator
;
1001 for (int i
= 0; i
< sl
.length
; i
++) {
1002 if (sl
[i
].charAt(0) == '.')
1003 continue; // ignore all "dot" files for now
1004 String md
= realdir
+ sl
[i
];
1005 File mf
= new File(md
);
1013 if (mf
.isDirectory()) {
1014 if (Match
.path(name
, pat
, File
.separatorChar
)) {
1016 name
+= File
.separator
;
1018 name
+= File
.separator
;
1019 if (Match
.path(name
, pat
, File
.separatorChar
))
1022 if (Match
.dir(name
, pat
, File
.separatorChar
))
1023 listWork(md
, name
, pat
, level
+ 1, flist
);
1025 if (Match
.path(name
, pat
, File
.separatorChar
))
1033 * Pattern matching support class for list().
1034 * Should probably be more public.
1036 // Translated from the c-client functions pmatch_full() and dmatch().
1039 * Pathname pattern match
1041 * @param s base string
1042 * @param pat pattern string
1043 * @param delim delimiter character
1044 * @return true if base matches pattern
1046 static public boolean path(String s
, String pat
, char delim
) {
1048 return path(s
, 0, s
.length(), pat
, 0, pat
.length(), delim
);
1049 } catch (StringIndexOutOfBoundsException e
) {
1054 static private boolean path(String s
, int s_index
, int s_len
,
1055 String pat
, int p_index
, int p_len
, char delim
)
1056 throws StringIndexOutOfBoundsException
{
1058 while (p_index
< p_len
) {
1059 char c
= pat
.charAt(p_index
);
1062 if (++p_index
>= p_len
) // % at end of pattern
1063 // ok if no delimiters
1064 return delim
== 0 || s
.indexOf(delim
, s_index
) < 0;
1065 // scan remainder until delimiter
1067 if (path(s
, s_index
, s_len
, pat
, p_index
, p_len
, delim
))
1069 } while (s
.charAt(s_index
) != delim
&& ++s_index
< s_len
);
1070 // ran into a delimiter or ran out of string without a match
1074 if (++p_index
>= p_len
) // end of pattern?
1075 return true; // unconditional match
1077 if (path(s
, s_index
, s_len
, pat
, p_index
, p_len
, delim
))
1079 } while (++s_index
< s_len
);
1080 // ran out of string without a match
1084 // if ran out of string or no match, fail
1085 if (s_index
>= s_len
|| c
!= s
.charAt(s_index
))
1088 // try the next string and pattern characters
1093 return s_index
>= s_len
;
1097 * Directory pattern match
1099 * @param s base string
1100 * @param pat pattern string
1101 * @return true if base is a matching directory of pattern
1103 static public boolean dir(String s
, String pat
, char delim
) {
1105 return dir(s
, 0, s
.length(), pat
, 0, pat
.length(), delim
);
1106 } catch (StringIndexOutOfBoundsException e
) {
1111 static private boolean dir(String s
, int s_index
, int s_len
,
1112 String pat
, int p_index
, int p_len
, char delim
)
1113 throws StringIndexOutOfBoundsException
{
1115 while (p_index
< p_len
) {
1116 char c
= pat
.charAt(p_index
);
1119 if (s_index
>= s_len
) // end of base?
1120 return true; // subset match
1121 if (++p_index
>= p_len
) // % at end of pattern?
1122 return false; // no inferiors permitted
1124 if (dir(s
, s_index
, s_len
, pat
, p_index
, p_len
, delim
))
1126 } while (s
.charAt(s_index
) != delim
&& ++s_index
< s_len
);
1128 if (s_index
+ 1 == s_len
) // s ends with a delimiter
1129 return true; // must be a subset of pattern
1130 return dir(s
, s_index
, s_len
, pat
, p_index
, p_len
, delim
);
1133 return true; // unconditional match
1136 if (s_index
>= s_len
) // end of base?
1137 return c
== delim
; // matched if at delimiter
1139 if (c
!= s
.charAt(s_index
))
1142 // try the next string and pattern characters
1147 return s_index
>= s_len
;
1152 * A ByteArrayOutputStream that allows us to share the byte array
1153 * rather than copy it. Eventually could replace this with something
1154 * that doesn't require a single contiguous byte array.
1156 class SharedByteArrayOutputStream
extends ByteArrayOutputStream
{
1157 public SharedByteArrayOutputStream(int size
) {
1161 public InputStream
toStream() {
1162 return new SharedByteArrayInputStream(buf
, 0, count
);