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 }