FileDocCategorySizeDatePackage
MessagePart.javaAPI DocphoneME MR2 API (J2ME)17848Wed May 02 18:00:44 BST 2007com.sun.tck.wma

MessagePart.java

/* 
 *   
 *
 * Copyright  1990-2007 Sun Microsystems, Inc. All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License version
 * 2 only, as published by the Free Software Foundation.
 * 
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License version 2 for more details (a copy is
 * included at /legal/license.txt).
 * 
 * You should have received a copy of the GNU General Public License
 * version 2 along with this work; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 * 
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
 * Clara, CA 95054 or visit www.sun.com if you need additional
 * information or have any questions.
 */

package com.sun.tck.wma;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;

import java.io.IOException;

/**
 * Instances of the <code>MessagePart</code> class can be added to a 
 * <code>MultipartMessage</code>. Each <code>MessagePart</code> consists
 * of the content element, MIME type and content-id. The Content can be of
 * any type. Additionally, it's possible to specify the content location and 
 * the encoding scheme.
 * @since WMA 2.0
 */
public class MessagePart {
 
    // IMPL_NOTE: allow maximum part size to be externally set
    /** Maximum size for message part. */
    static int MAX_PART_SIZE_BYTES = 30720; // 30K
    /**
     * Constructs a message.
     * @param contents byte array containing the contents for the
     *      <code>MessagePart</code>.
     * @param offset start position
     * @param length the number of bytes to be included in the 
     *      <code>MessagePart</code>.
     * @param mimeType the MIME Content-Type for the <code>MessagePart</code>
     *      [RFC 2046]
     * @param contentId the content-id header field value for the
     *      <code>MessagePart</code> [RFC 2045]. The content-id is unique 
     *      over all <code>MessagePart</code>s of a 
     *      <code>MultipartMessage</code> and must always be set for each
     *      message part.
     * @param contentLocation the content location which specifies the
     *      file name of the file that is attached. If the content location is
     *      set to <code>null</code> no content location will be set for this
     *      <code>MessagePart</code>.
     * @param enc the encoding scheme for the <code>MessagePart</code>.
     *      if <code>enc</code> is set to <code>null</code> no encoding will
     *      be used for this <code>MessagePart</code>.
     * @throws java.lang.IllegalArgumentException if mimeType or contentId is
     *      <code>null</code>. This exception will be thrown if
     *      <code>contentID</code> or <code>contentLocation</code> contains
     *      other characters than specified in US-ASCII format. This exception
     *      will be thrown if either <code>length</code> is less than 0 or
     *      <code>offset + length</code> exceeds the <code>length</code> of the
     *      <code>content</code> or if <code>offset</code> is less than 0 or if
     *      the specified encoding scheme is unknown.
     * @throws SizeExceededException if the <code>contents</code> is larger than
     *      the available memory or supported size for the message part
     */ 
    void construct(byte[] contents, int offset, int length, 
        java.lang.String mimeType, java.lang.String contentId,
        java.lang.String contentLocation, java.lang.String enc) throws
        SizeExceededException {

        if (length > MAX_PART_SIZE_BYTES) {
            throw new SizeExceededException(
                "InputStream data exceeds " +
                "MessagePart size limit");            
        }

        if (mimeType == null) {
            throw new IllegalArgumentException("mimeType must be specified");
        }
        checkContentID(contentId);
        checkContentLocation(contentLocation);
        if (length < 0) {
            throw new IllegalArgumentException("length must be >= 0");
        }
        if (contents != null && offset + length > contents.length) {
            throw new IllegalArgumentException(
                "offset + length exceeds contents length");
        }
        if (offset < 0) {
            throw new IllegalArgumentException("offset must be >= 0");
        }
        checkEncodingScheme(enc);
    
        if (contents != null) {
            this.content = new byte[length]; 
            System.arraycopy(contents, offset, this.content, 0, length);
        }
        
        this.mimeType = mimeType;
        this.contentID = contentId;
        this.contentLocation = contentLocation;
        this.encoding = enc;
    }
 
    /**
     * Constructs a <code>MessagePart</code> object from a subset of the byte 
     * array. This constructor is only useful if the data size is small
     * (roughly less than 10K). For larger content the <code>InputStream</code>
     * based constructor should be used.
     * @param contents byte array containing the contents for the
     *      <code>MessagePart</code>.
     * @param offset start position
     * @param length the number of bytes to be included in the 
     *      <code>MessagePart</code>.
     * @param mimeType the MIME Content-Type for the <code>MessagePart</code>
     *      [RFC 2046]
     * @param contentId the content-id header field value for the
     *      <code>MessagePart</code> [RFC 2045]. The content-id is unique 
     *      over all <code>MessagePart</code>s of a 
     *      <code>MultipartMessage</code> and must always be set for each
     *      message part.
     * @param contentLocation the content location which specifies the
     *      file name of the file that is attached. If the content location is
     *      set to <code>null</code> no content location will be set for this
     *      <code>MessagePart</code>.
     * @param enc the encoding scheme for the <code>MessagePart</code>.
     *      if <code>enc</code> is set to <code>null</code> no encoding will
     *      be used for this <code>MessagePart</code>.
     * @throws java.lang.IllegalArgumentException if mimeType or contentId is
     *      <code>null</code>. This exception will be thrown if
     *      <code>contentID</code> or <code>contentLocation</code> contains
     *      other characters than specified in US-ASCII format. This exception
     *      will be thrown if either <code>length</code> is less than 0 or
     *      <code>offset + length</code> exceeds the <code>length</code> of the
     *      <code>content</code> or if <code>offset</code> is less than 0 or if
     *      the specified encoding scheme is unknown.
     * @throws SizeExceededException if the <code>contents</code> is larger than
     *      the available memory or supported size for the message part
     */
    public MessagePart(byte[] contents, int offset, int length, 
        java.lang.String mimeType, java.lang.String contentId,
        java.lang.String contentLocation, java.lang.String enc) throws
        SizeExceededException {
        construct(contents, offset, length, mimeType, contentId,
            contentLocation, enc);
    }
       
    /**
     * Construct a <code>MessagePart</code> object from a byte array. This
     * constructor is only useful if the data size is small (roughly 10K).
     * For larger content the <code>InputStream</code> based constructor
     * should be used.
     * @param contents byte array containing the contents for the
     *      <code>MessagePart</code>. The contents of the array will be
     *      copied into the <code>MessagePart</code>.
     * @param mimeType the MIME Content-Type for the <code>MessagePart</code>
     *      [RFC 2046]
     * @param contentId the content-id header field value for the
     *      <code>MessagePart</code> [RFC 2045]. The content-id is unique 
     *      over all <code>MessagePart</code>s of a 
     *      <code>MultipartMessage</code> and must always be set for each
     *      message part.
     * @param contentLocation the content location which specifies the
     *      file name of the file that is attached. If the content location is
     *      set to <code>null</code> no content location will be set for this
     *      <code>MessagePart</code>.
     * @param enc the encoding scheme for the <code>MessagePart</code>.
     *      if <code>enc</code> is set to <code>null</code> no encoding will
     *      be used for this <code>MessagePart</code>.
     * @throws java.lang.IllegalArgumentException if mimeType or contentId is
     *      <code>null</code>. This exception will be thrown if
     *      <code>contentID</code> or <code>contentLocation</code> contains
     *      other characters than specified in US-ASCII format or if
     *      the specified encoding scheme is unknown.
     * @throws SizeExceededException if the <code>contents</code> is larger than
     *      the available memory or supported size for the message part
     */       
    public MessagePart(byte[] contents, java.lang.String mimeType, 
        java.lang.String contentId, java.lang.String contentLocation,
        java.lang.String enc) throws SizeExceededException {
        construct(contents, 0, (contents == null ? 0 : contents.length),
            mimeType, contentId, contentLocation, enc);
    }
    /** Buffer size 2048. */    
    static final int BUFFER_SIZE = 2048;
    
    /**
     * Constructs a <code>MessagePart</code> object from an 
     * <code>InputStream</code>. The contents of the <code>MessagePart</code>
     * are loaded from the <code>InputStream</code> during the constructor
     * call until the end of the stream is reached.
     * @param is <code>InputStream</code> from which the contents of the 
     *      <code>MessagePart</code> are read.
     * @param mimeType the MIME Content-Type for the <code>MessagePart</code>
     *      [RFC 2046]
     * @param contentId the content-id header field value for the
     *      <code>MessagePart</code> [RFC 2045]. The content-id is unique 
     *      over all <code>MessagePart</code>s of a 
     *      <code>MultipartMessage</code> and must always be set for each
     *      message part.
     * @param contentLocation the content location which specifies the
     *      file name of the file that is attached. If the content location is
     *      set to <code>null</code> no content location will be set for this
     *      <code>MessagePart</code>.
     * @param enc the encoding scheme for the <code>MessagePart</code>.
     *      if <code>enc</code> is set to <code>null</code> no encoding will
     *      be used for this <code>MessagePart</code>.
     * @throws java.io.IOException if reading the <code>InputStream</code>
     *      causes an exception other than <code>EOFException</code>.
     * @throws java.lang.IllegalArgumentException if mimeType or contentId is
     *      <code>null</code>. This exception will be thrown if
     *      <code>contentID</code> or <code>contentLocation</code> contains
     *      other characters than specified in US-ASCII format or if
     *      the specified encoding scheme is unknown.
     * @throws SizeExceededException of the content from the 
     *      <code>InputStream</code> is larger than the available memory or
     *      supported size for the message part.
     */
    public MessagePart(java.io.InputStream is, java.lang.String mimeType, 
        java.lang.String contentId, java.lang.String contentLocation,
        java.lang.String enc) throws IOException, SizeExceededException {
        byte[] bytes = {};
        if (is != null) {
            ByteArrayOutputStream accumulator = new ByteArrayOutputStream();
            byte[] buffer = new byte[BUFFER_SIZE];
            int readBytes = 0;
            while ((readBytes = is.read(buffer)) != -1) {
                accumulator.write(buffer, 0, readBytes);
            }
            bytes = accumulator.toByteArray();
        }
        construct(bytes, 0, bytes.length, mimeType, contentId, 
            contentLocation, enc);
    }
    
    /**
     * Returns the content of the <code>MessagePart</code> as an array of
     * bytes. If it's not possible to create an arary which can contain all 
     * data, this method must throw an <code>OutOfMemoryError</code>.
     * @return <code>MessagePart</code> data as byte array
     */
    public byte[] getContent() {
        if (content == null) {
            return null;
        }
        byte[] copyOfContent = new byte[content.length];
        System.arraycopy(content, 0, copyOfContent, 0, content.length);
        return copyOfContent;
    }
    
    /**
     * Returns an <code>InputStream</code> for reading the contents of the
     * <code>MessagePart</code>. Returns an empty stream if no content is
     * available.
     * @return an <code>InputStream</code> that can be used for reading the
     *  contents of this <code>MessagePart</code>.
     */
    public java.io.InputStream getContentAsStream() {
        if (content == null) {
            return new ByteArrayInputStream(new byte[0]);
        } else {
            return new ByteArrayInputStream(content);
        }
    }
    
    /**
     * Returns the content-id value of the <code>MessagePart</code>.
     * @return the value of the content-id as a String, or <code>null</code>
     *      if the content-id is not set (possible if the message was sent
     *      from a not JSR 205 compliant client).
     */
    public java.lang.String getContentID() {
        return contentID;
    }
    
    /**
     * Returns content location of the <code>MessagePart</code>.
     * @return content location
     */
    public java.lang.String getContentLocation() {
        return contentLocation;
    }
    
    /**
     * Returns the encoding of the content, e.g. "US-ASCII", "UTF-8",
     * "UTF-16", ... as a <code>String</code>.
     * @return the encoding of the <code>MessagePart</code> content or
     *      </code>null</code> if the encoding scheme of the 
     *      <code>MessagePart</code> cannot be determined.
     */
    public java.lang.String getEncoding() {
        return encoding;
    }
    
    /**
     * Returns the content size of this <code>MessagePart</code>.
     * @return Content size (in bytes) of this <code>MessagePart</code> or 0 if
     *      the <code>MessagePart</code> is empty.
     */
    public int getLength() {
        return content == null ? 0 : content.length; 
    }
    
    /**
     * Returns the mime type of the <code>MessagePart</code>.
     * @return MIME type of the <code>MessagePart</code>.
     */
    public java.lang.String getMIMEType() {
        return mimeType;
    }
    
    /** Content byte array. */
    byte[] content;
    /** MIME Content ID. */
    String contentID;
    /** Content location. */
    String contentLocation;
    /** Content encoding. */
    String encoding;
    /** MIME type. */
    String mimeType;

    /**
     * Verifies the content identifier.
     * @param contentId content id to be checked
     * @exception IllegalArgumentException if content id is not valid
     */    
    static void checkContentID(String contentId) 
        throws IllegalArgumentException {
        if (contentId == null) {
            throw new IllegalArgumentException("contentId must be specified");
        }
        if (contentId.length() > 100) { // MMS Conformance limit
            throw new IllegalArgumentException(
                "contentId exceeds 100 char limit");
        }
        if (containsNonUSASCII(contentId)) {
            throw new IllegalArgumentException(
                "contentId must not contain non-US-ASCII characters");
        }
    }
 
    /**
     * Verifies the content location.
     * @param contentLoc content location to be checked.
     * @exception IllegalArgumentException if content location is not valid.
     */    
    static void checkContentLocation(String contentLoc)
        throws IllegalArgumentException {
        if (contentLoc != null) {
            if (containsNonUSASCII(contentLoc)) {
                throw new IllegalArgumentException(
                    "contentLocation must not contain non-US-ASCII characters");
            }
            if (contentLoc.length() > 100) { // MMS Conformance limit
                throw new IllegalArgumentException(
                    "contentLocation exceeds 100 char limit");
            }
        }
    }
    
    /**
     * Verifies the content encoding.
     * @param encoding The content encoding to be checked.
     * @exception IllegalArgumentException if content encoding is not valid.
     */    
    static void checkEncodingScheme(String encoding)
        throws IllegalArgumentException {
        // IMPL_NOTE: check for a valid encoding scheme        
    }

    /** Lowest valid ASCII character. */
    static final char US_ASCII_LOWEST_VALID_CHAR = 32;

    /** Mask for ASCII character checks. */
    static final char US_ASCII_VALID_BIT_MASK = 0x7F;
    
    /**
     * Checks if a string contains non-ASCII characters.
     * @param str Text to be checked.
     * @return <code>true</code> if non-ASCII characters are found.
     */    
    static boolean containsNonUSASCII(String str) {
        int numChars = str.length();
        for (int i = 0; i < numChars; ++i) {
            char thisChar = str.charAt(i);
            if (thisChar < US_ASCII_LOWEST_VALID_CHAR ||
                thisChar != (thisChar & US_ASCII_VALID_BIT_MASK))
                return true;
        }
        return false;
    }
}