001/****************************************************************
002 * Licensed to the Apache Software Foundation (ASF) under one   *
003 * or more contributor license agreements.  See the NOTICE file *
004 * distributed with this work for additional information        *
005 * regarding copyright ownership.  The ASF licenses this file   *
006 * to you under the Apache License, Version 2.0 (the            *
007 * "License"); you may not use this file except in compliance   *
008 * with the License.  You may obtain a copy of the License at   *
009 *                                                              *
010 *   http://www.apache.org/licenses/LICENSE-2.0                 *
011 *                                                              *
012 * Unless required by applicable law or agreed to in writing,   *
013 * software distributed under the License is distributed on an  *
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
015 * KIND, either express or implied.  See the License for the    *
016 * specific language governing permissions and limitations      *
017 * under the License.                                           *
018 ****************************************************************/
019
020package org.apache.james.mime4j.samples.tree;
021
022import java.awt.Dimension;
023import java.awt.GridLayout;
024import java.io.FileInputStream;
025import java.io.FileNotFoundException;
026import java.io.IOException;
027import java.io.InputStream;
028import java.io.Reader;
029import java.util.Date;
030import java.util.Map;
031
032import javax.swing.JFrame;
033import javax.swing.JPanel;
034import javax.swing.JScrollPane;
035import javax.swing.JSplitPane;
036import javax.swing.JTextArea;
037import javax.swing.JTree;
038import javax.swing.event.TreeSelectionEvent;
039import javax.swing.event.TreeSelectionListener;
040import javax.swing.tree.DefaultMutableTreeNode;
041import javax.swing.tree.TreeSelectionModel;
042
043import org.apache.james.mime4j.MimeException;
044import org.apache.james.mime4j.dom.BinaryBody;
045import org.apache.james.mime4j.dom.Body;
046import org.apache.james.mime4j.dom.Entity;
047import org.apache.james.mime4j.dom.Header;
048import org.apache.james.mime4j.dom.Message;
049import org.apache.james.mime4j.dom.MessageBuilder;
050import org.apache.james.mime4j.dom.Multipart;
051import org.apache.james.mime4j.dom.TextBody;
052import org.apache.james.mime4j.dom.address.Mailbox;
053import org.apache.james.mime4j.dom.address.MailboxList;
054import org.apache.james.mime4j.dom.field.AddressListField;
055import org.apache.james.mime4j.dom.field.ContentTypeField;
056import org.apache.james.mime4j.dom.field.DateTimeField;
057import org.apache.james.mime4j.dom.field.UnstructuredField;
058import org.apache.james.mime4j.field.address.AddressFormatter;
059import org.apache.james.mime4j.message.BodyPart;
060import org.apache.james.mime4j.message.MessageImpl;
061import org.apache.james.mime4j.message.DefaultMessageBuilder;
062import org.apache.james.mime4j.stream.Field;
063
064/**
065 * Displays a parsed Message in a window. The window will be divided into
066 * two panels. The left panel displays the Message tree. Clicking on a
067 * node in the tree shows information on that node in the right panel.
068 *
069 * Some of this code have been copied from the Java tutorial's JTree section.
070 */
071public class MessageTree extends JPanel implements TreeSelectionListener {
072    private static final long serialVersionUID = 1L;
073
074    private JPanel contentPane;
075    private JTextArea textView;
076    private JTree tree;
077
078    /**
079     * Wraps an Object and associates it with a text. All message parts
080     * (headers, bodies, multiparts, body parts) will be wrapped in
081     * ObjectWrapper instances before they are added to the JTree instance.
082     */
083    public static class ObjectWrapper {
084        private String text = "";
085        private Object object = null;
086
087        public ObjectWrapper(String text, Object object) {
088            this.text = text;
089            this.object = object;
090        }
091
092        @Override
093        public String toString() {
094            return text;
095        }
096
097        public Object getObject() {
098            return object;
099        }
100    }
101
102    /**
103     * Creates a new <code>MessageTree</code> instance displaying the
104     * specified <code>Message</code>.
105     *
106     * @param message the message to display.
107     */
108    public MessageTree(Message message) {
109        super(new GridLayout(1,0));
110
111        DefaultMutableTreeNode root = createNode(message);
112
113        tree = new JTree(root);
114        tree.getSelectionModel().setSelectionMode(
115                TreeSelectionModel.SINGLE_TREE_SELECTION);
116
117        tree.addTreeSelectionListener(this);
118
119        JScrollPane treeView = new JScrollPane(tree);
120
121        contentPane = new JPanel(new GridLayout(1,0));
122        JScrollPane contentView = new JScrollPane(contentPane);
123
124        JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
125        splitPane.setLeftComponent(treeView);
126        splitPane.setRightComponent(contentView);
127
128        Dimension minimumSize = new Dimension(100, 50);
129        contentView.setMinimumSize(minimumSize);
130        treeView.setMinimumSize(minimumSize);
131        splitPane.setDividerLocation(250);
132        splitPane.setPreferredSize(new Dimension(750, 500));
133
134        add(splitPane);
135
136        textView = new JTextArea();
137        textView.setEditable(false);
138        textView.setLineWrap(true);
139        contentPane.add(textView);
140    }
141
142    /**
143     * Create a node given a Multipart body.
144     * Add the Preamble, all Body parts and the Epilogue to the node.
145     *
146     * @param multipart the Multipart.
147     * @return the root node of the tree.
148     */
149    private DefaultMutableTreeNode createNode(Header header) {
150        DefaultMutableTreeNode node = new DefaultMutableTreeNode(
151                new ObjectWrapper("Header", header));
152
153        for (Field field : header.getFields()) {
154            String name = field.getName();
155
156            node.add(new DefaultMutableTreeNode(new ObjectWrapper(name, field)));
157        }
158
159        return node;
160    }
161
162    /**
163     * Create a node given a Multipart body.
164     * Add the Preamble, all Body parts and the Epilogue to the node.
165     *
166     * @param multipart the Multipart.
167     * @return the root node of the tree.
168     */
169    private DefaultMutableTreeNode createNode(Multipart multipart) {
170        DefaultMutableTreeNode node = new DefaultMutableTreeNode(
171                new ObjectWrapper("Multipart", multipart));
172
173        node.add(new DefaultMutableTreeNode(
174                       new ObjectWrapper("Preamble", multipart.getPreamble())));
175        for (Entity part : multipart.getBodyParts()) {
176            node.add(createNode(part));
177        }
178        node.add(new DefaultMutableTreeNode(
179                       new ObjectWrapper("Epilogue", multipart.getEpilogue())));
180
181        return node;
182    }
183
184    /**
185     * Creates the tree nodes given a MIME entity (either a Message or
186     * a BodyPart).
187     *
188     * @param entity the entity.
189     * @return the root node of the tree displaying the specified entity and
190     *         its children.
191     */
192    private DefaultMutableTreeNode createNode(Entity entity) {
193
194        /*
195         * Create the root node for the entity. It's either a
196         * Message or a Body part.
197         */
198        String type = "Message";
199        if (entity instanceof BodyPart) {
200            type = "Body part";
201        }
202        DefaultMutableTreeNode node = new DefaultMutableTreeNode(
203                                            new ObjectWrapper(type, entity));
204
205        /*
206         * Add the node encapsulating the entity Header.
207         */
208        node.add(createNode(entity.getHeader()));
209
210        Body body = entity.getBody();
211
212        if (body instanceof Multipart) {
213            /*
214             * The body of the entity is a Multipart.
215             */
216
217            node.add(createNode((Multipart) body));
218        } else if (body instanceof MessageImpl) {
219            /*
220             * The body is another Message.
221             */
222
223            node.add(createNode((MessageImpl) body));
224
225        } else {
226            /*
227             * Discrete Body (either of type TextBody or BinaryBody).
228             */
229            type = "Text body";
230            if (body instanceof BinaryBody) {
231                type = "Binary body";
232            }
233
234            type += " (" + entity.getMimeType() + ")";
235            node.add(new DefaultMutableTreeNode(new ObjectWrapper(type, body)));
236
237        }
238
239        return node;
240    }
241
242    /**
243     * Called whenever the selection changes in the JTree instance showing
244     * the Message nodes.
245     *
246     * @param e the event.
247     */
248    public void valueChanged(TreeSelectionEvent e) {
249        DefaultMutableTreeNode node = (DefaultMutableTreeNode)
250                tree.getLastSelectedPathComponent();
251
252        textView.setText("");
253
254        if (node == null) {
255            return;
256        }
257
258        Object o = ((ObjectWrapper) node.getUserObject()).getObject();
259
260        if (node.isLeaf()) {
261
262            if (o instanceof TextBody){
263                /*
264                 * A text body. Display its contents.
265                 */
266                TextBody body = (TextBody) o;
267                StringBuilder sb = new StringBuilder();
268                try {
269                    Reader r = body.getReader();
270                    int c;
271                    while ((c = r.read()) != -1) {
272                        sb.append((char) c);
273                    }
274                } catch (IOException ex) {
275                    ex.printStackTrace();
276                }
277                textView.setText(sb.toString());
278
279            } else if (o instanceof BinaryBody){
280                /*
281                 * A binary body. Display its MIME type and length in bytes.
282                 */
283                BinaryBody body = (BinaryBody) o;
284                int size = 0;
285                try {
286                    InputStream is = body.getInputStream();
287                    while ((is.read()) != -1) {
288                        size++;
289                    }
290                } catch (IOException ex) {
291                    ex.printStackTrace();
292                }
293                textView.setText("Binary body\n"
294                               + "MIME type: "
295                                   + body.getParent().getMimeType() + "\n"
296                               + "Size of decoded data: " + size + " bytes");
297
298            } else if (o instanceof ContentTypeField) {
299                /*
300                 * Content-Type field.
301                 */
302                ContentTypeField field = (ContentTypeField) o;
303                StringBuilder sb = new StringBuilder();
304                sb.append("MIME type: " + field.getMimeType() + "\n");
305                for (Map.Entry<String, String> entry : field.getParameters().entrySet()) {
306                    sb.append(entry.getKey() + " = " + entry.getValue() + "\n");
307                }
308                textView.setText(sb.toString());
309
310            } else if (o instanceof AddressListField) {
311                /*
312                 * An address field (From, To, Cc, etc)
313                 */
314                AddressListField field = (AddressListField) o;
315                MailboxList list = field.getAddressList().flatten();
316                StringBuilder sb = new StringBuilder();
317                for (int i = 0; i < list.size(); i++) {
318                    Mailbox mb = list.get(i);
319                    sb.append(AddressFormatter.DEFAULT.format(mb, false) + "\n");
320                }
321                textView.setText(sb.toString());
322
323            } else if (o instanceof DateTimeField) {
324                Date date = ((DateTimeField) o).getDate();
325                textView.setText(date.toString());
326            } else if (o instanceof UnstructuredField){
327                textView.setText(((UnstructuredField) o).getValue());
328            } else if (o instanceof Field){
329                textView.setText(((Field) o).getBody());
330            } else {
331                /*
332                 * The Object should be a Header or a String containing a
333                 * Preamble or Epilogue.
334                 */
335                textView.setText(o.toString());
336            }
337
338        }
339    }
340
341    /**
342     * Creates and displays the gui.
343     *
344     * @param message the Message to display in the tree.
345     */
346    private static void createAndShowGUI(Message message) {
347        /*
348         * Create and set up the window.
349         */
350        JFrame frame = new JFrame("MessageTree");
351        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
352
353        /*
354         * Create and set up the content pane.
355         */
356        MessageTree newContentPane = new MessageTree(message);
357        newContentPane.setOpaque(true);
358        frame.setContentPane(newContentPane);
359
360        /*
361         * Display the window.
362         */
363        frame.pack();
364        frame.setVisible(true);
365    }
366
367    public static void main(String[] args) {
368        try {
369            final MessageBuilder builder = new DefaultMessageBuilder();
370            final Message message = builder.parseMessage(new FileInputStream(args[0]));
371
372            javax.swing.SwingUtilities.invokeLater(new Runnable() {
373                public void run() {
374                    createAndShowGUI(message);
375                }
376            });
377
378        } catch (ArrayIndexOutOfBoundsException e) {
379            System.err.println("Wrong number of arguments.");
380            System.err.println("Usage: org.mime4j.samples.tree.MessageTree"
381                             + " path/to/message");
382        } catch (FileNotFoundException e) {
383            System.err.println("The file '" + args[0] + "' could not be found.");
384        } catch (IOException e) {
385            System.err.println("The file '" + args[0] + "' could not be read.");
386        } catch (MimeException e) {
387            System.err.println("The file '" + args[0] + "' is invalid.");
388        }
389    }
390
391}