/* * Copyright (c) 1997, 2018 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 com.sun.mail.imap; import java.util.Date; import java.util.Vector; import java.util.Hashtable; import java.util.List; import java.util.ArrayList; import java.util.Map; import java.util.NoSuchElementException; import java.util.Locale; import java.util.logging.Level; import java.io.*; import java.net.SocketTimeoutException; import java.nio.channels.SocketChannel; import javax.mail.*; import javax.mail.event.*; import javax.mail.internet.*; import javax.mail.search.*; import com.sun.mail.util.PropUtil; import com.sun.mail.util.MailLogger; import com.sun.mail.util.CRLFOutputStream; import com.sun.mail.iap.*; import com.sun.mail.imap.protocol.*; /** * This class implements an IMAP folder.
* * A closed IMAPFolder object shares a protocol connection with its IMAPStore * object. When the folder is opened, it gets its own protocol connection.
*
* Applications that need to make use of IMAP-specific features may cast
* a Folder
object to an IMAPFolder
object and
* use the methods on this class.
* * The {@link #getQuota getQuota} and * {@link #setQuota setQuota} methods support the IMAP QUOTA extension. * Refer to RFC 2087 * for more information.
* * The {@link #getACL getACL}, {@link #addACL addACL}, * {@link #removeACL removeACL}, {@link #addRights addRights}, * {@link #removeRights removeRights}, {@link #listRights listRights}, and * {@link #myRights myRights} methods support the IMAP ACL extension. * Refer to RFC 2086 * for more information.
* * The {@link #getSortedMessages getSortedMessages} * methods support the IMAP SORT extension. * Refer to RFC 5256 * for more information.
* * The {@link #open(int,com.sun.mail.imap.ResyncData) open(int,ResyncData)} * method and {@link com.sun.mail.imap.ResyncData ResyncData} class supports * the IMAP CONDSTORE and QRESYNC extensions. * Refer to RFC 4551 * and RFC 5162 * for more information.
* * The {@link #doCommand doCommand} method and * {@link IMAPFolder.ProtocolCommand IMAPFolder.ProtocolCommand} * interface support use of arbitrary IMAP protocol commands.
* * See the com.sun.mail.imap package * documentation for further information on the IMAP protocol provider.
* * WARNING: The APIs unique to this class should be * considered EXPERIMENTAL. They may be changed in the * future in ways that are incompatible with applications using the * current APIs. * * @author John Mani * @author Bill Shannon * @author Jim Glennon */ /* * The folder object itself serves as a lock for the folder's state * EXCEPT for the message cache (see below), typically by using * synchronized methods. When checking that a folder is open or * closed, the folder's lock must be held. It's important that the * folder's lock is acquired before the messageCacheLock (see below). * Thus, the locking hierarchy is that the folder lock, while optional, * must be acquired before the messageCacheLock, if it's acquired at * all. Be especially careful of callbacks that occur while holding * the messageCacheLock into (e.g.) superclass Folder methods that are * synchronized. Note that methods in IMAPMessage will acquire the * messageCacheLock without acquiring the folder lock.
* * When a folder is opened, it creates a messageCache (a Vector) of * empty IMAPMessage objects. Each Message has a messageNumber - which * is its index into the messageCache, and a sequenceNumber - which is * its IMAP sequence-number. All operations on a Message which involve * communication with the server, use the message's sequenceNumber.
* * The most important thing to note here is that the server can send * unsolicited EXPUNGE notifications as part of the responses for "most" * commands. Refer RFC 3501, sections 5.3 & 5.5 for gory details. Also, * the server sends these notifications AFTER the message has been * expunged. And once a message is expunged, the sequence-numbers of * those messages after the expunged one are renumbered. This essentially * means that the mapping between *any* Message and its sequence-number * can change in the period when a IMAP command is issued and its responses * are processed. Hence we impose a strict locking model as follows:
*
* We define one mutex per folder - this is just a Java Object (named
* messageCacheLock). Any time a command is to be issued to the IMAP
* server (i.e., anytime the corresponding IMAPProtocol method is
* invoked), follow the below style:
*
* synchronized (messageCacheLock) { // ACQUIRE LOCK
* issue command ()
*
* // The response processing is typically done within
* // the handleResponse() callback. A few commands (Fetch,
* // Expunge) return *all* responses and hence their
* // processing is done here itself. Now, as part of the
* // processing unsolicited EXPUNGE responses, we renumber
* // the necessary sequence-numbers. Thus the renumbering
* // happens within this critical-region, surrounded by
* // locks.
* process responses ()
* } // RELEASE LOCK
*
* This technique is used both by methods in IMAPFolder and by methods
* in IMAPMessage and other classes that operate on data in the folder.
* Note that holding the messageCacheLock has the side effect of
* preventing the folder from being closed, and thus ensuring that the
* folder's protocol object is still valid. The protocol object should
* only be accessed while holding the messageCacheLock (except for calls
* to IMAPProtocol.isREV1(), which don't need to be protected because it
* doesn't access the server).
*
* Note that interactions with the Store's protocol connection do
* not have to be protected as above, since the Store's protocol is
* never in a "meaningful" SELECT-ed state.
*/
public class IMAPFolder extends Folder implements UIDFolder, ResponseHandler {
protected volatile String fullName; // full name
protected String name; // name
protected int type; // folder type.
protected char separator; // separator
protected Flags availableFlags; // available flags
protected Flags permanentFlags; // permanent flags
protected volatile boolean exists; // whether this folder really exists ?
protected boolean isNamespace = false; // folder is a namespace name
protected volatile String[] attributes;// name attributes from LIST response
protected volatile IMAPProtocol protocol; // this folder's protocol object
protected MessageCache messageCache;// message cache
// accessor lock for message cache
protected final Object messageCacheLock = new Object();
protected Hashtable
*
* An example of how a client uses this is below:
*
*
* SIZE was moved to FetchProfile.Item in JavaMail 1.5.
*
* @deprecated
*/
@Deprecated
public static final FetchProfileItem SIZE =
new FetchProfileItem("SIZE");
/**
* MESSAGE is a fetch profile item that can be included in a
*
*
* An example of how a client uses this is below:
*
*
* An example of how a client uses this is below:
*
*
* Depends on the APPENDUID response code defined by the
* UIDPLUS extension -
* RFC 4315.
*
* @param msgs the messages to append
* @return array of AppendUID objects
* @exception MessagingException for failures
* @since JavaMail 1.4
*/
public synchronized AppendUID[] appendUIDMessages(Message[] msgs)
throws MessagingException {
checkExists(); // verify that self exists
// XXX - have to verify that messages are in a different
// store (if any) than target folder, otherwise could
// deadlock trying to fetch messages on the same connection
// we're using for the append.
int maxsize = ((IMAPStore)store).getAppendBufferSize();
AppendUID[] uids = new AppendUID[msgs.length];
for (int i = 0; i < msgs.length; i++) {
final Message m = msgs[i];
final MessageLiteral mos;
try {
// if we know the message is too big, don't buffer any of it
mos = new MessageLiteral(m,
m.getSize() > maxsize ? 0 : maxsize);
} catch (IOException ex) {
throw new MessagingException(
"IOException while appending messages", ex);
} catch (MessageRemovedException mrex) {
continue; // just skip this expunged message
}
Date d = m.getReceivedDate(); // retain dates
if (d == null)
d = m.getSentDate();
final Date dd = d;
final Flags f = m.getFlags();
AppendUID auid = (AppendUID)doCommand(new ProtocolCommand() {
@Override
public Object doCommand(IMAPProtocol p)
throws ProtocolException {
return p.appenduid(fullName, f, dd, mos);
}
});
uids[i] = auid;
}
return uids;
}
/**
* Append the given messages into this folder.
* Return array of Message objects representing
* the messages in the destination folder. Note
* that the folder must be open.
* Each element of the returned array corresponds to
* an element of the
*
* Depends on the APPENDUID response code defined by the
* UIDPLUS extension -
* RFC 4315.
*
* @param msgs the messages to add
* @return the messages in this folder
* @exception MessagingException for failures
* @since JavaMail 1.4
*/
public synchronized Message[] addMessages(Message[] msgs)
throws MessagingException {
checkOpened();
Message[] rmsgs = new MimeMessage[msgs.length];
AppendUID[] uids = appendUIDMessages(msgs);
for (int i = 0; i < uids.length; i++) {
AppendUID auid = uids[i];
if (auid != null) {
if (auid.uidvalidity == uidvalidity) {
try {
rmsgs[i] = getMessageByUID(auid.uid);
} catch (MessagingException mex) {
// ignore errors at this stage
}
}
}
}
return rmsgs;
}
/**
* Copy the specified messages from this folder, to the
* specified destination.
*/
@Override
public synchronized void copyMessages(Message[] msgs, Folder folder)
throws MessagingException {
copymoveMessages(msgs, folder, false);
}
/**
* Copy the specified messages from this folder, to the
* specified destination.
* Return array of AppendUID objects containing
* UIDs of these messages in the destination folder.
* Each element of the returned array corresponds to
* an element of the
*
* Depends on the COPYUID response code defined by the
* UIDPLUS extension -
* RFC 4315.
*
* @param msgs the messages to copy
* @param folder the folder to copy the messages to
* @return array of AppendUID objects
* @exception MessagingException for failures
* @since JavaMail 1.5.1
*/
public synchronized AppendUID[] copyUIDMessages(Message[] msgs,
Folder folder) throws MessagingException {
return copymoveUIDMessages(msgs, folder, false);
}
/**
* Move the specified messages from this folder, to the
* specified destination.
*
* Depends on the MOVE extension
* (RFC 6851).
*
* @param msgs the messages to move
* @param folder the folder to move the messages to
* @exception MessagingException for failures
*
* @since JavaMail 1.5.4
*/
public synchronized void moveMessages(Message[] msgs, Folder folder)
throws MessagingException {
copymoveMessages(msgs, folder, true);
}
/**
* Move the specified messages from this folder, to the
* specified destination.
* Return array of AppendUID objects containing
* UIDs of these messages in the destination folder.
* Each element of the returned array corresponds to
* an element of the
*
* Depends on the MOVE extension
* (RFC 6851)
* and the COPYUID response code defined by the
* UIDPLUS extension
* (RFC 4315).
*
* @param msgs the messages to move
* @param folder the folder to move the messages to
* @return array of AppendUID objects
* @exception MessagingException for failures
* @since JavaMail 1.5.4
*/
public synchronized AppendUID[] moveUIDMessages(Message[] msgs,
Folder folder) throws MessagingException {
return copymoveUIDMessages(msgs, folder, true);
}
/**
* Copy or move the specified messages from this folder, to the
* specified destination.
*
* @since JavaMail 1.5.4
*/
private synchronized void copymoveMessages(Message[] msgs, Folder folder,
boolean move) throws MessagingException {
checkOpened();
if (msgs.length == 0) // boundary condition
return;
// If the destination belongs to our same store, optimize
if (folder.getStore() == store) {
synchronized(messageCacheLock) {
try {
IMAPProtocol p = getProtocol();
if (p.hasCapability("X-UIDONLY") ||
(p.hasCapability("UIDPLUS") &&
Boolean.parseBoolean(System.getProperty("fairemail.uid_command")))) {
// Verizon
FetchProfile fp = new FetchProfile();
fp.add(UIDFolder.FetchProfileItem.UID);
fetch(msgs, fp);
UIDSet[] uids = Utility.toUIDSet(msgs);
if (uids == null)
return;
Argument args = new Argument();
args.writeAtom(UIDSet.toString(uids));
p.writeMailboxName(args, folder.getFullName());
Response[] r = p.command(move ? "UID MOVE" : "UID COPY", args);
p.notifyResponseHandlers(r);
p.handleResult(r[r.length - 1]);
return;
}
MessageSet[] ms = Utility.toMessageSet(msgs, null);
if (ms == null)
throw new MessageRemovedException(
"Messages have been removed");
if (move)
p.move(ms, folder.getFullName());
else
p.copy(ms, folder.getFullName());
} catch (CommandFailedException cfx) {
if (cfx.getMessage().indexOf("TRYCREATE") != -1)
throw new FolderNotFoundException(
folder,
folder.getFullName() + " does not exist"
);
else
throw new MessagingException(cfx.getMessage(), cfx);
} catch (ConnectionException cex) {
throw new FolderClosedException(this, cex.getMessage());
} catch (ProtocolException pex) {
throw new MessagingException(pex.getMessage(), pex);
}
}
} else // destination is a different store.
if (move)
throw new MessagingException(
"Move between stores not supported");
else
super.copyMessages(msgs, folder);
}
/**
* Copy or move the specified messages from this folder, to the
* specified destination.
* Return array of AppendUID objects containing
* UIDs of these messages in the destination folder.
* Each element of the returned array corresponds to
* an element of the
*
* Depends on the COPYUID response code defined by the
* UIDPLUS extension -
* RFC 4315.
* Move depends on the MOVE extension -
* RFC 6851.
*
* @param msgs the messages to copy
* @param folder the folder to copy the messages to
* @param move move instead of copy?
* @return array of AppendUID objects
* @exception MessagingException for failures
* @since JavaMail 1.5.4
*/
private synchronized AppendUID[] copymoveUIDMessages(Message[] msgs,
Folder folder, boolean move) throws MessagingException {
checkOpened();
if (msgs.length == 0) // boundary condition
return null;
// the destination must belong to our same store
if (folder.getStore() != store) // destination is a different store.
throw new MessagingException(
move ?
"can't moveUIDMessages to a different store" :
"can't copyUIDMessages to a different store");
// call fetch to make sure we have all the UIDs
// necessary to interpret the COPYUID response
FetchProfile fp = new FetchProfile();
fp.add(UIDFolder.FetchProfileItem.UID);
fetch(msgs, fp);
// XXX - could pipeline the FETCH with the COPY/MOVE below
synchronized (messageCacheLock) {
try {
IMAPProtocol p = getProtocol();
// XXX - messages have to be from this Folder, who checks?
MessageSet[] ms = Utility.toMessageSet(msgs, null);
if (ms == null)
throw new MessageRemovedException(
"Messages have been removed");
CopyUID cuid;
if (move)
cuid = p.moveuid(ms, folder.getFullName());
else
cuid = p.copyuid(ms, folder.getFullName());
/*
* Correlate source UIDs with destination UIDs.
* This won't be time or space efficient if there's
* a lot of messages.
*
* In order to make sense of the returned UIDs, we need
* the UIDs for every one of the original messages.
* We fetch them above, to make sure we have them.
* This is critical for MOVE since after the MOVE the
* messages are gone/expunged.
*
* Assume the common case is that the messages are
* in order by UID. Map the returned source
* UIDs to their corresponding Message objects.
* Step through the msgs array looking for the
* Message object in the returned source message
* list. Most commonly the source message (UID)
* for the Nth original message will be in the Nth
* position in the returned source message (UID)
* list. Thus, the destination UID is in the Nth
* position in the returned destination UID list.
* But if the source message isn't where expected,
* we have to search the entire source message
* list, starting from where we expect it and
* wrapping around until we've searched it all.
* (Gmail will often return the lists in an unexpected order.)
*
* A possible optimization:
* If the number of UIDs returned is the same as the
* number of messages being copied/moved, we could
* sort the source messages by message number, sort
* the source and destination parallel arrays by source
* UID, and the resulting message and destination UID
* arrays will correspond.
*
* If the returned UID array size is different, some
* message was expunged while we were trying to copy/move it.
* This should be rare but would mean falling back to the
* general algorithm.
*/
long[] srcuids = UIDSet.toArray(cuid.src);
long[] dstuids = UIDSet.toArray(cuid.dst);
// map source UIDs to Message objects
// XXX - could inline/optimize this
Message[] srcmsgs = getMessagesByUID(srcuids);
AppendUID[] result = new AppendUID[msgs.length];
for (int i = 0; i < msgs.length; i++) {
int j = i;
do {
if (msgs[i] == srcmsgs[j]) {
result[i] = new AppendUID(
cuid.uidvalidity, dstuids[j]);
break;
}
j++;
if (j >= srcmsgs.length)
j = 0;
} while (j != i);
}
return result;
} catch (CommandFailedException cfx) {
if (cfx.getMessage().indexOf("TRYCREATE") != -1)
throw new FolderNotFoundException(
folder,
folder.getFullName() + " does not exist"
);
else
throw new MessagingException(cfx.getMessage(), cfx);
} catch (ConnectionException cex) {
throw new FolderClosedException(this, cex.getMessage());
} catch (ProtocolException pex) {
throw new MessagingException(pex.getMessage(), pex);
}
}
}
/**
* Expunge all messages marked as DELETED.
*/
@Override
public synchronized Message[] expunge() throws MessagingException {
return expunge(null);
}
/**
* Expunge the indicated messages, which must have been marked as DELETED.
*
* Depends on the UIDPLUS extension -
* RFC 4315.
*
* @param msgs the messages to expunge
* @return the expunged messages
* @exception MessagingException for failures
*/
public synchronized Message[] expunge(Message[] msgs)
throws MessagingException {
checkOpened();
if (msgs != null) {
// call fetch to make sure we have all the UIDs
FetchProfile fp = new FetchProfile();
fp.add(UIDFolder.FetchProfileItem.UID);
fetch(msgs, fp);
}
IMAPMessage[] rmsgs;
synchronized(messageCacheLock) {
doExpungeNotification = false; // We do this ourselves later
try {
IMAPProtocol p = getProtocol();
if (msgs != null)
p.uidexpunge(Utility.toUIDSet(msgs));
else
p.expunge();
} catch (CommandFailedException cfx) {
// expunge not allowed, perhaps due to a permission problem?
if (mode != READ_WRITE)
throw new IllegalStateException(
"Cannot expunge READ_ONLY folder: " + fullName);
else
throw new MessagingException(cfx.getMessage(), cfx);
} catch (ConnectionException cex) {
throw new FolderClosedException(this, cex.getMessage());
} catch (ProtocolException pex) {
// Bad bad server ..
throw new MessagingException(pex.getMessage(), pex);
} finally {
doExpungeNotification = true;
}
// Cleanup expunged messages and sync messageCache with reality.
if (msgs != null)
rmsgs = messageCache.removeExpungedMessages(msgs);
else
rmsgs = messageCache.removeExpungedMessages();
if (uidTable != null) {
for (int i = 0; i < rmsgs.length; i++) {
IMAPMessage m = rmsgs[i];
/* remove this message from the UIDTable */
long uid = m.getUID();
if (uid != -1)
uidTable.remove(Long.valueOf(uid));
}
}
// Update 'total'
total = messageCache.size();
}
// Notify listeners. This time its for real, guys.
if (rmsgs.length > 0)
notifyMessageRemovedListeners(true, rmsgs);
return rmsgs;
}
/**
* Search whole folder for messages matching the given term.
* If the property
*
* Depends on the SORT extension -
* RFC 5256.
*
* @param term the SortTerms
* @return the messages in sorted order
* @exception MessagingException for failures
* @since JavaMail 1.4.4
*/
public synchronized Message[] getSortedMessages(SortTerm[] term)
throws MessagingException {
return getSortedMessages(term, null);
}
/**
* Sort the messages in the folder according to the sort criteria.
* The messages are returned in the sorted order, but the order of
* the messages in the folder is not changed. Only messages matching
* the search criteria are considered.
*
* Depends on the SORT extension -
* RFC 5256.
*
* @param term the SortTerms
* @param sterm the SearchTerm
* @return the messages in sorted order
* @exception MessagingException for failures
* @since JavaMail 1.4.4
*/
public synchronized Message[] getSortedMessages(SortTerm[] term,
SearchTerm sterm) throws MessagingException {
checkOpened();
try {
Message[] matchMsgs = null;
synchronized(messageCacheLock) {
int[] matches = getProtocol().sort(term, sterm);
if (matches != null)
matchMsgs = getMessagesBySeqNumbers(matches);
}
return matchMsgs;
} catch (CommandFailedException cfx) {
// unsupported charset or search criterion
throw new MessagingException(cfx.getMessage(), cfx);
} catch (SearchException sex) {
// too complex for IMAP
throw new MessagingException(sex.getMessage(), sex);
} catch (ConnectionException cex) {
throw new FolderClosedException(this, cex.getMessage());
} catch (ProtocolException pex) {
// bug in our IMAP layer ?
throw new MessagingException(pex.getMessage(), pex);
}
}
/*
* Override Folder method to keep track of whether we have any
* message count listeners. Normally we won't have any, so we
* can avoid creating message objects to pass to the notify
* method. It's too hard to keep track of when all listeners
* are removed, and that's a rare case, so we don't try.
*/
@Override
public synchronized void addMessageCountListener(MessageCountListener l) {
super.addMessageCountListener(l);
hasMessageCountListener = true;
}
/***********************************************************
* UIDFolder interface methods
**********************************************************/
/**
* Returns the UIDValidity for this folder.
*/
@Override
public synchronized long getUIDValidity() throws MessagingException {
if (opened) // we already have this information
return uidvalidity;
IMAPProtocol p = null;
Status status = null;
try {
p = getStoreProtocol(); // XXX
String[] item = { "UIDVALIDITY" };
status = p.status(fullName, item);
} catch (BadCommandException bex) {
// Probably a RFC1730 server
throw new MessagingException("Cannot obtain UIDValidity", bex);
} catch (ConnectionException cex) {
// Oops, the store or folder died on us.
throwClosedException(cex);
} catch (ProtocolException pex) {
throw new MessagingException(pex.getMessage(), pex);
} finally {
releaseStoreProtocol(p);
}
if (status == null)
throw new MessagingException("Cannot obtain UIDValidity");
return status.uidvalidity;
}
/**
* Returns the predicted UID that will be assigned to the
* next message that is appended to this folder.
* If the folder is closed, the STATUS command is used to
* retrieve this value. If the folder is open, the value
* returned from the SELECT or EXAMINE command is returned.
* Note that messages may have been appended to the folder
* while it was open and thus this value may be out of
* date.
*
* Servers implementing RFC2060 likely won't return this value
* when a folder is opened. Servers implementing RFC3501
* should return this value when a folder is opened.
*
* @return the UIDNEXT value, or -1 if unknown
* @exception MessagingException for failures
* @since JavaMail 1.3.3
*/
@Override
public synchronized long getUIDNext() throws MessagingException {
if (opened) // we already have this information
return uidnext;
IMAPProtocol p = null;
Status status = null;
try {
p = getStoreProtocol(); // XXX
String[] item = { "UIDNEXT" };
status = p.status(fullName, item);
} catch (BadCommandException bex) {
// Probably a RFC1730 server
throw new MessagingException("Cannot obtain UIDNext", bex);
} catch (ConnectionException cex) {
// Oops, the store or folder died on us.
throwClosedException(cex);
} catch (ProtocolException pex) {
throw new MessagingException(pex.getMessage(), pex);
} finally {
releaseStoreProtocol(p);
}
if (status == null)
throw new MessagingException("Cannot obtain UIDNext");
return status.uidnext;
}
/**
* Get the Message corresponding to the given UID.
* If no such message exists,
* Returns Message objects for all valid messages in this range.
* Returns an empty array if no messages are found.
*/
@Override
public synchronized Message[] getMessagesByUID(long start, long end)
throws MessagingException {
checkOpened(); // insure that folder is open
Message[] msgs; // array of messages to be returned
try {
synchronized(messageCacheLock) {
if (uidTable == null)
uidTable = new Hashtable<>();
// Issue UID FETCH for given range
long[] ua = getProtocol().fetchSequenceNumbers(start, end);
List
*
*
*
* The server must support the CONDSTORE extension.
*
* @param start the first message number
* @param end the last message number
* @param modseq the MODSEQ value
* @return the changed messages
* @exception MessagingException for failures
* @see "RFC 4551"
* @since JavaMail 1.5.1
*/
public synchronized Message[] getMessagesByUIDChangedSince(
long start, long end, long modseq)
throws MessagingException {
checkOpened(); // insure that folder is open
try {
synchronized (messageCacheLock) {
IMAPProtocol p = getProtocol();
if (!p.hasCapability("CONDSTORE"))
throw new BadCommandException("CONDSTORE not supported");
// Issue FETCH for given range
int[] nums = p.uidfetchChangedSince(start, end, modseq);
return getMessagesBySeqNumbers(nums);
}
} catch(ConnectionException cex) {
throw new FolderClosedException(this, cex.getMessage());
} catch (ProtocolException pex) {
throw new MessagingException(pex.getMessage(), pex);
}
}
/**
* Get the quotas for the quotaroot associated with this
* folder. Note that many folders may have the same quotaroot.
* Quotas are controlled on the basis of a quotaroot, not
* (necessarily) a folder. The relationship between folders
* and quotaroots depends on the IMAP server. Some servers
* might implement a single quotaroot for all folders owned by
* a user. Other servers might implement a separate quotaroot
* for each folder. A single folder can even have multiple
* quotaroots, perhaps controlling quotas for different
* resources.
*
* @return array of Quota objects for the quotaroots associated with
* this folder
* @exception MessagingException if the server doesn't support the
* QUOTA extension
*/
public Quota[] getQuota() throws MessagingException {
return (Quota[])doOptionalCommand("QUOTA not supported",
new ProtocolCommand() {
@Override
public Object doCommand(IMAPProtocol p)
throws ProtocolException {
return p.getQuotaRoot(fullName);
}
});
}
/**
* Set the quotas for the quotaroot specified in the quota argument.
* Typically this will be one of the quotaroots associated with this
* folder, as obtained from the
*
* Note that this method lists the rights that it is possible to
* assign to the given identifier, not the rights that are
* actually granted to the given identifier. For the latter, see
* the
*
* The mail.imap.minidletime property enforces a minimum delay
* before returning from this method, to ensure that other threads
* have a chance to issue commands before the caller invokes this
* method again. The default delay is 10 milliseconds.
*
* @exception MessagingException if the server doesn't support the
* IDLE extension
* @exception IllegalStateException if the folder isn't open
*
* @since JavaMail 1.4.1
*/
public void idle() throws MessagingException {
idle(false);
}
/**
* Like {@link #idle}, but if
*
* This method will block if necessary to wait for an IDLE
* command to finish.
*
* @return the IMAPProtocol object used when the folder is open
* @exception ProtocolException for protocol errors
*/
protected IMAPProtocol getProtocol() throws ProtocolException {
assert Thread.holdsLock(messageCacheLock);
waitIfIdle();
// if we no longer have a protocol object after waiting, it probably
// means the connection has been closed due to a communnication error,
// or possibly because the folder has been closed
if (protocol == null)
throw new ConnectionException("Connection closed");
return protocol;
}
/**
* A simple interface for user-defined IMAP protocol commands.
*/
public static interface ProtocolCommand {
/**
* Execute the user-defined command using the supplied IMAPProtocol
* object.
*
* @param protocol the IMAPProtocol for the connection
* @return the results of the command
* @exception ProtocolException for protocol errors
*/
public Object doCommand(IMAPProtocol protocol) throws ProtocolException;
}
/**
* Execute a user-supplied IMAP command. The command is executed
* in the appropriate context with the necessary locks held and
* using the appropriate
*
* This method returns whatever the
*
* The following example shows how to execute the IMAP NOOP command.
* Executing more complex IMAP commands requires intimate knowledge
* of the
*
* Here's a more complex example showing how to use the proposed
* IMAP SORT extension:
*
* FetchProfile.Item
* class to add new FetchProfile item types, specific to IMAPFolders.
*
* @see FetchProfile
*/
public static class FetchProfileItem extends FetchProfile.Item {
protected FetchProfileItem(String name) {
super(name);
}
/**
* HEADERS is a fetch profile item that can be included in a
* FetchProfile
during a fetch request to a Folder.
* This item indicates that the headers for messages in the specified
* range are desired to be prefetched.
*/
public static final FetchProfileItem HEADERS =
new FetchProfileItem("HEADERS");
/**
* SIZE is a fetch profile item that can be included in a
*
*
* FetchProfile fp = new FetchProfile();
* fp.add(IMAPFolder.FetchProfileItem.HEADERS);
* folder.fetch(msgs, fp);
*
*
FetchProfile
during a fetch request to a Folder.
* This item indicates that the sizes of the messages in the specified
* range are desired to be prefetched. FetchProfile
during a fetch request to a Folder.
* This item indicates that the entire messages (headers and body,
* including all "attachments") in the specified
* range are desired to be prefetched. Note that the entire message
* content is cached in memory while the Folder is open. The cached
* message will be parsed locally to return header information and
* message content.
*
* @since JavaMail 1.5.2
*/
public static final FetchProfileItem MESSAGE =
new FetchProfileItem("MESSAGE");
/**
* INTERNALDATE is a fetch profile item that can be included in a
*
*
* FetchProfile fp = new FetchProfile();
* fp.add(IMAPFolder.FetchProfileItem.MESSAGE);
* folder.fetch(msgs, fp);
*
*
FetchProfile
during a fetch request to a Folder.
* This item indicates that the IMAP INTERNALDATE values
* (received date) of the messages in the specified
* range are desired to be prefetched.
*
* @since JavaMail 1.5.5
*/
public static final FetchProfileItem INTERNALDATE =
new FetchProfileItem("INTERNALDATE");
}
/**
* Constructor used to create a possibly non-existent folder.
*
* @param fullName fullname of this folder
* @param separator the default separator character for this
* folder's namespace
* @param store the Store
* @param isNamespace if this folder represents a namespace
*/
protected IMAPFolder(String fullName, char separator, IMAPStore store,
Boolean isNamespace) {
super(store);
if (fullName == null)
throw new NullPointerException("Folder name is null");
this.fullName = fullName;
this.separator = separator;
logger = new MailLogger(this.getClass(), "DEBUG IMAP",
store.getSession().getDebug(), store.getSession().getDebugOut());
connectionPoolLogger = store.getConnectionPoolLogger();
/*
* Work around apparent bug in Exchange. Exchange
* will return a name of "Public Folders/" from
* LIST "%".
*
* If name has one separator, and it's at the end,
* assume this is a namespace name and treat it
* accordingly. Usually this will happen as a result
* of the list method, but this also allows getFolder
* to work with namespace names.
*/
this.isNamespace = false;
if (separator != UNKNOWN_SEPARATOR && separator != '\0') {
int i = this.fullName.indexOf(separator);
if (i > 0 && i == this.fullName.length() - 1) {
this.fullName = this.fullName.substring(0, i);
this.isNamespace = true;
}
}
// if we were given a value, override default chosen above
if (isNamespace != null)
this.isNamespace = isNamespace.booleanValue();
}
/**
* Constructor used to create an existing folder.
*
* @param li the ListInfo for this folder
* @param store the store containing this folder
*/
protected IMAPFolder(ListInfo li, IMAPStore store) {
this(li.name, li.separator, store, null);
if (li.hasInferiors)
type |= HOLDS_FOLDERS;
if (li.canOpen)
type |= HOLDS_MESSAGES;
exists = true;
attributes = li.attrs;
}
/*
* Ensure that this folder exists. If 'exists' has been set to true,
* we don't attempt to validate it with the server again. Note that
* this can result in a possible loss of sync with the server.
* ASSERT: Must be called with this folder's synchronization lock held.
*/
protected void checkExists() throws MessagingException {
// If the boolean field 'exists' is false, check with the
// server by invoking exists() ..
if (!exists && !exists())
throw new FolderNotFoundException(
this, fullName + " not found");
}
/*
* Ensure the folder is closed.
* ASSERT: Must be called with this folder's synchronization lock held.
*/
protected void checkClosed() {
if (opened)
throw new IllegalStateException(
"This operation is not allowed on an open folder"
);
}
/*
* Ensure the folder is open.
* ASSERT: Must be called with this folder's synchronization lock held.
*/
protected void checkOpened() throws FolderClosedException {
assert Thread.holdsLock(this);
if (!opened) {
if (reallyClosed)
throw new IllegalStateException(
"This operation is not allowed on a closed folder"
);
else // Folder was closed "implicitly"
throw new FolderClosedException(this,
"Lost folder connection to server"
);
}
}
/*
* Check that the given message number is within the range
* of messages present in this folder. If the message
* number is out of range, we ping the server to obtain any
* pending new message notifications from the server.
*/
protected void checkRange(int msgno) throws MessagingException {
if (msgno < 1) // message-numbers start at 1
throw new IndexOutOfBoundsException("message number < 1");
if (msgno <= total)
return;
// Out of range, let's ping the server and see if
// the server has more messages for us.
synchronized(messageCacheLock) { // Acquire lock
try {
keepConnectionAlive(false);
} catch (ConnectionException cex) {
// Oops, lost connection
throw new FolderClosedException(this, cex.getMessage());
} catch (ProtocolException pex) {
throw new MessagingException(pex.getMessage(), pex);
}
} // Release lock
if (msgno > total) // Still out of range ? Throw up ...
throw new IndexOutOfBoundsException(msgno + " > " + total);
}
/*
* Check whether the given flags are supported by this server,
* and also verify that the folder allows setting flags.
*/
private void checkFlags(Flags flags) throws MessagingException {
assert Thread.holdsLock(this);
if (mode != READ_WRITE)
throw new IllegalStateException(
"Cannot change flags on READ_ONLY folder: " + fullName
);
/*
if (!availableFlags.contains(flags))
throw new MessagingException(
"These flags are not supported by this implementation"
);
*/
}
/**
* Get the name of this folder.
*/
@Override
public synchronized String getName() {
/* Return the last component of this Folder's full name.
* Folder components are delimited by the separator character.
*/
if (name == null) {
try {
name = fullName.substring(
fullName.lastIndexOf(getSeparator()) + 1
);
} catch (MessagingException mex) { }
}
return name;
}
/**
* Get the fullname of this folder.
*/
@Override
public String getFullName() {
return fullName;
}
/**
* Get this folder's parent.
*/
@Override
public synchronized Folder getParent() throws MessagingException {
char c = getSeparator();
int index;
if ((index = fullName.lastIndexOf(c)) != -1)
return ((IMAPStore)store).newIMAPFolder(
fullName.substring(0, index), c);
else
return new DefaultFolder((IMAPStore)store);
}
/**
* Check whether this folder really exists on the server.
*/
@Override
public synchronized boolean exists() throws MessagingException {
// Check whether this folder exists ..
ListInfo[] li = null;
final String lname;
if (isNamespace && separator != '\0')
lname = fullName + separator;
else
lname = fullName;
li = (ListInfo[])doCommand(new ProtocolCommand() {
@Override
public Object doCommand(IMAPProtocol p) throws ProtocolException {
return p.list("", lname);
}
});
if (li != null) {
int i = findName(li, lname);
fullName = li[i].name;
separator = li[i].separator;
int len = fullName.length();
if (separator != '\0' && len > 0 &&
fullName.charAt(len - 1) == separator) {
fullName = fullName.substring(0, len - 1);
}
type = 0;
if (li[i].hasInferiors)
type |= HOLDS_FOLDERS;
if (li[i].canOpen)
type |= HOLDS_MESSAGES;
exists = true;
attributes = li[i].attrs;
} else {
exists = opened;
attributes = null;
}
return exists;
}
/**
* Which entry in
*
* FetchProfile fp = new FetchProfile();
* fp.add(IMAPFolder.FetchProfileItem.INTERNALDATE);
* folder.fetch(msgs, fp);
*
*
li
matches lname
?
* If the name contains wildcards, more than one entry may be
* returned.
*/
private int findName(ListInfo[] li, String lname) {
int i;
// if the name contains a wildcard, there might be more than one
for (i = 0; i < li.length; i++) {
if (li[i].name.equals(lname))
break;
}
if (i >= li.length) { // nothing matched exactly
// XXX - possibly should fail? But what if server
// is case insensitive and returns the preferred
// case of the name here?
i = 0; // use first one
}
return i;
}
/**
* List all subfolders matching the specified pattern.
*/
@Override
public Folder[] list(String pattern) throws MessagingException {
return doList(pattern, false);
}
/**
* List all subscribed subfolders matching the specified pattern.
*/
@Override
public Folder[] listSubscribed(String pattern) throws MessagingException {
return doList(pattern, true);
}
private synchronized Folder[] doList(final String pattern,
final boolean subscribed) throws MessagingException {
checkExists(); // insure that this folder does exist.
// Why waste a roundtrip to the server?
if (attributes != null && !isDirectory())
return new Folder[0];
final char c = getSeparator();
ListInfo[] li = (ListInfo[])doCommandIgnoreFailure(
new ProtocolCommand() {
@Override
public Object doCommand(IMAPProtocol p)
throws ProtocolException {
if (subscribed)
return p.lsub("", fullName + c + pattern);
else
return p.list("", fullName + c + pattern);
}
});
if (li == null)
return new Folder[0];
/*
* The UW based IMAP4 servers (e.g. SIMS2.0) include
* current folder (terminated with the separator), when
* the LIST pattern is '%' or '*'. i.e,
* returns "mail/" as the first LIST response.
*
* Doesn't make sense to include the current folder in this
* case, so we filter it out. Note that I'm assuming that
* the offending response is the *first* one, my experiments
* with the UW & SIMS2.0 servers indicate that ..
*/
int start = 0;
// Check the first LIST response.
if (li.length > 0 && li[0].name.equals(fullName + c))
start = 1; // start from index = 1
IMAPFolder[] folders = new IMAPFolder[li.length - start];
IMAPStore st = (IMAPStore)store;
for (int i = start; i < li.length; i++)
folders[i-start] = st.newIMAPFolder(li[i]);
return folders;
}
/**
* Get the separator character.
*/
@Override
public synchronized char getSeparator() throws MessagingException {
if (separator == UNKNOWN_SEPARATOR) {
ListInfo[] li = null;
li = (ListInfo[])doCommand(new ProtocolCommand() {
@Override
public Object doCommand(IMAPProtocol p)
throws ProtocolException {
// REV1 allows the following LIST format to obtain
// the hierarchy delimiter of non-existent folders
if (p.isREV1()) // IMAP4rev1
return p.list(fullName, "");
else // IMAP4, note that this folder must exist for this
// to work :(
return p.list("", fullName);
}
});
if (li != null)
separator = li[0].separator;
else
separator = '/'; // punt !
}
return separator;
}
/**
* Get the type of this folder.
*/
@Override
public synchronized int getType() throws MessagingException {
if (opened) {
// never throw FolderNotFoundException if folder is open
if (attributes == null)
exists(); // try to fetch attributes
} else {
checkExists();
}
return type;
}
/**
* Check whether this folder is subscribed.
*/
@Override
public synchronized boolean isSubscribed() {
ListInfo[] li = null;
final String lname;
if (isNamespace && separator != '\0')
lname = fullName + separator;
else
lname = fullName;
try {
li = (ListInfo[])doProtocolCommand(new ProtocolCommand() {
@Override
public Object doCommand(IMAPProtocol p)
throws ProtocolException {
return p.lsub("", lname);
}
});
} catch (ProtocolException pex) {
}
if (li != null) {
int i = findName(li, lname);
return li[i].canOpen;
} else
return false;
}
/**
* Subscribe/Unsubscribe this folder.
*/
@Override
public synchronized void setSubscribed(final boolean subscribe)
throws MessagingException {
doCommandIgnoreFailure(new ProtocolCommand() {
@Override
public Object doCommand(IMAPProtocol p) throws ProtocolException {
if (subscribe)
p.subscribe(fullName);
else
p.unsubscribe(fullName);
return null;
}
});
}
/**
* Create this folder, with the specified type.
*/
@Override
public synchronized boolean create(final int type)
throws MessagingException {
char c = 0;
if ((type & HOLDS_MESSAGES) == 0) // only holds folders
c = getSeparator();
final char sep = c;
Object ret = doCommandIgnoreFailure(new ProtocolCommand() {
@Override
public Object doCommand(IMAPProtocol p)
throws ProtocolException {
if ((type & HOLDS_MESSAGES) == 0) // only holds folders
p.create(fullName + sep);
else {
p.create(fullName);
// Certain IMAP servers do not allow creation of folders
// that can contain messages *and* subfolders. So, if we
// were asked to create such a folder, we should verify
// that we could indeed do so.
if ((type & HOLDS_FOLDERS) != 0) {
// we want to hold subfolders and messages. Check
// whether we could create such a folder.
ListInfo[] li = p.list("", fullName);
if (li != null && !li[0].hasInferiors) {
// Hmm ..the new folder
// doesn't support Inferiors ? Fail
p.delete(fullName);
throw new ProtocolException("Unsupported type");
}
}
}
return Boolean.TRUE;
}
});
if (ret == null)
return false; // CREATE failure, maybe this
// folder already exists ?
// exists = true;
// this.type = type;
boolean retb = exists(); // set exists, type, and attributes
if (retb) // Notify listeners on self and our Store
notifyFolderListeners(FolderEvent.CREATED);
return retb;
}
/**
* Check whether this folder has new messages.
*/
@Override
public synchronized boolean hasNewMessages() throws MessagingException {
synchronized (messageCacheLock) {
if (opened) { // If we are open, we already have this information
// Folder is open, make sure information is up to date
// tickle the folder and store connections.
try {
keepConnectionAlive(true);
} catch (ConnectionException cex) {
throw new FolderClosedException(this, cex.getMessage());
} catch (ProtocolException pex) {
throw new MessagingException(pex.getMessage(), pex);
}
return recent > 0 ? true : false;
}
}
// First, the cheap way - use LIST and look for the \Marked
// or \Unmarked tag
ListInfo[] li = null;
final String lname;
if (isNamespace && separator != '\0')
lname = fullName + separator;
else
lname = fullName;
li = (ListInfo[])doCommandIgnoreFailure(new ProtocolCommand() {
@Override
public Object doCommand(IMAPProtocol p) throws ProtocolException {
return p.list("", lname);
}
});
// if folder doesn't exist, throw exception
if (li == null)
throw new FolderNotFoundException(this, fullName + " not found");
int i = findName(li, lname);
if (li[i].changeState == ListInfo.CHANGED)
return true;
else if (li[i].changeState == ListInfo.UNCHANGED)
return false;
// LIST didn't work. Try the hard way, using STATUS
try {
Status status = getStatus();
if (status.recent > 0)
return true;
else
return false;
} catch (BadCommandException bex) {
// Probably doesn't support STATUS, tough luck.
return false;
} catch (ConnectionException cex) {
throw new StoreClosedException(store, cex.getMessage());
} catch (ProtocolException pex) {
throw new MessagingException(pex.getMessage(), pex);
}
}
/**
* Get the named subfolder.
*/
@Override
public synchronized Folder getFolder(String name)
throws MessagingException {
// If we know that this folder is *not* a directory, don't
// send the request to the server at all ...
if (attributes != null && !isDirectory())
throw new MessagingException("Cannot contain subfolders");
char c = getSeparator();
return ((IMAPStore)store).newIMAPFolder(fullName + c + name, c);
}
/**
* Delete this folder.
*/
@Override
public synchronized boolean delete(boolean recurse)
throws MessagingException {
checkClosed(); // insure that this folder is closed.
if (recurse) {
// Delete all subfolders.
Folder[] f = list();
for (int i = 0; i < f.length; i++)
f[i].delete(recurse); // ignore intermediate failures
}
// Attempt to delete this folder
Object ret = doCommandIgnoreFailure(new ProtocolCommand() {
@Override
public Object doCommand(IMAPProtocol p) throws ProtocolException {
p.delete(fullName);
return Boolean.TRUE;
}
});
if (ret == null)
// Non-existent folder/No permission ??
return false;
// DELETE succeeded.
exists = false;
attributes = null;
// Notify listeners on self and our Store
notifyFolderListeners(FolderEvent.DELETED);
return true;
}
/**
* Rename this folder.
*/
@Override
public synchronized boolean renameTo(final Folder f)
throws MessagingException {
checkClosed(); // insure that we are closed.
checkExists();
if (f.getStore() != store)
throw new MessagingException("Can't rename across Stores");
Object ret = doCommandIgnoreFailure(new ProtocolCommand() {
@Override
public Object doCommand(IMAPProtocol p) throws ProtocolException {
p.rename(fullName, f.getFullName());
return Boolean.TRUE;
}
});
if (ret == null)
return false;
exists = false;
attributes = null;
notifyFolderRenamedListeners(f);
return true;
}
/**
* Open this folder in the given mode.
*/
@Override
public synchronized void open(int mode) throws MessagingException {
open(mode, null);
}
/**
* Open this folder in the given mode, with the given
* resynchronization data.
*
* @param mode the open mode (Folder.READ_WRITE or Folder.READ_ONLY)
* @param rd the ResyncData instance
* @return a List of MailEvent instances, or null if none
* @exception MessagingException if the open fails
* @since JavaMail 1.5.1
*/
public synchronized List
msgs
array. A null
* element means the server didn't return UID information
* for the appended message. msgs
array. A null
* element means the server didn't return UID information
* for the appended message. msgs
array. A null
* element means the server didn't return UID information
* for the copied message. msgs
array. A null
* element means the server didn't return UID information
* for the moved message. msgs
array. A null
* element means the server didn't return UID information
* for the copied message. mail.imap.throwsearchexception
is true,
* and the search term is too complex for the IMAP protocol,
* SearchException is thrown. Otherwise, if the search term is too
* complex, super.search
is called to do the search on
* the client.
*
* @param term the search term
* @return the messages that match
* @exception SearchException if mail.imap.throwsearchexception is
* true and the search is too complex for the IMAP protocol
* @exception MessagingException for other failures
*/
@Override
public synchronized Message[] search(SearchTerm term)
throws MessagingException {
checkOpened();
try {
Message[] matchMsgs = null;
synchronized(messageCacheLock) {
int[] matches = getProtocol().search(term);
if (matches != null)
matchMsgs = getMessagesBySeqNumbers(matches);
}
return matchMsgs;
} catch (CommandFailedException cfx) {
// unsupported charset or search criterion
eu.faircode.email.Log.e(new MessagingException(term.getClass().getName(), cfx));
if (term instanceof MessageIDTerm) // Yandex: NO [UNAVAILABLE] SEARCH Backend error. sc=...
return new Message[0];
return super.search(term);
} catch (SearchException sex) {
// too complex for IMAP
if (((IMAPStore)store).throwSearchException())
throw sex;
return super.search(term);
} catch (ConnectionException cex) {
throw new FolderClosedException(this, cex.getMessage());
} catch (ProtocolException pex) {
// bug in our IMAP layer ?
throw new MessagingException(pex.getMessage(), pex);
}
}
/**
* Search the folder for messages matching the given term. Returns
* array of matching messages. Returns an empty array if no matching
* messages are found.
*/
@Override
public synchronized Message[] search(SearchTerm term, Message[] msgs)
throws MessagingException {
checkOpened();
if (msgs.length == 0)
// need to return an empty array (not null!)
return msgs;
try {
Message[] matchMsgs = null;
synchronized(messageCacheLock) {
IMAPProtocol p = getProtocol();
MessageSet[] ms = Utility.toMessageSetSorted(msgs, null);
if (ms == null)
throw new MessageRemovedException(
"Messages have been removed");
int[] matches = p.search(ms, term);
if (matches != null)
matchMsgs = getMessagesBySeqNumbers(matches);
}
return matchMsgs;
} catch (CommandFailedException cfx) {
// unsupported charset or search criterion
return super.search(term, msgs);
} catch (SearchException sex) {
// too complex for IMAP
return super.search(term, msgs);
} catch (ConnectionException cex) {
throw new FolderClosedException(this, cex.getMessage());
} catch (ProtocolException pex) {
// bug in our IMAP layer ?
throw new MessagingException(pex.getMessage(), pex);
}
}
/**
* Sort the messages in the folder according to the sort criteria.
* The messages are returned in the sorted order, but the order of
* the messages in the folder is not changed. null
is returned.
*/
@Override
public synchronized Message getMessageByUID(long uid)
throws MessagingException {
checkOpened(); // insure folder is open
IMAPMessage m = null;
try {
synchronized(messageCacheLock) {
Long l = Long.valueOf(uid);
if (uidTable != null) {
// Check in uidTable
m = uidTable.get(l);
if (m != null) // found it
return m;
} else
uidTable = new Hashtable<>();
// Check with the server
// Issue UID FETCH command
getProtocol().fetchSequenceNumber(uid);
if (uidTable != null) {
// Check in uidTable
m = uidTable.get(l);
if (m != null) // found it
return m;
}
}
} catch(ConnectionException cex) {
throw new FolderClosedException(this, cex.getMessage());
} catch (ProtocolException pex) {
throw new MessagingException(pex.getMessage(), pex);
} catch (ArrayIndexOutOfBoundsException ex) {
eu.faircode.email.Log.w(ex);
/*
java.lang.ArrayIndexOutOfBoundsException: message number (0) out of bounds (110)
at com.sun.mail.imap.MessageCache.getMessage(SourceFile:116)
at com.sun.mail.imap.MessageCache.getMessageBySeqnum(SourceFile:148)
at com.sun.mail.imap.IMAPFolder.getMessageBySeqNumber(SourceFile:3999)
at com.sun.mail.imap.IMAPFolder.processFetchResponse(SourceFile:3604)
at com.sun.mail.imap.IMAPFolder.handleResponse(SourceFile:3586)
at com.sun.mail.iap.Protocol.notifyResponseHandlers(SourceFile:245)
at com.sun.mail.imap.protocol.IMAPProtocol.fetchSequenceNumber(SourceFile:2057)
at com.sun.mail.imap.IMAPFolder.getMessageByUID(SourceFile:2598)
*/
return null;
}
return m;
}
/**
* Get the Messages specified by the given range. uids.length()
elements are returned.
* If any UID in the array is invalid, a null
entry
* is returned for that element.
*/
@Override
public synchronized Message[] getMessagesByUID(long[] uids)
throws MessagingException {
checkOpened(); // insure that folder is open
try {
synchronized(messageCacheLock) {
long[] unavailUids = uids;
if (uidTable != null) {
// to collect unavailable UIDs
ListgetQuota
method, but it
* need not be.
*
* @param quota the quota to set
* @exception MessagingException if the server doesn't support the
* QUOTA extension
*/
public void setQuota(final Quota quota) throws MessagingException {
doOptionalCommand("QUOTA not supported",
new ProtocolCommand() {
@Override
public Object doCommand(IMAPProtocol p)
throws ProtocolException {
p.setQuota(quota);
return null;
}
});
}
/**
* Get the access control list entries for this folder.
*
* @return array of access control list entries
* @exception MessagingException if the server doesn't support the
* ACL extension
*/
public ACL[] getACL() throws MessagingException {
return (ACL[])doOptionalCommand("ACL not supported",
new ProtocolCommand() {
@Override
public Object doCommand(IMAPProtocol p)
throws ProtocolException {
return p.getACL(fullName);
}
});
}
/**
* Add an access control list entry to the access control list
* for this folder.
*
* @param acl the access control list entry to add
* @exception MessagingException if the server doesn't support the
* ACL extension
*/
public void addACL(ACL acl) throws MessagingException {
setACL(acl, '\0');
}
/**
* Remove any access control list entry for the given identifier
* from the access control list for this folder.
*
* @param name the identifier for which to remove all ACL entries
* @exception MessagingException if the server doesn't support the
* ACL extension
*/
public void removeACL(final String name) throws MessagingException {
doOptionalCommand("ACL not supported",
new ProtocolCommand() {
@Override
public Object doCommand(IMAPProtocol p)
throws ProtocolException {
p.deleteACL(fullName, name);
return null;
}
});
}
/**
* Add the rights specified in the ACL to the entry for the
* identifier specified in the ACL. If an entry for the identifier
* doesn't already exist, add one.
*
* @param acl the identifer and rights to add
* @exception MessagingException if the server doesn't support the
* ACL extension
*/
public void addRights(ACL acl) throws MessagingException {
setACL(acl, '+');
}
/**
* Remove the rights specified in the ACL from the entry for the
* identifier specified in the ACL.
*
* @param acl the identifer and rights to remove
* @exception MessagingException if the server doesn't support the
* ACL extension
*/
public void removeRights(ACL acl) throws MessagingException {
setACL(acl, '-');
}
/**
* Get all the rights that may be allowed to the given identifier.
* Rights are grouped per RFC 2086 and each group is returned as an
* element of the array. The first element of the array is the set
* of rights that are always granted to the identifier. Later
* elements are rights that may be optionally granted to the
* identifier. getACL
method.
*
* @param name the identifier to list rights for
* @return array of Rights objects representing possible
* rights for the identifier
* @exception MessagingException if the server doesn't support the
* ACL extension
*/
public Rights[] listRights(final String name) throws MessagingException {
return (Rights[])doOptionalCommand("ACL not supported",
new ProtocolCommand() {
@Override
public Object doCommand(IMAPProtocol p)
throws ProtocolException {
return p.listRights(fullName, name);
}
});
}
/**
* Get the rights allowed to the currently authenticated user.
*
* @return the rights granted to the current user
* @exception MessagingException if the server doesn't support the
* ACL extension
*/
public Rights myRights() throws MessagingException {
return (Rights)doOptionalCommand("ACL not supported",
new ProtocolCommand() {
@Override
public Object doCommand(IMAPProtocol p)
throws ProtocolException {
return p.myRights(fullName);
}
});
}
private void setACL(final ACL acl, final char mod)
throws MessagingException {
doOptionalCommand("ACL not supported",
new ProtocolCommand() {
@Override
public Object doCommand(IMAPProtocol p)
throws ProtocolException {
p.setACL(fullName, mod, acl);
return null;
}
});
}
/**
* Get the attributes that the IMAP server returns with the
* LIST response.
*
* @return array of attributes for this folder
* @exception MessagingException for failures
* @since JavaMail 1.3.3
*/
public synchronized String[] getAttributes() throws MessagingException {
checkExists();
if (attributes == null)
exists(); // do a LIST to set the attributes
return attributes == null ? new String[0] : attributes.clone();
}
/**
* Use the IMAP IDLE command (see
* RFC 2177),
* if supported by the server, to enter idle mode so that the server
* can send unsolicited notifications of new messages arriving, etc.
* without the need for the client to constantly poll the server.
* Use an appropriate listener to be notified of new messages or
* other events. When another thread (e.g., the listener thread)
* needs to issue an IMAP comand for this folder, the idle mode will
* be terminated and this method will return. Typically the caller
* will invoke this method in a loop. once
is true, abort the
* IDLE command after the first notification, to allow the caller
* to process any notification synchronously.
*
* @param once only do one notification?
* @exception MessagingException if the server doesn't support the
* IDLE extension
* @exception IllegalStateException if the folder isn't open
*
* @since JavaMail 1.4.3
*/
public void idle(boolean once) throws MessagingException {
synchronized (this) {
/*
* We can't support the idle method if we're using SocketChannels
* because SocketChannels don't allow simultaneous read and write.
* If we're blocked in a read waiting for IDLE responses, we can't
* send the DONE message to abort the IDLE. Sigh.
* XXX - We could do select here too, like IdleManager, instead
* of blocking in read, but that's more complicated.
*/
if (protocol != null && protocol.getChannel() != null)
throw new MessagingException(
"idle method not supported with SocketChannels");
}
if (!startIdle(null))
return;
/*
* We gave up the folder lock so that other threads
* can get into the folder far enough to see that we're
* in IDLE and abort the IDLE.
*
* Now we read responses from the IDLE command, especially
* including unsolicited notifications from the server.
* We don't hold the messageCacheLock while reading because
* it protects the idleState and other threads need to be
* able to examine the state.
*
* The messageCacheLock is held in handleIdle while processing
* the responses so that we can update the number of messages
* in the folder (for example).
*/
for (;;) {
if (!handleIdle(once))
break;
}
/*
* Enforce a minimum delay to give time to threads
* processing the responses that came in while we
* were idle.
*/
int minidle = ((IMAPStore)store).getMinIdleTime();
if (minidle > 0) {
try {
Thread.sleep(minidle);
} catch (InterruptedException ex) {
// restore the interrupted state, which callers might depend on
Thread.currentThread().interrupt();
}
}
}
/**
* Start the IDLE command and put this folder into the IDLE state.
* IDLE processing is done later in handleIdle(), e.g., called from
* the IdleManager.
*
* @return true if IDLE started, false otherwise
* @exception MessagingException if the server doesn't support the
* IDLE extension
* @exception IllegalStateException if the folder isn't open
* @since JavaMail 1.5.2
*/
boolean startIdle(final IdleManager im) throws MessagingException {
// ASSERT: Must NOT be called with this folder's
// synchronization lock held.
assert !Thread.holdsLock(this);
synchronized(this) {
checkOpened();
if (im != null && idleManager != null && im != idleManager)
throw new MessagingException(
"Folder already being watched by another IdleManager");
Boolean started = (Boolean)doOptionalCommand("IDLE not supported",
new ProtocolCommand() {
@Override
public Object doCommand(IMAPProtocol p)
throws ProtocolException {
// if the IdleManager is already watching this folder,
// there's nothing to do here
if (idleState == IDLE &&
im != null && im == idleManager)
return Boolean.TRUE; // already watching it
if (idleState == RUNNING) {
p.idleStart();
logger.finest("startIdle: set to IDLE");
idleState = IDLE;
idleManager = im;
return Boolean.TRUE;
} else {
// some other thread must be running the IDLE
// command, we'll just wait for it to finish
// without aborting it ourselves
try {
// give up lock and wait to be not idle
messageCacheLock.wait();
} catch (InterruptedException ex) {
// restore the interrupted state, which callers
// might depend on
Thread.currentThread().interrupt();
}
return Boolean.FALSE;
}
}
});
logger.log(Level.FINEST, "startIdle: return {0}", started);
return started.booleanValue();
}
}
/**
* Read a response from the server while we're in the IDLE state.
* We hold the messageCacheLock while processing the
* responses so that we can update the number of messages
* in the folder (for example).
*
* @param once only do one notification?
* @return true if we should look for more IDLE responses,
* false if IDLE is done
* @exception MessagingException for errors
* @since JavaMail 1.5.2
*/
boolean handleIdle(boolean once) throws MessagingException {
Response r = null;
do {
r = protocol.readIdleResponse();
try {
synchronized (messageCacheLock) {
if (r.isBYE() && r.isSynthetic() && idleState == IDLE) {
/*
* If it was a timeout and no bytes were transferred
* we ignore it and go back and read again.
* If the I/O was otherwise interrupted, and no
* bytes were transferred, we take it as a request
* to abort the IDLE.
*/
Exception ex = r.getException();
if (ex instanceof InterruptedIOException &&
((InterruptedIOException)ex).
bytesTransferred == 0) {
if (ex instanceof SocketTimeoutException) {
logger.finest(
"handleIdle: ignoring socket timeout");
r = null; // repeat do/while loop
} else {
logger.finest("handleIdle: interrupting IDLE");
IdleManager im = idleManager;
if (im != null) {
logger.finest(
"handleIdle: request IdleManager to abort");
im.requestAbort(this);
} else {
logger.finest("handleIdle: abort IDLE");
protocol.idleAbort();
idleState = ABORTING;
}
// normally will exit the do/while loop
}
continue;
}
}
boolean done = true;
try {
if (protocol == null ||
!protocol.processIdleResponse(r))
return false; // done
done = false;
} finally {
if (done) {
logger.finest("handleIdle: set to RUNNING");
idleState = RUNNING;
idleManager = null;
messageCacheLock.notifyAll();
}
}
if (once) {
if (idleState == IDLE) {
try {
protocol.idleAbort();
} catch (Exception ex) {
// ignore any failures, still have to abort.
// connection failures will be detected above
// in the call to readIdleResponse.
}
idleState = ABORTING;
}
}
}
} catch (ConnectionException cex) {
// Oops, the folder died on us.
throw new FolderClosedException(this, cex.getMessage());
} catch (ProtocolException pex) {
throw new MessagingException(pex.getMessage(), pex);
}
// keep processing responses already in our buffer
} while (r == null || protocol.hasResponse());
return true;
}
/*
* If an IDLE command is in progress, abort it if necessary,
* and wait until it completes.
* ASSERT: Must be called with the message cache lock held.
*/
void waitIfIdle() throws ProtocolException {
assert Thread.holdsLock(messageCacheLock);
while (idleState != RUNNING) {
if (idleState == IDLE) {
IdleManager im = idleManager;
if (im != null) {
logger.finest("waitIfIdle: request IdleManager to abort");
im.requestAbort(this);
} else {
logger.finest("waitIfIdle: abort IDLE");
protocol.idleAbort();
idleState = ABORTING;
}
} else
logger.log(Level.FINEST, "waitIfIdle: idleState {0}", idleState);
try {
// give up lock and wait to be not idle
if (logger.isLoggable(Level.FINEST))
logger.finest("waitIfIdle: wait to be not idle: " +
Thread.currentThread());
messageCacheLock.wait();
if (logger.isLoggable(Level.FINEST))
logger.finest("waitIfIdle: wait done, idleState " +
idleState + ": " + Thread.currentThread());
} catch (InterruptedException ex) {
// restore the interrupted state, which callers might depend on
Thread.currentThread().interrupt();
// If someone is trying to interrupt us we can't keep going
// around the loop waiting for IDLE to complete, but we can't
// just return because callers expect the idleState to be
// RUNNING when we return. Throwing this exception seems
// like the best choice.
throw new ProtocolException("Interrupted waitIfIdle", ex);
}
}
}
/*
* Send the DONE command that aborts the IDLE; used by IdleManager.
*/
void idleAbort() {
synchronized (messageCacheLock) {
if (idleState == IDLE && protocol != null) {
protocol.idleAbort();
idleState = ABORTING;
}
}
}
/*
* Send the DONE command that aborts the IDLE and wait for the response;
* used by IdleManager.
*/
void idleAbortWait() {
synchronized (messageCacheLock) {
if (idleState == IDLE && protocol != null) {
protocol.idleAbort();
idleState = ABORTING;
// read responses until OK or connection failure
try {
for (;;) {
if (!handleIdle(false))
break;
}
} catch (Exception ex) {
// assume it's a connection failure; nothing more to do
logger.log(Level.FINEST, "Exception in idleAbortWait", ex);
}
logger.finest("IDLE aborted");
}
}
}
/**
* Return the SocketChannel for this connection, if any, for use
* in IdleManager.
*/
SocketChannel getChannel() {
return protocol != null ? protocol.getChannel() : null;
}
/**
* Send the IMAP ID command (if supported by the server) and return
* the result from the server. The ID command identfies the client
* to the server and returns information about the server to the client.
* See RFC 2971.
* The returned Map is unmodifiable.
*
* @param clientParams a Map of keys and values identifying the client
* @return a Map of keys and values identifying the server
* @exception MessagingException if the server doesn't support the
* ID extension
* @since JavaMail 1.5.1
*/
@SuppressWarnings("unchecked")
public Map
*
* ASSERT: Must be called with this folder's synchronization lock held.
*
* @return the IMAPProtocol for the Store's connection
* @exception ProtocolException for protocol errors
*/
protected synchronized IMAPProtocol getStoreProtocol()
throws ProtocolException {
connectionPoolLogger.fine("getStoreProtocol() borrowing a connection");
return ((IMAPStore)store).getFolderStoreProtocol();
}
/**
* Throw the appropriate 'closed' exception.
*
* @param cex the ConnectionException
* @exception FolderClosedException if the folder is closed
* @exception StoreClosedException if the store is closed
*/
protected synchronized void throwClosedException(ConnectionException cex)
throws FolderClosedException, StoreClosedException {
// If it's the folder's protocol object, throw a FolderClosedException;
// otherwise, throw a StoreClosedException.
// If a command has failed because the connection is closed,
// the folder will have already been forced closed by the
// time we get here and our protocol object will have been
// released, so if we no longer have a protocol object we base
// this decision on whether we *think* the folder is open.
if ((protocol != null && cex.getProtocol() == protocol) ||
(protocol == null && !reallyClosed))
throw new FolderClosedException(this, cex.getMessage());
else
throw new StoreClosedException(store, cex.getMessage());
}
/**
* Return the IMAPProtocol object for this folder.
* IMAPProtocol p = null;
* try {
* p = getStoreProtocol();
* // perform the command
* } catch (WhateverException ex) {
* // handle it
* } finally {
* releaseStoreProtocol(p);
* }
*
IMAPProtocol
object. ProtocolCommand
* object's doCommand
method returns. If the
* doCommand
method throws a ConnectionException
* it is translated into a StoreClosedException
or
* FolderClosedException
as appropriate. If the
* doCommand
method throws a ProtocolException
* it is translated into a MessagingException
. com.sun.mail.iap
and
* com.sun.mail.imap.protocol
packages, best acquired by
* reading the source code.
*
*
*
* import com.sun.mail.iap.*;
* import com.sun.mail.imap.*;
* import com.sun.mail.imap.protocol.*;
*
* ...
*
* IMAPFolder f = (IMAPFolder)folder;
* Object val = f.doCommand(new IMAPFolder.ProtocolCommand() {
* public Object doCommand(IMAPProtocol p)
* throws ProtocolException {
* p.simpleCommand("NOOP", null);
* return null;
* }
* });
*
*
* @param cmd the protocol command
* @return the result of the command
* @exception MessagingException for failures
*/
public Object doCommand(ProtocolCommand cmd) throws MessagingException {
try {
return doProtocolCommand(cmd);
} catch (ConnectionException cex) {
// Oops, the store or folder died on us.
throwClosedException(cex);
} catch (ProtocolException pex) {
throw new MessagingException(pex.getMessage(), pex);
}
return null;
}
public Object doOptionalCommand(String err, ProtocolCommand cmd)
throws MessagingException {
try {
return doProtocolCommand(cmd);
} catch (BadCommandException bex) {
throw new MessagingException(err, bex);
} catch (ConnectionException cex) {
// Oops, the store or folder died on us.
throwClosedException(cex);
} catch (ProtocolException pex) {
throw new MessagingException(pex.getMessage(), pex);
}
return null;
}
public Object doCommandIgnoreFailure(ProtocolCommand cmd)
throws MessagingException {
try {
return doProtocolCommand(cmd);
} catch (CommandFailedException cfx) {
return null;
} catch (ConnectionException cex) {
// Oops, the store or folder died on us.
throwClosedException(cex);
} catch (ProtocolException pex) {
throw new MessagingException(pex.getMessage(), pex);
}
return null;
}
protected synchronized Object doProtocolCommand(ProtocolCommand cmd)
throws ProtocolException {
/*
* Check whether we have a protocol object, not whether we're
* opened, to allow use of the exsting protocol object in the
* open method before the state is changed to "opened".
*/
if (protocol != null) {
synchronized (messageCacheLock) {
return cmd.doCommand(getProtocol());
}
}
// only get here if using store's connection
IMAPProtocol p = null;
try {
p = getStoreProtocol();
return cmd.doCommand(p);
} finally {
releaseStoreProtocol(p);
}
}
/**
* Release the store protocol object. If we borrowed a protocol
* object from the connection pool, give it back. If we used our
* own protocol object, nothing to do.
*
* ASSERT: Must be called with this folder's synchronization lock held.
*
* @param p the IMAPProtocol object
*/
protected synchronized void releaseStoreProtocol(IMAPProtocol p) {
if (p != protocol)
((IMAPStore)store).releaseFolderStoreProtocol(p);
else {
// XXX - should never happen
logger.fine("releasing our protocol as store protocol?");
}
}
/**
* Release the protocol object.
*
* ASSERT: This method must be called only when holding the
* messageCacheLock
*
* @param returnToPool return the protocol object to the pool?
*/
protected void releaseProtocol(boolean returnToPool) {
if (protocol != null) {
protocol.removeResponseHandler(this);
if (returnToPool)
((IMAPStore)store).releaseProtocol(this, protocol);
else {
protocol.disconnect(); // make sure it's disconnected
((IMAPStore)store).releaseProtocol(this, null);
}
protocol = null;
}
}
/**
* Issue a noop command for the connection if the connection has not been
* used in more than a second. If
* import com.sun.mail.iap.*;
* import com.sun.mail.imap.*;
* import com.sun.mail.imap.protocol.*;
*
* ...
*
* IMAPFolder f = (IMAPFolder)folder;
* Object val = f.doCommand(new IMAPFolder.ProtocolCommand() {
* public Object doCommand(IMAPProtocol p)
* throws ProtocolException {
* // Issue command
* Argument args = new Argument();
* Argument list = new Argument();
* list.writeString("SUBJECT");
* args.writeArgument(list);
* args.writeString("UTF-8");
* args.writeString("ALL");
* Response[] r = p.command("SORT", args);
* Response response = r[r.length-1];
*
* // Grab response
* Vector v = new Vector();
* if (response.isOK()) { // command succesful
* for (int i = 0, len = r.length; i < len; i++) {
* if (!(r[i] instanceof IMAPResponse))
* continue;
*
* IMAPResponse ir = (IMAPResponse)r[i];
* if (ir.keyEquals("SORT")) {
* String num;
* while ((num = ir.readAtomString()) != null)
* System.out.println(num);
* r[i] = null;
* }
* }
* }
*
* // dispatch remaining untagged responses
* p.notifyResponseHandlers(r);
* p.handleResult(response);
*
* return null;
* }
* });
*
keepStoreAlive
is true,
* also issue a noop over the store's connection.
*
* ASSERT: This method must be called only when holding the
* messageCacheLock
*
* @param keepStoreAlive keep the Store alive too?
* @exception ProtocolException for protocol errors
*/
protected void keepConnectionAlive(boolean keepStoreAlive)
throws ProtocolException {
assert Thread.holdsLock(messageCacheLock);
if (protocol == null) // in case connection was closed
return;
if (System.currentTimeMillis() - protocol.getTimestamp() > 1000) {
waitIfIdle();
if (protocol != null)
protocol.noop();
}
if (keepStoreAlive && ((IMAPStore)store).hasSeparateStoreConnection() &&
!((IMAPStore)store).isStoreConnectionInUse()) {
IMAPProtocol p = null;
try {
p = ((IMAPStore)store).getFolderStoreProtocol();
if (System.currentTimeMillis() - p.getTimestamp() > 1000)
p.noop();
} finally {
((IMAPStore)store).releaseFolderStoreProtocol(p);
}
}
}
/**
* Get the message object for the given sequence number. If
* none found, null is returned.
*
* ASSERT: This method must be called only when holding the
* messageCacheLock
*
* @param seqnum the message sequence number
* @return the IMAPMessage object
*/
protected IMAPMessage getMessageBySeqNumber(int seqnum) {
if (seqnum < 1) {
// rfc3501 2.3.1.2: "A relative position from 1 to the number of messages in the mailbox."
// Some servers return sequence number zero when there are no messages (found)
eu.faircode.email.Log.w("Sequence=" + seqnum);
return null;
}
if (seqnum > messageCache.size()) {
// Microsoft Exchange will sometimes return message
// numbers that it has not yet notified the client
// about via EXISTS; ignore those messages here.
// GoDaddy IMAP does this too.
if (logger.isLoggable(Level.FINE))
logger.fine("ignoring message number " +
seqnum + " outside range " + messageCache.size());
return null;
}
return messageCache.getMessageBySeqnum(seqnum);
}
/**
* Get the message objects for the given sequence numbers.
*
* ASSERT: This method must be called only when holding the
* messageCacheLock
*
* @param seqnums the array of message sequence numbers
* @return the IMAPMessage objects
* @since JavaMail 1.5.3
*/
protected IMAPMessage[] getMessagesBySeqNumbers(int[] seqnums) {
IMAPMessage[] msgs = new IMAPMessage[seqnums.length];
int nulls = 0;
// Map seq-numbers into actual Messages.
for (int i = 0; i < seqnums.length; i++) {
msgs[i] = getMessageBySeqNumber(seqnums[i]);
if (msgs[i] == null)
nulls++;
}
if (nulls > 0) { // compress the array to remove the nulls
IMAPMessage[] nmsgs = new IMAPMessage[seqnums.length - nulls];
for (int i = 0, j = 0; i < msgs.length; i++) {
if (msgs[i] != null)
nmsgs[j++] = msgs[i];
}
msgs = nmsgs;
}
return msgs;
}
private boolean isDirectory() {
return ((type & HOLDS_FOLDERS) != 0);
}
}
/**
* An object that holds a Message object
* and reports its size and writes it to another OutputStream
* on demand. Used by appendMessages to avoid the need to
* buffer the entire message in memory in a single byte array
* before sending it to the server.
*/
class MessageLiteral implements Literal {
private Message msg;
private int msgSize = -1;
private byte[] buf; // the buffered message, if not null
public MessageLiteral(Message msg, int maxsize)
throws MessagingException, IOException {
this.msg = msg;
// compute the size here so exceptions can be returned immediately
LengthCounter lc = new LengthCounter(maxsize);
OutputStream os = new CRLFOutputStream(lc);
msg.writeTo(os);
os.flush();
msgSize = lc.getSize();
buf = lc.getBytes();
}
@Override
public int size() {
return msgSize;
}
@Override
public void writeTo(OutputStream os) throws IOException {
// the message should not change between the constructor and this call
try {
if (buf != null)
os.write(buf, 0, msgSize);
else {
os = new CRLFOutputStream(os);
msg.writeTo(os);
}
} catch (MessagingException mex) {
// exceptions here are bad, "should" never happen
throw new IOException("MessagingException while appending message: "
+ mex);
}
}
}
/**
* Count the number of bytes written to the stream.
* Also, save a copy of small messages to avoid having to process
* the data again.
*/
class LengthCounter extends OutputStream {
private int size = 0;
private byte[] buf;
private int maxsize;
public LengthCounter(int maxsize) {
buf = new byte[8192];
this.maxsize = maxsize;
}
@Override
public void write(int b) {
int newsize = size + 1;
if (buf != null) {
if (newsize > maxsize && maxsize >= 0) {
buf = null;
} else if (newsize > buf.length) {
byte newbuf[] = new byte[Math.max(buf.length << 1, newsize)];
System.arraycopy(buf, 0, newbuf, 0, size);
buf = newbuf;
buf[size] = (byte)b;
} else {
buf[size] = (byte)b;
}
}
size = newsize;
}
@Override
public void write(byte b[], int off, int len) {
if ((off < 0) || (off > b.length) || (len < 0) ||
((off + len) > b.length) || ((off + len) < 0)) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return;
}
int newsize = size + len;
if (buf != null) {
if (newsize > maxsize && maxsize >= 0) {
buf = null;
} else if (newsize > buf.length) {
byte newbuf[] = new byte[Math.max(buf.length << 1, newsize)];
System.arraycopy(buf, 0, newbuf, 0, size);
buf = newbuf;
System.arraycopy(b, off, buf, size, len);
} else {
System.arraycopy(b, off, buf, size, len);
}
}
size = newsize;
}
@Override
public void write(byte[] b) throws IOException {
write(b, 0, b.length);
}
public int getSize() {
return size;
}
public byte[] getBytes() {
return buf;
}
}