| 1 21 22 27 28 package com.sun.mail.imap; 29 30 import java.util.Date ; 31 import java.util.Vector ; 32 import java.util.Hashtable ; 33 import java.util.NoSuchElementException ; 34 import java.io.*; 35 36 import javax.mail.*; 37 import javax.mail.event.*; 38 import javax.mail.internet.*; 39 import javax.mail.search.*; 40 41 import com.sun.mail.util.*; 42 import com.sun.mail.iap.*; 43 import com.sun.mail.imap.protocol.*; 44 45 82 83 144 145 public class IMAPFolder extends Folder implements UIDFolder, ResponseHandler { 146 147 protected String fullName; protected String name; protected int type; protected char separator; protected Flags availableFlags; protected Flags permanentFlags; protected boolean exists = false; protected boolean isNamespace = false; protected String [] attributes; 157 protected IMAPProtocol protocol; protected Vector messageCache; protected Object messageCacheLock; 161 protected Hashtable uidTable; 163 169 static final protected char UNKNOWN_SEPARATOR = '\uffff'; 170 171 private boolean opened = false; 173 183 private boolean reallyClosed = true; 184 185 private int total = -1; private int recent = -1; private int realTotal = -1; private int uidvalidity = -1; private int uidnext = -1; private boolean doExpungeNotification = true; 194 private Status cachedStatus = null; 195 private long cachedStatusTime = 0; 196 197 private boolean debug = false; 198 private PrintStream out; 200 private boolean connectionPoolDebug; 201 202 209 public static class FetchProfileItem extends FetchProfile.Item { 210 protected FetchProfileItem(String name) { 211 super(name); 212 } 213 214 229 public static final FetchProfileItem HEADERS = 230 new FetchProfileItem("HEADERS"); 231 232 240 public static final FetchProfileItem SIZE = 241 new FetchProfileItem("SIZE"); 242 } 243 244 252 protected IMAPFolder(String fullName, char separator, IMAPStore store) { 253 this(fullName, separator, store, false); 254 } 255 256 264 protected IMAPFolder(String fullName, char separator, IMAPStore store, 265 boolean isNamespace) { 266 super(store); 267 if (fullName == null) 268 throw new NullPointerException ("Folder name is null"); 269 this.fullName = fullName; 270 this.separator = separator; 271 this.isNamespace = isNamespace; 272 messageCacheLock = new Object (); 273 debug = store.getSession().getDebug(); 274 connectionPoolDebug = ((IMAPStore)store).getConnectionPoolDebug(); 275 out = store.getSession().getDebugOut(); 276 if (out == null) out = System.out; 278 } 279 280 283 protected IMAPFolder(ListInfo li, IMAPStore store) { 284 this(li.name, li.separator, store); 285 286 if (li.hasInferiors) 287 type |= HOLDS_FOLDERS; 288 if (li.canOpen) 289 type |= HOLDS_MESSAGES; 290 exists = true; 291 attributes = li.attrs; 292 } 293 294 298 private void checkExists() throws MessagingException { 299 if (!exists && !exists()) 302 throw new FolderNotFoundException( 303 this, fullName + " not found"); 304 } 305 306 310 private void checkClosed() { 311 if (opened) 312 throw new IllegalStateException ( 313 "This operation is not allowed on an open folder" 314 ); 315 } 316 317 321 private void checkOpened() throws FolderClosedException { 322 if (!opened) { 323 if (reallyClosed) 324 throw new IllegalStateException ( 325 "This operation is not allowed on a closed folder" 326 ); 327 else throw new FolderClosedException(this, 329 "Lost folder connection to server" 330 ); 331 } 332 } 333 334 339 private void checkRange(int msgno) throws MessagingException { 340 if (msgno < 1) throw new IndexOutOfBoundsException (); 342 343 if (msgno <= total) 344 return; 345 346 349 synchronized(messageCacheLock) { try { 351 keepConnectionAlive(false); 352 } catch (ConnectionException cex) { 353 throw new FolderClosedException(this, cex.getMessage()); 355 } catch (ProtocolException pex) { 356 throw new MessagingException(pex.getMessage(), pex); 357 } 358 } 360 if (msgno > total) throw new IndexOutOfBoundsException (); 362 } 363 364 367 private void checkFlags(Flags flags) throws MessagingException { 368 if (mode != READ_WRITE) 369 throw new IllegalStateException ( 370 "Cannot change flags on READ_ONLY folder: " + fullName 371 ); 372 if (!availableFlags.contains(flags)) 373 throw new MessagingException( 374 "These flags are not supported by this implementation" 375 ); 376 } 377 378 381 public String getName() { 382 385 if (name == null) { 386 try { 387 name = fullName.substring( 388 fullName.lastIndexOf(getSeparator()) + 1 389 ); 390 } catch (MessagingException mex) { } 391 } 392 return name; 393 } 394 395 398 public String getFullName() { 399 return fullName; 400 } 401 402 405 public Folder getParent() throws MessagingException { 406 char c = getSeparator(); 407 int index; 408 if ((index = fullName.lastIndexOf(c)) != -1) 409 return new IMAPFolder(fullName.substring(0, index), 410 c, (IMAPStore)store); 411 else 412 return new DefaultFolder((IMAPStore)store); 413 } 414 415 418 public boolean exists() throws MessagingException { 419 ListInfo[] li = null; 421 final String lname; 422 if (isNamespace && separator != '\0') 423 lname = fullName + separator; 424 else 425 lname = fullName; 426 427 li = (ListInfo[])doCommand(new ProtocolCommand() { 428 public Object doCommand(IMAPProtocol p) throws ProtocolException { 429 return p.list("", lname); 430 } 431 }); 432 433 if (li != null) { 434 int i = findName(li, lname); 435 fullName = li[i].name; 436 separator = li[i].separator; 437 int len = fullName.length(); 438 if (separator != '\0' && len > 0 && 439 fullName.charAt(len - 1) == separator) { 440 fullName = fullName.substring(0, len - 1); 441 } 442 type = 0; 443 if (li[i].hasInferiors) 444 type |= HOLDS_FOLDERS; 445 if (li[i].canOpen) 446 type |= HOLDS_MESSAGES; 447 exists = true; 448 attributes = li[i].attrs; 449 } else 450 exists = false; 451 452 return exists; 453 } 454 455 460 private int findName(ListInfo[] li, String lname) { 461 int i; 462 for (i = 0; i < li.length; i++) { 464 if (li[i].name.equals(lname)) 465 break; 466 } 467 if (i >= li.length) { i = 0; } 473 return i; 474 } 475 476 479 public Folder[] list(String pattern) throws MessagingException { 480 return doList(pattern, false); 481 } 482 483 486 public Folder[] listSubscribed(String pattern) throws MessagingException { 487 return doList(pattern, true); 488 } 489 490 private Folder[] doList(final String pattern, final boolean subscribed) 491 throws MessagingException { 492 checkExists(); 494 if (!isDirectory()) return new Folder[0]; 496 497 final char c = getSeparator(); 498 499 ListInfo[] li = (ListInfo[])doCommandIgnoreFailure( 500 new ProtocolCommand() { 501 public Object doCommand(IMAPProtocol p) 502 throws ProtocolException { 503 if (subscribed) 504 return p.lsub("", fullName + c + pattern); 505 else 506 return p.list("", fullName + c + pattern); 507 } 508 }); 509 510 if (li == null) 511 return new Folder[0]; 512 513 524 int start = 0; 525 if (li.length > 0 && li[0].name.equals(fullName + c)) 527 start = 1; 529 IMAPFolder[] folders = new IMAPFolder[li.length - start]; 530 for (int i = start; i < li.length; i++) 531 folders[i-start] = new IMAPFolder(li[i], (IMAPStore)store); 532 return folders; 533 } 534 535 538 public synchronized char getSeparator() throws MessagingException { 539 if (separator == UNKNOWN_SEPARATOR) { 540 ListInfo[] li = null; 541 542 li = (ListInfo[])doCommand(new ProtocolCommand() { 543 public Object doCommand(IMAPProtocol p) 544 throws ProtocolException { 545 if (p.isREV1()) return p.list(fullName, ""); 549 else return p.list("", fullName); 552 } 553 }); 554 555 if (li != null) 556 separator = li[0].separator; 557 else 558 separator = '/'; } 560 return separator; 561 } 562 563 566 public int getType() throws MessagingException { 567 checkExists(); 568 return type; 569 } 570 571 574 public boolean isSubscribed() { 575 ListInfo[] li = null; 576 final String lname; 577 if (isNamespace && separator != '\0') 578 lname = fullName + separator; 579 else 580 lname = fullName; 581 582 try { 583 li = (ListInfo[])doProtocolCommand(new ProtocolCommand() { 584 public Object doCommand(IMAPProtocol p) 585 throws ProtocolException { 586 return p.lsub("", lname); 587 } 588 }); 589 } catch (ProtocolException pex) { 590 } 591 592 if (li != null) { 593 int i = findName(li, lname); 594 return li[i].canOpen; 595 } else 596 return false; 597 } 598 599 602 public void setSubscribed(final boolean subscribe) 603 throws MessagingException { 604 doCommandIgnoreFailure(new ProtocolCommand() { 605 public Object doCommand(IMAPProtocol p) throws ProtocolException { 606 if (subscribe) 607 p.subscribe(fullName); 608 else 609 p.unsubscribe(fullName); 610 return null; 611 } 612 }); 613 } 614 615 618 public synchronized boolean create(final int type) 619 throws MessagingException { 620 621 char c = 0; 622 if ((type & HOLDS_MESSAGES) == 0) c = getSeparator(); 624 final char sep = c; 625 Object ret = doCommandIgnoreFailure(new ProtocolCommand() { 626 public Object doCommand(IMAPProtocol p) 627 throws ProtocolException { 628 if ((type & HOLDS_MESSAGES) == 0) p.create(fullName + sep); 630 else { 631 p.create(fullName); 632 633 if ((type & HOLDS_FOLDERS) != 0) { 638 ListInfo[] li = p.list("", fullName); 641 if (li != null && !li[0].hasInferiors) { 642 p.delete(fullName); 645 throw new ProtocolException("Unsupported type"); 646 } 647 } 648 } 649 return Boolean.TRUE; 650 } 651 }); 652 653 if (ret == null) 654 return false; 657 boolean retb = exists(); notifyFolderListeners(FolderEvent.CREATED); 662 return retb; 663 } 664 665 668 public synchronized boolean hasNewMessages() throws MessagingException { 669 checkExists(); 670 671 if (opened) { synchronized(messageCacheLock) { 674 try { 676 keepConnectionAlive(true); 677 } catch (ConnectionException cex) { 678 throw new FolderClosedException(this, cex.getMessage()); 679 } catch (ProtocolException pex) { 680 throw new MessagingException(pex.getMessage(), pex); 681 } 682 } 683 return recent > 0 ? true : false; 684 } 685 686 689 Boolean b = (Boolean )doCommandIgnoreFailure(new ProtocolCommand() { 690 public Object doCommand(IMAPProtocol p) throws ProtocolException { 691 ListInfo[] li = p.list("", fullName); 692 if (li != null) { 693 if (li[0].changeState == ListInfo.CHANGED) 694 return Boolean.TRUE; 695 else if (li[0].changeState == ListInfo.UNCHANGED) 696 return Boolean.FALSE; 697 } 698 699 Status status = getStatus(); 701 if (status.recent > 0) 702 return Boolean.TRUE; 703 else 704 return Boolean.FALSE; 705 } 706 }); 707 if (b == null) 708 return false; 710 return b.booleanValue(); 711 } 712 713 716 public Folder getFolder(String name) throws MessagingException { 717 if (exists && !isDirectory()) 720 throw new MessagingException("Cannot contain subfolders"); 721 722 char c = getSeparator(); 723 return new IMAPFolder(fullName + c + name, c, (IMAPStore)store); 724 } 725 726 729 public synchronized boolean delete(boolean recurse) 730 throws MessagingException { 731 checkClosed(); 733 if (recurse) { 734 Folder[] f = list(); 736 for (int i = 0; i < f.length; i++) 737 f[i].delete(recurse); } 739 740 742 Object ret = doCommandIgnoreFailure(new ProtocolCommand() { 743 public Object doCommand(IMAPProtocol p) throws ProtocolException { 744 p.delete(fullName); 745 return Boolean.TRUE; 746 } 747 }); 748 749 if (ret == null) 750 return false; 752 753 exists = false; 755 756 notifyFolderListeners(FolderEvent.DELETED); 758 return true; 759 } 760 761 764 public synchronized boolean renameTo(final Folder f) 765 throws MessagingException { 766 checkClosed(); checkExists(); 768 if (f.getStore() != store) 769 throw new MessagingException("Can't rename across Stores"); 770 771 772 Object ret = doCommandIgnoreFailure(new ProtocolCommand() { 773 public Object doCommand(IMAPProtocol p) throws ProtocolException { 774 p.rename(fullName, f.getFullName()); 775 return Boolean.TRUE; 776 } 777 }); 778 779 if (ret == null) 780 return false; 781 782 exists = false; 783 notifyFolderRenamedListeners(f); 784 return true; 785 } 786 787 790 public synchronized void open(int mode) throws MessagingException { 791 checkClosed(); 793 MailboxInfo mi = null; 794 protocol = ((IMAPStore)store).getProtocol(this); 796 797 CommandFailedException exc = null; 798 lock: 799 synchronized(messageCacheLock) { 801 807 protocol.addResponseHandler(this); 808 809 try { 810 if (mode == READ_ONLY) 811 mi = protocol.examine(fullName); 812 else 813 mi = protocol.select(fullName); 814 } catch (CommandFailedException cex) { 815 releaseProtocol(true); 817 protocol = null; 818 exc = cex; 819 break lock; 820 } catch (ProtocolException pex) { 821 try { 823 protocol.logout(); 824 } catch (ProtocolException pex2) { 825 } finally { 827 releaseProtocol(false); 828 protocol = null; 829 throw new MessagingException(pex.getMessage(), pex); 830 } 831 } 832 833 if (mi.mode != mode) { 834 if (mode == READ_WRITE && mi.mode == READ_ONLY && 835 ((IMAPStore)store).allowReadOnlySelect()) { 836 ; } else { try { 839 protocol.close(); 841 releaseProtocol(true); 842 } catch (ProtocolException pex) { 843 try { 845 protocol.logout(); 846 } catch (ProtocolException pex2) { 847 } finally { 849 releaseProtocol(false); 850 } 851 } finally { 852 protocol = null; 853 throw new ReadOnlyFolderException(this, 854 "Cannot open in desired mode"); 855 } 856 857 } 858 } 859 860 opened = true; 862 reallyClosed = false; 863 this.mode = mi.mode; 864 availableFlags = mi.availableFlags; 865 permanentFlags = mi.permanentFlags; 866 total = realTotal = mi.total; 867 recent = mi.recent; 868 uidvalidity = mi.uidvalidity; 869 uidnext = mi.uidnext; 870 871 messageCache = new Vector (total); 873 for (int i = 0; i < total; i++) 875 messageCache.addElement(new IMAPMessage(this, i+1, i+1)); 876 877 } 879 884 if (exc != null) { 885 checkExists(); 887 if ((type & HOLDS_MESSAGES) == 0) 888 throw new MessagingException("folder cannot contain messages"); 889 throw new MessagingException(exc.getMessage(), exc); 890 } 891 892 notifyConnectionListeners(ConnectionEvent.OPENED); 894 } 895 896 899 public synchronized void fetch(Message[] msgs, FetchProfile fp) 900 throws MessagingException { 901 checkOpened(); 902 IMAPMessage.fetch(this, msgs, fp); 903 } 904 905 908 public synchronized void setFlags(Message[] msgs, Flags flag, boolean value) 909 throws MessagingException { 910 checkOpened(); 911 checkFlags(flag); 913 if (msgs.length == 0) return; 915 916 synchronized(messageCacheLock) { 917 try { 918 MessageSet[] ms = Utility.toMessageSet(msgs, null); 919 if (ms == null) 920 throw new MessageRemovedException( 921 "Messages have been removed"); 922 protocol.storeFlags(ms, flag, value); 923 } catch (ConnectionException cex) { 924 throw new FolderClosedException(this, cex.getMessage()); 925 } catch (ProtocolException pex) { 926 throw new MessagingException(pex.getMessage(), pex); 927 } 928 } 929 } 930 931 934 public synchronized void close(boolean expunge) throws MessagingException { 935 close(expunge, false); 936 } 937 938 941 public synchronized void forceClose() throws MessagingException { 942 close(false, true); 943 } 944 945 private void close(boolean expunge, boolean force) 946 throws MessagingException { 947 synchronized(messageCacheLock) { 948 954 if (!opened && reallyClosed) 955 throw new IllegalStateException ( 956 "This operation is not allowed on a closed folder" 957 ); 958 959 reallyClosed = true; 961 if (!opened) 966 return; 967 968 try { 969 |