View Javadoc

1   /*
2    * Copyright (c) 2005-2007 Creative Sphere Limited.
3    * All rights reserved. This program and the accompanying materials
4    * are made available under the terms of the Eclipse Public License v1.0
5    * which accompanies this distribution, and is available at
6    * http://www.eclipse.org/legal/epl-v10.html
7    *
8    * Contributors:
9    *
10   *   Creative Sphere - initial API and implementation
11   *
12   */
13  package org.abstracthorizon.mercury.maildir;
14  
15  import java.io.File;
16  import java.io.IOException;
17  import java.lang.ref.Reference;
18  import java.lang.ref.WeakReference;
19  import java.util.ArrayList;
20  import java.util.Collection;
21  import java.util.Collections;
22  import java.util.HashMap;
23  import java.util.Iterator;
24  import java.util.List;
25  import java.util.WeakHashMap;
26  import java.util.regex.Pattern;
27  
28  import javax.mail.Flags;
29  import javax.mail.Folder;
30  import javax.mail.Message;
31  import javax.mail.MessagingException;
32  import javax.mail.internet.MimeMessage;
33  
34  import org.abstracthorizon.mercury.maildir.util.MessageBase;
35  import org.abstracthorizon.mercury.maildir.util.MessageWrapper;
36  
37  
38  /**
39   * <p>Folder data actually represents the model of the maildir folder. This class
40   * handles all model operations - all operations that are low level and deal
41   * with files and directories.
42   * </p>
43   *
44   * <p>This folder data serves as central repository for messages over
45   * a directory. Each directory will have only one folder data for store.
46   * That is maintained in store itself</p>
47   *
48   * <p>Messages that are present in folder data are later wrapped inside of
49   * folder. Pair of (message, file) is kept in folder data's cache.
50   * Cache for each directory is maintained as long as there are opened folders
51   * over that directory. After that time it is left to be collected throught
52   * weak reference. If folder over folder data is opened before reference
53   * is garbage collected it is reused.
54   * </p>
55   *
56   * @author Daniel Sendula
57   */
58  public class MaildirFolderData extends Folder {
59  
60      /**
61       * This constant defines a filename of a zero length
62       * flag file that denotes no subfolders are suppose
63       * to be created for this folder */
64      public static final String NO_SUBFOLDERS_FILENAME = ".nosubfolders";
65  
66      /** Permanent flags for root are user defined &quot;\\Noselect&quot; */
67      public static final Flags rootPermanentFlags = new Flags("\\Noselect");
68  
69      /** Permanent flags cache */
70      protected Flags permanentFlags;
71  
72      /** Maildir store */
73      protected MaildirStore store;
74  
75      /** Directory folder data is for */
76      protected File base;
77  
78      /** Flag to denote is this root folder or not */
79      protected boolean rootFolder;
80  
81      /** Type of the folder. See {@link javax.mail.Folder#HOLDS_FOLDERS} and {@link javax.mail.Folder#HOLDS_MESSAGES}. */
82      protected int type = -1;
83  
84      /** Tmp subdirectory */
85      protected File tmp;
86  
87      /** Cur subdirectory */
88      protected File cur;
89  
90      /** New subdirectory */
91      protected File nw;
92  
93      /** Cached folder's full name */
94      protected String cachedFullName;
95  
96      /** Cached folder's name */
97      protected String cachedName;
98  
99      /** Last time folder data was accessed or 0 if no open folders */
100     protected long lastAccess;
101 
102     /** Amount of time between two accesses. TODO - make this as an attribute */
103     protected int delay = 1000; // 1 sec
104 
105     /** Delay factor - amount of time needed for reading directory vs delay. TODO - make this as an attribute */
106     protected int delayFactor = 3;
107 
108     /** List of open folders */
109     protected WeakHashMap<Folder, Object> openedFolders = new WeakHashMap<Folder, Object>();
110 
111     /** Folder's data */
112     protected Data data;
113 
114     /** Weak reference to data when there are no open folders */
115     protected Reference<Data> closedRef;
116 
117     /** Count of open folders. When count reaches zero, storage may remove this folder data */
118     protected int openCount = 0;
119 
120     /**
121      * Constructor
122      * @param store store
123      * @param file directory
124      */
125     public MaildirFolderData(MaildirStore store, File file) {
126         super(store);
127         this.store = store;
128         setFolderFile(file);
129         rootFolder = base.equals(store.getBaseFile());
130         tmp = new File(base, "tmp");
131         cur = new File(base, "cur");
132         nw = new File(base, "new");
133     }
134 
135     /**
136      * Returns maildir store
137      * @return maildir store
138      */
139     public MaildirStore getMaildirStore() {
140         return store;
141     }
142 
143     /**
144      * Returns folder's directory
145      * @return folder's directory
146      */
147     public File getFolderFile() {
148         return base;
149     }
150 
151     /**
152      * Sets folder's directory
153      * @param file folder's directory
154      */
155     protected void setFolderFile(File file) {
156         this.base = file;
157     }
158 
159     /**
160      * Returns when this folder data is last accessd
161      * @return when this folder data is last accessd
162      */
163     public long getLastAccessed() {
164         return lastAccess;
165     }
166 
167     /**
168      * Returns <code>true</code> if it is root folder
169      * @return <code>true</code> if it is root folder
170      */
171     protected boolean isRootFolder() {
172         return rootFolder;
173     }
174 
175     /**
176      * Returns folder's new directory
177      * @return folder's new directory
178      */
179     protected File getNewDir() {
180         return nw;
181     }
182 
183     /**
184      * Returns folder's cur directory
185      * @return folder's cur directory
186      */
187     protected File getCurDir() {
188         return cur;
189     }
190 
191     /**
192      * Returns folder's tmp directory
193      * @return folder's tmp directory
194      */
195     protected File getTmpDir() {
196         return tmp;
197     }
198 
199     /**
200      * Returns folder's name
201      * @return folder's name
202      */
203     public String getName() {
204         if (cachedName != null) {
205             return cachedName;
206         }
207         if (rootFolder) {
208             cachedName = "";
209         } else {
210             String name = getFolderFile().getName();
211             int i = name.lastIndexOf('.');
212             if (i > 0) {
213                 name = name.substring(i+1);
214             }
215             if (store.isLeadingDot() && name.startsWith(".")) {
216                 name = name.substring(1);
217             }
218             cachedName = name;
219         }
220         return cachedName;
221     }
222 
223     /**
224      * Returns folder's full name (path and name)
225      * @return folder's full name
226      */
227     public String getFullName() {
228         if (cachedFullName != null) {
229             return cachedFullName;
230         }
231         if (rootFolder) {
232             cachedFullName = "";
233         } else {
234             String name = getFolderFile().getName();
235             if (store.isLeadingDot() && name.startsWith(".")) {
236                 name = name.substring(1);
237             }
238             cachedFullName = name.replace('.', '/');
239         }
240         return cachedFullName;
241     }
242 
243     /**
244      * Returns folder parent's name
245      * @return folder parent's name
246      */
247     public String getParentFolderName() {
248         if (rootFolder) {
249             return null;
250         } else {
251             String name = getFullName();
252 
253             int i = name.lastIndexOf('/');
254             if (i > 0) {
255                 name = name.substring(0, i);
256             } else {
257                 name = "";
258             }
259 
260             return name;
261         }
262     }
263 
264     /**
265      * Returns <code>true</code> if folder exists. This method checks if folder's directory exist.
266      * @return <code>true</code> if folder exists
267      * @throws MessagingException
268      */
269     public boolean exists() throws MessagingException {
270         return getFolderFile().exists();
271     }
272 
273     /**
274      * Lists subfolder names.
275      * @param pattern pattern
276      * @return list of subfolder names
277      * @throws MessagingException
278      */
279     public String[] listNames(String pattern) throws MessagingException {
280         if ((pattern == null) || (pattern.length() == 0)) {
281             pattern = ".*";
282         } else {
283             pattern = pattern.replaceAll("[*]", ".*?");
284             pattern = pattern.replaceAll("[%]", "[^/]*?");
285         }
286 
287         String n = getFullName();
288 
289         if (n.length() > 0) {
290             pattern = n+'/'+pattern;
291         }
292         Pattern p = Pattern.compile(pattern);
293 
294         boolean leadingDot = store.isLeadingDot();
295         ArrayList<String> res = new ArrayList<String>();
296 
297         File[] files = store.getBaseFile().listFiles();
298         if (files != null) {
299             for (int i = 0; i < files.length; i++) {
300                 if (files[i].isDirectory()) {
301                     String name = files[i].getName();
302                     if (!"tmp".equals(name) && !"new".equals(name) && !"cur".equals(name) &&
303                             ((leadingDot && name.startsWith(".")) || (!leadingDot && !name.startsWith(".")))) {
304 
305                         if (leadingDot) {
306                             name = name.substring(1);
307                         }
308                         name = name.replace('.', '/');
309                         if (p.matcher(name).matches()) {
310                             res.add(name);
311                         }
312                     }
313                 }
314             }
315         }
316 
317         String[] array = new String[res.size()];
318         return res.toArray(array);
319     }
320 
321     /**
322      * Returns &quot;/&quot;
323      * @return &quot;/&quot;
324      */
325     public char getSeparator() {
326         return '/';
327     }
328 
329     /**
330      * Returns name for given subfolder.
331      * @param name name of subfolder
332      * @return subfolder's full name
333      */
334     public String getSubFolderName(String name) {
335         if (rootFolder) {
336             return name;
337         } else {
338             return getFullName()+getSeparator()+name;
339         }
340     }
341 
342     /**
343      * Creates folder.
344      * @param type See {@link javax.mail.Folder#HOLDS_FOLDERS} and {@link javax.mail.Folder#HOLDS_MESSAGES}.
345      * @return <code>true</code> if subfolder is successfully created.
346      * @throws MessagingException in case of a problem while creating folder.
347      */
348     public boolean create(int type) throws MessagingException {
349         if (rootFolder) {
350             return false;
351         }
352         MaildirFolderData parent = store.getFolderData(getParentFolderName());
353         if (!parent.isRootFolder() && !parent.exists()) {
354             if (!parent.create(Folder.HOLDS_FOLDERS | Folder.HOLDS_MESSAGES) || !parent.create(Folder.HOLDS_FOLDERS)) {
355                 return false;
356             }
357         }
358 
359         if (!getFolderFile().mkdir()) {
360             return false;
361         }
362         checkDirs();
363         if ((type & Folder.HOLDS_FOLDERS) == 0) {
364             File f = new File(getFolderFile(), NO_SUBFOLDERS_FILENAME);
365             try {
366                 f.createNewFile();
367             } catch (IOException e) {
368                 throw new MessagingException("Cannot prevent folder of having subfolders; cannot create file=" + f, e);
369             }
370         }
371         this.type = type;
372         return true;
373     }
374 
375     /**
376      * Deletes folder.
377      * @param recursive if <code>true</code> deletes all subfolders as well.
378      * @return <code>true</code> if operation was successful.
379      * @throws MessagingException in case of an error while deleting folder
380      */
381     public boolean delete(boolean recursive) throws MessagingException {
382 
383         if (rootFolder) {
384             return false;
385         }
386         if (recursive) {
387             String[] subfolders = listNames("%");
388             boolean res = true;
389             for (int i = 0; i < subfolders.length; i++) {
390                 MaildirFolderData subFolderData = store.getFolderData(subfolders[i]);
391                 res = subFolderData.delete(true) && res;
392             }
393             if (!res) {
394                 return false;
395             }
396         }
397 
398         return deleteAll(getFolderFile());
399     }
400 
401     /**
402      * Utility method that deletes all subdirectories and files from given directory.
403      * @param file directory to be deleted
404      * @return <code>true<code> if operation was successful.
405      */
406     protected boolean deleteAll(File file) {
407         boolean res = true;
408 
409         if (file.isDirectory()) {
410             File[] files = file.listFiles();
411             for (int i = 0; i < files.length; i++) {
412                 if (files[i].isDirectory()) {
413                     res = deleteAll(files[i]) && res;
414                 } else {
415                     res = files[i].delete() && res;
416                 }
417             }
418         }
419         if (res) {
420             res = file.delete() && res;
421         }
422         return res;
423     }
424 
425     /**
426      * Returns folder's type. It checks for flag file to determine are subfolders are allowed.
427      * In this implementation messages are always allowed.
428      * @return folder's type. See {@link javax.mail.Folder#HOLDS_FOLDERS} and {@link javax.mail.Folder#HOLDS_MESSAGES}.
429      * @throws MessagingException never.
430      */
431     public int getType() throws MessagingException {
432         if (type == -1) {
433             if (rootFolder) {
434                 type = Folder.HOLDS_FOLDERS;
435             } else {
436                 File f = new File(getFolderFile(), NO_SUBFOLDERS_FILENAME);
437                 if (f.exists()) {
438                     type = Folder.HOLDS_MESSAGES;
439                 } else {
440                     type = Folder.HOLDS_FOLDERS | Folder.HOLDS_MESSAGES;
441                 }
442             }
443         }
444         return type;
445     }
446 
447     /**
448      * Renames folder to given folder data
449      * @param folderData folder data
450      * @return <code>true</code> if folder was successfully renamed
451      * @throws MessagingException thrown in case when there is a problem renaming folder
452      */
453     public MaildirFolderData renameTo(MaildirFolderData folderData) throws MessagingException {
454         if (folderData.store != store) {
455             throw new IllegalStateException("Folder belongs to wrong store; "+getFullName());
456         }
457         if (folderData.exists()) {
458             return null;
459         }
460         // setFolderFile(folderData.getFolderFile());
461         // rootFolder = folderData.rootFolder;
462 
463         return folderData;
464     }
465 
466     /**
467      * This method checks if new, cur and tmp directories exist. If not it tries to create them.
468      * @throws MessagingException in case subdirectories cannot be created.
469      */
470     protected void checkDirs() throws MessagingException {
471         if (!getTmpDir().exists()) {
472             if (!getTmpDir().mkdirs()) {
473                 throw new MessagingException("Cannot open folder; cannot create tmp directory; "+getFullName());
474             }
475         }
476         if (!getCurDir().exists()) {
477             if (!getCurDir().mkdirs()) {
478                 throw new MessagingException("Cannot open folder; cannot create cur directory; "+getFullName());
479             }
480         }
481         if (!getNewDir().exists()) {
482             if (!getNewDir().mkdirs()) {
483                 throw new MessagingException("Cannot open folder; cannot create new directory; "+getFullName());
484             }
485         }
486     }
487 
488    // ---------------------------------------------------------------------------------------
489 
490     /**
491      * Returns message count. This implementation checks files on the disk - new and cur directories.
492      * @return message count
493      * @throws MessagingException never
494      */
495     public int getMessageCount() throws MessagingException {
496         synchronized (this) {
497             if (data != null) {
498                 return data.messages.size();
499             }
500         }
501 
502         int len = 0;
503         if (getNewDir().exists()) {
504             String[] fs = getNewDir().list();
505             if (fs != null) {
506                 len = len + fs.length;
507             }
508         }
509         if (getCurDir().exists()) {
510             String[] fs = getCurDir().list();
511             if (fs != null) {
512                 len = len + fs.length;
513             }
514         }
515 
516         return len;
517     }
518 
519     /**
520      * Returns new message count. This implementation checks files on the disk - only in new directory.
521      * @return new message count
522      * @throws MessagingException never
523      */
524     public int getNewMessageCount() throws MessagingException {
525         synchronized (this) {
526             if (data != null) {
527                 int cnt = 0;
528                 Iterator<MaildirMessage> it = data.messages.iterator();
529                 while (it.hasNext()) {
530                     Message msg = it.next();
531                     if (msg.isSet(Flags.Flag.RECENT)) {
532                         cnt++;
533                     }
534                 }
535                 return cnt;
536             }
537         }
538         if (getNewDir().exists()) {
539             return getNewDir().list().length;
540         }
541         return 0;
542     }
543 
544     /**
545      * This method is called by folder that is being opened. This implementation reads all files from the new and cur directories and
546      * wraps them in appropriate message message objects (see {@link #createExistingMaildirMessage(File, int)}).
547      * @param folder folder that asked opening
548      * @throws MessagingException thrown if an error is encountered while creating messages
549      * @throws MessagingException thrown if an error is encountered while creating messages
550      */
551     protected void open(MaildirFolder folder) throws MessagingException {
552         openCount = openCount + 1;
553         if (data == null) { // Must be same as openCount == 1
554             if (closedRef != null) {
555                 data = closedRef.get();
556             }
557             if (data == null) {
558                 data = new Data();
559                 data.messages = new ArrayList<MaildirMessage>();
560                 data.files = new HashMap<String, MaildirMessage>();
561             }
562             closedRef = null;
563         }
564 
565         folder.setFolderMessages(createFolderMessages());
566         obtainMessages();
567 
568         // If folder is pronounced as opened here
569         // it won't be filled with newly discovered messages so...
570         synchronized (openedFolders) {
571             openedFolders.put(folder, null);
572         }
573         // ... this statement here will be only one that will add messages
574         folder.addMessages(data.messages, false);
575     }
576 
577     /**
578      * This method is called with folder that is closing.
579      * @param folder folder that is closed
580      */
581     protected void close(MaildirFolder folder) {
582         synchronized (openedFolders) {
583             openedFolders.remove(folder);
584         }
585 
586         openCount = openCount - 1;
587         if (openCount == 0) {
588             closedRef = new WeakReference<Data>(data);
589             data = null;
590         }
591     }
592 
593     /**
594      * Appends messages to the folder.
595      * @param folder folder that initiated appending messages
596      * @param messages array of messages
597      * @throws MessagingException thrown while creating new message.
598      */
599     protected void appendMessages(MaildirFolder folder, Message[] messages) throws MessagingException {
600         ArrayList<MaildirMessage> addedMessages = new ArrayList<MaildirMessage>();
601         Exception exception = null;
602         int num = 0;
603         if ((folder != null) && folder.isOpen()) {
604             num = folder.getMessageCount();
605         }
606         for (int i = 0; i < messages.length; i++) {
607             try {
608                 MaildirMessage message = createNewMaildirMessage((MimeMessage)messages[i], num);
609                 if (data != null) {
610                     data.files.put(message.getBaseName(), message);
611                 }
612                 num = num + 1;
613                 addedMessages.add(message);
614             } catch (IOException e) {
615                 exception = e;
616             }
617         }
618         if (addedMessages.size() > 0) {
619             // folder.addMessages(addedMessages, true);
620             addMessages(folder, addedMessages);
621         }
622         if (exception != null) {
623             throw new MessagingException("Adding message failed", exception);
624         }
625     }
626 
627     /**
628      * Appends messages to the maildir data. This method doesn't append messages to any
629      * specific folder - but all opened.
630      * @param messages messages to be appended
631      * @throws MessagingException
632      */
633     public void appendMessages(Message[] messages) throws MessagingException {
634         appendMessages(null, messages);
635     }
636 
637     /**
638      * Expunges messages for given folder.
639      * @param folder folder
640      * @param explicit should folder be notified of messages
641      * @return list of messages that were expunged
642      * @throws MessagingException thrown in <code>javax.mail.Message.getFlags()</code> method
643      */
644     protected List<? extends Message> expunge(MaildirFolder folder, boolean explicit) throws MessagingException {
645         ArrayList<MaildirMessage> expunged = new ArrayList<MaildirMessage>();
646         List<MaildirMessage> messages = getFolderMessages(folder);
647 
648         Iterator<MaildirMessage> it = messages.iterator();
649         while (it.hasNext()) {
650             MaildirMessage message = it.next();
651             if (!message.isExpunged() && message.getFlags().contains(Flags.Flag.DELETED)) {
652                 expunged.add(message);
653             }
654         }
655         if (expunged.size() > 0) {
656             it = expunged.iterator();
657             while (it.hasNext()) {
658                 MaildirMessage msg = (MaildirMessage)it.next();
659                 if (!expungeMessage(msg)) {
660                     it.remove();
661                 }
662                 data.messages.remove(msg);
663                 data.files.remove(msg.getBaseName());
664             }
665 
666             removeMessages(folder, expunged);
667             // Remove method removes for all but folder
668             return folder.removeMessages(expunged, explicit);
669         } else {
670             // Zero sized list
671             return expunged;
672         }
673     }
674 
675 
676     /**
677      * This method is only to satisfy Folder interface. Not to be used.
678      * @return <code>null</code>
679      * @throws MessagingException
680      */
681     public Message[] expunge() throws MessagingException {
682         // TODO Try to give this method some meaning...
683         return null;
684     }
685 
686     /**
687      * Returns folder's messages for given folder.
688      * This implementation unwraps messages from the folder.
689      *
690      * @param folder
691      * @return list of messages
692      */
693     protected List<MaildirMessage> getFolderMessages(MaildirFolder folder) {
694         List<? extends MessageBase> messages = folder.getFolderMessages();
695         int size = messages.size();
696         ArrayList<MaildirMessage> unwrapped = new ArrayList<MaildirMessage>(size);
697         for (int i = 0; i < size; i++) {
698             MaildirMessage wrapped = (MaildirMessage)((MessageWrapper)messages.get(i)).getMessage();
699             unwrapped.add(wrapped);
700         }
701         return unwrapped;
702     }
703 
704     /**
705      * This method reads directory and creates messages for given folder.
706      * @throws MessagingException
707      */
708     @SuppressWarnings("unchecked")
709     protected void obtainMessages() throws MessagingException {
710         try {
711             // It is called from open so we do not need to sycnhronise - open is synchronised.
712             long time = System.currentTimeMillis();
713             if ((time - lastAccess) > delay) {
714                 int msgno = 1;
715                 HashMap<String, MaildirMessage> expunged = (HashMap<String, MaildirMessage>)data.files.clone();
716                 ArrayList<MaildirMessage> newMessages = new ArrayList<MaildirMessage>();
717                 File[] oldFiles = getCurDir().listFiles();
718                 File[] newFiles = getNewDir().listFiles();
719                 if (oldFiles != null) {
720                     for (int i = 0; i < oldFiles.length; i++) {
721                         String baseName = MaildirMessage.baseNameFromFile(oldFiles[i]);
722                         MaildirMessage message = (MaildirMessage)data.files.get(baseName);
723                         if (message == null) {
724                             // new file
725                             message = createExistingMaildirMessage(oldFiles[i], msgno);
726                             msgno++;
727                             newMessages.add(message);
728                             if (data != null) {
729                                 data.files.put(message.getBaseName(), message);
730                             }
731                         } else {
732                             // we still have that file
733                             expunged.remove(baseName);
734                         }
735                     }
736                 }
737                 if (newFiles != null) {
738                     for (int i = 0; i < newFiles.length; i++) {
739                         String baseName = MaildirMessage.baseNameFromFile(newFiles[i]);
740                         MaildirMessage message = (MaildirMessage)data.files.get(baseName);
741                         if (message == null) {
742                             // new file
743                             message = createExistingMaildirMessage(newFiles[i], msgno);
744                             msgno++;
745                             newMessages.add(message);
746                             if (data != null) {
747                                 data.files.put(message.getBaseName(), message);
748                             }
749                         } else {
750                             // we still have that file
751                             expunged.remove(baseName);
752                         }
753                     }
754                 }
755                 if (newMessages.size() > 0) {
756                     addMessages(null, newMessages);
757                 }
758                 if (expunged.size() > 0) {
759                     Iterator it = expunged.values().iterator();
760                     while (it.hasNext()) {
761                         MaildirMessage msg = (MaildirMessage)it.next();
762                         data.messages.remove(msg);
763                         data.files.remove(msg.getBaseName());
764                     }
765                     removeMessages((MaildirFolder)null, expunged.values());
766                 }
767 
768                 long now = System.currentTimeMillis();
769                 if ((now - time) > (delay / delayFactor)) {
770                     delay = delay * delayFactor;
771                 }
772 
773                 lastAccess = now;
774             }
775         } catch (IOException e) {
776             throw new MessagingException("Cannot create message file", e);
777         }
778     }
779 
780 
781 
782 
783     /**
784      * This method adds messages to the folder.
785      * @param folder folder where messages should be added without being notified. All others will be notified. If null supplied then all will be notified.
786      * @param messages messages to be added
787      * @throws MessagingException
788      */
789     protected void addMessages(MaildirFolder folder, List<MaildirMessage> messages) throws MessagingException {
790         if (data != null) {
791             // There are opened folders
792             Collections.sort(messages);
793             renumerateMessages(data.messages.size()+1, messages);
794             data.messages.addAll(messages);
795 
796             synchronized (openedFolders) {
797                 Iterator<Folder> it = openedFolders.keySet().iterator();
798                 while (it.hasNext()) {
799                     MaildirFolder extFolder = (MaildirFolder)it.next();
800                     extFolder.addMessages(messages, folder != extFolder);
801                 }
802             }
803         }
804     }
805 
806     /**
807      * This method removes folder messages. Called only for implicite removal.
808      * @param folder folder that initiated call. That folder will be excluded from removal
809      * and method folder.removeMessages must be called separately
810      * @param messages messages to be removed
811      * @throws MessagingException
812      */
813     protected void removeMessages(MaildirFolder folder, Collection<? extends MimeMessage> messages) throws MessagingException {
814         synchronized (openedFolders) {
815             Iterator<Folder> it = openedFolders.keySet().iterator();
816             while (it.hasNext()) {
817                 MaildirFolder extFolder = (MaildirFolder)it.next();
818 //                extFolder.removeMessages(messages, (folder == extFolder) && explicit);
819                 if (extFolder != folder) {
820                     extFolder.removeMessages(messages, false);
821                 }
822             }
823         }
824     }
825 
826     /**
827      * This method creates collection structure for storing messages in the folder.
828      * This implementation returns <code>ArrayList</code>
829      * @return list for messages to be stored in.
830      */
831     protected List<MessageBase> createFolderMessages() {
832         return new ArrayList<MessageBase>();
833     }
834 
835     /**
836      * Expunges one message
837      * @param message message to be expunged
838      * @return <code>true</code> if expunge succed
839      */
840     protected boolean expungeMessage(MaildirMessage message) {
841         return message.expunge();
842     }
843 
844     /**
845      * Renumerates given list of maildir message objects
846      * @param from first number for renumeration to start with
847      * @param messages maildir message objects
848      */
849     public static void renumerateMessages(int from, List<? extends MessageBase> messages) {
850         for (int i = 0; i < messages.size(); i++) {
851             MessageBase msg = messages.get(i);
852             msg.setMessageNumber(from + i);
853         }
854     }
855 
856 
857     /**
858      * This method creates new maildir message for folder data (not folder). It is expected
859      * for this method to create appropriate file in proper directory based on flags in
860      * supplied message (cur or new).
861      * @param message message whose content will be copied to new message
862      * @param num message number
863      * @return new maildir message this folder will keep for its folder data
864      * @throws IOException
865      * @throws MessagingException
866      */
867     protected MaildirMessage createNewMaildirMessage(MimeMessage message, int num) throws IOException, MessagingException {
868         return new MaildirMessage(this, message, num);
869     }
870 
871     /**
872      * This method creates new maildir message object for existing file in folder data's directory.
873      * @param file file message object is going to be created
874      * @param num message number
875      * @return new maildir message this folder will keep for its folder data
876      * @throws IOException
877      * @throws MessagingException
878      */
879     protected MaildirMessage createExistingMaildirMessage(File file, int num) throws IOException, MessagingException {
880         return new MaildirMessage(this, file, num);
881     }
882 
883 
884     /**
885      * Folders data class. This class keeps list of messages and map for files, messages pair.
886      * It is moved to separate class just for easier handling of moving instance of this class
887      * under weak reference regime when there are no open folders. That means garbage collector
888      * can more easily remove not used messages.
889      */
890     protected static class Data {
891         /** Messages in this directory (folder(s)) */
892         protected List<MaildirMessage> messages;
893 
894         /** Map from files to message objects */
895         protected HashMap<String, MaildirMessage> files;
896     }
897 
898 
899 
900     /**
901      * This method is only to satisfy Folder interface. Not to be used.
902      * @return <code>null</code>
903      * @throws MessagingException
904      */
905     public Folder getParent() throws MessagingException {
906         return null;
907     }
908 
909     /**
910      * This method is only to satisfy Folder interface. Not to be used.
911      * @param arg0
912      * @return <code>null</code>
913      * @throws MessagingException
914      */
915     public Folder[] list(String arg0) throws MessagingException {
916         return null;
917     }
918 
919     /**
920      * This method is only to satisfy Folder interface. Not to be used.
921      * @return <code>getMessageCount() &gt; 0</code>
922      * @throws MessagingException
923      */
924     public boolean hasNewMessages() throws MessagingException {
925         return getMessageCount() > 0;
926     }
927 
928     /**
929      * This method is only to satisfy Folder interface. Not to be used.
930      * @param arg0
931      * @return <code>null</code>
932      * @throws MessagingException
933      */
934     public Folder getFolder(String arg0) throws MessagingException {
935         return null;
936     }
937 
938     /**
939      * This method is only to satisfy Folder interface. Not to be used.
940      * @param folder
941      * @return result of {@link #renameTo(MaildirFolderData)} method.
942      * @throws MessagingException
943      */
944     public boolean renameTo(Folder folder) throws MessagingException {
945         if (folder instanceof MaildirFolderData) {
946             return renameTo((MaildirFolderData)folder) != null;
947         }
948         return false;
949     }
950 
951     /**
952      * This method is only to satisfy Folder interface. Not to be used.
953      * @param arg0
954      * @throws MessagingException
955      */
956     public void open(int arg0) throws MessagingException {
957     }
958 
959     /**
960      * This method is only to satisfy Folder interface. Not to be used.
961      * @param arg0
962      * @throws MessagingException
963      */
964     public void close(boolean arg0) throws MessagingException {
965     }
966 
967     /**
968      * This method is only to satisfy Folder interface. Not to be used.
969      * @return <code> openCount &gt; 0</code>
970      */
971     public boolean isOpen() {
972         return openCount > 0;
973     }
974 
975     /**
976      * This method is only to satisfy Folder interface. Not to be used.
977      * @return {@link #rootPermanentFlags} if root folder or {@link #permanentFlags} otherwise
978      */
979     public Flags getPermanentFlags() {
980         // TODO add check for this as parameter in store
981         if (isRootFolder()) {
982             return rootPermanentFlags;
983         } else {
984             if (permanentFlags == null) {
985                 permanentFlags = new Flags();
986                 permanentFlags.add(javax.mail.Flags.Flag.ANSWERED);
987                 permanentFlags.add(javax.mail.Flags.Flag.SEEN);
988                 permanentFlags.add(javax.mail.Flags.Flag.DELETED);
989                 permanentFlags.add(javax.mail.Flags.Flag.DRAFT);
990                 permanentFlags.add(javax.mail.Flags.Flag.FLAGGED);
991             }
992             return permanentFlags;
993         }
994     }
995 
996     /**
997      * This method is only to satisfy Folder interface. Not to be used.
998      * @param i index
999      * @return returns message with given index
1000      * @throws MessagingException
1001      */
1002     public Message getMessage(int i) throws MessagingException {
1003         if (data != null) {
1004             return (MaildirMessage)data.messages.get(i);
1005         } else {
1006             return null;
1007         }
1008     }
1009 }