All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.angus.mail.imap.MessageCache Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0, which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * This Source Code may also be made available under the following Secondary
 * Licenses when the conditions for such availability set forth in the
 * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
 * version 2 with the GNU Classpath Exception, which is available at
 * https://www.gnu.org/software/classpath/license.html.
 *
 * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
 */

package org.eclipse.angus.mail.imap;

import jakarta.mail.Message;
import org.eclipse.angus.mail.util.MailLogger;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Level;

/**
 * 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 logger
    private MailLogger logger;

    /**
     * Grow the array by at least this much, to avoid constantly
     * reallocating the array.
     */
    private static final int SLOP = 64;

    /**
     * Construct a new message cache of the indicated size.
     */
    MessageCache(IMAPFolder folder, IMAPStore store, int size) {
        this.folder = folder;
        logger = folder.logger.getSubLogger("messagecache", "DEBUG IMAP MC",
                store.getMessageCacheDebug());
        if (logger.isLoggable(Level.CONFIG))
            logger.config("create cache of size " + size);
        ensureCapacity(size, 1);
    }

    /**
     * Constructor for debugging and testing.
     */
    MessageCache(int size, boolean debug) {
        this.folder = null;
        logger = new MailLogger(
                this.getClass(), "messagecache",
                "DEBUG IMAP MC", debug, System.out);
        if (logger.isLoggable(Level.CONFIG))
            logger.config("create DEBUG cache of size " + size);
        ensureCapacity(size, 1);
    }

    /**
     * Size of cache.
     *
     * @return the size of the 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.
     *
     * @param    msgnum    the message number
     * @return the message
     */
    public IMAPMessage getMessage(int msgnum) {
        // check range
        if (msgnum < 1 || msgnum > size)
            throw new ArrayIndexOutOfBoundsException(
                    "message number (" + msgnum + ") out of bounds (" + size + ")");
        IMAPMessage msg = messages[msgnum - 1];
        if (msg == null) {
            if (logger.isLoggable(Level.FINE))
                logger.fine("create message number " + msgnum);
            msg = folder.newIMAPMessage(msgnum);
            messages[msgnum - 1] = msg;
            // mark message expunged if no seqnum
            if (seqnumOf(msgnum) <= 0) {
                logger.fine("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.
     *
     * @param    seqnum    the sequence number of the message
     * @return the message
     */
    public IMAPMessage getMessageBySeqnum(int seqnum) {
        int msgnum = msgnumOf(seqnum);
        if (msgnum < 0) {        // XXX - < 1 ?
            if (logger.isLoggable(Level.FINE))
                logger.fine("no message seqnum " + seqnum);
            return null;
        } else
            return getMessage(msgnum);
    }

    /**
     * Expunge the message with the given sequence number.
     *
     * @param    seqnum    the sequence number of the message to expunge
     */
    public void expungeMessage(int seqnum) {
        int msgnum = msgnumOf(seqnum);
        if (msgnum < 0) {
            if (logger.isLoggable(Level.FINE))
                logger.fine("expunge no seqnum " + seqnum);
            return;        // XXX - should never happen
        }
        IMAPMessage msg = messages[msgnum - 1];
        if (msg != null) {
            if (logger.isLoggable(Level.FINE))
                logger.fine("expunge existing " + msgnum);
            msg.setExpunged(true);
        }
        if (seqnums == null) {        // time to fill it in
            logger.fine("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.
     *
     * @return the removed messages
     */
    public IMAPMessage[] removeExpungedMessages() {
        logger.fine("remove expunged messages");
        // list of expunged messages
        List mlist = new ArrayList<>();

        /*
         * 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 (logger.isLoggable(Level.FINE))
            logger.fine("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.
     *
     * @param    msgs    the messages
     * @return the removed messages
     */
    public IMAPMessage[] removeExpungedMessages(Message[] msgs) {
        logger.fine("remove expunged messages");
        // list of expunged messages
        List mlist = new ArrayList<>();

        /*
         * 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 (logger.isLoggable(Level.FINE))
            logger.fine("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 (logger.isLoggable(Level.FINE))
            logger.fine("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
            logger.fine("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 (logger.isLoggable(Level.FINE))
                logger.fine("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.
     * newSeqNum is the sequence number of the first message added.
     *
     * @param    count    the number of messges
     * @param    newSeqNum    sequence number of first message
     */
    public void addMessages(int count, int newSeqNum) {
        if (logger.isLoggable(Level.FINE))
            logger.fine("add " + count + " messages");
        // don't have to do anything other than making sure there's space
        ensureCapacity(size + count, newSeqNum);
    }

    /*
     * Make sure the arrays are at least big enough to hold
     * "newsize" messages.
     */
    private void ensureCapacity(int newsize, int newSeqNum) {
        if (messages == null)
            messages = new IMAPMessage[newsize + SLOP];
        else if (messages.length < newsize) {
            if (logger.isLoggable(Level.FINE))
                logger.fine("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);
                for (int i = size; i < news.length; i++)
                    news[i] = newSeqNum++;
                seqnums = news;
                if (logger.isLoggable(Level.FINE))
                    logger.fine("message " + newsize +
                            " has sequence number " + seqnums[newsize - 1]);
            }
        } else if (newsize < size) {        // shrinking?
            // this should never happen
            if (logger.isLoggable(Level.FINE))
                logger.fine("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.
     *
     * @param    msgnum    the message number
     * @return the sequence number
     */
    public int seqnumOf(int msgnum) {
        if (seqnums == null)
            return msgnum;
        else {
            if (logger.isLoggable(Level.FINE))
                logger.fine("msgnum " + msgnum + " is seqnum " +
                        seqnums[msgnum - 1]);
            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 (logger.isLoggable(Level.FINE))
                logger.fine("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;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy