001 /***********************************************************************************************************************
002 *
003 * JavaMail Mock2 Provider - open source mock classes for mock up JavaMail
004 * =======================================================================
005 *
006 * Copyright (C) 2014 by Hendrik Saly (http://saly.de)
007 *
008 * Based on ideas from Kohsuke Kawaguchi's Mock-javamail (https://java.net/projects/mock-javamail)
009 *
010 ***********************************************************************************************************************
011 *
012 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
013 * the License. You may obtain a copy of the License at
014 *
015 * http://www.apache.org/licenses/LICENSE-2.0
016 *
017 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
018 * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
019 * specific language governing permissions and limitations under the License.
020 *
021 ***********************************************************************************************************************
022 *
023 * $Id:$
024 *
025 **********************************************************************************************************************/
026 package de.saly.javamail.mock2;
027
028 import java.util.ArrayList;
029 import java.util.Arrays;
030 import java.util.Collections;
031 import java.util.HashMap;
032 import java.util.List;
033 import java.util.Map;
034 import java.util.TreeSet;
035
036 import javax.mail.Flags;
037 import javax.mail.Flags.Flag;
038 import javax.mail.Message;
039 import javax.mail.MessagingException;
040 import javax.mail.UIDFolder;
041 import javax.mail.internet.MimeMessage;
042
043 public class MailboxFolder implements MockMessage.FlagChangeListener {
044
045 public static final char SEPARATOR = '/';
046
047 private final List<MailboxFolder> children = new ArrayList<MailboxFolder>();
048 private boolean exists = true;
049 private final MockMailbox mailbox;
050 private volatile List<MailboxEventListener> mailboxEventListeners = Collections.synchronizedList(new ArrayList<MailboxEventListener>());
051
052 private final Map<Long, MockMessage> messages = new HashMap<Long, MockMessage>();
053
054 private String name;
055
056 private MailboxFolder parent;
057 private boolean simulateError = false;
058 private long uidValidity = 50;
059 private long uniqueMessageId = 10;
060 protected final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(this.getClass());
061
062 protected MailboxFolder(final String name, final MockMailbox mb, final boolean exists) {
063 super();
064
065 if (name == null) {
066 this.name = "";
067 } else {
068 this.name = name;
069 }
070
071 this.mailbox = mb;
072 this.exists = exists;
073
074 logger.debug("Created " + name + " (exists: " + exists + ")");
075 }
076
077 public synchronized Message add(final MimeMessage e) throws MessagingException {
078 checkExists();
079
080 uniqueMessageId++;
081
082 final MockMessage mockMessage = new MockMessage(e, uniqueMessageId, this, this);
083
084 mockMessage.setSpecialHeader("Message-ID", String.valueOf(uniqueMessageId));
085 mockMessage.setSpecialHeader("X-Mock-Folder", getFullName());
086 mockMessage.setFlags(new Flags(Flag.RECENT), true);
087 // unread.add(e);
088
089 messages.put(uniqueMessageId, mockMessage);
090
091 for (final MailboxEventListener mailboxEventListener : mailboxEventListeners) {
092 mailboxEventListener.messageAdded(this, mockMessage);
093 }
094
095 logger.debug("Message ID " + uniqueMessageId + " to " + getFullName() + " added for user " + mailbox.getAddress());
096
097 return mockMessage;
098 }
099
100 public synchronized void addMailboxEventListener(final MailboxEventListener l) {
101 if (l != null) {
102 mailboxEventListeners.add(l);
103 }
104 }
105
106 public synchronized MailboxFolder create() {
107 if (isExists()) {
108 throw new IllegalStateException("already exists");
109 }
110 checkFolderName(this.name);
111
112 exists = true;
113
114 // TODO set parent and/or children to exists?
115
116 if (parent != null && !parent.isExists()) {
117 parent.create();
118 }
119
120 /*children.clear();
121
122 if (parent != null) {
123 parent.children.add(this);
124 }
125
126 if (mailboxEventListener != null) {
127 mailboxEventListener.folderCreated(this);
128 }*/
129
130 for (final MailboxEventListener mailboxEventListener : mailboxEventListeners) {
131 mailboxEventListener.folderCreated(this);
132 }
133
134 logger.debug("Folder " + this.getFullName() + " created");
135 return this;
136
137 }
138
139 public synchronized void deleteFolder(final boolean recurse) {
140 checkExists();
141 checkFolderName(this.name);
142
143 if (isRoot()) {
144 throw new IllegalArgumentException("root cannot be deleted");
145 }
146
147 messages.clear();
148 // unread.clear();
149
150 if (recurse) {
151 for (final MailboxFolder mf : getChildren()) {
152 mf.deleteFolder(recurse);
153 }
154 }
155
156 parent.children.remove(this);
157 this.exists = false;
158
159 for (final MailboxEventListener mailboxEventListener : mailboxEventListeners) {
160 mailboxEventListener.folderDeleted(this);
161 }
162 logger.debug("Folder " + this.getFullName() + " deleted");
163
164 }
165
166 @Override
167 public boolean equals(final Object obj) {
168 if (this == obj) {
169 return true;
170 }
171 if (obj == null) {
172 return false;
173 }
174 if (getClass() != obj.getClass()) {
175 return false;
176 }
177 final MailboxFolder other = (MailboxFolder) obj;
178 if (name == null) {
179 if (other.name != null) {
180 return false;
181 }
182 } else if (!name.equals(other.name)) {
183 return false;
184 }
185 if (parent == null) {
186 if (other.parent != null) {
187 return false;
188 }
189 } else if (!parent.equals(other.parent)) {
190 return false;
191 }
192 return true;
193 }
194
195 public synchronized Message[] expunge() throws MessagingException {
196 checkExists();
197 final List<Message> expunged = new ArrayList<Message>();
198 for (final Message msg : getByFlags(new Flags(Flag.DELETED), true)) {
199
200 expunged.add(messages.remove(((MockMessage) msg).getMockid()));
201 ((MockMessage) msg).setExpunged(true);
202
203 for (final MailboxEventListener mailboxEventListener : mailboxEventListeners) {
204 mailboxEventListener.messageExpunged(this, (MockMessage) msg, true);
205 }
206 }
207
208 logger.debug(expunged.size() + " messages expunged (deleted) from" + getFullName());
209 return expunged.toArray(new Message[expunged.size()]);
210
211 }
212
213 public synchronized Message[] expunge(final Message[] msgs) throws MessagingException {
214 checkExists();
215 final List<Long> toExpunge = new ArrayList<Long>();
216
217 for (final Message msg : msgs) {
218 toExpunge.add(((MockMessage) msg).getMockid());
219 }
220
221 final List<Message> expunged = new ArrayList<Message>();
222 for (final Message msg : getByFlags(new Flags(Flag.DELETED), true)) {
223
224 if (!toExpunge.contains(new Long(((MockMessage) msg).getMockid()))) {
225 continue;
226 }
227
228 expunged.add(messages.remove(((MockMessage) msg).getMockid()));
229 ((MockMessage) msg).setExpunged(true);
230
231 for (final MailboxEventListener mailboxEventListener : mailboxEventListeners) {
232 mailboxEventListener.messageExpunged(this, (MockMessage) msg, true);
233 }
234 }
235
236 logger.debug(expunged.size() + " messages expunged (deleted) from " + getFullName());
237 return expunged.toArray(new Message[expunged.size()]);
238
239 }
240
241 public synchronized Message[] getByFlags(final Flags flags, final boolean mustSet /*final Folder folder*/) throws MessagingException {
242 checkExists();
243 final List<MockMessage> sms = new ArrayList<MockMessage>();
244 int num = 0;
245
246 for (final MockMessage mockMessage : new TreeSet<MockMessage>(messages.values())) {
247
248 if (mustSet && mockMessage.getFlags().contains(flags) || !mustSet && !mockMessage.getFlags().contains(flags)) {
249 mockMessage.setMessageNumber(++num);
250 // mockMessage.setFolder(folder);
251 sms.add(mockMessage);
252 }
253
254 }
255 logger.debug("getByFlags() for " + getFullName() + " returns " + sms.size());
256 return sms.toArray(new Message[sms.size()]);
257 }
258
259 public synchronized Message getById(final long id /*final Folder folder*/) {
260 checkExists();
261 final Message m = messages.get(id);
262
263 if (m == null) {
264
265 logger.debug("No message with id " + id + ", return null");
266 return null;
267
268 }
269
270 // ((MockMessage) m).setFolder(folder);
271 logger.debug("getById(" + id + ") for " + getFullName() + " returns successful");
272
273 return m;
274 }
275
276 public synchronized Message[] getByIds(final long start, final long end/* final Folder folder*/) {
277 checkExists();
278 final List<MockMessage> sms = new ArrayList<MockMessage>();
279 int num = 0;
280
281 MockMessage lastMsg = null;
282
283 for (final MockMessage mockMessage : new TreeSet<MockMessage>(messages.values())) {
284
285 lastMsg = mockMessage;
286
287 if (end == UIDFolder.LASTUID) {
288 if (getMessageCount() != 1 && mockMessage.getMockid() < start) { // TODO
289 // check?
290 continue;
291 }
292 } else {
293 if (mockMessage.getMockid() < start || mockMessage.getMockid() > end) {
294 continue;
295 }
296 }
297
298 mockMessage.setMessageNumber(++num);
299 // mockMessage.setFolder(folder);
300 sms.add(mockMessage);
301 }
302
303 if (end == UIDFolder.LASTUID && sms.size() == 0) {
304 lastMsg.setMessageNumber(++num);
305 // lastMsg.setFolder(folder);
306 sms.add(lastMsg);
307 }
308
309 logger.debug("getByIds(" + start + "," + end + " for " + getFullName() + " returns " + sms.size());
310 return sms.toArray(new Message[sms.size()]);
311 }
312
313 public synchronized Message[] getByIds(final long[] id /*final Folder folder*/) {
314 checkExists();
315 final List<Long> idlist = new ArrayList<Long>();
316 for (final long value : id) {
317 idlist.add(value);
318 }
319 final List<MockMessage> sms = new ArrayList<MockMessage>();
320 int num = 0;
321
322 for (final MockMessage mockMessage : new TreeSet<MockMessage>(messages.values())) {
323
324 if (!idlist.contains(mockMessage.getMockid())) {
325 continue;
326 }
327
328 mockMessage.setMessageNumber(++num);
329 // mockMessage.setFolder(folder);
330 sms.add(mockMessage);
331 }
332
333 logger.debug("getByIds(" + Arrays.toString(id) + ") for " + getFullName() + " returns " + sms.size());
334 return sms.toArray(new Message[sms.size()]);
335 }
336
337 // private List<Message> unread = new ArrayList<Message>();
338
339 public synchronized Message getByMsgNum(final int msgnum/*, final Folder folder*/) {
340 checkExists();
341 final List<MockMessage> sms = new ArrayList<MockMessage>();
342
343 int num = 0;
344
345 for (final MockMessage mockMessage : new TreeSet<MockMessage>(messages.values())) {
346
347 mockMessage.setMessageNumber(++num);
348 // mockMessage.setFolder(folder);
349 sms.add(mockMessage);
350 }
351
352 logger.debug("getByMsgNum(" + msgnum + "), size is " + sms.size());
353
354 if (msgnum - 1 < 0 || msgnum > sms.size()) {
355 throw new ArrayIndexOutOfBoundsException("message number (" + msgnum + ") out of bounds (" + sms.size() + ") for "
356 + getFullName());
357 }
358
359 final Message m = sms.get(msgnum - 1);
360 return m;
361 }
362
363 /**
364 *
365 * @return Unmodifieable new list copy
366 */
367 public synchronized List<MailboxFolder> getChildren() {
368 checkExists();
369 return Collections.unmodifiableList(new ArrayList<MailboxFolder>(children));
370 }
371
372 public synchronized String getFullName() {
373 // checkExists();
374 if (isRoot()) {
375 return "";
376 }
377
378 return parent.isRoot() ? name : parent.getFullName() + SEPARATOR + name;
379
380 }
381
382 /**
383 * @return the mailbox
384 */
385 public MockMailbox getMailbox() {
386 return mailbox;
387 }
388
389 public synchronized int getMessageCount() {
390 checkExists();
391 logger.debug("getMessageCount() for " + getFullName() + " returns " + messages.size());
392 return messages.size();
393 }
394
395 public synchronized Message[] getMessages(/*final Folder folder*/) {
396 checkExists();
397 final List<MockMessage> sms = new ArrayList<MockMessage>();
398 int num = 0;
399
400 for (final MockMessage mockMessage : new TreeSet<MockMessage>(messages.values())) {
401
402 mockMessage.setMessageNumber(++num);
403 // mockMessage.setFolder(folder);
404 sms.add(mockMessage);
405 }
406 logger.debug("getMessages() for " + getFullName() + " returns " + sms.size());
407 return sms.toArray(new Message[sms.size()]);
408 }
409
410 public String getName() {
411 return name;
412 }
413
414 public MailboxFolder getOrAddSubFolder(final String name) throws MessagingException {
415 // checkExists();
416
417 if (name == null || "".equals(name.trim())) {
418 throw new MessagingException("cannot get or add root folder");
419 }
420
421 logger.debug("getOrAddSubFolder(" + name + ") on " + getFullName());
422
423 final String[] path = name.split(String.valueOf(SEPARATOR));
424
425 MailboxFolder last = this;
426 for (int i = 0; i < path.length; i++) {
427
428 final String element = path[i];
429
430 if ("inbox".equalsIgnoreCase(element)) {
431 last = mailbox.getInbox();
432 } else {
433 checkFolderName(element);
434 final MailboxFolder mbt = new MailboxFolder(element, mailbox, false);
435 mbt.parent = last;
436
437 int index = -1;
438 if ((index = last.children.indexOf(mbt)) != -1) {
439
440 final MailboxFolder tmp = last.children.get(index);
441 if (tmp.isExists()) {
442 last = tmp;
443 continue;
444 }
445 }
446
447 last.children.add(mbt);
448
449 logger.debug("Subfolder " + mbt.getFullName() + " added");
450 last = mbt;
451 }
452
453 }
454
455 return last;
456
457 }
458
459 public synchronized MailboxFolder getParent() {
460 checkExists();
461 return parent;
462 }
463
464 public synchronized int getSizeInBytes() throws MessagingException {
465 checkExists();
466 int size = 0;
467
468 for (final MockMessage mockMessage : new TreeSet<MockMessage>(messages.values())) {
469
470 if (mockMessage.getSize() > 0) {
471 size += mockMessage.getSize();
472 }
473
474 }
475
476 return size;
477 }
478
479 public synchronized long getUID(final Message msg) {
480 checkExists();
481 return ((MockMessage) msg).getMockid();
482 }
483
484 /**
485 * @return the uidValidity
486 */
487 public synchronized long getUidValidity() {
488 checkExists();
489 return uidValidity;
490 }
491
492 @Override
493 public int hashCode() {
494 final int prime = 31;
495 int result = 1;
496 result = prime * result + (name == null ? 0 : name.hashCode());
497 result = prime * result + (parent == null ? 0 : parent.hashCode());
498 return result;
499 }
500
501 public synchronized boolean hasMessages() {
502 checkExists();
503 return messages.isEmpty();
504 }
505
506 public synchronized void invalidateUid() {
507 checkExists();
508 uidValidity += 10;
509
510 for (final MailboxEventListener mailboxEventListener : mailboxEventListeners) {
511 mailboxEventListener.uidInvalidated();
512 }
513 logger.debug("UidValidity invalidated, new UidValidity is " + uidValidity);
514 }
515
516 /**
517 * @return the exists
518 */
519 public boolean isExists() {
520 return exists;
521 }
522
523 public boolean isInbox() {
524 return name != null && name.equalsIgnoreCase("inbox");
525 }
526
527 public boolean isRoot() {
528 return name == null || name.equals("") || parent == null;
529 }
530
531 /**
532 * @return the simulateError
533 */
534 public boolean isSimulateError() {
535 return simulateError;
536 }
537
538 public synchronized void markMessageAsDeleted(final Message e) throws MessagingException {
539 checkExists();
540 ((MockMessage) e).setFlag(Flag.DELETED, true);
541 // if(mailboxEventListener!=null)
542 // mailboxEventListener.messageRemoved(this, ((MockMessage)e), false);
543 logger.debug("Mark message " + ((MockMessage) e).getMockid() + " as deleted (Flag DELETED set)");
544 }
545
546 public synchronized void markMessageAsSeen(final Message e) throws MessagingException {
547 checkExists();
548 ((MockMessage) e).setFlag(Flag.SEEN, true);
549 // if(mailboxEventListener!=null)
550 // mailboxEventListener.messageRemoved(this, ((MockMessage)e), false);
551 logger.debug("Mark message " + ((MockMessage) e).getMockid() + " as seen (Flag SEEN set)");
552 }
553
554 @Override
555 public void onFlagChange(final MockMessage msg, final Flags flags, final boolean set) {
556
557 for (final MailboxEventListener mailboxEventListener : mailboxEventListeners) {
558 mailboxEventListener.messageChanged(this, msg, false, true);
559 }
560
561 logger.debug("Flags of message " + msg.getMockid() + " change");
562
563 if (messages.size() > 0 && messages.get(msg.getMockid()) != null) {
564 try {
565 if (set && messages.get(msg.getMockid()).getFlags().contains(flags)) {
566 return;
567
568 }
569
570 if (set && !messages.get(msg.getMockid()).getFlags().contains(flags)) {
571 messages.get(msg.getMockid()).setFlags(flags, set);
572
573 }
574
575 if (!set && messages.get(msg.getMockid()).getFlags().contains(flags)) {
576 messages.get(msg.getMockid()).setFlags(flags, set);
577
578 }
579
580 if (!set && !messages.get(msg.getMockid()).getFlags().contains(flags)) {
581 return;
582
583 }
584 } catch (final Exception e) {
585 logger.error("Error while changing flags " + e.toString(), e);
586 }
587 }
588
589 }
590
591 public synchronized void removeMailboxEventListener(final MailboxEventListener l) {
592 if (l != null) {
593 mailboxEventListeners.remove(l);
594 }
595 }
596
597 public synchronized void renameFolder(final String newName) {
598 checkExists();
599 checkFolderName(this.name);
600 checkFolderName(newName);
601 final String tmpOldName = name;
602
603 name = newName;
604
605 for (final MailboxEventListener mailboxEventListener : mailboxEventListeners) {
606 mailboxEventListener.folderRenamed(tmpOldName, this);
607 }
608
609 // TODO purge old folders, exists =false
610
611 // TODO notify children?
612 /*for (MailboxFolder mf: children) {
613 renameFolder(mf.name); //do not really change name of children, just notify because parent changes
614 }*/
615
616 logger.debug("Folder " + tmpOldName + " renamed to " + newName + newName + " - New Fullname is " + this.getFullName());
617
618 }
619
620 /**
621 * @param simulateError
622 * the simulateError to set
623 */
624 public void setSimulateError(final boolean simulateError) {
625 this.simulateError = simulateError;
626 }
627
628 protected MailboxFolder addSpecialSubFolder(final String name) {
629 final MailboxFolder mbt = new MailboxFolder(name, mailbox, true);
630 mbt.parent = this;
631 children.add(mbt);
632 return mbt;
633 }
634
635 protected void checkExists() {
636 if (!exists) {
637 throw new IllegalStateException("folder does not exist");
638 }
639 }
640
641 protected void checkFolderName(final String name) {
642 checkFolderName(name, true);
643 }
644
645 protected void checkFolderName(final String name, final boolean checkSeparator) {
646 // TODO regex for valid folder names?
647
648 if (name == null || name.trim().equals("") || name.equalsIgnoreCase("inbox") || checkSeparator
649 && name.contains(String.valueOf(SEPARATOR))) {
650 throw new IllegalArgumentException("name '" + name + "' is not valid");
651 }
652 }
653
654 /**
655 * @return the uniqueMessageId
656 */
657 protected long getUniqueMessageId() {
658 return uniqueMessageId;
659 }
660
661 public static interface MailboxEventListener {
662
663 void folderCreated(MailboxFolder mf);
664
665 void folderDeleted(MailboxFolder mf);
666
667 void folderRenamed(String from, MailboxFolder to);
668
669 void messageAdded(MailboxFolder mf, MockMessage msg);
670
671 void messageChanged(MailboxFolder mf, MockMessage msg, boolean headerChanged, boolean flagsChanged); // TODO
672 // header
673 // change
674 // can
675 // not
676 // happen
677 // because
678 // MockMessage
679 // is
680 // readonly?
681
682 void messageExpunged(MailboxFolder mf, MockMessage msg, boolean removed);
683
684 void uidInvalidated();
685
686 }
687 }