Package: MessageCache
MessageCache
name | instruction | branch | complexity | line | method | ||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
MessageCache(IMAPFolder, IMAPStore, int) |
|
|
|
|
|
||||||||||||||||||||
MessageCache(int, boolean) |
|
|
|
|
|
||||||||||||||||||||
addMessages(int, int) |
|
|
|
|
|
||||||||||||||||||||
ensureCapacity(int, int) |
|
|
|
|
|
||||||||||||||||||||
expungeMessage(int) |
|
|
|
|
|
||||||||||||||||||||
getMessage(int) |
|
|
|
|
|
||||||||||||||||||||
getMessageBySeqnum(int) |
|
|
|
|
|
||||||||||||||||||||
msgnumOf(int) |
|
|
|
|
|
||||||||||||||||||||
removeExpungedMessages() |
|
|
|
|
|
||||||||||||||||||||
removeExpungedMessages(Message[]) |
|
|
|
|
|
||||||||||||||||||||
seqnumOf(int) |
|
|
|
|
|
||||||||||||||||||||
shrink(int, int) |
|
|
|
|
|
||||||||||||||||||||
size() |
|
|
|
|
|
||||||||||||||||||||
static {...} |
|
|
|
|
|
Coverage
1: /*
2: * Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
3: *
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.
7: *
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.
13: *
14: * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15: */
16:
17: package org.eclipse.angus.mail.imap;
18:
19: import java.util.*;
20: import java.util.logging.Level;
21:
22: import org.eclipse.angus.mail.util.MailLogger;
23:
24: import jakarta.mail.*;
25:
26: /**
27: * A cache of IMAPMessage objects along with the
28: * mapping from message number to IMAP sequence number.
29: *
30: * All operations on this object are protected by the messageCacheLock
31: * in IMAPFolder.
32: */
33: public class MessageCache {
34: /*
35: * The array of IMAPMessage objects. Elements of the array might
36: * be null if no one has asked for the message. The array expands
37: * as needed and might be larger than the number of messages in the
38: * folder. The "size" field indicates the number of entries that
39: * are valid.
40: */
41: private IMAPMessage[] messages;
42:
43: /*
44: * A parallel array of sequence numbers for each message. If the
45: * array pointer is null, the sequence number of a message is just
46: * its message number. This is the common case, until a message is
47: * expunged.
48: */
49: private int[] seqnums;
50:
51: /*
52: * The amount of the messages (and seqnum) array that is valid.
53: * Might be less than the actual size of the array.
54: */
55: private int size;
56:
57: /**
58: * The folder these messages belong to.
59: */
60: private IMAPFolder folder;
61:
62: // debugging logger
63: private MailLogger logger;
64:
65: /**
66: * Grow the array by at least this much, to avoid constantly
67: * reallocating the array.
68: */
69: private static final int SLOP = 64;
70:
71: /**
72: * Construct a new message cache of the indicated size.
73: */
74: MessageCache(IMAPFolder folder, IMAPStore store, int size) {
75:         this.folder = folder;
76:         logger = folder.logger.getSubLogger("messagecache", "DEBUG IMAP MC",
77:                                                 store.getMessageCacheDebug());
78:•        if (logger.isLoggable(Level.CONFIG))
79:          logger.config("create cache of size " + size);
80:         ensureCapacity(size, 1);
81: }
82:
83: /**
84: * Constructor for debugging and testing.
85: */
86: MessageCache(int size, boolean debug) {
87:         this.folder = null;
88:         logger = new MailLogger(
89:                  this.getClass(), "messagecache",
90:                  "DEBUG IMAP MC", debug, System.out);
91:•        if (logger.isLoggable(Level.CONFIG))
92:          logger.config("create DEBUG cache of size " + size);
93:         ensureCapacity(size, 1);
94: }
95:
96: /**
97: * Size of cache.
98: *
99: * @return        the size of the cache
100: */
101: public int size() {
102:         return size;
103: }
104:
105: /**
106: * Get the message object for the indicated message number.
107: * If the message object hasn't been created, create it.
108: *
109: * @param        msgnum        the message number
110: * @return                the message
111: */
112: public IMAPMessage getMessage(int msgnum) {
113:         // check range
114:•        if (msgnum < 1 || msgnum > size)
115:          throw new ArrayIndexOutOfBoundsException(
116:                 "message number (" + msgnum + ") out of bounds (" + size + ")");
117:         IMAPMessage msg = messages[msgnum-1];
118:•        if (msg == null) {
119:•         if (logger.isLoggable(Level.FINE))
120:                 logger.fine("create message number " + msgnum);
121:          msg = folder.newIMAPMessage(msgnum);
122:          messages[msgnum-1] = msg;
123:          // mark message expunged if no seqnum
124:•         if (seqnumOf(msgnum) <= 0) {
125:                 logger.fine("it's expunged!");
126:                 msg.setExpunged(true);
127:          }
128:         }
129:         return msg;
130: }
131:
132: /**
133: * Get the message object for the indicated sequence number.
134: * If the message object hasn't been created, create it.
135: * Return null if there's no message with that sequence number.
136: *
137: * @param        seqnum        the sequence number of the message
138: * @return                the message
139: */
140: public IMAPMessage getMessageBySeqnum(int seqnum) {
141:         int msgnum = msgnumOf(seqnum);
142:•        if (msgnum < 0) {                // XXX - < 1 ?
143:•         if (logger.isLoggable(Level.FINE))
144:                 logger.fine("no message seqnum " + seqnum);
145:          return null;
146:         } else
147:          return getMessage(msgnum);
148: }
149:
150: /**
151: * Expunge the message with the given sequence number.
152: *
153: * @param        seqnum        the sequence number of the message to expunge
154: */
155: public void expungeMessage(int seqnum) {
156:         int msgnum = msgnumOf(seqnum);
157:•        if (msgnum < 0) {
158:•         if (logger.isLoggable(Level.FINE))
159:                 logger.fine("expunge no seqnum " + seqnum);
160:          return;                // XXX - should never happen
161:         }
162:         IMAPMessage msg = messages[msgnum-1];
163:•        if (msg != null) {
164:•         if (logger.isLoggable(Level.FINE))
165:                 logger.fine("expunge existing " + msgnum);
166:          msg.setExpunged(true);
167:         }
168:•        if (seqnums == null) {                // time to fill it in
169:          logger.fine("create seqnums array");
170:          seqnums = new int[messages.length];
171:•         for (int i = 1; i < msgnum; i++)
172:                 seqnums[i-1] = i;
173:          seqnums[msgnum - 1] = 0;
174:•         for (int i = msgnum + 1; i <= seqnums.length; i++)
175:                 seqnums[i-1] = i - 1;
176:         } else {
177:          seqnums[msgnum - 1] = 0;
178:•         for (int i = msgnum + 1; i <= seqnums.length; i++) {
179:•                assert seqnums[i-1] != 1;
180:•                if (seqnums[i-1] > 0)
181:                  seqnums[i-1]--;
182:          }
183:         }
184: }
185:
186: /**
187: * Remove all the expunged messages from the array,
188: * returning a list of removed message objects.
189: *
190: * @return        the removed messages
191: */
192: public IMAPMessage[] removeExpungedMessages() {
193:         logger.fine("remove expunged messages");
194:         // list of expunged messages
195:         List<IMAPMessage> mlist = new ArrayList<>();
196:
197:         /*
198:          * Walk through the array compressing it by copying
199:          * higher numbered messages further down in the array,
200:          * effectively removing expunged messages from the array.
201:          * oldnum is the index we use to walk through the array.
202:          * newnum is the index where we copy the next valid message.
203:          * oldnum == newnum until we encounter an expunged message.
204:          */
205:         int oldnum = 1;
206:         int newnum = 1;
207:•        while (oldnum <= size) {
208:          // is message expunged?
209:•         if (seqnumOf(oldnum) <= 0) {
210:                 IMAPMessage m = getMessage(oldnum);
211:                 mlist.add(m);
212:          } else {
213:                 // keep this message
214:•                if (newnum != oldnum) {
215:                  // move message down in the array (compact array)
216:                  messages[newnum-1] = messages[oldnum-1];
217:•                 if (messages[newnum-1] != null)
218:                         messages[newnum-1].setMessageNumber(newnum);
219:                 }
220:                 newnum++;
221:          }
222:          oldnum++;
223:         }
224:         seqnums = null;
225:         shrink(newnum, oldnum);
226:
227:         IMAPMessage[] rmsgs = new IMAPMessage[mlist.size()];
228:•        if (logger.isLoggable(Level.FINE))
229:          logger.fine("return " + rmsgs.length);
230:         mlist.toArray(rmsgs);
231:         return rmsgs;
232: }
233:
234: /**
235: * Remove expunged messages in msgs from the array,
236: * returning a list of removed message objects.
237: * All messages in msgs must be IMAPMessage objects
238: * from this folder.
239: *
240: * @param        msgs        the messages
241: * @return                the removed messages
242: */
243: public IMAPMessage[] removeExpungedMessages(Message[] msgs) {
244:         logger.fine("remove expunged messages");
245:         // list of expunged messages
246:         List<IMAPMessage> mlist = new ArrayList<>();
247:
248:         /*
249:          * Copy the message numbers of the expunged messages into
250:          * a separate array and sort the array to make it easier to
251:          * process later.
252:          */
253:         int[] mnum = new int[msgs.length];
254:•        for (int i = 0; i < msgs.length; i++)
255:          mnum[i] = msgs[i].getMessageNumber();
256:         Arrays.sort(mnum);
257:
258:         /*
259:          * Walk through the array compressing it by copying
260:          * higher numbered messages further down in the array,
261:          * effectively removing expunged messages from the array.
262:          * oldnum is the index we use to walk through the array.
263:          * newnum is the index where we copy the next valid message.
264:          * oldnum == newnum until we encounter an expunged message.
265:          *
266:          * Even though we know the message number of the first possibly
267:          * expunged message, we still start scanning at message number 1
268:          * so that we can check whether there's any message whose
269:          * sequence number is different than its message number. If there
270:          * is, we can't throw away the seqnums array when we're done.
271:          */
272:         int oldnum = 1;
273:         int newnum = 1;
274:         int mnumi = 0;                // index into mnum
275:         boolean keepSeqnums = false;
276:•        while (oldnum <= size) {
277:          /*
278:          * Are there still expunged messsages in msgs to consider,
279:          * and is the message we're considering the next one in the
280:          * list, and is it expunged?
281:          */
282:•         if (mnumi < mnum.length &&
283:                  oldnum == mnum[mnumi] &&
284:•                 seqnumOf(oldnum) <= 0) {
285:                 IMAPMessage m = getMessage(oldnum);
286:                 mlist.add(m);
287:                 /*
288:                  * Just in case there are duplicate entries in the msgs array,
289:                  * we keep advancing mnumi past any duplicates, but of course
290:                  * stop when we get to the end of the array.
291:                  */
292:•                while (mnumi < mnum.length && mnum[mnumi] <= oldnum)
293:                  mnumi++;        // consider next message in array
294:          } else {
295:                 // keep this message
296:•                if (newnum != oldnum) {
297:                  // move message down in the array (compact array)
298:                  messages[newnum-1] = messages[oldnum-1];
299:•                 if (messages[newnum-1] != null)
300:                         messages[newnum-1].setMessageNumber(newnum);
301:•                 if (seqnums != null)
302:                         seqnums[newnum-1] = seqnums[oldnum-1];
303:                 }
304:•                if (seqnums != null && seqnums[newnum-1] != newnum)
305:                  keepSeqnums = true;
306:                 newnum++;
307:          }
308:          oldnum++;
309:         }
310:
311:•        if (!keepSeqnums)
312:          seqnums = null;
313:         shrink(newnum, oldnum);
314:
315:         IMAPMessage[] rmsgs = new IMAPMessage[mlist.size()];
316:•        if (logger.isLoggable(Level.FINE))
317:          logger.fine("return " + rmsgs.length);
318:         mlist.toArray(rmsgs);
319:         return rmsgs;
320: }
321:
322: /**
323: * Shrink the messages and seqnums arrays. newend is one past last
324: * valid element. oldend is one past the previous last valid element.
325: */
326: private void shrink(int newend, int oldend) {
327:         size = newend - 1;
328:•        if (logger.isLoggable(Level.FINE))
329:          logger.fine("size now " + size);
330:•        if (size == 0) {        // no messages left
331:          messages = null;
332:          seqnums = null;
333:•        } else if (size > SLOP && size < messages.length / 2) {
334:          // if array shrinks by too much, reallocate it
335:          logger.fine("reallocate array");
336:          IMAPMessage[] newm = new IMAPMessage[size + SLOP];
337:          System.arraycopy(messages, 0, newm, 0, size);
338:          messages = newm;
339:•         if (seqnums != null) {
340:                 int[] news = new int[size + SLOP];
341:                 System.arraycopy(seqnums, 0, news, 0, size);
342:                 seqnums = news;
343:          }
344:         } else {
345:•         if (logger.isLoggable(Level.FINE))
346:                 logger.fine("clean " + newend + " to " + oldend);
347:          // clear out unused entries in array
348:•         for (int msgnum = newend; msgnum < oldend; msgnum++) {
349:                 messages[msgnum-1] = null;
350:•                if (seqnums != null)
351:                  seqnums[msgnum-1] = 0;
352:          }
353:         }
354: }
355:
356: /**
357: * Add count messages to the cache.
358: * newSeqNum is the sequence number of the first message added.
359: *
360: * @param        count        the number of messges
361: * @param        newSeqNum        sequence number of first message
362: */
363: public void addMessages(int count, int newSeqNum) {
364:•        if (logger.isLoggable(Level.FINE))
365:          logger.fine("add " + count + " messages");
366:         // don't have to do anything other than making sure there's space
367:         ensureCapacity(size + count, newSeqNum);
368: }
369:
370: /*
371: * Make sure the arrays are at least big enough to hold
372: * "newsize" messages.
373: */
374: private void ensureCapacity(int newsize, int newSeqNum) {
375:•        if (messages == null)
376:          messages = new IMAPMessage[newsize + SLOP];
377:•        else if (messages.length < newsize) {
378:•         if (logger.isLoggable(Level.FINE))
379:                 logger.fine("expand capacity to " + newsize);
380:          IMAPMessage[] newm = new IMAPMessage[newsize + SLOP];
381:          System.arraycopy(messages, 0, newm, 0, messages.length);
382:          messages = newm;
383:•         if (seqnums != null) {
384:                 int[] news = new int[newsize + SLOP];
385:                 System.arraycopy(seqnums, 0, news, 0, seqnums.length);
386:•                for (int i = size; i < news.length; i++)
387:                  news[i] = newSeqNum++;
388:                 seqnums = news;
389:•                if (logger.isLoggable(Level.FINE))
390:                  logger.fine("message " + newsize +
391:                         " has sequence number " + seqnums[newsize-1]);
392:          }
393:•        } else if (newsize < size) {                // shrinking?
394:          // this should never happen
395:•         if (logger.isLoggable(Level.FINE))
396:                 logger.fine("shrink capacity to " + newsize);
397:•         for (int msgnum = newsize + 1; msgnum <= size; msgnum++) {
398:                 messages[msgnum-1] = null;
399:•                if (seqnums != null)
400:                  seqnums[msgnum-1] = -1;
401:          }
402:         }
403:         size = newsize;
404: }
405:
406: /**
407: * Return the sequence number for the given message number.
408: *
409: * @param        msgnum        the message number
410: * @return                the sequence number
411: */
412: public int seqnumOf(int msgnum) {
413:•        if (seqnums == null)
414:          return msgnum;
415:         else {
416:•         if (logger.isLoggable(Level.FINE))
417:                 logger.fine("msgnum " + msgnum + " is seqnum " +
418:                          seqnums[msgnum-1]);
419:          return seqnums[msgnum-1];
420:         }
421: }
422:
423: /**
424: * Return the message number for the given sequence number.
425: */
426: private int msgnumOf(int seqnum) {
427:•        if (seqnums == null)
428:          return seqnum;
429:•        if (seqnum < 1) {                // should never happen
430:•         if (logger.isLoggable(Level.FINE))
431:                 logger.fine("bad seqnum " + seqnum);
432:          return -1;
433:         }
434:•        for (int msgnum = seqnum; msgnum <= size; msgnum++) {
435:•         if (seqnums[msgnum-1] == seqnum)
436:                 return msgnum;
437:•         if (seqnums[msgnum-1] > seqnum)
438:                 break;                // message doesn't exist
439:         }
440:         return -1;
441: }
442: }