geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPMessage.java [79:1298]:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
public class IMAPMessage extends MimeMessage {

    private static final byte[] CRLF = new byte[]{'\r', '\n'};

    // the Store we're stored in (which manages the connection and other stuff).
    protected IMAPStore store;

    // the IMAP server sequence number (potentially updated during the life of this message object).
    protected int sequenceNumber;
    // the IMAP uid value;
    protected long uid = -1;
    // the section identifier.  This is only really used for nested messages.  The toplevel version
    // will be null, and each nested message will set the appropriate part identifier
    protected String section;
    // the loaded message envelope (delayed until needed)
    protected IMAPEnvelope envelope;
    // the body structure information (also lazy loaded).
    protected IMAPBodyStructure bodyStructure;
    // the IMAP INTERNALDATE value.
    protected Date receivedDate;
    // the size item, which is maintained separately from the body structure
    // as it can be retrieved without getting the body structure
    protected int size;
    // turned on once we've requested the entire header set.
    protected boolean allHeadersRetrieved = false;
    // singleton date formatter for this class.
    static protected MailDateFormat dateFormat = new MailDateFormat();


    /**
     * Contruct an IMAPMessage instance.
     *
     * @param folder   The hosting folder for the message.
     * @param store    The Store owning the article (and folder).
     * @param msgnum   The article message number.  This is assigned by the Folder, and is unique
     *                 for each message in the folder.  The message numbers are only valid
     *                 as long as the Folder is open.
     * @param sequenceNumber The IMAP server manages messages by sequence number, which is subject to
     *                 change whenever messages are expunged.  This is the server retrieval number
     *                 of the message, which needs to be synchronized with status updates
     *                 sent from the server.
     *
     * @exception MessagingException
     */
	IMAPMessage(IMAPFolder folder, IMAPStore store, int msgnum, int sequenceNumber) {
		super(folder, msgnum);
        this.sequenceNumber = sequenceNumber;
		this.store = store;
        // The default constructor creates an empty Flags item.  We need to clear this out so we
        // know if the flags need to be fetched from the server when requested.
        flags = null;
        // make sure this is a totally fresh set of headers.  We'll fill things in as we retrieve them.
        headers = new InternetHeaders();
	}


    /**
     * Override for the Message class setExpunged() method to allow
     * us to do additional cleanup for expunged messages.
     *
     * @param value  The new expunge setting.
     */
    public void setExpunged(boolean value) {
        // super class handles most of the details
        super.setExpunged(value);
        // if we're now expunged, this removes us from the server message sequencing scheme, so
        // we need to invalidate the sequence number.
        if (isExpunged()) {
            sequenceNumber = -1;
        }
    }


    /**
     * Return a copy the flags associated with this message.
     *
     * @return a copy of the flags for this message
     * @throws MessagingException if there was a problem accessing the Store
     */
    public synchronized Flags getFlags() throws MessagingException {
        // load the flags, if needed
        loadFlags();
        return super.getFlags();
    }


    /**
     * Check whether the supplied flag is set.
     * The default implementation checks the flags returned by {@link #getFlags()}.
     *
     * @param flag the flags to check for
     * @return true if the flags is set
     * @throws MessagingException if there was a problem accessing the Store
     */
    public synchronized boolean isSet(Flags.Flag flag) throws MessagingException {
        // load the flags, if needed
        loadFlags();
        return super.isSet(flag);
    }

    /**
     * Set or clear a flag value.
     *
     * @param flags  The set of flags to effect.
     * @param set    The value to set the flag to (true or false).
     *
     * @exception MessagingException
     */
    public synchronized void setFlags(Flags flag, boolean set) throws MessagingException {
        // make sure this is in a valid state.
        checkValidity();

        // we need to ensure that we're the only ones with access to the folder's
        // message cache any time we need to talk to the server.  This needs to be
        // held until after we release the connection so that any pending EXPUNGE
        // untagged responses are processed before the next time the folder connection is
        // used.
        synchronized (folder) {
            IMAPConnection connection = getConnection();

            try {
                // set the flags for this item and update the
                // internal state with the new values returned from the
                // server.
                flags = connection.setFlags(sequenceNumber, flag, set);
            } finally {
                releaseConnection(connection);
            }
        }
    }


    /**
     * Return an InputStream instance for accessing the
     * message content.
     *
     * @return An InputStream instance for accessing the content
     *         (body) of the message.
     * @exception MessagingException
     * @see javax.mail.internet.MimeMessage#getContentStream()
     */
	protected InputStream getContentStream() throws MessagingException {

        // no content loaded yet?
        if (content == null) {
            // make sure we're still valid
            checkValidity();
            // make sure the content is fully loaded
            loadContent();
        }

        // allow the super class to handle creating it from the loaded content.
        return super.getContentStream();
	}


    /**
     * Write out the byte data to the provided output stream.
     *
     * @param out    The target stream.
     *
     * @exception IOException
     * @exception MessagingException
     */
    public void writeTo(OutputStream out) throws IOException, MessagingException {
        // no content loaded yet?
        if (content == null) {
            // make sure we're still valid
            checkValidity();
            // make sure the content is fully loaded
            loadContent();
        }

        loadHeaders();

        Enumeration e = headers.getAllHeaderLines();
        while(e.hasMoreElements()) {
            String line = (String)e.nextElement();
            out.write(line.getBytes("ISO8859-1"));
            out.write(CRLF);
        }
        out.write(CRLF);
        out.write(CRLF);
        out.write(content);
    }

	/******************************************************************
	 * Following is a set of methods that deal with information in the
	 * envelope.  These methods ensure the enveloper is loaded and
     * retrieve the information.
	 ********************************************************************/


    /**
     * Get the message "From" addresses.  This looks first at the
     * "From" headers, and no "From" header is found, the "Sender"
     * header is checked.  Returns null if not found.
     *
     * @return An array of addresses identifying the message from target.  Returns
     *         null if this is not resolveable from the headers.
     * @exception MessagingException
     */
    public Address[] getFrom() throws MessagingException {
        // make sure we've retrieved the envelope information.
        loadEnvelope();
        // make sure we return a copy of the array so this can't be changed.
        Address[] addresses = envelope.from;
        if (addresses == null) {
            return null;
        }
        return (Address[])addresses.clone();
    }


    /**
     * Return the "Sender" header as an address.
     *
     * @return the "Sender" header as an address, or null if not present
     * @throws MessagingException if there was a problem parsing the header
     */
    public Address getSender() throws MessagingException {
        // make sure we've retrieved the envelope information.
        loadEnvelope();
        // make sure we return a copy of the array so this can't be changed.
        Address[] addresses = envelope.sender;
        if (addresses == null) {
            return null;
        }
        // There's only a single sender, despite IMAP potentially returning a list
        return addresses[0];
    }

    /**
     * Gets the recipients by type.  Returns null if there are no
     * headers of the specified type.  Acceptable RecipientTypes are:
     *
     *   javax.mail.Message.RecipientType.TO
     *   javax.mail.Message.RecipientType.CC
     *   javax.mail.Message.RecipientType.BCC
     *   javax.mail.internet.MimeMessage.RecipientType.NEWSGROUPS
     *
     * @param type   The message RecipientType identifier.
     *
     * @return The array of addresses for the specified recipient types.
     * @exception MessagingException
     */
    public Address[] getRecipients(Message.RecipientType type) throws MessagingException {
        // make sure we've retrieved the envelope information.
        loadEnvelope();
        Address[] addresses = null;

        if (type == Message.RecipientType.TO) {
            addresses = envelope.to;
        }
        else if (type == Message.RecipientType.CC) {
            addresses = envelope.cc;
        }
        else if (type == Message.RecipientType.BCC) {
            addresses = envelope.bcc;
        }
        else {
            // this could be a newsgroup type, which will tickle the message headers.
            return super.getRecipients(type);
        }
        // make sure we return a copy of the array so this can't be changed.
        if (addresses == null) {
            return null;
        }
        return (Address[])addresses.clone();
    }

    /**
     * Get the ReplyTo address information.  The headers are parsed
     * using the "mail.mime.address.strict" setting.  If the "Reply-To" header does
     * not have any addresses, then the value of the "From" field is used.
     *
     * @return An array of addresses obtained from parsing the header.
     * @exception MessagingException
     */
    public Address[] getReplyTo() throws MessagingException {
        // make sure we've retrieved the envelope information.
        loadEnvelope();
        // make sure we return a copy of the array so this can't be changed.
        Address[] addresses = envelope.replyTo;
        if (addresses == null) {
            return null;
        }
        return (Address[])addresses.clone();
    }

    /**
     * Returns the value of the "Subject" header.  If the subject
     * is encoded as an RFC 2047 value, the value is decoded before
     * return.  If decoding fails, the raw string value is
     * returned.
     *
     * @return The String value of the subject field.
     * @exception MessagingException
     */
    public String getSubject() throws MessagingException {
        // make sure we've retrieved the envelope information.
        loadEnvelope();

        if (envelope.subject == null) {
            return null;
        }
        // the subject could be encoded.  If there is a decoding error,
        // return the raw subject string.
        try {
            return MimeUtility.decodeText(envelope.subject);
        } catch (UnsupportedEncodingException e) {
            return envelope.subject;
        }
    }

    /**
     * Get the value of the "Date" header field.  Returns null if
     * if the field is absent or the date is not in a parseable format.
     *
     * @return A Date object parsed according to RFC 822.
     * @exception MessagingException
     */
    public Date getSentDate() throws MessagingException {
        // make sure we've retrieved the envelope information.
        loadEnvelope();
        // just return that directly
        return envelope.date;
    }


    /**
     * Get the message received date.
     *
     * @return Always returns the formatted INTERNALDATE, if available.
     * @exception MessagingException
     */
    public Date getReceivedDate() throws MessagingException {
        loadEnvelope();
        return receivedDate;
    }


    /**
     * Retrieve the size of the message content.  The content will
     * be retrieved from the server, if necessary.
     *
     * @return The size of the content.
     * @exception MessagingException
     */
	public int getSize() throws MessagingException {
        // make sure we've retrieved the envelope information.  We load the
        // size when we retrieve that.
        loadEnvelope();
        return size;
	}


    /**
     * Get a line count for the IMAP message.  This is potentially
     * stored in the Lines article header.  If not there, we return
     * a default of -1.
     *
     * @return The header line count estimate, or -1 if not retrieveable.
     * @exception MessagingException
     */
    public int getLineCount() throws MessagingException {
        loadBodyStructure();
        return bodyStructure.lines;
    }

    /**
     * Return the IMAP in reply to information (retrieved with the
     * ENVELOPE).
     *
     * @return The in reply to String value, if available.
     * @exception MessagingException
     */
    public String getInReplyTo() throws MessagingException {
        loadEnvelope();
        return envelope.inReplyTo;
    }

    /**
     * Returns the current content type (defined in the "Content-Type"
     * header.  If not available, "text/plain" is the default.
     *
     * @return The String name of the message content type.
     * @exception MessagingException
     */
    public String getContentType() throws MessagingException {
        loadBodyStructure();
        return bodyStructure.mimeType.toString();
    }


    /**
     * Tests to see if this message has a mime-type match with the
     * given type name.
     *
     * @param type   The tested type name.
     *
     * @return If this is a type match on the primary and secondare portion of the types.
     * @exception MessagingException
     */
    public boolean isMimeType(String type) throws MessagingException {
        loadBodyStructure();
        return bodyStructure.mimeType.match(type);
    }

    /**
     * Retrieve the message "Content-Disposition" header field.
     * This value represents how the part should be represented to
     * the user.
     *
     * @return The string value of the Content-Disposition field.
     * @exception MessagingException
     */
    public String getDisposition() throws MessagingException {
        loadBodyStructure();
        if (bodyStructure.disposition != null) {
            return bodyStructure.disposition.getDisposition();
        }
        return null;
    }

    /**
     * Decode the Content-Transfer-Encoding header to determine
     * the transfer encoding type.
     *
     * @return The string name of the required encoding.
     * @exception MessagingException
     */
    public String getEncoding() throws MessagingException {
        loadBodyStructure();
        return bodyStructure.transferEncoding;
    }

    /**
     * Retrieve the value of the "Content-ID" header.  Returns null
     * if the header does not exist.
     *
     * @return The current header value or null.
     * @exception MessagingException
     */
    public String getContentID() throws MessagingException {
        loadBodyStructure();
        return bodyStructure.contentID;
    }

    public String getContentMD5() throws MessagingException {
        loadBodyStructure();
        return bodyStructure.md5Hash;
    }


    public String getDescription() throws MessagingException {
        loadBodyStructure();

        if (bodyStructure.contentDescription == null) {
            return null;
        }
        // the subject could be encoded.  If there is a decoding error,
        // return the raw subject string.
        try {
            return MimeUtility.decodeText(bodyStructure.contentDescription);
        } catch (UnsupportedEncodingException e) {
            return bodyStructure.contentDescription;
        }
    }

    /**
     * Return the content languages associated with this
     * message.
     *
     * @return
     * @exception MessagingException
     */
    public String[] getContentLanguage() throws MessagingException {
        loadBodyStructure();

        if (!bodyStructure.languages.isEmpty()) {
            return (String[])bodyStructure.languages.toArray(new String[bodyStructure.languages.size()]);
        }
        return null;
    }

    public String getMessageID() throws MessagingException {
        loadEnvelope();
        return envelope.messageID;
    }

    public void setFrom(Address address) throws MessagingException {
        throw new IllegalWriteException("IMAP messages are read-only");
    }

    public void addFrom(Address[] address) throws MessagingException {
        throw new IllegalWriteException("IMAP messages are read-only");
    }

    public void setSender(Address address) throws MessagingException {
        throw new IllegalWriteException("IMAP messages are read-only");
    }

    public void setRecipients(Message.RecipientType type, Address[] addresses) throws MessagingException {
        throw new IllegalWriteException("IMAP messages are read-only");
    }

    public void setRecipients(Message.RecipientType type, String address) throws MessagingException {
        throw new IllegalWriteException("IMAP messages are read-only");
    }

    public void addRecipients(Message.RecipientType type, Address[] address) throws MessagingException {
        throw new IllegalWriteException("IMAP messages are read-only");
    }

    public void setReplyTo(Address[] address) throws MessagingException {
        throw new IllegalWriteException("IMAP messages are read-only");
    }

    public void setSubject(String subject) throws MessagingException {
        throw new IllegalWriteException("IMAP messages are read-only");
    }

    public void setSubject(String subject, String charset) throws MessagingException {
        throw new IllegalWriteException("IMAP messages are read-only");
    }

    public void setSentDate(Date sent) throws MessagingException {
        throw new IllegalWriteException("IMAP messages are read-only");
    }

    public void setDisposition(String disposition) throws MessagingException {
        throw new IllegalWriteException("IMAP messages are read-only");
    }

    public void setContentID(String cid) throws MessagingException {
        throw new IllegalWriteException("IMAP messages are read-only");
    }

    public void setContentMD5(String md5) throws MessagingException {
        throw new IllegalWriteException("IMAP messages are read-only");
    }

    public void setDescription(String description) throws MessagingException {
        throw new IllegalWriteException("IMAP messages are read-only");
    }

    public void setDescription(String description, String charset) throws MessagingException {
        throw new IllegalWriteException("IMAP messages are read-only");
    }

    public void setContentLanguage(String[] languages) throws MessagingException {
        throw new IllegalWriteException("IMAP messages are read-only");
    }


	/******************************************************************
	 * Following is a set of methods that deal with headers
	 * These methods are just overrides on the superclass methods to
     * allow lazy loading of the header information.
	 ********************************************************************/

	public String[] getHeader(String name) throws MessagingException {
        loadHeaders();
		return headers.getHeader(name);
	}

	public String getHeader(String name, String delimiter) throws MessagingException {
        loadHeaders();
		return headers.getHeader(name, delimiter);
	}

	public Enumeration getAllHeaders() throws MessagingException {
        loadHeaders();
		return headers.getAllHeaders();
	}

	public Enumeration getMatchingHeaders(String[] names)  throws MessagingException {
        loadHeaders();
		return headers.getMatchingHeaders(names);
	}

	public Enumeration getNonMatchingHeaders(String[] names) throws MessagingException {
        loadHeaders();
		return headers.getNonMatchingHeaders(names);
	}

	public Enumeration getAllHeaderLines() throws MessagingException {
        loadHeaders();
		return headers.getAllHeaderLines();
	}

	public Enumeration getMatchingHeaderLines(String[] names) throws MessagingException {
        loadHeaders();
		return headers.getMatchingHeaderLines(names);
	}

	public Enumeration getNonMatchingHeaderLines(String[] names) throws MessagingException {
        loadHeaders();
		return headers.getNonMatchingHeaderLines(names);
	}

    // the following are overrides for header modification methods.  These messages are read only,
    // so the headers cannot be modified.
    public void addHeader(String name, String value) throws MessagingException {
        throw new IllegalWriteException("IMAP messages are read-only");
    }

    public void setHeader(String name, String value) throws MessagingException {
        throw new IllegalWriteException("IMAP messages are read-only");
    }


    public void removeHeader(String name) throws MessagingException {
        throw new IllegalWriteException("IMAP messages are read-only");
    }

    public void addHeaderLine(String line) throws MessagingException {
        throw new IllegalWriteException("IMAP messages are read-only");
    }

	/**
	 * We cannot modify these messages
	 */
	public void saveChanges() throws MessagingException {
		throw new IllegalWriteException("IMAP messages are read-only");
	}


    /**
     * Utility method for synchronizing IMAP envelope information and
     * the message headers.
     *
     * @param header    The target header name.
     * @param addresses The update addresses.
     */
    protected void updateHeader(String header, InternetAddress[] addresses) throws MessagingException {
        if (addresses != null) {
            headers.addHeader(header, InternetAddress.toString(addresses));
        }
    }

    /**
     * Utility method for synchronizing IMAP envelope information and
     * the message headers.
     *
     * @param header    The target header name.
     * @param address   The update address.
     */
    protected void updateHeader(String header, Address address) throws MessagingException {
        if (address != null) {
            headers.setHeader(header, address.toString());
        }
    }

    /**
     * Utility method for synchronizing IMAP envelope information and
     * the message headers.
     *
     * @param header    The target header name.
     * @param value     The update value.
     */
    protected void updateHeader(String header, String value) throws MessagingException {
        if (value != null) {
            headers.setHeader(header, value);
        }
    }


    /**
     * Create the DataHandler object for this message.
     *
     * @return The DataHandler object that processes the content set for this
     *         message.
     * @exception MessagingException
     */
    public synchronized DataHandler getDataHandler() throws MessagingException {
        // check the validity and make sure we have the body structure information.
        checkValidity();
        loadBodyStructure();
        if (dh == null) {
            // are we working with a multipart message here?
            if (bodyStructure.isMultipart()) {
                dh = new DataHandler(new IMAPMultipartDataSource(this, this, section, bodyStructure));
                return dh;
            }
            else if (bodyStructure.isAttachedMessage()) {
                dh = new DataHandler(new IMAPAttachedMessage(this, section, bodyStructure.nestedEnvelope, bodyStructure.nestedBody),
                     bodyStructure.mimeType.toString());
                return dh;
            }
        }

        // single part messages get handled the normal way.
        return super.getDataHandler();
    }

    public void setDataHandler(DataHandler content) throws MessagingException {
        throw new IllegalWriteException("IMAP body parts are read-only");
    }

    /**
     * Update the message headers from an input stream.
     *
     * @param in     The InputStream source for the header information.
     *
     * @exception MessagingException
     */
    public void updateHeaders(InputStream in) throws MessagingException {
        // wrap a stream around the reply data and read as headers.
        headers = new InternetHeaders(in);
        allHeadersRetrieved = true;
    }

    /**
     * Load the flag set for this message from the server.
     *
     * @exception MessagingeException
     */
    public void loadFlags() throws MessagingException {
        // make sure this is in a valid state.
        checkValidity();
        // if the flags are already loaded, nothing to do
        if (flags != null) {
            return;
        }
        // we need to ensure that we're the only ones with access to the folder's
        // message cache any time we need to talk to the server.  This needs to be
        // held until after we release the connection so that any pending EXPUNGE
        // untagged responses are processed before the next time the folder connection is
        // used.
        synchronized (folder) {
            IMAPConnection connection = getConnection();

            try {
                // fetch the flags for this item.
                flags = connection.fetchFlags(sequenceNumber);
            } finally {
                releaseConnection(connection);
            }
        }
    }


    /**
     * Retrieve the message raw message headers from the IMAP server, synchronizing with the existing header set.
     *
     * @exception MessagingException
     */
    protected synchronized void loadHeaders() throws MessagingException {
        // don't retrieve if already loaded.
        if (allHeadersRetrieved) {
            return;
        }

        // make sure this is in a valid state.
        checkValidity();
        // we need to ensure that we're the only ones with access to the folder's
        // message cache any time we need to talk to the server.  This needs to be
        // held until after we release the connection so that any pending EXPUNGE
        // untagged responses are processed before the next time the folder connection is
        // used.
        synchronized (folder) {
            IMAPConnection connection = getConnection();

            try {
                // get the headers and set
                headers = connection.fetchHeaders(sequenceNumber, section);
                // we have the entire header set, not just a subset.
                allHeadersRetrieved = true;
            } finally {
                releaseConnection(connection);
            }
        }
    }


    /**
     * Retrieve the message envelope from the IMAP server, synchronizing the headers with the
     * information.
     *
     * @exception MessagingException
     */
    protected synchronized void loadEnvelope() throws MessagingException {
        // don't retrieve if already loaded.
        if (envelope != null) {
            return;
        }

        // make sure this is in a valid state.
        checkValidity();
        // we need to ensure that we're the only ones with access to the folder's
        // message cache any time we need to talk to the server.  This needs to be
        // held until after we release the connection so that any pending EXPUNGE
        // untagged responses are processed before the next time the folder connection is
        // used.
        synchronized (folder) {
            IMAPConnection connection = getConnection();
            try {
                // fetch the envelope information for this
                List fetches = connection.fetchEnvelope(sequenceNumber);
                // now process all of the fetch responses before releasing the folder lock.
                // it's possible that an unsolicited update on another thread might try to
                // make an update, causing a potential deadlock.
                for (int i = 0; i < fetches.size(); i++) {
                    // get the returned data items from each of the fetch responses
                    // and process.
                    IMAPFetchResponse fetch = (IMAPFetchResponse)fetches.get(i);
                    // update the internal info
                    updateMessageInformation(fetch);
                }
            } finally {
                releaseConnection(connection);
            }
        }
    }


    /**
     * Retrieve the message envelope from the IMAP server, synchronizing the headers with the
     * information.
     *
     * @exception MessagingException
     */
    protected synchronized void updateEnvelope(IMAPEnvelope envelope) throws MessagingException {
        // set the envelope item
        this.envelope = envelope;

        // copy header type information from the envelope into the headers.
        updateHeader("From", envelope.from);
        if (envelope.sender != null) {
            // we can only have a single sender, even though the envelope theoretically supports more.
            updateHeader("Sender", envelope.sender[0]);
        }
        updateHeader("To", envelope.to);
        updateHeader("Cc", envelope.cc);
        updateHeader("Bcc", envelope.bcc);
        updateHeader("Reply-To", envelope.replyTo);
        // NB:  This is already in encoded form, if needed.
        updateHeader("Subject", envelope.subject);
        updateHeader("Message-ID", envelope.messageID);
    }


    /**
     * Retrieve the BODYSTRUCTURE information from the IMAP server.
     *
     * @exception MessagingException
     */
    protected synchronized void loadBodyStructure() throws MessagingException {
        // don't retrieve if already loaded.
        if (bodyStructure != null) {
            return;
        }

        // make sure this is in a valid state.
        checkValidity();
        // we need to ensure that we're the only ones with access to the folder's
        // message cache any time we need to talk to the server.  This needs to be
        // held until after we release the connection so that any pending EXPUNGE
        // untagged responses are processed before the next time the folder connection is
        // used.
        synchronized (folder) {
            IMAPConnection connection = getConnection();
            try {
                // fetch the envelope information for this
                bodyStructure = connection.fetchBodyStructure(sequenceNumber);
                // go update all of the information
            } finally {
                releaseConnection(connection);
            }

            // update this before we release the folder lock so we can avoid
            // deadlock.
            updateBodyStructure(bodyStructure);
        }
    }


    /**
     * Update the BODYSTRUCTURE information from the IMAP server.
     *
     * @exception MessagingException
     */
    protected synchronized void updateBodyStructure(IMAPBodyStructure structure) throws MessagingException {
        // save the reference.
        bodyStructure = structure;
        // now update various headers with the information from the body structure

        // now update header information with the body structure data.
        if (bodyStructure.lines != -1) {
            updateHeader("Lines", Integer.toString(bodyStructure.lines));
        }

        // languages are a little more complicated
        if (bodyStructure.languages != null) {
            // this is a duplicate of what happens in the super class, but
            // the superclass methods call setHeader(), which we override and
            // throw an exception for.  We need to set the headers ourselves.
            if (bodyStructure.languages.size() == 1) {
                updateHeader("Content-Language", (String)bodyStructure.languages.get(0));
            }
            else {
                StringBuffer buf = new StringBuffer(bodyStructure.languages.size() * 20);
                buf.append(bodyStructure.languages.get(0));
                for (int i = 1; i < bodyStructure.languages.size(); i++) {
                    buf.append(',').append(bodyStructure.languages.get(i));
                }
                updateHeader("Content-Language", buf.toString());
            }
        }

        updateHeader("Content-Type", bodyStructure.mimeType.toString());
        if (bodyStructure.disposition != null) {
            updateHeader("Content-Disposition", bodyStructure.disposition.toString());
        }

        updateHeader("Content-Transfer-Encoding", bodyStructure.transferEncoding);
        updateHeader("Content-ID", bodyStructure.contentID);
        // NB:  This is already in encoded form, if needed.
        updateHeader("Content-Description", bodyStructure.contentDescription);
    }


    /**
     * Load the message content into the Message object.
     *
     * @exception MessagingException
     */
    protected void loadContent() throws MessagingException {
        // if we've loaded this already, just return
        if (content != null) {
            return;
        }

        // we need to ensure that we're the only ones with access to the folder's
        // message cache any time we need to talk to the server.  This needs to be
        // held until after we release the connection so that any pending EXPUNGE
        // untagged responses are processed before the next time the folder connection is
        // used.
        synchronized (folder) {
            IMAPConnection connection = getConnection();
            try {
                // load the content from the server.
                content = connection.fetchContent(getSequenceNumber(), section);
            } finally {
                releaseConnection(connection);
            }
        }
    }


    /**
     * Retrieve the sequence number assigned to this message.
     *
     * @return The messages assigned sequence number.  This maps back to the server's assigned number for
     * this message.
     */
    int getSequenceNumber() {
        return sequenceNumber;
    }

    /**
     * Set the sequence number for the message.  This
     * is updated whenever messages get expunged from
     * the folder.
     *
     * @param s      The new sequence number.
     */
    void setSequenceNumber(int s) {
        sequenceNumber = s;
    }


    /**
     * Retrieve the message UID value.
     *
     * @return The assigned UID value, if we have the information.
     */
    long getUID() {
        return uid;
    }

    /**
     * Set the message UID value.
     *
     * @param uid    The new UID value.
     */
    void setUID(long uid) {
        this.uid = uid;
    }


    /**
     * get the current connection pool attached to the folder.  We need
     * to do this dynamically, to A) ensure we're only accessing an
     * currently open folder, and B) to make sure we're using the
     * correct connection attached to the folder.
     *
     * @return A connection attached to the hosting folder.
     */
    protected IMAPConnection getConnection() throws MessagingException {
        // the folder owns everything.
        return ((IMAPFolder)folder).getMessageConnection();
    }

    /**
     * Release the connection back to the Folder after performing an operation
     * that requires a connection.
     *
     * @param connection The previously acquired connection.
     */
    protected void releaseConnection(IMAPConnection connection) throws MessagingException {
        // the folder owns everything.
        ((IMAPFolder)folder).releaseMessageConnection(connection);
    }


    /**
     * Check the validity of the current message.  This ensures that
     * A) the folder is currently open, B) that the message has not
     * been expunged (after getting the latest status from the server).
     *
     * @exception MessagingException
     */
    protected void checkValidity() throws MessagingException {
        checkValidity(false);
    }


    /**
     * Check the validity of the current message.  This ensures that
     * A) the folder is currently open, B) that the message has not
     * been expunged (after getting the latest status from the server).
     *
     * @exception MessagingException
     */
    protected void checkValidity(boolean update) throws MessagingException {
        // we need to ensure that we're the only ones with access to the folder's
        // message cache any time we need to talk to the server.  This needs to be
        // held until after we release the connection so that any pending EXPUNGE
        // untagged responses are processed before the next time the folder connection is
        // used.
        if (update) {
            synchronized (folder) {
                // have the connection update the folder status.  This might result in this message
                // changing its state to expunged.  It might also result in an exception if the
                // folder has been closed.
                IMAPConnection connection = getConnection();

                try {
                    connection.updateMailboxStatus();
                } finally {
                    // this will force any expunged messages to be processed before we release
                    // the lock.
                    releaseConnection(connection);
                }
            }
        }

        // now see if we've been expunged, this is a bad op on the message.
        if (isExpunged()) {
            throw new MessageRemovedException("Illegal opertion on a deleted message");
        }
    }


    /**
     * Evaluate whether this message requires any of the information
     * in a FetchProfile to be fetched from the server.  If the messages
     * already contains the information in the profile, it returns false.
     * This allows IMAPFolder to optimize fetch() requests to just the
     * messages that are missing any of the requested information.
     *
     * NOTE:  If any of the items in the profile are missing, then this
     * message will be updated with ALL of the items.
     *
     * @param profile The FetchProfile indicating the information that should be prefetched.
     *
     * @return true if any of the profile information requires fetching.  false if this
     *         message already contains the given information.
     */
    protected boolean evaluateFetch(FetchProfile profile) {
        // the fetch profile can contain a number of different item types.  Validate
        // whether we need any of these and return true on the first mismatch.

        // the UID is a common fetch request, put it first.
        if (profile.contains(UIDFolder.FetchProfileItem.UID) && uid == -1) {
            return true;
        }
        if (profile.contains(FetchProfile.Item.ENVELOPE) && envelope == null) {
            return true;
        }
        if (profile.contains(FetchProfile.Item.FLAGS) && flags == null) {
            return true;
        }
        if (profile.contains(FetchProfile.Item.CONTENT_INFO) && bodyStructure == null) {
            return true;
        }
        // The following profile items are our implementation of items that the
        // Sun IMAPFolder implementation supports.
        if (profile.contains(IMAPFolder.FetchProfileItem.HEADERS) && !allHeadersRetrieved) {
            return true;
        }
        if (profile.contains(IMAPFolder.FetchProfileItem.SIZE) && bodyStructure.bodySize < 0) {
            return true;
        }
        // last bit after checking each of the information types is to see if
        // particular headers have been requested and whether those are on the
        // set we do have loaded.
        String [] requestedHeaders = profile.getHeaderNames();

        // ok, any missing header in the list is enough to force us to request the
        // information.
        for (int i = 0; i < requestedHeaders.length; i++) {
            if (headers.getHeader(requestedHeaders[i]) == null) {
                return true;
            }
        }
        // this message, at least, does not need anything fetched.
        return false;
    }

    /**
     * Update a message instance with information retrieved via an IMAP FETCH
     * command.  The command response for this message may contain multiple pieces
     * that we need to process.
     *
     * @param response The response line, which may contain multiple data items.
     *
     * @exception MessagingException
     */
    void updateMessageInformation(IMAPFetchResponse response) throws MessagingException {
        // get the list of data items associated with this response.  We can have
        // a large number of items returned in a single update.
        List items = response.getDataItems();

        for (int i = 0; i < items.size(); i++) {
            IMAPFetchDataItem item = (IMAPFetchDataItem)items.get(i);

            switch (item.getType()) {
                // if the envelope has been requested, we'll end up with all of these items.
                case IMAPFetchDataItem.ENVELOPE:
                    // update the envelope and map the envelope items into the headers.
                    updateEnvelope((IMAPEnvelope)item);
                    break;
                case IMAPFetchDataItem.INTERNALDATE:
                    receivedDate = ((IMAPInternalDate)item).getDate();;
                    break;
                case IMAPFetchDataItem.SIZE:
                    size = ((IMAPMessageSize)item).size;
                    break;
                case IMAPFetchDataItem.UID:
                    uid = ((IMAPUid)item).uid;
                    // make sure the folder knows about the UID update.
                    ((IMAPFolder)folder).addToUidCache(new Long(uid), this);
                    break;
                case IMAPFetchDataItem.BODYSTRUCTURE:
                    updateBodyStructure((IMAPBodyStructure)item);
                    break;
                    // a partial or full header update
                case IMAPFetchDataItem.HEADER:
                {
                    // if we've fetched the complete set, then replace what we have
                    IMAPInternetHeader h = (IMAPInternetHeader)item;
                    if (h.isComplete()) {
                        // we've got a complete header set now.
                        this.headers = h.headers;
                        allHeadersRetrieved = true;
                    }
                    else {
                        // need to merge the requested headers in with
                        // our existing set.  We need to be careful, since we
                        // don't want to add duplicates.
                        mergeHeaders(h.headers);
                    }
                }
                default:
            }
        }
    }


    /**
     * Merge a subset of the requested headers with our existing partial set.
     * The new set will contain all headers requested from the server, plus
     * any of our existing headers that were not included in the retrieved set.
     *
     * @param newHeaders The retrieved set of headers.
     */
    protected synchronized void mergeHeaders(InternetHeaders newHeaders) {
        // This is sort of tricky to manage.  The input headers object is a fresh set
        // retrieved from the server, but it's a subset of the headers.  Our existing set
        // might not be complete, but it may contain duplicates of information in the
        // retrieved set, plus headers that are not in the retrieved set.  To keep from
        // adding duplicates, we'll only add headers that are not in the retrieved set to
        // that set.

        // start by running through the list of headers
        Enumeration e = headers.getAllHeaders();

        while (e.hasMoreElements()) {
            Header header = (Header)e.nextElement();
            // if there are no headers with this name in the new set, then
            // we can add this.  Note that to add the header, we need to
            // retrieve all instances by this name and add them as a unit.
            // When we hit one of the duplicates again with the enumeration,
            // we'll skip it then because the merge target will have everything.
            if (newHeaders.getHeader(header.getName()) == null) {
                // get all occurrences of this name and stuff them into the
                // new list
                String name = header.getName();
                String[] a = headers.getHeader(name);
                for (int i = 0; i < a.length; i++) {
                    newHeaders.addHeader(name, a[i]);
                }
            }
        }
        // and replace the current header set
        headers = newHeaders;
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -



geronimo-javamail_1.6/geronimo-javamail_1.6_provider/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPMessage.java [79:1298]:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
public class IMAPMessage extends MimeMessage {

    private static final byte[] CRLF = new byte[]{'\r', '\n'};

    // the Store we're stored in (which manages the connection and other stuff).
    protected IMAPStore store;

    // the IMAP server sequence number (potentially updated during the life of this message object).
    protected int sequenceNumber;
    // the IMAP uid value;
    protected long uid = -1;
    // the section identifier.  This is only really used for nested messages.  The toplevel version
    // will be null, and each nested message will set the appropriate part identifier
    protected String section;
    // the loaded message envelope (delayed until needed)
    protected IMAPEnvelope envelope;
    // the body structure information (also lazy loaded).
    protected IMAPBodyStructure bodyStructure;
    // the IMAP INTERNALDATE value.
    protected Date receivedDate;
    // the size item, which is maintained separately from the body structure
    // as it can be retrieved without getting the body structure
    protected int size;
    // turned on once we've requested the entire header set.
    protected boolean allHeadersRetrieved = false;
    // singleton date formatter for this class.
    static protected MailDateFormat dateFormat = new MailDateFormat();


    /**
     * Contruct an IMAPMessage instance.
     *
     * @param folder   The hosting folder for the message.
     * @param store    The Store owning the article (and folder).
     * @param msgnum   The article message number.  This is assigned by the Folder, and is unique
     *                 for each message in the folder.  The message numbers are only valid
     *                 as long as the Folder is open.
     * @param sequenceNumber The IMAP server manages messages by sequence number, which is subject to
     *                 change whenever messages are expunged.  This is the server retrieval number
     *                 of the message, which needs to be synchronized with status updates
     *                 sent from the server.
     *
     * @exception MessagingException
     */
	IMAPMessage(IMAPFolder folder, IMAPStore store, int msgnum, int sequenceNumber) {
		super(folder, msgnum);
        this.sequenceNumber = sequenceNumber;
		this.store = store;
        // The default constructor creates an empty Flags item.  We need to clear this out so we
        // know if the flags need to be fetched from the server when requested.
        flags = null;
        // make sure this is a totally fresh set of headers.  We'll fill things in as we retrieve them.
        headers = new InternetHeaders();
	}


    /**
     * Override for the Message class setExpunged() method to allow
     * us to do additional cleanup for expunged messages.
     *
     * @param value  The new expunge setting.
     */
    public void setExpunged(boolean value) {
        // super class handles most of the details
        super.setExpunged(value);
        // if we're now expunged, this removes us from the server message sequencing scheme, so
        // we need to invalidate the sequence number.
        if (isExpunged()) {
            sequenceNumber = -1;
        }
    }


    /**
     * Return a copy the flags associated with this message.
     *
     * @return a copy of the flags for this message
     * @throws MessagingException if there was a problem accessing the Store
     */
    public synchronized Flags getFlags() throws MessagingException {
        // load the flags, if needed
        loadFlags();
        return super.getFlags();
    }


    /**
     * Check whether the supplied flag is set.
     * The default implementation checks the flags returned by {@link #getFlags()}.
     *
     * @param flag the flags to check for
     * @return true if the flags is set
     * @throws MessagingException if there was a problem accessing the Store
     */
    public synchronized boolean isSet(Flags.Flag flag) throws MessagingException {
        // load the flags, if needed
        loadFlags();
        return super.isSet(flag);
    }

    /**
     * Set or clear a flag value.
     *
     * @param flags  The set of flags to effect.
     * @param set    The value to set the flag to (true or false).
     *
     * @exception MessagingException
     */
    public synchronized void setFlags(Flags flag, boolean set) throws MessagingException {
        // make sure this is in a valid state.
        checkValidity();

        // we need to ensure that we're the only ones with access to the folder's
        // message cache any time we need to talk to the server.  This needs to be
        // held until after we release the connection so that any pending EXPUNGE
        // untagged responses are processed before the next time the folder connection is
        // used.
        synchronized (folder) {
            IMAPConnection connection = getConnection();

            try {
                // set the flags for this item and update the
                // internal state with the new values returned from the
                // server.
                flags = connection.setFlags(sequenceNumber, flag, set);
            } finally {
                releaseConnection(connection);
            }
        }
    }


    /**
     * Return an InputStream instance for accessing the
     * message content.
     *
     * @return An InputStream instance for accessing the content
     *         (body) of the message.
     * @exception MessagingException
     * @see MimeMessage#getContentStream()
     */
	protected InputStream getContentStream() throws MessagingException {

        // no content loaded yet?
        if (content == null) {
            // make sure we're still valid
            checkValidity();
            // make sure the content is fully loaded
            loadContent();
        }

        // allow the super class to handle creating it from the loaded content.
        return super.getContentStream();
	}


    /**
     * Write out the byte data to the provided output stream.
     *
     * @param out    The target stream.
     *
     * @exception IOException
     * @exception MessagingException
     */
    public void writeTo(OutputStream out) throws IOException, MessagingException {
        // no content loaded yet?
        if (content == null) {
            // make sure we're still valid
            checkValidity();
            // make sure the content is fully loaded
            loadContent();
        }

        loadHeaders();

        Enumeration e = headers.getAllHeaderLines();
        while(e.hasMoreElements()) {
            String line = (String)e.nextElement();
            out.write(line.getBytes("ISO8859-1"));
            out.write(CRLF);
        }
        out.write(CRLF);
        out.write(CRLF);
        out.write(content);
    }

	/******************************************************************
	 * Following is a set of methods that deal with information in the
	 * envelope.  These methods ensure the enveloper is loaded and
     * retrieve the information.
	 ********************************************************************/


    /**
     * Get the message "From" addresses.  This looks first at the
     * "From" headers, and no "From" header is found, the "Sender"
     * header is checked.  Returns null if not found.
     *
     * @return An array of addresses identifying the message from target.  Returns
     *         null if this is not resolveable from the headers.
     * @exception MessagingException
     */
    public Address[] getFrom() throws MessagingException {
        // make sure we've retrieved the envelope information.
        loadEnvelope();
        // make sure we return a copy of the array so this can't be changed.
        Address[] addresses = envelope.from;
        if (addresses == null) {
            return null;
        }
        return (Address[])addresses.clone();
    }


    /**
     * Return the "Sender" header as an address.
     *
     * @return the "Sender" header as an address, or null if not present
     * @throws MessagingException if there was a problem parsing the header
     */
    public Address getSender() throws MessagingException {
        // make sure we've retrieved the envelope information.
        loadEnvelope();
        // make sure we return a copy of the array so this can't be changed.
        Address[] addresses = envelope.sender;
        if (addresses == null) {
            return null;
        }
        // There's only a single sender, despite IMAP potentially returning a list
        return addresses[0];
    }

    /**
     * Gets the recipients by type.  Returns null if there are no
     * headers of the specified type.  Acceptable RecipientTypes are:
     *
     *   javax.mail.Message.RecipientType.TO
     *   javax.mail.Message.RecipientType.CC
     *   javax.mail.Message.RecipientType.BCC
     *   javax.mail.internet.MimeMessage.RecipientType.NEWSGROUPS
     *
     * @param type   The message RecipientType identifier.
     *
     * @return The array of addresses for the specified recipient types.
     * @exception MessagingException
     */
    public Address[] getRecipients(Message.RecipientType type) throws MessagingException {
        // make sure we've retrieved the envelope information.
        loadEnvelope();
        Address[] addresses = null;

        if (type == Message.RecipientType.TO) {
            addresses = envelope.to;
        }
        else if (type == Message.RecipientType.CC) {
            addresses = envelope.cc;
        }
        else if (type == Message.RecipientType.BCC) {
            addresses = envelope.bcc;
        }
        else {
            // this could be a newsgroup type, which will tickle the message headers.
            return super.getRecipients(type);
        }
        // make sure we return a copy of the array so this can't be changed.
        if (addresses == null) {
            return null;
        }
        return (Address[])addresses.clone();
    }

    /**
     * Get the ReplyTo address information.  The headers are parsed
     * using the "mail.mime.address.strict" setting.  If the "Reply-To" header does
     * not have any addresses, then the value of the "From" field is used.
     *
     * @return An array of addresses obtained from parsing the header.
     * @exception MessagingException
     */
    public Address[] getReplyTo() throws MessagingException {
        // make sure we've retrieved the envelope information.
        loadEnvelope();
        // make sure we return a copy of the array so this can't be changed.
        Address[] addresses = envelope.replyTo;
        if (addresses == null) {
            return null;
        }
        return (Address[])addresses.clone();
    }

    /**
     * Returns the value of the "Subject" header.  If the subject
     * is encoded as an RFC 2047 value, the value is decoded before
     * return.  If decoding fails, the raw string value is
     * returned.
     *
     * @return The String value of the subject field.
     * @exception MessagingException
     */
    public String getSubject() throws MessagingException {
        // make sure we've retrieved the envelope information.
        loadEnvelope();

        if (envelope.subject == null) {
            return null;
        }
        // the subject could be encoded.  If there is a decoding error,
        // return the raw subject string.
        try {
            return MimeUtility.decodeText(envelope.subject);
        } catch (UnsupportedEncodingException e) {
            return envelope.subject;
        }
    }

    /**
     * Get the value of the "Date" header field.  Returns null if
     * if the field is absent or the date is not in a parseable format.
     *
     * @return A Date object parsed according to RFC 822.
     * @exception MessagingException
     */
    public Date getSentDate() throws MessagingException {
        // make sure we've retrieved the envelope information.
        loadEnvelope();
        // just return that directly
        return envelope.date;
    }


    /**
     * Get the message received date.
     *
     * @return Always returns the formatted INTERNALDATE, if available.
     * @exception MessagingException
     */
    public Date getReceivedDate() throws MessagingException {
        loadEnvelope();
        return receivedDate;
    }


    /**
     * Retrieve the size of the message content.  The content will
     * be retrieved from the server, if necessary.
     *
     * @return The size of the content.
     * @exception MessagingException
     */
	public int getSize() throws MessagingException {
        // make sure we've retrieved the envelope information.  We load the
        // size when we retrieve that.
        loadEnvelope();
        return size;
	}


    /**
     * Get a line count for the IMAP message.  This is potentially
     * stored in the Lines article header.  If not there, we return
     * a default of -1.
     *
     * @return The header line count estimate, or -1 if not retrieveable.
     * @exception MessagingException
     */
    public int getLineCount() throws MessagingException {
        loadBodyStructure();
        return bodyStructure.lines;
    }

    /**
     * Return the IMAP in reply to information (retrieved with the
     * ENVELOPE).
     *
     * @return The in reply to String value, if available.
     * @exception MessagingException
     */
    public String getInReplyTo() throws MessagingException {
        loadEnvelope();
        return envelope.inReplyTo;
    }

    /**
     * Returns the current content type (defined in the "Content-Type"
     * header.  If not available, "text/plain" is the default.
     *
     * @return The String name of the message content type.
     * @exception MessagingException
     */
    public String getContentType() throws MessagingException {
        loadBodyStructure();
        return bodyStructure.mimeType.toString();
    }


    /**
     * Tests to see if this message has a mime-type match with the
     * given type name.
     *
     * @param type   The tested type name.
     *
     * @return If this is a type match on the primary and secondare portion of the types.
     * @exception MessagingException
     */
    public boolean isMimeType(String type) throws MessagingException {
        loadBodyStructure();
        return bodyStructure.mimeType.match(type);
    }

    /**
     * Retrieve the message "Content-Disposition" header field.
     * This value represents how the part should be represented to
     * the user.
     *
     * @return The string value of the Content-Disposition field.
     * @exception MessagingException
     */
    public String getDisposition() throws MessagingException {
        loadBodyStructure();
        if (bodyStructure.disposition != null) {
            return bodyStructure.disposition.getDisposition();
        }
        return null;
    }

    /**
     * Decode the Content-Transfer-Encoding header to determine
     * the transfer encoding type.
     *
     * @return The string name of the required encoding.
     * @exception MessagingException
     */
    public String getEncoding() throws MessagingException {
        loadBodyStructure();
        return bodyStructure.transferEncoding;
    }

    /**
     * Retrieve the value of the "Content-ID" header.  Returns null
     * if the header does not exist.
     *
     * @return The current header value or null.
     * @exception MessagingException
     */
    public String getContentID() throws MessagingException {
        loadBodyStructure();
        return bodyStructure.contentID;
    }

    public String getContentMD5() throws MessagingException {
        loadBodyStructure();
        return bodyStructure.md5Hash;
    }


    public String getDescription() throws MessagingException {
        loadBodyStructure();

        if (bodyStructure.contentDescription == null) {
            return null;
        }
        // the subject could be encoded.  If there is a decoding error,
        // return the raw subject string.
        try {
            return MimeUtility.decodeText(bodyStructure.contentDescription);
        } catch (UnsupportedEncodingException e) {
            return bodyStructure.contentDescription;
        }
    }

    /**
     * Return the content languages associated with this
     * message.
     *
     * @return
     * @exception MessagingException
     */
    public String[] getContentLanguage() throws MessagingException {
        loadBodyStructure();

        if (!bodyStructure.languages.isEmpty()) {
            return (String[])bodyStructure.languages.toArray(new String[bodyStructure.languages.size()]);
        }
        return null;
    }

    public String getMessageID() throws MessagingException {
        loadEnvelope();
        return envelope.messageID;
    }

    public void setFrom(Address address) throws MessagingException {
        throw new IllegalWriteException("IMAP messages are read-only");
    }

    public void addFrom(Address[] address) throws MessagingException {
        throw new IllegalWriteException("IMAP messages are read-only");
    }

    public void setSender(Address address) throws MessagingException {
        throw new IllegalWriteException("IMAP messages are read-only");
    }

    public void setRecipients(Message.RecipientType type, Address[] addresses) throws MessagingException {
        throw new IllegalWriteException("IMAP messages are read-only");
    }

    public void setRecipients(Message.RecipientType type, String address) throws MessagingException {
        throw new IllegalWriteException("IMAP messages are read-only");
    }

    public void addRecipients(Message.RecipientType type, Address[] address) throws MessagingException {
        throw new IllegalWriteException("IMAP messages are read-only");
    }

    public void setReplyTo(Address[] address) throws MessagingException {
        throw new IllegalWriteException("IMAP messages are read-only");
    }

    public void setSubject(String subject) throws MessagingException {
        throw new IllegalWriteException("IMAP messages are read-only");
    }

    public void setSubject(String subject, String charset) throws MessagingException {
        throw new IllegalWriteException("IMAP messages are read-only");
    }

    public void setSentDate(Date sent) throws MessagingException {
        throw new IllegalWriteException("IMAP messages are read-only");
    }

    public void setDisposition(String disposition) throws MessagingException {
        throw new IllegalWriteException("IMAP messages are read-only");
    }

    public void setContentID(String cid) throws MessagingException {
        throw new IllegalWriteException("IMAP messages are read-only");
    }

    public void setContentMD5(String md5) throws MessagingException {
        throw new IllegalWriteException("IMAP messages are read-only");
    }

    public void setDescription(String description) throws MessagingException {
        throw new IllegalWriteException("IMAP messages are read-only");
    }

    public void setDescription(String description, String charset) throws MessagingException {
        throw new IllegalWriteException("IMAP messages are read-only");
    }

    public void setContentLanguage(String[] languages) throws MessagingException {
        throw new IllegalWriteException("IMAP messages are read-only");
    }


	/******************************************************************
	 * Following is a set of methods that deal with headers
	 * These methods are just overrides on the superclass methods to
     * allow lazy loading of the header information.
	 ********************************************************************/

	public String[] getHeader(String name) throws MessagingException {
        loadHeaders();
		return headers.getHeader(name);
	}

	public String getHeader(String name, String delimiter) throws MessagingException {
        loadHeaders();
		return headers.getHeader(name, delimiter);
	}

	public Enumeration getAllHeaders() throws MessagingException {
        loadHeaders();
		return headers.getAllHeaders();
	}

	public Enumeration getMatchingHeaders(String[] names)  throws MessagingException {
        loadHeaders();
		return headers.getMatchingHeaders(names);
	}

	public Enumeration getNonMatchingHeaders(String[] names) throws MessagingException {
        loadHeaders();
		return headers.getNonMatchingHeaders(names);
	}

	public Enumeration getAllHeaderLines() throws MessagingException {
        loadHeaders();
		return headers.getAllHeaderLines();
	}

	public Enumeration getMatchingHeaderLines(String[] names) throws MessagingException {
        loadHeaders();
		return headers.getMatchingHeaderLines(names);
	}

	public Enumeration getNonMatchingHeaderLines(String[] names) throws MessagingException {
        loadHeaders();
		return headers.getNonMatchingHeaderLines(names);
	}

    // the following are overrides for header modification methods.  These messages are read only,
    // so the headers cannot be modified.
    public void addHeader(String name, String value) throws MessagingException {
        throw new IllegalWriteException("IMAP messages are read-only");
    }

    public void setHeader(String name, String value) throws MessagingException {
        throw new IllegalWriteException("IMAP messages are read-only");
    }


    public void removeHeader(String name) throws MessagingException {
        throw new IllegalWriteException("IMAP messages are read-only");
    }

    public void addHeaderLine(String line) throws MessagingException {
        throw new IllegalWriteException("IMAP messages are read-only");
    }

	/**
	 * We cannot modify these messages
	 */
	public void saveChanges() throws MessagingException {
		throw new IllegalWriteException("IMAP messages are read-only");
	}


    /**
     * Utility method for synchronizing IMAP envelope information and
     * the message headers.
     *
     * @param header    The target header name.
     * @param addresses The update addresses.
     */
    protected void updateHeader(String header, InternetAddress[] addresses) throws MessagingException {
        if (addresses != null) {
            headers.addHeader(header, InternetAddress.toString(addresses));
        }
    }

    /**
     * Utility method for synchronizing IMAP envelope information and
     * the message headers.
     *
     * @param header    The target header name.
     * @param address   The update address.
     */
    protected void updateHeader(String header, Address address) throws MessagingException {
        if (address != null) {
            headers.setHeader(header, address.toString());
        }
    }

    /**
     * Utility method for synchronizing IMAP envelope information and
     * the message headers.
     *
     * @param header    The target header name.
     * @param value     The update value.
     */
    protected void updateHeader(String header, String value) throws MessagingException {
        if (value != null) {
            headers.setHeader(header, value);
        }
    }


    /**
     * Create the DataHandler object for this message.
     *
     * @return The DataHandler object that processes the content set for this
     *         message.
     * @exception MessagingException
     */
    public synchronized DataHandler getDataHandler() throws MessagingException {
        // check the validity and make sure we have the body structure information.
        checkValidity();
        loadBodyStructure();
        if (dh == null) {
            // are we working with a multipart message here?
            if (bodyStructure.isMultipart()) {
                dh = new DataHandler(new IMAPMultipartDataSource(this, this, section, bodyStructure));
                return dh;
            }
            else if (bodyStructure.isAttachedMessage()) {
                dh = new DataHandler(new IMAPAttachedMessage(this, section, bodyStructure.nestedEnvelope, bodyStructure.nestedBody),
                     bodyStructure.mimeType.toString());
                return dh;
            }
        }

        // single part messages get handled the normal way.
        return super.getDataHandler();
    }

    public void setDataHandler(DataHandler content) throws MessagingException {
        throw new IllegalWriteException("IMAP body parts are read-only");
    }

    /**
     * Update the message headers from an input stream.
     *
     * @param in     The InputStream source for the header information.
     *
     * @exception MessagingException
     */
    public void updateHeaders(InputStream in) throws MessagingException {
        // wrap a stream around the reply data and read as headers.
        headers = new InternetHeaders(in);
        allHeadersRetrieved = true;
    }

    /**
     * Load the flag set for this message from the server.
     *
     * @exception MessagingeException
     */
    public void loadFlags() throws MessagingException {
        // make sure this is in a valid state.
        checkValidity();
        // if the flags are already loaded, nothing to do
        if (flags != null) {
            return;
        }
        // we need to ensure that we're the only ones with access to the folder's
        // message cache any time we need to talk to the server.  This needs to be
        // held until after we release the connection so that any pending EXPUNGE
        // untagged responses are processed before the next time the folder connection is
        // used.
        synchronized (folder) {
            IMAPConnection connection = getConnection();

            try {
                // fetch the flags for this item.
                flags = connection.fetchFlags(sequenceNumber);
            } finally {
                releaseConnection(connection);
            }
        }
    }


    /**
     * Retrieve the message raw message headers from the IMAP server, synchronizing with the existing header set.
     *
     * @exception MessagingException
     */
    protected synchronized void loadHeaders() throws MessagingException {
        // don't retrieve if already loaded.
        if (allHeadersRetrieved) {
            return;
        }

        // make sure this is in a valid state.
        checkValidity();
        // we need to ensure that we're the only ones with access to the folder's
        // message cache any time we need to talk to the server.  This needs to be
        // held until after we release the connection so that any pending EXPUNGE
        // untagged responses are processed before the next time the folder connection is
        // used.
        synchronized (folder) {
            IMAPConnection connection = getConnection();

            try {
                // get the headers and set
                headers = connection.fetchHeaders(sequenceNumber, section);
                // we have the entire header set, not just a subset.
                allHeadersRetrieved = true;
            } finally {
                releaseConnection(connection);
            }
        }
    }


    /**
     * Retrieve the message envelope from the IMAP server, synchronizing the headers with the
     * information.
     *
     * @exception MessagingException
     */
    protected synchronized void loadEnvelope() throws MessagingException {
        // don't retrieve if already loaded.
        if (envelope != null) {
            return;
        }

        // make sure this is in a valid state.
        checkValidity();
        // we need to ensure that we're the only ones with access to the folder's
        // message cache any time we need to talk to the server.  This needs to be
        // held until after we release the connection so that any pending EXPUNGE
        // untagged responses are processed before the next time the folder connection is
        // used.
        synchronized (folder) {
            IMAPConnection connection = getConnection();
            try {
                // fetch the envelope information for this
                List fetches = connection.fetchEnvelope(sequenceNumber);
                // now process all of the fetch responses before releasing the folder lock.
                // it's possible that an unsolicited update on another thread might try to
                // make an update, causing a potential deadlock.
                for (int i = 0; i < fetches.size(); i++) {
                    // get the returned data items from each of the fetch responses
                    // and process.
                    IMAPFetchResponse fetch = (IMAPFetchResponse)fetches.get(i);
                    // update the internal info
                    updateMessageInformation(fetch);
                }
            } finally {
                releaseConnection(connection);
            }
        }
    }


    /**
     * Retrieve the message envelope from the IMAP server, synchronizing the headers with the
     * information.
     *
     * @exception MessagingException
     */
    protected synchronized void updateEnvelope(IMAPEnvelope envelope) throws MessagingException {
        // set the envelope item
        this.envelope = envelope;

        // copy header type information from the envelope into the headers.
        updateHeader("From", envelope.from);
        if (envelope.sender != null) {
            // we can only have a single sender, even though the envelope theoretically supports more.
            updateHeader("Sender", envelope.sender[0]);
        }
        updateHeader("To", envelope.to);
        updateHeader("Cc", envelope.cc);
        updateHeader("Bcc", envelope.bcc);
        updateHeader("Reply-To", envelope.replyTo);
        // NB:  This is already in encoded form, if needed.
        updateHeader("Subject", envelope.subject);
        updateHeader("Message-ID", envelope.messageID);
    }


    /**
     * Retrieve the BODYSTRUCTURE information from the IMAP server.
     *
     * @exception MessagingException
     */
    protected synchronized void loadBodyStructure() throws MessagingException {
        // don't retrieve if already loaded.
        if (bodyStructure != null) {
            return;
        }

        // make sure this is in a valid state.
        checkValidity();
        // we need to ensure that we're the only ones with access to the folder's
        // message cache any time we need to talk to the server.  This needs to be
        // held until after we release the connection so that any pending EXPUNGE
        // untagged responses are processed before the next time the folder connection is
        // used.
        synchronized (folder) {
            IMAPConnection connection = getConnection();
            try {
                // fetch the envelope information for this
                bodyStructure = connection.fetchBodyStructure(sequenceNumber);
                // go update all of the information
            } finally {
                releaseConnection(connection);
            }

            // update this before we release the folder lock so we can avoid
            // deadlock.
            updateBodyStructure(bodyStructure);
        }
    }


    /**
     * Update the BODYSTRUCTURE information from the IMAP server.
     *
     * @exception MessagingException
     */
    protected synchronized void updateBodyStructure(IMAPBodyStructure structure) throws MessagingException {
        // save the reference.
        bodyStructure = structure;
        // now update various headers with the information from the body structure

        // now update header information with the body structure data.
        if (bodyStructure.lines != -1) {
            updateHeader("Lines", Integer.toString(bodyStructure.lines));
        }

        // languages are a little more complicated
        if (bodyStructure.languages != null) {
            // this is a duplicate of what happens in the super class, but
            // the superclass methods call setHeader(), which we override and
            // throw an exception for.  We need to set the headers ourselves.
            if (bodyStructure.languages.size() == 1) {
                updateHeader("Content-Language", (String)bodyStructure.languages.get(0));
            }
            else {
                StringBuffer buf = new StringBuffer(bodyStructure.languages.size() * 20);
                buf.append(bodyStructure.languages.get(0));
                for (int i = 1; i < bodyStructure.languages.size(); i++) {
                    buf.append(',').append(bodyStructure.languages.get(i));
                }
                updateHeader("Content-Language", buf.toString());
            }
        }

        updateHeader("Content-Type", bodyStructure.mimeType.toString());
        if (bodyStructure.disposition != null) {
            updateHeader("Content-Disposition", bodyStructure.disposition.toString());
        }

        updateHeader("Content-Transfer-Encoding", bodyStructure.transferEncoding);
        updateHeader("Content-ID", bodyStructure.contentID);
        // NB:  This is already in encoded form, if needed.
        updateHeader("Content-Description", bodyStructure.contentDescription);
    }


    /**
     * Load the message content into the Message object.
     *
     * @exception MessagingException
     */
    protected void loadContent() throws MessagingException {
        // if we've loaded this already, just return
        if (content != null) {
            return;
        }

        // we need to ensure that we're the only ones with access to the folder's
        // message cache any time we need to talk to the server.  This needs to be
        // held until after we release the connection so that any pending EXPUNGE
        // untagged responses are processed before the next time the folder connection is
        // used.
        synchronized (folder) {
            IMAPConnection connection = getConnection();
            try {
                // load the content from the server.
                content = connection.fetchContent(getSequenceNumber(), section);
            } finally {
                releaseConnection(connection);
            }
        }
    }


    /**
     * Retrieve the sequence number assigned to this message.
     *
     * @return The messages assigned sequence number.  This maps back to the server's assigned number for
     * this message.
     */
    int getSequenceNumber() {
        return sequenceNumber;
    }

    /**
     * Set the sequence number for the message.  This
     * is updated whenever messages get expunged from
     * the folder.
     *
     * @param s      The new sequence number.
     */
    void setSequenceNumber(int s) {
        sequenceNumber = s;
    }


    /**
     * Retrieve the message UID value.
     *
     * @return The assigned UID value, if we have the information.
     */
    long getUID() {
        return uid;
    }

    /**
     * Set the message UID value.
     *
     * @param uid    The new UID value.
     */
    void setUID(long uid) {
        this.uid = uid;
    }


    /**
     * get the current connection pool attached to the folder.  We need
     * to do this dynamically, to A) ensure we're only accessing an
     * currently open folder, and B) to make sure we're using the
     * correct connection attached to the folder.
     *
     * @return A connection attached to the hosting folder.
     */
    protected IMAPConnection getConnection() throws MessagingException {
        // the folder owns everything.
        return ((IMAPFolder)folder).getMessageConnection();
    }

    /**
     * Release the connection back to the Folder after performing an operation
     * that requires a connection.
     *
     * @param connection The previously acquired connection.
     */
    protected void releaseConnection(IMAPConnection connection) throws MessagingException {
        // the folder owns everything.
        ((IMAPFolder)folder).releaseMessageConnection(connection);
    }


    /**
     * Check the validity of the current message.  This ensures that
     * A) the folder is currently open, B) that the message has not
     * been expunged (after getting the latest status from the server).
     *
     * @exception MessagingException
     */
    protected void checkValidity() throws MessagingException {
        checkValidity(false);
    }


    /**
     * Check the validity of the current message.  This ensures that
     * A) the folder is currently open, B) that the message has not
     * been expunged (after getting the latest status from the server).
     *
     * @exception MessagingException
     */
    protected void checkValidity(boolean update) throws MessagingException {
        // we need to ensure that we're the only ones with access to the folder's
        // message cache any time we need to talk to the server.  This needs to be
        // held until after we release the connection so that any pending EXPUNGE
        // untagged responses are processed before the next time the folder connection is
        // used.
        if (update) {
            synchronized (folder) {
                // have the connection update the folder status.  This might result in this message
                // changing its state to expunged.  It might also result in an exception if the
                // folder has been closed.
                IMAPConnection connection = getConnection();

                try {
                    connection.updateMailboxStatus();
                } finally {
                    // this will force any expunged messages to be processed before we release
                    // the lock.
                    releaseConnection(connection);
                }
            }
        }

        // now see if we've been expunged, this is a bad op on the message.
        if (isExpunged()) {
            throw new MessageRemovedException("Illegal opertion on a deleted message");
        }
    }


    /**
     * Evaluate whether this message requires any of the information
     * in a FetchProfile to be fetched from the server.  If the messages
     * already contains the information in the profile, it returns false.
     * This allows IMAPFolder to optimize fetch() requests to just the
     * messages that are missing any of the requested information.
     *
     * NOTE:  If any of the items in the profile are missing, then this
     * message will be updated with ALL of the items.
     *
     * @param profile The FetchProfile indicating the information that should be prefetched.
     *
     * @return true if any of the profile information requires fetching.  false if this
     *         message already contains the given information.
     */
    protected boolean evaluateFetch(FetchProfile profile) {
        // the fetch profile can contain a number of different item types.  Validate
        // whether we need any of these and return true on the first mismatch.

        // the UID is a common fetch request, put it first.
        if (profile.contains(UIDFolder.FetchProfileItem.UID) && uid == -1) {
            return true;
        }
        if (profile.contains(FetchProfile.Item.ENVELOPE) && envelope == null) {
            return true;
        }
        if (profile.contains(FetchProfile.Item.FLAGS) && flags == null) {
            return true;
        }
        if (profile.contains(FetchProfile.Item.CONTENT_INFO) && bodyStructure == null) {
            return true;
        }
        // The following profile items are our implementation of items that the
        // Sun IMAPFolder implementation supports.
        if (profile.contains(IMAPFolder.FetchProfileItem.HEADERS) && !allHeadersRetrieved) {
            return true;
        }
        if (profile.contains(IMAPFolder.FetchProfileItem.SIZE) && bodyStructure.bodySize < 0) {
            return true;
        }
        // last bit after checking each of the information types is to see if
        // particular headers have been requested and whether those are on the
        // set we do have loaded.
        String [] requestedHeaders = profile.getHeaderNames();

        // ok, any missing header in the list is enough to force us to request the
        // information.
        for (int i = 0; i < requestedHeaders.length; i++) {
            if (headers.getHeader(requestedHeaders[i]) == null) {
                return true;
            }
        }
        // this message, at least, does not need anything fetched.
        return false;
    }

    /**
     * Update a message instance with information retrieved via an IMAP FETCH
     * command.  The command response for this message may contain multiple pieces
     * that we need to process.
     *
     * @param response The response line, which may contain multiple data items.
     *
     * @exception MessagingException
     */
    void updateMessageInformation(IMAPFetchResponse response) throws MessagingException {
        // get the list of data items associated with this response.  We can have
        // a large number of items returned in a single update.
        List items = response.getDataItems();

        for (int i = 0; i < items.size(); i++) {
            IMAPFetchDataItem item = (IMAPFetchDataItem)items.get(i);

            switch (item.getType()) {
                // if the envelope has been requested, we'll end up with all of these items.
                case IMAPFetchDataItem.ENVELOPE:
                    // update the envelope and map the envelope items into the headers.
                    updateEnvelope((IMAPEnvelope)item);
                    break;
                case IMAPFetchDataItem.INTERNALDATE:
                    receivedDate = ((IMAPInternalDate)item).getDate();;
                    break;
                case IMAPFetchDataItem.SIZE:
                    size = ((IMAPMessageSize)item).size;
                    break;
                case IMAPFetchDataItem.UID:
                    uid = ((IMAPUid)item).uid;
                    // make sure the folder knows about the UID update.
                    ((IMAPFolder)folder).addToUidCache(new Long(uid), this);
                    break;
                case IMAPFetchDataItem.BODYSTRUCTURE:
                    updateBodyStructure((IMAPBodyStructure)item);
                    break;
                    // a partial or full header update
                case IMAPFetchDataItem.HEADER:
                {
                    // if we've fetched the complete set, then replace what we have
                    IMAPInternetHeader h = (IMAPInternetHeader)item;
                    if (h.isComplete()) {
                        // we've got a complete header set now.
                        this.headers = h.headers;
                        allHeadersRetrieved = true;
                    }
                    else {
                        // need to merge the requested headers in with
                        // our existing set.  We need to be careful, since we
                        // don't want to add duplicates.
                        mergeHeaders(h.headers);
                    }
                }
                default:
            }
        }
    }


    /**
     * Merge a subset of the requested headers with our existing partial set.
     * The new set will contain all headers requested from the server, plus
     * any of our existing headers that were not included in the retrieved set.
     *
     * @param newHeaders The retrieved set of headers.
     */
    protected synchronized void mergeHeaders(InternetHeaders newHeaders) {
        // This is sort of tricky to manage.  The input headers object is a fresh set
        // retrieved from the server, but it's a subset of the headers.  Our existing set
        // might not be complete, but it may contain duplicates of information in the
        // retrieved set, plus headers that are not in the retrieved set.  To keep from
        // adding duplicates, we'll only add headers that are not in the retrieved set to
        // that set.

        // start by running through the list of headers
        Enumeration e = headers.getAllHeaders();

        while (e.hasMoreElements()) {
            Header header = (Header)e.nextElement();
            // if there are no headers with this name in the new set, then
            // we can add this.  Note that to add the header, we need to
            // retrieve all instances by this name and add them as a unit.
            // When we hit one of the duplicates again with the enumeration,
            // we'll skip it then because the merge target will have everything.
            if (newHeaders.getHeader(header.getName()) == null) {
                // get all occurrences of this name and stuff them into the
                // new list
                String name = header.getName();
                String[] a = headers.getHeader(name);
                for (int i = 0; i < a.length; i++) {
                    newHeaders.addHeader(name, a[i]);
                }
            }
        }
        // and replace the current header set
        headers = newHeaders;
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -



