1/****************************************************************
2 * Licensed to the Apache Software Foundation (ASF) under one   *
3 * or more contributor license agreements.  See the NOTICE file *
4 * distributed with this work for additional information        *
5 * regarding copyright ownership.  The ASF licenses this file   *
6 * to you under the Apache License, Version 2.0 (the            *
7 * "License"); you may not use this file except in compliance   *
8 * with the License.  You may obtain a copy of the License at   *
9 *                                                              *
10 *   http://www.apache.org/licenses/LICENSE-2.0                 *
11 *                                                              *
12 * Unless required by applicable law or agreed to in writing,   *
13 * software distributed under the License is distributed on an  *
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
15 * KIND, either express or implied.  See the License for the    *
16 * specific language governing permissions and limitations      *
17 * under the License.                                           *
18 ****************************************************************/
19
20package org.apache.james.mime4j.message;
21
22import java.io.IOException;
23import java.io.InputStream;
24import java.io.OutputStream;
25import java.util.Stack;
26
27import org.apache.james.mime4j.BodyDescriptor;
28import org.apache.james.mime4j.ContentHandler;
29import org.apache.james.mime4j.MimeStreamParser;
30import org.apache.james.mime4j.decoder.Base64InputStream;
31import org.apache.james.mime4j.decoder.QuotedPrintableInputStream;
32import org.apache.james.mime4j.field.Field;
33import org.apache.james.mime4j.field.UnstructuredField;
34
35
36/**
37 * Represents a MIME message. The following code parses a stream into a
38 * <code>Message</code> object.
39 *
40 * <pre>
41 *      Message msg = new Message(new BufferedInputStream(
42 *                                      new FileInputStream("mime.msg")));
43 * </pre>
44 *
45 *
46 *
47 * @version $Id: Message.java,v 1.3 2004/10/02 12:41:11 ntherning Exp $
48 */
49public class Message extends Entity implements Body {
50
51    /**
52     * Creates a new empty <code>Message</code>.
53     */
54    public Message() {
55    }
56
57    /**
58     * Parses the specified MIME message stream into a <code>Message</code>
59     * instance.
60     *
61     * @param is the stream to parse.
62     * @throws IOException on I/O errors.
63     */
64    public Message(InputStream is) throws IOException {
65        MimeStreamParser parser = new MimeStreamParser();
66        parser.setContentHandler(new MessageBuilder());
67        parser.parse(is);
68    }
69
70
71    /**
72     * Gets the <code>Subject</code> field.
73     *
74     * @return the <code>Subject</code> field or <code>null</code> if it
75     *         doesn't exist.
76     */
77    public UnstructuredField getSubject() {
78        return (UnstructuredField) getHeader().getField(Field.SUBJECT);
79    }
80
81    /**
82     *
83     * @see org.apache.james.mime4j.message.Entity#writeTo(java.io.OutputStream)
84     */
85    @Override
86    public void writeTo(OutputStream out) throws IOException {
87        getHeader().writeTo(out);
88
89        Body body = getBody();
90        if (body instanceof Multipart) {
91            Multipart mp = (Multipart) body;
92            mp.writeTo(out);
93        } else {
94            body.writeTo(out);
95        }
96    }
97
98
99    private class MessageBuilder implements ContentHandler {
100        private Stack<Object> stack = new Stack<Object>();
101
102        public MessageBuilder() {
103        }
104
105        private void expect(Class c) {
106            if (!c.isInstance(stack.peek())) {
107                throw new IllegalStateException("Internal stack error: "
108                        + "Expected '" + c.getName() + "' found '"
109                        + stack.peek().getClass().getName() + "'");
110            }
111        }
112
113        /**
114         * @see org.apache.james.mime4j.ContentHandler#startMessage()
115         */
116        public void startMessage() {
117            if (stack.isEmpty()) {
118                stack.push(Message.this);
119            } else {
120                expect(Entity.class);
121                Message m = new Message();
122                ((Entity) stack.peek()).setBody(m);
123                stack.push(m);
124            }
125        }
126
127        /**
128         * @see org.apache.james.mime4j.ContentHandler#endMessage()
129         */
130        public void endMessage() {
131            expect(Message.class);
132            stack.pop();
133        }
134
135        /**
136         * @see org.apache.james.mime4j.ContentHandler#startHeader()
137         */
138        public void startHeader() {
139            stack.push(new Header());
140        }
141
142        /**
143         * @see org.apache.james.mime4j.ContentHandler#field(java.lang.String)
144         */
145        public void field(String fieldData) {
146            expect(Header.class);
147            ((Header) stack.peek()).addField(Field.parse(fieldData));
148        }
149
150        /**
151         * @see org.apache.james.mime4j.ContentHandler#endHeader()
152         */
153        public void endHeader() {
154            expect(Header.class);
155            Header h = (Header) stack.pop();
156            expect(Entity.class);
157            ((Entity) stack.peek()).setHeader(h);
158        }
159
160        /**
161         * @see org.apache.james.mime4j.ContentHandler#startMultipart(org.apache.james.mime4j.BodyDescriptor)
162         */
163        public void startMultipart(BodyDescriptor bd) {
164            expect(Entity.class);
165
166            Entity e = (Entity) stack.peek();
167            Multipart multiPart = new Multipart();
168            e.setBody(multiPart);
169            stack.push(multiPart);
170        }
171
172        /**
173         * @see org.apache.james.mime4j.ContentHandler#body(org.apache.james.mime4j.BodyDescriptor, java.io.InputStream)
174         */
175        public void body(BodyDescriptor bd, InputStream is) throws IOException {
176            expect(Entity.class);
177
178            String enc = bd.getTransferEncoding();
179            if ("base64".equals(enc)) {
180                is = new Base64InputStream(is);
181            } else if ("quoted-printable".equals(enc)) {
182                is = new QuotedPrintableInputStream(is);
183            }
184
185            Body body = null;
186            if (bd.getMimeType().startsWith("text/")) {
187                body = new MemoryTextBody(is, bd.getCharset());
188            } else {
189                body = new MemoryBinaryBody(is);
190            }
191
192            ((Entity) stack.peek()).setBody(body);
193        }
194
195        /**
196         * @see org.apache.james.mime4j.ContentHandler#endMultipart()
197         */
198        public void endMultipart() {
199            stack.pop();
200        }
201
202        /**
203         * @see org.apache.james.mime4j.ContentHandler#startBodyPart()
204         */
205        public void startBodyPart() {
206            expect(Multipart.class);
207
208            BodyPart bodyPart = new BodyPart();
209            ((Multipart) stack.peek()).addBodyPart(bodyPart);
210            stack.push(bodyPart);
211        }
212
213        /**
214         * @see org.apache.james.mime4j.ContentHandler#endBodyPart()
215         */
216        public void endBodyPart() {
217            expect(BodyPart.class);
218            stack.pop();
219        }
220
221        /**
222         * @see org.apache.james.mime4j.ContentHandler#epilogue(java.io.InputStream)
223         */
224        public void epilogue(InputStream is) throws IOException {
225            expect(Multipart.class);
226            StringBuffer sb = new StringBuffer();
227            int b;
228            while ((b = is.read()) != -1) {
229                sb.append((char) b);
230            }
231            ((Multipart) stack.peek()).setEpilogue(sb.toString());
232        }
233
234        /**
235         * @see org.apache.james.mime4j.ContentHandler#preamble(java.io.InputStream)
236         */
237        public void preamble(InputStream is) throws IOException {
238            expect(Multipart.class);
239            StringBuffer sb = new StringBuffer();
240            int b;
241            while ((b = is.read()) != -1) {
242                sb.append((char) b);
243            }
244            ((Multipart) stack.peek()).setPreamble(sb.toString());
245        }
246
247        /**
248         * TODO: Implement me
249         *
250         * @see org.apache.james.mime4j.ContentHandler#raw(java.io.InputStream)
251         */
252        public void raw(InputStream is) throws IOException {
253            throw new UnsupportedOperationException("Not supported");
254        }
255
256    }
257}
258