commits@javamail.java.net

[mercurial:86] Significantly rework the message cache implementation in IMAPFolder

From: <shannon_at_kenai.com>
Date: Tue, 11 Nov 2008 01:05:12 +0000 (GMT)

Repository: mercurial
Revision: 86
Author: Bill Shannon <bill.shannon_at_sun.com>
Date: 2008-11-05 19:12:52 UTC

Log Message:
-----------
Significantly rework the message cache implementation in IMAPFolder
to allow IMAPMessage objects to be created on demand. This allows
IMAPFolder to better support extremely large mailboxes with less
memory usage.

Modified Paths:
--------------
    doc/release/CHANGES.txt
    mail/src/main/java/com/sun/mail/imap/IMAPFolder.java
    mail/src/main/java/com/sun/mail/imap/IMAPMessage.java
    mail/src/main/java/com/sun/mail/imap/IMAPStore.java

Added Paths:
-----------
    mail/src/main/java/com/sun/mail/imap/MessageCache.java
    mail/src/oldtest/java/javax/mail/internet/messagecachetest.java

Diffs:
-----
diff -r 9a4783d20c83 -r e6cc06a28737 doc/release/CHANGES.txt
--- a/doc/release/CHANGES.txt Fri Sep 19 13:43:41 2008 -0700
+++ b/doc/release/CHANGES.txt Wed Nov 05 11:12:52 2008 -0800
@@ -47,6 +47,7 @@
 <no id> add mail.mime.parameters.strict property to control
ParameterList parse
 <no id> fix possible NPE in MimeMessage if flags is not set in
copy constructor
 <no id> SMTP I/O failure incorrectly reports valid sent
addresses
+<no id> avoid creating IMAPMessage objects until they're
actually needed
 
 
                  CHANGES IN THE 1.4.1 RELEASE
diff -r 9a4783d20c83 -r e6cc06a28737
mail/src/main/java/com/sun/mail/imap/IMAPFolder.java
--- a/mail/src/main/java/com/sun/mail/imap/IMAPFolder.java Fri Sep
19 13:43:41 2008 -0700
+++ b/mail/src/main/java/com/sun/mail/imap/IMAPFolder.java Wed Nov
05 11:12:52 2008 -0800
@@ -163,7 +163,7 @@
     protected String[] attributes; // name attributes from LIST
response
 
     protected IMAPProtocol protocol; // this folder's own protocol
object
- protected Vector messageCache; // message cache
+ protected MessageCache messageCache;// message cache
     // accessor lock for message cache
     protected final Object messageCacheLock = new Object();
 
@@ -254,6 +254,8 @@
 
     private Status cachedStatus = null;
     private long cachedStatusTime = 0;
+
+ private boolean hasMessageCountListener = false; // optimize
notification
 
     private boolean debug = false;
     private PrintStream out; // debug output stream
@@ -441,7 +443,7 @@
        } // Release lock
 
        if (msgno > total) // Still out of range ? Throw up ...
- throw new IndexOutOfBoundsException();
+ throw new IndexOutOfBoundsException(msgno + " > " + total);
     }
 
     /*
@@ -994,11 +996,8 @@
            uidvalidity = mi.uidvalidity;
            uidnext = mi.uidnext;
 
- // Create the message cache vector of appropriate size
- messageCache = new Vector(total);
- // Fill up the cache with light-weight IMAPMessage objects
- for (int i = 0; i < total; i++)
- messageCache.addElement(new IMAPMessage(this, i+1,
i+1));
+ // Create the message cache of appropriate size
+ messageCache = new MessageCache(this, (IMAPStore)store,
total);
 
        } // Release lock
 
@@ -1375,7 +1374,7 @@
        checkOpened();
        checkRange(msgnum);
 
- return (Message)messageCache.elementAt(msgnum-1);
+ return messageCache.getMessage(msgnum);
     }
 
     /**
@@ -1578,6 +1577,7 @@
            fetch(msgs, fp);
        }
 
+ IMAPMessage[] rmsgs;
        synchronized(messageCacheLock) {
            doExpungeNotification = false; // We do this ourselves
later
            try {
@@ -1602,45 +1602,26 @@
                doExpungeNotification = true;
            }
 
- // Cleanup expunged messages and sync messageCache with
- // reality.
- for (int i = 0; i < messageCache.size(); ) {
- IMAPMessage m = (IMAPMessage)messageCache.elementAt(i);
- if (m.isExpunged()) {
- v.addElement(m); // add into vector of expunged
messages
-
- /* remove this message from the messageCache.
- *
- * Note that this also causes all succeeding
messages
- * in the cache to shifted downward in the vector,
- * therby decrementing the vector's size. (and
hence
- * we need to do messageCache.size() at the top of
- * this loop.
- */
- messageCache.removeElementAt(i);
-
+ // Cleanup expunged messages and sync messageCache with
reality.
+ if (msgs != null)
+ rmsgs = messageCache.removeExpungedMessages(msgs);
+ else
+ rmsgs = messageCache.removeExpungedMessages();
+ if (uidTable != null) {
+ for (int i = 0; i < rmsgs.length; i++) {
+ IMAPMessage m = rmsgs[i];
                    /* remove this message from the UIDTable */
- if (uidTable != null) {
- long uid = m.getUID();
- if (uid != -1)
- uidTable.remove(new Long(uid));
- }
- } else {
- /* Valid message, sync its message number with
- * its sequence number.
- */
- m.setMessageNumber(m.getSequenceNumber());
- i++; // done; increment index, go check next
message
+ long uid = m.getUID();
+ if (uid != -1)
+ uidTable.remove(new Long(uid));
                }
            }
+
+ // Update 'total'
+ total = messageCache.size();
        }
 
- // Update 'total'
- total = messageCache.size();
-
        // Notify listeners. This time its for real, guys.
- Message[] rmsgs = new Message[v.size()];
- v.copyInto(rmsgs);
        if (rmsgs.length > 0)
            notifyMessageRemovedListeners(true, rmsgs);
        return rmsgs;
@@ -1724,6 +1705,18 @@
            // bug in our IMAP layer ?
            throw new MessagingException(pex.getMessage(), pex);
        }
+ }
+
+ /*
+ * Override Folder method to keep track of whether we have any
+ * message count listeners. Normally we won't have any, so we
+ * can avoid creating message objects to pass to the notify
+ * method. It's too hard to keep track of when all listeners
+ * are removed, and that's a rare case, so we don't try.
+ */
+ public synchronized void
addMessageCountListener(MessageCountListener l) {
+ super.addMessageCountListener(l);
+ hasMessageCountListener = true;
     }
 
     /***********************************************************
@@ -2347,42 +2340,32 @@
            Message[] msgs = new Message[count];
 
            // Add 'count' new IMAPMessage objects into the
messageCache
- for (int i = 0; i < count; i++) {
- // Note that as a side-effect, we also increment
- // total & realTotal
- IMAPMessage msg = new IMAPMessage(this, ++total,
++realTotal);
- msgs[i] = msg;
- messageCache.addElement(msg);
+ messageCache.addMessages(count);
+ int oldtotal = total; // used in loop below
+ realTotal += count;
+ total += count;
+
+ // avoid instantiating Message objects if no listeners.
+ if (hasMessageCountListener) {
+ for (int i = 0; i < count; i++)
+ msgs[i] = messageCache.getMessage(++oldtotal);
+
+ // Notify listeners.
+ notifyMessageAddedListeners(msgs);
            }
-
- // Notify listeners.
- notifyMessageAddedListeners(msgs);
 
        } else if (ir.keyEquals("EXPUNGE")) {
            // EXPUNGE response.
 
- IMAPMessage msg = getMessageBySeqNumber(ir.getNumber());
- msg.setExpunged(true); // mark this message expunged.
-
- // Renumber the cache, starting from just beyond
- // the expunged message.
- for (int i = msg.getMessageNumber(); i < total; i++) {
- // Note that 'i' actually indexes the message
- // beyond the expunged message.
- IMAPMessage m = (IMAPMessage)messageCache.elementAt(i);
- if (m.isExpunged()) // an expunged message, skip
- continue;
-
- // Decrement this message's seqnum
- m.setSequenceNumber(m.getSequenceNumber() - 1);
- } // Whew, done.
+ int seqnum = ir.getNumber();
+ messageCache.expungeMessage(seqnum);
 
            // decrement 'realTotal'; but leave 'total' unchanged
            realTotal--;
 
- if (doExpungeNotification) {
+ if (doExpungeNotification && hasMessageCountListener) {
                // Do the notification here.
- Message[] msgs = {msg};
+ Message[] msgs = { getMessageBySeqNumber(seqnum) };
                notifyMessageRemovedListeners(false, msgs);
            }
 
@@ -2717,16 +2700,7 @@
      * messageCacheLock
      */
     IMAPMessage getMessageBySeqNumber(int seqnum) {
- /* Check messageCache for message matching the given
- * sequence number. We start the search from position
(seqnum-1)
- * and continue down the vector till we get a match.
- */
- for (int i = seqnum-1; i < total; i++) {
- IMAPMessage msg = (IMAPMessage)messageCache.elementAt(i);
- if (msg.getSequenceNumber() == seqnum)
- return msg;
- }
- return null;
+ return messageCache.getMessageBySeqnum(seqnum);
     }
 
     private boolean isDirectory() {
diff -r 9a4783d20c83 -r e6cc06a28737
mail/src/main/java/com/sun/mail/imap/IMAPMessage.java
--- a/mail/src/main/java/com/sun/mail/imap/IMAPMessage.java Fri Sep
19 13:43:41 2008 -0700
+++ b/mail/src/main/java/com/sun/mail/imap/IMAPMessage.java Wed Nov
05 11:12:52 2008 -0800
@@ -83,8 +83,6 @@
 
     private boolean peek; // use BODY.PEEK when fetching
content?
 
- // this message's IMAP sequence number
- private int seqnum;
     // this message's IMAP UID
     private long uid = -1;
 
@@ -116,9 +114,8 @@
     /**
      * Constructor.
      */
- protected IMAPMessage(IMAPFolder folder, int msgnum, int seqnum) {
+ protected IMAPMessage(IMAPFolder folder, int msgnum) {
        super(folder, msgnum);
- this.seqnum = seqnum;
        flags = null;
     }
 
@@ -174,17 +171,7 @@
      * messageCacheLock.
      */
     protected int getSequenceNumber() {
- return seqnum;
- }
-
- /**
- * Set this message's IMAP sequence number.
- *
- * ASSERT: This method must be called only when holding the
- * messageCacheLock.
- */
- protected void setSequenceNumber(int seqnum) {
- this.seqnum = seqnum;
+ return
((IMAPFolder)folder).messageCache.seqnumOf(getMessageNumber());
     }
 
     /**
@@ -203,10 +190,9 @@
        this.uid = uid;
     }
 
- // overrides super.setExpunged()
+ // expose to MessageCache
     protected void setExpunged(boolean set) {
        super.setExpunged(set);
- seqnum = -1;
     }
 
     // Convenience routine
diff -r 9a4783d20c83 -r e6cc06a28737
mail/src/main/java/com/sun/mail/imap/IMAPStore.java
--- a/mail/src/main/java/com/sun/mail/imap/IMAPStore.java Fri Sep
19 13:43:41 2008 -0700
+++ b/mail/src/main/java/com/sun/mail/imap/IMAPStore.java Wed Nov
05 11:12:52 2008 -0800
@@ -207,6 +207,8 @@
     private final Object connectionFailedLock = new Object();
 
     private PrintStream out; // debug output stream
+
+ private boolean messageCacheDebug;
 
     // Connection pool info
 
@@ -521,6 +523,10 @@
            "mail." + name + ".enableimapevents", false);
        if (debug && enableImapEvents)
            out.println("DEBUG: enable IMAP events");
+
+ // check if message cache debugging set
+ messageCacheDebug = PropUtil.getBooleanSessionProperty(session,
+ "mail." + name + ".messagecache.debug", false);
 
        pool = new ConnectionPool(name, session);
     }
@@ -964,6 +970,13 @@
      */
     boolean getConnectionPoolDebug() {
         return pool.debug;
+ }
+
+ /**
+ * Report whether message cache debugging is enabled.
+ */
+ boolean getMessageCacheDebug() {
+ return messageCacheDebug;
     }
  
     /**
diff -r 9a4783d20c83 -r e6cc06a28737
mail/src/main/java/com/sun/mail/imap/MessageCache.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mail/src/main/java/com/sun/mail/imap/MessageCache.java Wed Nov
05 11:12:52 2008 -0800
@@ -0,0 +1,421 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 1997-2008 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.
+ */
+
+package com.sun.mail.imap;
+
+import java.io.PrintStream;
+import java.util.*;
+
+import javax.mail.*;
+import com.sun.mail.util.PropUtil;
+
+/**
+ * A cache of IMAPMessage objects along with the
+ * mapping from message number to IMAP sequence number.
+ *
+ * All operations on this object are protected by the messageCacheLock
+ * in IMAPFolder.
+ */
+public class MessageCache {
+ /*
+ * The array of IMAPMessage objects. Elements of the array might
+ * be null if no one has asked for the message. The array expands
+ * as needed and might be larger than the number of messages in
the
+ * folder. The "size" field indicates the number of entries that
+ * are valid.
+ */
+ private IMAPMessage[] messages;
+
+ /*
+ * A parallel array of sequence numbers for each message. If the
+ * array pointer is null, the sequence number of a message is just
+ * its message number. This is the common case, until a message
is
+ * expunged.
+ */
+ private int[] seqnums;
+
+ /*
+ * The amount of the messages (and seqnum) array that is valid.
+ * Might be less than the actual size of the array.
+ */
+ private int size;
+
+ /**
+ * The folder these messages belong to.
+ */
+ private IMAPFolder folder;
+
+ // debugging flag and stream
+ private boolean debug;
+ private PrintStream out;
+
+ /**
+ * Grow the array by at least this much, to avoid constantly
+ * reallocating the array.
+ */
+ private final int SLOP = 64;
+
+ /**
+ * Construct a new message cache of the indicated size.
+ */
+ MessageCache(IMAPFolder folder, IMAPStore store, int size) {
+ this.folder = folder;
+ this.debug = store.getMessageCacheDebug();
+ this.out = store.getSession().getDebugOut();
+ if (debug)
+ out.println("DEBUG IMAP MC: create cache of size " + size);
+ ensureCapacity(size);
+ }
+
+ /**
+ * Size of cache.
+ */
+ public int size() {
+ return size;
+ }
+
+ /**
+ * Get the message object for the indicated message number.
+ * If the message object hasn't been created, create it.
+ */
+ public IMAPMessage getMessage(int msgnum) {
+ // check range
+ if (msgnum < 1 || msgnum > size)
+ throw new ArrayIndexOutOfBoundsException(
+ "message number out of bounds");
+ IMAPMessage msg = messages[msgnum-1];
+ if (msg == null) {
+ if (debug)
+ out.println("DEBUG IMAP MC: create message number " +
msgnum);
+ msg = new IMAPMessage(folder, msgnum);
+ messages[msgnum-1] = msg;
+ // mark message expunged if no seqnum
+ if (seqnumOf(msgnum) <= 0) {
+ if (debug)
+ out.println("DEBUG IMAP MC: it's expunged!");
+ msg.setExpunged(true);
+ }
+ }
+ return msg;
+ }
+
+ /**
+ * Get the message object for the indicated sequence number.
+ * If the message object hasn't been created, create it.
+ * Return null if there's no message with that sequence number.
+ */
+ public IMAPMessage getMessageBySeqnum(int seqnum) {
+ int msgnum = msgnumOf(seqnum);
+ if (msgnum < 0) { // XXX - < 1 ?
+ if (debug)
+ out.println("DEBUG IMAP MC: no message seqnum " +
seqnum);
+ return null;
+ } else
+ return getMessage(msgnum);
+ }
+
+ /**
+ * Expunge the message with the given sequence number.
+ */
+ public void expungeMessage(int seqnum) {
+ int msgnum = msgnumOf(seqnum);
+ if (msgnum < 0) {
+ if (debug)
+ out.println("DEBUG IMAP MC: expunge no seqnum " +
seqnum);
+ return; // XXX - should never happen
+ }
+ IMAPMessage msg = messages[msgnum-1];
+ if (msg != null) {
+ if (debug)
+ out.println("DEBUG IMAP MC: expunge existing " +
msgnum);
+ msg.setExpunged(true);
+ }
+ if (seqnums == null) { // time to fill it in
+ if (debug)
+ out.println("DEBUG IMAP MC: create seqnums array");
+ seqnums = new int[messages.length];
+ for (int i = 1; i < msgnum; i++)
+ seqnums[i-1] = i;
+ seqnums[msgnum - 1] = 0;
+ for (int i = msgnum + 1; i <= seqnums.length; i++)
+ seqnums[i-1] = i - 1;
+ } else {
+ seqnums[msgnum - 1] = 0;
+ for (int i = msgnum + 1; i <= seqnums.length; i++) {
+ assert seqnums[i-1] != 1;
+ if (seqnums[i-1] > 0)
+ seqnums[i-1]--;
+ }
+ }
+ }
+
+ /**
+ * Remove all the expunged messages from the array,
+ * returning a list of removed message objects.
+ */
+ public IMAPMessage[] removeExpungedMessages() {
+ if (debug)
+ out.println("DEBUG IMAP MC: remove expunged messages");
+ List mlist = new ArrayList(); // list of expunged messages
+
+ /*
+ * Walk through the array compressing it by copying
+ * higher numbered messages further down in the array,
+ * effectively removing expunged messages from the array.
+ * oldnum is the index we use to walk through the array.
+ * newnum is the index where we copy the next valid message.
+ * oldnum == newnum until we encounter an expunged message.
+ */
+ int oldnum = 1;
+ int newnum = 1;
+ while (oldnum <= size) {
+ // is message expunged?
+ if (seqnumOf(oldnum) <= 0) {
+ IMAPMessage m = getMessage(oldnum);
+ mlist.add(m);
+ } else {
+ // keep this message
+ if (newnum != oldnum) {
+ // move message down in the array (compact array)
+ messages[newnum-1] = messages[oldnum-1];
+ if (messages[newnum-1] != null)
+ messages[newnum-1].setMessageNumber(newnum);
+ }
+ newnum++;
+ }
+ oldnum++;
+ }
+ seqnums = null;
+ shrink(newnum, oldnum);
+
+ IMAPMessage[] rmsgs = new IMAPMessage[mlist.size()];
+ if (debug)
+ out.println("DEBUG IMAP MC: return " + rmsgs.length);
+ mlist.toArray(rmsgs);
+ return rmsgs;
+ }
+
+ /**
+ * Remove expunged messages in msgs from the array,
+ * returning a list of removed message objects.
+ * All messages in msgs must be IMAPMessage objects
+ * from this folder.
+ */
+ public IMAPMessage[] removeExpungedMessages(Message[] msgs) {
+ if (debug)
+ out.println("DEBUG IMAP MC: remove expunged messages");
+ List mlist = new ArrayList(); // list of expunged messages
+
+ /*
+ * Copy the message numbers of the expunged messages into
+ * a separate array and sort the array to make it easier to
+ * process later.
+ */
+ int[] mnum = new int[msgs.length];
+ for (int i = 0; i < msgs.length; i++)
+ mnum[i] = msgs[i].getMessageNumber();
+ Arrays.sort(mnum);
+
+ /*
+ * Walk through the array compressing it by copying
+ * higher numbered messages further down in the array,
+ * effectively removing expunged messages from the array.
+ * oldnum is the index we use to walk through the array.
+ * newnum is the index where we copy the next valid message.
+ * oldnum == newnum until we encounter an expunged message.
+ *
+ * Even though we know the message number of the first possibly
+ * expunged message, we still start scanning at message number
1
+ * so that we can check whether there's any message whose
+ * sequence number is different than its message number. If
there
+ * is, we can't throw away the seqnums array when we're done.
+ */
+ int oldnum = 1;
+ int newnum = 1;
+ int mnumi = 0; // index into mnum
+ boolean keepSeqnums = false;
+ while (oldnum <= size) {
+ /*
+ * Are there still expunged messsages in msgs to consider,
+ * and is the message we're considering the next one in the
+ * list, and is it expunged?
+ */
+ if (mnumi < mnum.length &&
+ oldnum == mnum[mnumi] &&
+ seqnumOf(oldnum) <= 0) {
+ IMAPMessage m = getMessage(oldnum);
+ mlist.add(m);
+ /*
+ * Just in case there are duplicate entries in the msgs
array,
+ * we keep advancing mnumi past any duplicates, but of
course
+ * stop when we get to the end of the array.
+ */
+ while (mnumi < mnum.length && mnum[mnumi] <= oldnum)
+ mnumi++; // consider next message in array
+ } else {
+ // keep this message
+ if (newnum != oldnum) {
+ // move message down in the array (compact array)
+ messages[newnum-1] = messages[oldnum-1];
+ if (messages[newnum-1] != null)
+ messages[newnum-1].setMessageNumber(newnum);
+ if (seqnums != null)
+ seqnums[newnum-1] = seqnums[oldnum-1];
+ }
+ if (seqnums != null && seqnums[newnum-1] != newnum)
+ keepSeqnums = true;
+ newnum++;
+ }
+ oldnum++;
+ }
+
+ if (!keepSeqnums)
+ seqnums = null;
+ shrink(newnum, oldnum);
+
+ IMAPMessage[] rmsgs = new IMAPMessage[mlist.size()];
+ if (debug)
+ out.println("DEBUG IMAP MC: return " + rmsgs.length);
+ mlist.toArray(rmsgs);
+ return rmsgs;
+ }
+
+ /**
+ * Shrink the messages and seqnums arrays. newend is one past
last
+ * valid element. oldend is one past the previous last valid
element.
+ */
+ private void shrink(int newend, int oldend) {
+ size = newend - 1;
+ if (debug)
+ out.println("DEBUG IMAP MC: size now " + size);
+ if (size == 0) { // no messages left
+ messages = null;
+ seqnums = null;
+ } else if (size > SLOP && size < messages.length / 2) {
+ // if array shrinks by too much, reallocate it
+ if (debug)
+ out.println("DEBUG IMAP MC: reallocate array");
+ IMAPMessage[] newm = new IMAPMessage[size + SLOP];
+ System.arraycopy(messages, 0, newm, 0, size);
+ messages = newm;
+ if (seqnums != null) {
+ int[] news = new int[size + SLOP];
+ System.arraycopy(seqnums, 0, news, 0, size);
+ seqnums = news;
+ }
+ } else {
+ if (debug)
+ out.println("DEBUG IMAP MC: clean " + newend + " to " +
oldend);
+ // clear out unused entries in array
+ for (int msgnum = newend; msgnum < oldend; msgnum++) {
+ messages[msgnum-1] = null;
+ if (seqnums != null)
+ seqnums[msgnum-1] = 0;
+ }
+ }
+ }
+
+ /**
+ * Add count messages to the cache.
+ */
+ public void addMessages(int count) {
+ if (debug)
+ out.println("DEBUG IMAP MC: add " + count + " messages");
+ // don't have to do anything other than making sure there's
space
+ ensureCapacity(size + count);
+ }
+
+ /*
+ * Make sure the arrays are at least big enough to hold
+ * "newsize" messages.
+ */
+ private void ensureCapacity(int newsize) {
+ if (messages == null)
+ messages = new IMAPMessage[newsize + SLOP];
+ else if (messages.length < newsize) {
+ if (debug)
+ out.println("DEBUG IMAP MC: expand capacity to " +
newsize);
+ IMAPMessage[] newm = new IMAPMessage[newsize + SLOP];
+ System.arraycopy(messages, 0, newm, 0, messages.length);
+ messages = newm;
+ if (seqnums != null) {
+ int[] news = new int[newsize + SLOP];
+ System.arraycopy(seqnums, 0, news, 0, seqnums.length);
+ seqnums = news;
+ }
+ } else if (newsize < size) { // shrinking?
+ // this should never happen
+ if (debug)
+ out.println("DEBUG IMAP MC: shrink capacity to " +
newsize);
+ for (int msgnum = newsize + 1; msgnum <= size; msgnum++) {
+ messages[msgnum-1] = null;
+ if (seqnums != null)
+ seqnums[msgnum-1] = -1;
+ }
+ }
+ size = newsize;
+ }
+
+ /**
+ * Return the sequence number for the given message number.
+ */
+ public int seqnumOf(int msgnum) {
+ if (seqnums == null)
+ return msgnum;
+ else
+ return seqnums[msgnum-1];
+ }
+
+ /**
+ * Return the message number for the given sequence number.
+ */
+ private int msgnumOf(int seqnum) {
+ if (seqnums == null)
+ return seqnum;
+ if (seqnum < 1) { // should never happen
+ if (debug)
+ out.println("DEBUG IMAP MC: bad seqnum " + seqnum);
+ return -1;
+ }
+ for (int msgnum = seqnum; msgnum <= size; msgnum++) {
+ if (seqnums[msgnum-1] == seqnum)
+ return msgnum;
+ if (seqnums[msgnum-1] > seqnum)
+ break; // message doesn't exist
+ }
+ return -1;
+ }
+}
diff -r 9a4783d20c83 -r e6cc06a28737
mail/src/oldtest/java/javax/mail/internet/messagecachetest.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mail/src/oldtest/java/javax/mail/internet/messagecachetest.java
Wed Nov 05 11:12:52 2008 -0800
@@ -0,0 +1,322 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 1997-2008 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.
+ */
+
+import java.util.*;
+import java.io.*;
+import javax.mail.*;
+import javax.mail.event.*;
+import javax.mail.internet.*;
+import javax.activation.*;
+
+/*
+ * Test IMAP message cache.
+ *
+ * @author Bill Shannon
+ */
+
+public class messagecachetest {
+
+ static String protocol;
+ static String host = null;
+ static String user = null;
+ static String password = null;
+ static String mbox = null;
+ static String url = null;
+ static int port = -1;
+ static boolean verbose = false;
+ static boolean debug = false;
+ static Session session;
+
+ public static void main(String argv[]) {
+ int nummsg = 256;
+ int optind;
+
+ for (optind = 0; optind < argv.length; optind++) {
+ if (argv[optind].equals("-T")) {
+ protocol = argv[++optind];
+ } else if (argv[optind].equals("-H")) {
+ host = argv[++optind];
+ } else if (argv[optind].equals("-U")) {
+ user = argv[++optind];
+ } else if (argv[optind].equals("-P")) {
+ password = argv[++optind];
+ } else if (argv[optind].equals("-v")) {
+ verbose = true;
+ } else if (argv[optind].equals("-D")) {
+ debug = true;
+ } else if (argv[optind].equals("-f")) {
+ mbox = argv[++optind];
+ } else if (argv[optind].equals("-L")) {
+ url = argv[++optind];
+ } else if (argv[optind].equals("-p")) {
+ port = Integer.parseInt(argv[++optind]);
+ } else if (argv[optind].equals("--")) {
+ optind++;
+ break;
+ } else if (argv[optind].startsWith("-")) {
+ System.out.println(
+"Usage: messagecachetest [-L url] [-T protocol] [-H host] [-p port]
[-U user]");
+ System.out.println(
+"\t[-P password] [-f mailbox] [msgnum] [-v] [-D]");
+ System.exit(1);
+ } else {
+ break;
+ }
+ }
+
+ try {
+ if (optind < argv.length)
+ nummsg = Integer.parseInt(argv[optind]);
+
+ // Get a Properties object
+ Properties props = System.getProperties();
+
+ // Get a Session object
+ session = Session.getInstance(props, null);
+ session.setDebug(debug);
+
+ // Get a Store object
+ Store store = null;
+ if (url != null) {
+ URLName urln = new URLName(url);
+ store = session.getStore(urln);
+ store.connect();
+ } else {
+ if (protocol != null)
+ store = session.getStore(protocol);
+ else
+ store = session.getStore();
+
+ // Connect
+ if (host != null || user != null || password != null)
+ store.connect(host, port, user, password);
+ else
+ store.connect();
+ }
+
+
+ // Open the Folder
+
+ Folder folder = store.getDefaultFolder();
+ if (folder == null) {
+ System.out.println("No default folder");
+ System.exit(1);
+ }
+
+ if (mbox == null)
+ mbox = "messagecachetest";
+ folder = folder.getFolder(mbox);
+ if (folder == null) {
+ System.out.println("Invalid folder");
+ System.exit(1);
+ }
+
+ Message[] msgs = createMessages(nummsg);
+ if (folder.exists())
+ folder.delete(false);
+ folder.create(Folder.HOLDS_MESSAGES);
+
+ folder.open(Folder.READ_WRITE);
+ if (verbose)
+ System.out.println("fill folder");
+ folder.appendMessages(msgs);
+ folder.close(false);
+
+ folder.open(Folder.READ_WRITE);
+ if (verbose)
+ System.out.println("test message number");
+ testMessageNumber(folder);
+ folder.close(false);
+ folder.open(Folder.READ_WRITE);
+ if (verbose)
+ System.out.println("test expunge forward");
+ testExpungeForward(folder);
+ folder.close(false);
+
+ folder.open(Folder.READ_WRITE);
+ folder.appendMessages(msgs);
+ folder.close(false);
+ folder.open(Folder.READ_WRITE);
+ if (verbose)
+ System.out.println("test expunge reverse");
+ testExpungeReverse(folder);
+ folder.close(false);
+
+ folder.open(Folder.READ_WRITE);
+ folder.appendMessages(msgs);
+ folder.close(false);
+ folder.open(Folder.READ_WRITE);
+ if (verbose)
+ System.out.println("test expunge random");
+ testExpungeRandom(folder);
+ folder.close(false);
+
+ folder.open(Folder.READ_WRITE);
+ folder.appendMessages(msgs);
+ folder.close(false);
+ folder.open(Folder.READ_WRITE);
+ if (verbose)
+ System.out.println("test expunge other");
+ testExpungeOther(folder);
+ folder.close(false);
+ store.close();
+ } catch (Exception ex) {
+ System.out.println("Oops, got exception! " +
ex.getMessage());
+ ex.printStackTrace();
+ System.exit(1);
+ }
+ System.exit(0);
+ }
+
+ private static Message[] createMessages(int num) throws
MessagingException {
+ Message[] msgs = new Message[num];
+ for (int i = 1; i <= num; i++) {
+ MimeMessage msg = new MimeMessage(session);
+ msg.setSentDate(new Date());
+ msg.setFrom();
+ msg.setRecipients(Message.RecipientType.TO,
"nobody_at_nowhere.com");
+ msg.setSubject(Integer.toString(i));
+ msg.setText(i + "\n");
+ msg.saveChanges();
+ msgs[i-1] = msg;
+ }
+ return msgs;
+ }
+
+ private static void testMessageNumber(Folder folder)
+ throws MessagingException {
+ int nummsg = folder.getMessageCount();
+ Message msgs[] = new Message[nummsg];
+ for (int i = 1; i <= nummsg; i++) {
+ Message msg = folder.getMessage(i);
+ msgs[i-1] = msg;
+ if (Integer.valueOf(msg.getSubject()) != i) {
+ System.out.println("FAIL: Wrong message! Got " +
+ msg.getSubject() + ", Expected " + i);
+ }
+ }
+ for (int i = 1; i <= nummsg; i++) {
+ Message msg = folder.getMessage(i);
+ if (msgs[i-1] != msg || msg.getMessageNumber() != i) {
+ System.out.println("FAIL: Wrong message! Got " +
+ msg.getMessageNumber() + ", Expected "
+ i);
+ }
+ }
+ }
+
+ private static void testExpungeForward(Folder folder)
+ throws MessagingException {
+ int nummsg = folder.getMessageCount();
+ for (int i = 1; i <= nummsg; i++) {
+ Message msg = folder.getMessage(1);
+ msg.setFlag(Flags.Flag.DELETED, true);
+ folder.expunge();
+ for (int j = 1; j < nummsg - i; j++) {
+ msg = folder.getMessage(j);
+ if (msg.getMessageNumber() != j) {
+ System.out.println("FAIL: Wrong message! Got " +
+ msg.getMessageNumber() + ",
Expected " + j);
+ }
[trimmed for length]