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    
020    package org.apache.james.mime4j.samples.tree;
021    
022    import java.awt.Dimension;
023    import java.awt.GridLayout;
024    import java.io.FileInputStream;
025    import java.io.FileNotFoundException;
026    import java.io.IOException;
027    import java.io.InputStream;
028    import java.io.Reader;
029    import java.util.Date;
030    import java.util.Map;
031    
032    import javax.swing.JFrame;
033    import javax.swing.JPanel;
034    import javax.swing.JScrollPane;
035    import javax.swing.JSplitPane;
036    import javax.swing.JTextArea;
037    import javax.swing.JTree;
038    import javax.swing.event.TreeSelectionEvent;
039    import javax.swing.event.TreeSelectionListener;
040    import javax.swing.tree.DefaultMutableTreeNode;
041    import javax.swing.tree.TreeSelectionModel;
042    
043    import org.apache.james.mime4j.MimeException;
044    import org.apache.james.mime4j.dom.BinaryBody;
045    import org.apache.james.mime4j.dom.Body;
046    import org.apache.james.mime4j.dom.Entity;
047    import org.apache.james.mime4j.dom.Header;
048    import org.apache.james.mime4j.dom.Message;
049    import org.apache.james.mime4j.dom.MessageBuilder;
050    import org.apache.james.mime4j.dom.Multipart;
051    import org.apache.james.mime4j.dom.TextBody;
052    import org.apache.james.mime4j.dom.address.Mailbox;
053    import org.apache.james.mime4j.dom.address.MailboxList;
054    import org.apache.james.mime4j.dom.field.AddressListField;
055    import org.apache.james.mime4j.dom.field.ContentTypeField;
056    import org.apache.james.mime4j.dom.field.DateTimeField;
057    import org.apache.james.mime4j.dom.field.UnstructuredField;
058    import org.apache.james.mime4j.field.address.AddressFormatter;
059    import org.apache.james.mime4j.message.BodyPart;
060    import org.apache.james.mime4j.message.MessageImpl;
061    import org.apache.james.mime4j.message.DefaultMessageBuilder;
062    import 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     */
071    public 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    }