KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > armedbear > j > mail > SendMail


1 /*
2  * SendMail.java
3  *
4  * Copyright (C) 2000-2003 Peter Graves
5  * $Id: SendMail.java,v 1.9 2003/08/09 17:42:30 piso Exp $
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20  */

21
22 package org.armedbear.j.mail;
23
24 import gnu.regexp.RE;
25 import gnu.regexp.REMatch;
26 import gnu.regexp.UncheckedRE;
27 import java.io.BufferedInputStream JavaDoc;
28 import java.io.BufferedReader JavaDoc;
29 import java.io.BufferedWriter JavaDoc;
30 import java.io.FileInputStream JavaDoc;
31 import java.io.FileWriter JavaDoc;
32 import java.io.IOException JavaDoc;
33 import java.io.InputStreamReader JavaDoc;
34 import java.io.OutputStreamWriter JavaDoc;
35 import java.io.Writer JavaDoc;
36 import java.text.SimpleDateFormat JavaDoc;
37 import java.util.ArrayList JavaDoc;
38 import java.util.Calendar JavaDoc;
39 import java.util.List JavaDoc;
40 import java.util.Random JavaDoc;
41 import javax.swing.Icon JavaDoc;
42 import javax.swing.SwingUtilities JavaDoc;
43 import javax.swing.undo.CompoundEdit JavaDoc;
44 import org.armedbear.j.Buffer;
45 import org.armedbear.j.BufferIterator;
46 import org.armedbear.j.Debug;
47 import org.armedbear.j.Directories;
48 import org.armedbear.j.Editor;
49 import org.armedbear.j.EditorIterator;
50 import org.armedbear.j.Expansion;
51 import org.armedbear.j.File;
52 import org.armedbear.j.FastStringBuffer;
53 import org.armedbear.j.Headers;
54 import org.armedbear.j.Line;
55 import org.armedbear.j.Log;
56 import org.armedbear.j.MessageDialog;
57 import org.armedbear.j.OpenFileDialog;
58 import org.armedbear.j.Platform;
59 import org.armedbear.j.Position;
60 import org.armedbear.j.Preferences;
61 import org.armedbear.j.Property;
62 import org.armedbear.j.Region;
63 import org.armedbear.j.Sidebar;
64 import org.armedbear.j.SimpleEdit;
65 import org.armedbear.j.Utilities;
66 import org.armedbear.j.Version;
67
68 public final class SendMail extends Buffer
69 {
70     private final static String JavaDoc HEADER_SEPARATOR = "--text follows this line--";
71     private final static String JavaDoc DEFAULT_TITLE = "Compose";
72
73     private static Preferences preferences;
74
75     private boolean reply;
76     private List JavaDoc group;
77     private Mailbox mailbox;
78     private MailboxEntry entryRepliedTo;
79     private String JavaDoc boundary;
80     private String JavaDoc smtp;
81     private SmtpSession session;
82     private boolean hasBeenSent;
83
84     public SendMail()
85     {
86         super();
87         init();
88         try {
89             lockWrite();
90         }
91         catch (InterruptedException JavaDoc e) {
92             Log.debug(e);
93             return;
94         }
95         try {
96             appendFrom();
97             appendLine("To: ");
98             appendLine("Subject: ");
99             appendDefaultHeaders();
100             appendLine(HEADER_SEPARATOR);
101             appendLine("");
102             appendSignature();
103             renumber();
104             formatter.parseBuffer();
105             setLoaded(true);
106         }
107         finally {
108             unlockWrite();
109         }
110     }
111
112     // Re-opening an unsent message.
113
public SendMail(File file)
114     {
115         super();
116         setFile(file);
117         init();
118     }
119
120     // Forward.
121
public SendMail(MessageBuffer messageBuffer)
122     {
123         super();
124         init();
125         mailbox = messageBuffer.getMailbox();
126         entryRepliedTo = messageBuffer.getMailboxEntry();
127         try {
128             lockWrite();
129         }
130         catch (InterruptedException JavaDoc e) {
131             Log.debug(e);
132             return;
133         }
134         try {
135             appendFrom();
136             appendLine("To: ");
137             appendLine("Subject: [Fwd: " + entryRepliedTo.getSubject() + "]");
138             appendDefaultHeaders();
139             appendLine(HEADER_SEPARATOR);
140             appendLine("");
141             Position pos = new Position(getLastLine(), 0);
142             insertString(pos, "----- Forwarded message -----\n\n");
143             insertString(pos, messageBuffer.getText());
144             insertString(pos, "\n\n----- End of forwarded message -----");
145             unmodified();
146             renumber();
147             formatter.parseBuffer();
148             setLoaded(true);
149         }
150         finally {
151             unlockWrite();
152         }
153     }
154
155     // Reply.
156
public SendMail(MessageBuffer messageBuffer, boolean replyToGroup)
157     {
158         super();
159         init();
160         mailbox = messageBuffer.getMailbox();
161         entryRepliedTo = messageBuffer.getMailboxEntry();
162         reply = true;
163         MailAddress[] replyTo = entryRepliedTo.getReplyTo();
164         MailAddress[] from = entryRepliedTo.getFrom();
165         MailAddress[] to = entryRepliedTo.getTo();
166         MailAddress[] cc = entryRepliedTo.getCc();
167         boolean skipTo = false;
168         try {
169             lockWrite();
170         }
171         catch (InterruptedException JavaDoc e) {
172             Log.debug(e);
173             return;
174         }
175         try {
176             appendFrom();
177             // Get senders.
178
List JavaDoc senders = null;
179             if (replyTo != null && replyTo.length > 0) {
180                 senders = new ArrayList JavaDoc();
181                 for (int i = 0; i < replyTo.length; i++)
182                     senders.add(replyTo[i]);
183             } else if (from != null && from.length > 0) {
184                 senders = new ArrayList JavaDoc();
185                 for (int i = 0; i < from.length; i++)
186                     senders.add(from[i]);
187             }
188             if (senders != null) {
189                 Debug.assertTrue(senders.size() > 0);
190                 // Remove user's address from list of senders.
191
for (int i = senders.size(); i-- > 0;) {
192                     MailAddress a = (MailAddress) senders.get(i);
193                     if (a.addressMatches(Mail.getUserMailAddress()))
194                         senders.remove(i);
195                 }
196                 if (senders.size() == 0) {
197                     // User was the only sender of the original message.
198
// Use "To" addresses instead.
199
for (int i = 0; i < to.length; i++)
200                         senders.add(to[i]);
201                     // Don't add "To" addresses to group.
202
skipTo = true;
203                 }
204                 removeDuplicateAddresses(senders);
205                 appendAddressHeader("To: ", senders);
206             }
207             // Gather addresses for reply to group.
208
group = new ArrayList JavaDoc();
209             if (!skipTo && to != null) {
210                 for (int i = 0; i < to.length; i++) {
211                     MailAddress a = to[i];
212                     if (!a.addressMatches(Mail.getUserMailAddress()))
213                         group.add(a);
214                 }
215             }
216             if (cc != null) {
217                 for (int i = 0; i < cc.length; i++) {
218                     MailAddress a = cc[i];
219                     if (!a.addressMatches(Mail.getUserMailAddress()))
220                         group.add(a);
221                 }
222             }
223             removeDuplicateAddresses(group);
224             if (senders != null) {
225                 // Make sure we don't duplicate entries in the "To:" header.
226
for (int i = senders.size(); i-- > 0;) {
227                     MailAddress toAddress = (MailAddress) senders.get(i);
228                     for (int j = group.size(); j-- > 0;) {
229                         MailAddress a = (MailAddress) group.get(j);
230                         if (a.equals(toAddress)) {
231                             // It's a duplicate. Remove it from the group.
232
group.remove(j);
233                             break;
234                         }
235                     }
236                 }
237             }
238             // Add group to recipients if applicable.
239
if (replyToGroup && group.size() > 0)
240                 appendAddressHeader("Cc: ", group);
241             String JavaDoc subject = entryRepliedTo.getSubject();
242             if (!subject.toLowerCase().startsWith("re:"))
243                 subject = "Re: ".concat(subject);
244             appendLine("Subject: ".concat(subject));
245             appendLine("In-Reply-To: " + entryRepliedTo.getMessageId());
246             appendReferences();
247             appendDefaultHeaders();
248             appendLine(HEADER_SEPARATOR);
249             String JavaDoc attribution = getAttribution(messageBuffer);
250             if (attribution != null)
251                 appendLine(attribution);
252             String JavaDoc s =
253                 messageBuffer.quoteBody(getIntegerProperty(Property.WRAP_COL));
254             if (s != null)
255                 append(s);
256             appendLine("");
257             appendSignature();
258             unmodified();
259             renumber();
260             formatter.parseBuffer();
261             setLoaded(true);
262         }
263         finally {
264             unlockWrite();
265         }
266     }
267
268     private void init()
269     {
270         if (preferences == null)
271             preferences = Editor.preferences();
272         initializeUndo();
273         type = TYPE_NORMAL;
274         title = DEFAULT_TITLE;
275         if (getFile() == null) {
276             final File dir = Directories.getDraftsFolder();
277             if (!dir.isDirectory())
278                 dir.mkdirs();
279             if (dir.isDirectory()) {
280                 setFile(Utilities.getTempFile(dir));
281             } else {
282                 // Use temp directory as fallback. (Shouldn't happen.)
283
setFile(Utilities.getTempFile());
284             }
285         }
286         autosaveEnabled = true;
287         mode = SendMailMode.getMode();
288         formatter = mode.getFormatter(this);
289         if (!getFile().isFile())
290             lineSeparator = "\n";
291         setInitialized(true);
292     }
293
294     public int load()
295     {
296         super.load();
297         title = getSubject();
298         if (title == null || title.length() == 0)
299             title = DEFAULT_TITLE;
300         return LOAD_COMPLETED;
301     }
302
303     public boolean save()
304     {
305         boolean result = super.save();
306         for (BufferIterator it = new BufferIterator(); it.hasNext();) {
307             Buffer buf = it.nextBuffer();
308             if (buf instanceof Drafts) {
309                 Drafts drafts = (Drafts) buf;
310                 drafts.reload();
311                 break;
312             }
313         }
314         return result;
315     }
316
317     public boolean hasBeenSent()
318     {
319         return hasBeenSent;
320     }
321
322     private static void removeDuplicateAddresses(List JavaDoc list)
323     {
324         // Remove duplicate entries from list.
325
for (int i = list.size(); i-- > 0;) {
326             MailAddress ma = (MailAddress) list.get(i);
327             String JavaDoc addr = ma.getAddress();
328             for (int j = i-1; j >= 0; j--) {
329                 MailAddress ma2 = (MailAddress) list.get(j);
330                 if (ma.equals(ma2)) {
331                     list.remove(i);
332                     break;
333                 }
334                 // Not strictly equal. Check for same address.
335
String JavaDoc addr2 = ma2.getAddress();
336                 if (addr.equals(addr2)) {
337                     list.remove(i);
338                     // We've removed ma from the list.
339
// Don't lose any extra information it may have.
340
if (ma2.getPersonal() == null || ma2.getPersonal().length() == 0) {
341                         if (ma.getPersonal() != null && ma.getPersonal().length() > 0)
342                             list.set(j, ma);
343                     }
344                 }
345             }
346         }
347     }
348
349     private void appendAddressHeader(String JavaDoc prefix, List JavaDoc list)
350     {
351         if (list == null)
352             return;
353         if (list.size() == 0)
354             return;
355         append(MailUtilities.constructAddressHeader(prefix, list));
356     }
357
358     private void appendFrom()
359     {
360         if (!preferences.getBooleanProperty(Property.CONFIRM_SEND)) {
361             MailAddress ma = Mail.getUserMailAddress();
362             if (ma != null)
363                 appendLine("From: ".concat(ma.toString()));
364         }
365     }
366
367     private void replaceFrom(String JavaDoc from)
368     {
369         final Editor editor = Editor.currentEditor();
370         final Position savedDot = editor.getDotCopy();
371         try {
372             lockWrite();
373         }
374         catch (InterruptedException JavaDoc e) {
375             Log.error(e);
376             return;
377         }
378         try {
379             CompoundEdit JavaDoc compoundEdit = editor.beginCompoundEdit();
380             // Remove existing "From:" header(s).
381
removeHeaders(editor, "from:");
382             // Move dot to beginning of buffer.
383
editor.addUndo(SimpleEdit.MOVE);
384             editor.getDot().moveTo(getFirstLine(), 0);
385             // Insert new "From:" header.
386
editor.addUndo(SimpleEdit.INSERT_STRING);
387             insertString(editor.getDot(), "From: ".concat(from).concat("\n"));
388             // Restore dot to saved position if possible.
389
Line dotLine = savedDot.getLine();
390             if (contains(dotLine)) {
391                 int dotOffset = savedDot.getOffset();
392                 if (dotOffset > dotLine.length())
393                     dotOffset = dotLine.length();
394                 editor.addUndo(SimpleEdit.MOVE);
395                 editor.getDot().moveTo(dotLine, dotOffset);
396             }
397             editor.addUndo(SimpleEdit.MOVE);
398             editor.moveCaretToDotCol();
399             editor.endCompoundEdit(compoundEdit);
400             getFormatter().parseBuffer();
401         }
402         finally {
403             unlockWrite();
404         }
405         repaint();
406     }
407
408     private void replaceBcc(List JavaDoc bccList)
409     {
410         final Editor editor = Editor.currentEditor();
411         final Position savedDot = editor.getDotCopy();
412         try {
413             lockWrite();
414         }
415         catch (InterruptedException JavaDoc e) {
416             Log.error(e);
417             return;
418         }
419         try {
420             CompoundEdit JavaDoc compoundEdit = editor.beginCompoundEdit();
421             // Remove existing "Bcc:" header(s).
422
removeHeaders(editor, "bcc:");
423             // Move dot to end of header area and insert new "Bcc:" header.
424
for (Line line = getFirstLine(); line != null; line = line.next()) {
425                 if (line.getText().equals(HEADER_SEPARATOR)) {
426                     editor.addUndo(SimpleEdit.MOVE);
427                     editor.getDot().moveTo(line, 0);
428                     editor.addUndo(SimpleEdit.INSERT_STRING);
429                     insertString(editor.getDot(),
430                         MailUtilities.constructAddressHeader("Bcc: ", bccList).concat("\n"));
431                     break;
432                 }
433             }
434             // Restore dot to saved position.
435
Line dotLine = savedDot.getLine();
436             if (contains(dotLine)) {
437                 int dotOffset = savedDot.getOffset();
438                 if (dotOffset > dotLine.length())
439                     dotOffset = dotLine.length();
440                 editor.addUndo(SimpleEdit.MOVE);
441                 editor.getDot().moveTo(dotLine, dotOffset);
442             }
443             editor.addUndo(SimpleEdit.MOVE);
444             editor.moveCaretToDotCol();
445             editor.endCompoundEdit(compoundEdit);
446             getFormatter().parseBuffer();
447         }
448         finally {
449             unlockWrite();
450         }
451         repaint();
452     }
453
454     private void appendReferences()
455     {
456         if (entryRepliedTo != null) {
457             final String JavaDoc prefix = "References: ";
458             FastStringBuffer sb = new FastStringBuffer(prefix);
459             int length = prefix.length();
460             String JavaDoc[] oldReferences = entryRepliedTo.getReferences();
461             final String JavaDoc[] references;
462             if (oldReferences != null) {
463                 references = new String JavaDoc[oldReferences.length+1];
464                 System.arraycopy(oldReferences, 0, references, 0, oldReferences.length);
465             } else
466                 references = new String JavaDoc[1];
467             references[references.length-1] = entryRepliedTo.getMessageId();
468             for (int i = 0; i < references.length; i++) {
469                 String JavaDoc s = references[i];
470                 if (i > 0 && length + s.length() > 990) {
471                     // Won't fit on current line.
472
sb.append(lineSeparator);
473                     final String JavaDoc indent = " ";
474                     sb.append(indent); // Continuation.
475
sb.append(s);
476                     length = indent.length() + s.length();
477                 } else {
478                     if (i > 0) {
479                         sb.append(" ");
480                         length++;
481                     }
482                     sb.append(s);
483                     length += s.length();
484                 }
485             }
486             append(sb.toString());
487         }
488     }
489
490     private void appendDefaultHeaders()
491     {
492         String JavaDoc bcc = preferences.getStringProperty("bcc");
493         if (bcc != null)
494             appendLine("Bcc: ".concat(bcc));
495     }
496
497     private void appendSignature()
498     {
499         File file = null;
500         String JavaDoc fileName = preferences.getStringProperty(Property.SIGNATURE);
501         if (fileName != null)
502             file = File.getInstance(fileName);
503         else if (Platform.isPlatformUnix())
504             file = File.getInstance(Directories.getUserHomeDirectory(),
505                 ".signature");
506         if (file == null || !file.isFile())
507             return;
508         FastStringBuffer sb = new FastStringBuffer();
509         try {
510             BufferedReader JavaDoc reader =
511                 new BufferedReader JavaDoc(new InputStreamReader JavaDoc(file.getInputStream()));
512             while (true) {
513                 String JavaDoc s = reader.readLine();
514                 if (s == null)
515                     break;
516                 if (sb.length() > 0)
517                     sb.append('\n');
518                 sb.append(s);
519             }
520             reader.close();
521         }
522         catch (IOException JavaDoc e) {
523             Log.error(e);
524         }
525         if (sb.length() > 0)
526             append(sb.toString());
527     }
528
529     public static final String JavaDoc getHeaderSeparator()
530     {
531         return HEADER_SEPARATOR;
532     }
533
534     public void modified()
535     {
536         super.modified();
537         setTitle();
538     }
539
540     private void setTitle()
541     {
542         String JavaDoc s = getSubject();
543         if (s == null || s.length() == 0)
544             s = DEFAULT_TITLE;
545         if (title == null || !title.equals(s)) {
546             title = s;
547             Sidebar.setUpdateFlagInAllFrames(SIDEBAR_BUFFER_LIST_CHANGED);
548         }
549     }
550
551     public void attachFile()
552     {
553         Editor editor = Editor.currentEditor();
554         File file = OpenFileDialog.getLocalFile(editor, "Attach File");
555         if (file != null && file.isFile()) {
556             for (Line line = getFirstLine(); line != null; line = line.next()) {
557                 if (line.getText().equals(HEADER_SEPARATOR)) {
558                     Position pos = new Position(line, 0);
559                     insertString(pos,
560                         "Attachment: " + file.canonicalPath() + "\n");
561                     break;
562                 }
563             }
564         }
565     }
566
567     public void send()
568     {
569         if (!checkHeader())
570             return;
571         checkRecipients();
572         checkEmpty();
573         if (preferences.getBooleanProperty(Property.CONFIRM_SEND)) {
574             if (!confirmSend())
575                 return;
576         }
577         Runnable JavaDoc sendRunnable = new Runnable JavaDoc() {
578             public void run()
579             {
580                 boolean succeeded = false;
581                 if (smtp != null)
582                     session = SmtpSession.getSession(smtp);
583                 else
584                     session = SmtpSession.getDefaultSession();
585                 if (session != null) {
586                     File messageFile = Utilities.getTempFile();
587                     try {
588                         OutputStreamWriter JavaDoc writer =
589                             new OutputStreamWriter JavaDoc(messageFile.getOutputStream());
590                         writeMessageText(writer);
591                         writer.flush();
592                         writer.close();
593                         succeeded = session.sendMessage(SendMail.this,
594                             messageFile);
595                         if (succeeded)
596                             writeFcc(messageFile);
597                         messageFile.delete();
598                     }
599                     catch (IOException JavaDoc e) {
600                         Log.error(e);
601                     }
602                 }
603                 hasBeenSent = succeeded;
604                 SwingUtilities.invokeLater(succeeded ? succeededRunnable :
605                     errorRunnable);
606             }
607         };
608         setBusy(true);
609         new Thread JavaDoc(sendRunnable).start();
610     }
611
612     private boolean confirmSend()
613     {
614         final Editor editor = Editor.currentEditor();
615         ConfirmSendDialog d = new ConfirmSendDialog(editor, this);
616         editor.centerDialog(d);
617         d.show();
618         if (d.cancelled())
619             return false;
620         String JavaDoc from = d.getFrom();
621         if (from != null)
622             replaceFrom(from);
623         if (d.bccAddSender() || d.bccAddOther()) {
624             List JavaDoc bccList = new ArrayList JavaDoc();
625             MailAddress[] bcc = MailAddress.parseAddresses(getBcc());
626             if (bcc != null) {
627                 for (int i = 0; i < bcc.length; i++)
628                     bccList.add(bcc[i]);
629             }
630             if (d.bccAddSender() && from != null)
631                 bccList.add(MailAddress.parseAddress(from));
632             if (d.bccAddOther()) {
633                 String JavaDoc bccOther = d.getBccOther();
634                 if (bccOther != null && bccOther.length() > 0)
635                     bccList.add(MailAddress.parseAddress(bccOther));
636             }
637             if (bccList.size() > 0) {
638                 removeDuplicateAddresses(bccList);
639                 replaceBcc(bccList);
640             }
641         }
642         smtp = d.getSmtp();
643         return true;
644     }
645
646     private boolean checkHeader()
647     {
648         for (Line line = getFirstLine(); line != null; line = line.next()) {
649             if (line.getText().equals(HEADER_SEPARATOR))
650                 return true;
651         }
652         MessageDialog.showMessageDialog(Editor.currentEditor(),
653             "Message separator line is missing", "Error");
654         return false;
655         // BUG!! Should confirm here that we have a valid "From" address!
656
}
657
658     // Make sure all continued address header lines end with commas.
659
private void checkRecipients()
660     {
661         for (Line line = getFirstLine(); line != null; line = line.next()) {
662             String JavaDoc text = line.getText();
663             if (text.equals(HEADER_SEPARATOR))
664                 return;
665             String JavaDoc lower = text.toLowerCase();
666             if (lower.startsWith("to:") || lower.startsWith("cc:") ||
667                 lower.startsWith("bcc:")) {
668                 Line continuation = line.next();
669                 while (continuation != null && continuation.length() > 0) {
670                     char c = continuation.charAt(0);
671                     if (c == ' ' || c == '\t') {
672                         // It's really a continuation line. Make sure the
673
// preceding line ends with a comma.
674
String JavaDoc s = trimTrailing(line.getText());
675                         int length = s.length();
676                         if (length > 0 && s.charAt(length - 1) != ',')
677                             line.setText(s.concat(","));
678                         line = continuation;
679                         continuation = continuation.next();
680                     } else
681                         break;
682                 }
683             }
684         }
685     }
686
687     // Trims trailing whitespace.
688
private static String JavaDoc trimTrailing(String JavaDoc s)
689     {
690         int length = s.length();
691         if (length == 0 || !Character.isWhitespace(s.charAt(length - 1)))
692             return s;
693         do {
694             --length;
695         } while (length > 0 && Character.isWhitespace(s.charAt(length - 1)));
696         return s.substring(0, length);
697     }
698
699     private void checkEmpty()
700     {
701         String JavaDoc eom = getStringProperty(Property.EOM);
702         if (eom == null || eom.length() == 0)
703             return;
704         Line subjectLine = null;
705         Line line;
706         for (line = getFirstLine(); line != null; line = line.next()) {
707             String JavaDoc text = line.getText();
708             if (text.toLowerCase().startsWith("subject:"))
709                 subjectLine = line;
710             else if (text.toLowerCase().startsWith("attachment:"))
711                 return; // Not empty.
712
else if (text.equals(HEADER_SEPARATOR))
713                 break;
714         }
715         if (subjectLine == null)
716             return;
717         if (line != null) {
718             line = line.next();
719             // Now we're on the first line of the message body.
720
while (line != null) {
721                 if (!line.isBlank())
722                     return; // Not empty.
723
line = line.next();
724             }
725         }
726         // Empty.
727
String JavaDoc text = subjectLine.getText();
728         if (!text.endsWith(eom))
729             subjectLine.setText(text + eom);
730     }
731
732     private Runnable JavaDoc succeededRunnable = new Runnable JavaDoc() {
733         public void run()
734         {
735             unmodified();
736             if (reply && mailbox != null && entryRepliedTo != null)
737                 mailbox.setAnsweredFlag(entryRepliedTo);
738             File file = getFile();
739             if (file.isFile()) {
740                 Log.debug("deleting draft " + file);
741                 file.delete();
742                 for (BufferIterator it = new BufferIterator(); it.hasNext();) {
743                     Buffer buf = it.nextBuffer();
744                     if (buf instanceof Drafts) {
745                         Drafts drafts = (Drafts) buf;
746                         drafts.reload();
747                         break;
748                     }
749                 }
750             }
751             setBusy(false);
752             kill();
753             EditorIterator iter = new EditorIterator();
754             while (iter.hasNext())
755                 iter.nextEditor().updateDisplay();
756         }
757     };
758
759     private Runnable JavaDoc errorRunnable = new Runnable JavaDoc() {
760         public void run()
761         {
762             setBusy(false);
763             final Editor editor = Editor.currentEditor();
764             editor.updateDisplay();
765             MessageDialog.showMessageDialog(editor,
766                 session != null ? session.getErrorText() : "Unable to send message",
767                 "Send Mail");
768         }
769     };
770
771     private void writeMessageText(Writer JavaDoc writer)
772     {
773         final String JavaDoc separator = "\n";
774         try {
775             writer.write("Date: " + RFC822Date.getDateTimeString() + separator);
776             if (getFrom() == null)
777                 writer.write("From: " + getDefaultFromAddress() + separator);
778             Line line;
779             // Headers.
780
boolean inBcc = false;
781             List JavaDoc attachments = null;
782             for (line = getFirstLine(); line != null; line = line.next()) {
783                 String JavaDoc text = line.getText();
784                 if (text.length() == 0) {
785                     // Found empty line before reaching end of headers.
786
// Discard it.
787
continue;
788                 }
789                 if (text.equals(HEADER_SEPARATOR)) {
790                     // Reached end of headers.
791
writer.write("Message-ID: " + Mail.generateMessageId() + separator);
792                     writer.write("User-Agent: " + Version.getLongVersionString() + separator);
793                     attachments = parseAttachments();
794                     if (attachments != null)
795                         writer.write(generateMimeHeaders(separator));
796                     // Skip header separator line.
797
line = line.next();
798                     break;
799                 }
800                 if (inBcc) {
801                     char c = text.charAt(0);
802                     if (c == ' ' || c == '\t') {
803                         // It's a continuation line. Skip it.
804
continue;
805                     } else {
806                         // Start of next header.
807
inBcc = false;
808                     }
809                 }
810                 if (text.toLowerCase().startsWith("bcc:")) {
811                     inBcc = true;
812                     continue;
813                 }
814                 if (text.toLowerCase().startsWith("attachment:"))
815                     continue;
816                 writer.write(line.getText());
817                 writer.write(separator);
818             }
819             final Line startOfBody = line;
820             // Scan body of message.
821
boolean qp = false;
822             for (line = startOfBody; line != null; line = line.next()) {
823                 if (requiresEncoding(line)) {
824                     qp = true;
825                     break;
826                 }
827             }
828             String JavaDoc transferEncoding = qp ? "quoted-printable" : "7bit";
829             String JavaDoc characterEncoding =
830                 getStringProperty(Property.DEFAULT_ENCODING);
831             if (attachments != null) {
832                 // Append extra separator at end of headers.
833
writer.write(separator);
834                 writer.write("This is a multi-part message in MIME format.");
835                 writer.write(separator);
836                 writer.write("--");
837                 writer.write(getBoundary());
838                 writer.write(separator);
839                 writer.write("Content-Type: text/plain");
840                 if (qp) {
841                     writer.write("; charset=");
842                     writer.write(getCharSetName(characterEncoding));
843                 }
844                 writer.write(separator);
845                 writer.write("Content-Transfer-Encoding: ");
846                 writer.write(transferEncoding);
847                 writer.write(separator);
848                 writer.write(separator);
849             } else {
850                 // No attachments.
851
if (qp) {
852                     writer.write("Content-Type: text/plain");
853                     if (qp) {
854                         writer.write("; charset=");
855                         writer.write(getCharSetName(characterEncoding));
856                     }
857                     writer.write(separator);
858                     writer.write("Content-Transfer-Encoding: ");
859                     writer.write(transferEncoding);
860                     writer.write(separator);
861                 }
862                 // Append extra separator at end of headers.
863
writer.write(separator);
864             }
865             // Body.
866
for (line = startOfBody; line != null; line = line.next()) {
867                 String JavaDoc s = line.getText();
868                 if (qp) {
869                     writer.write(QuotedPrintableEncoder.encode(s,
870                         characterEncoding, separator));
871                 } else {
872                     // Dot stuffing.
873
if (s.length() > 0 && s.charAt(0) == '.')
874                         writer.write('.');
875                     writer.write(s);
876                 }
877                 writer.write(separator);
878             }
879             // Attachments.
880
if (attachments != null) {
881                 for (int i = 0; i < attachments.size(); i++) {
882                     String JavaDoc fullPath = (String JavaDoc) attachments.get(i);
883                     File file = File.getInstance(fullPath);
884                     String JavaDoc contentType = getContentTypeForFile(file);
885                     Log.debug("contentType = " + contentType);
886                     writer.write("--");
887                     writer.write(getBoundary());
888                     writer.write(separator);
889                     writer.write("Content-Type: ");
890                     writer.write(contentType);
891                     writer.write(separator);
892                     writer.write("Content-Transfer-Encoding: base64");
893                     writer.write(separator);
894                     writer.write("Content-Disposition: attachment; filename=\"");
895                     writer.write(file.getName());
896                     writer.write('"');
897                     writer.write(separator);
898                     writer.write(separator);
899                     writeEncodedFile(file, writer, separator);
900                     writer.write(separator);
901                 }
902                 writer.write("--");
903                 writer.write(getBoundary());
904                 writer.write("--");
905                 writer.write(separator);
906             }
907         }
908         catch (IOException JavaDoc e) {
909             Log.error(e);
910         }
911     }
912
913     private void writeEncodedFile(File file, Writer JavaDoc writer, String JavaDoc separator)
914     {
915         if (file == null || !file.isFile() || !file.canRead())
916             return;
917         try {
918             FileInputStream JavaDoc inputStream = file.getInputStream();
919             Base64Encoder encoder = new Base64Encoder(inputStream);
920             String JavaDoc s;
921             while ((s = encoder.encodeLine()) != null) {
922                 writer.write(s);
923                 writer.write(separator);
924             }
925             writer.flush();
926             inputStream.close();
927         }
928         catch (IOException JavaDoc e) {
929             Log.error(e);
930         }
931     }
932
933     public String JavaDoc getFrom()
934     {
935         return getHeaderValue("from");
936     }
937
938     public String JavaDoc getFromAddress()
939     {
940         String JavaDoc from = getFrom();
941         if (from != null)
942             return getAddress(from);
943         // No "From:" header.
944
return Mail.getUserMailAddress().getAddress();
945     }
946
947     private final String JavaDoc getDefaultFromAddress()
948     {
949         return Mail.getUserMailAddress().toString();
950     }
951
952     // Given "Piso Mojado <piso@armedbear.yi.org>", returns
953
// "piso@armedbear.yi.org".
954
public static String JavaDoc getAddress(String JavaDoc s)
955     {
956         if (s == null)
957             return null;
958         int index = s.indexOf('@');
959         if (index < 0)
960             return null;
961         int begin = 0;
962         int end = s.length();
963         for (int i = index; i-- > 0;) {
964             char c = s.charAt(i);
965             if (c == ',' || c == '"' || c == '<' || Character.isWhitespace(c)) {
966                 begin = i + 1;
967                 break;
968             }
969         }
970         for (int i = index + 1; i < end; i++) {
971             char c = s.charAt(i);
972             if (c == ',' || c == '"' || c == '>' || Character.isWhitespace(c)) {
973                 end = i;
974                 break;
975             }
976         }
977         return s.substring(begin, end);
978     }
979
980     public String JavaDoc getTo()
981     {
982         Log.debug("getTo to = |" + getHeaderValue("to") + "|");
983         return getHeaderValue("to");
984     }
985
986     public String JavaDoc getCc()
987     {
988         Log.debug("getCc cc = |" + getHeaderValue("cc") + "|");
989         return getHeaderValue("cc");
990     }
991
992     public String JavaDoc getBcc()
993     {
994         Log.debug("getBcc bcc = |" + getHeaderValue("bcc") + "|");
995         return getHeaderValue("bcc");
996     }
997
998     public void ccGroup()
999     {
1000        if (group == null)
1001            return;
1002        // Entries from the original group will come first in the new list.
1003
List JavaDoc newList = new ArrayList JavaDoc(group);
1004        // Add the entries from the existing "Cc:" header (if any) back in.
1005
MailAddress[] cc = MailAddress.parseAddresses(getCc());
1006        if (cc != null) {
1007            for (int i = 0; i < cc.length; i++) {
1008                MailAddress oldAddress = cc[i];
1009                // Skip entries that are already in the list.
1010
boolean isDuplicate = false;
1011                for (int j = newList.size()-1; j >= 0; j--) {
1012                    MailAddress a = (MailAddress) newList.get(j);
1013                    if (oldAddress.equals(a)) {
1014                        isDuplicate = true;
1015                        break;
1016                    }
1017                }
1018                if (!isDuplicate)
1019                    newList.add(oldAddress);
1020            }
1021        }
1022        // Make sure we don't duplicate entries in the "To:" header.
1023
MailAddress[] to = MailAddress.parseAddresses(getTo());
1024        if (to != null) {
1025            for (int i = to.length-1; i >= 0; i--) {
1026                MailAddress toAddress = to[i];
1027                for (int j = newList.size()-1; j >= 0; j--) {
1028                    MailAddress a = (MailAddress) newList.get(j);
1029                    if (a.equals(toAddress)) {
1030                        // It's a duplicate. Remove it from the new list.
1031
Log.debug("removing " + (MailAddress)newList.get(j));
1032                        newList.remove(j);
1033                        break;
1034                    }
1035                }
1036            }
1037        }
1038        final Editor editor = Editor.currentEditor();
1039        final Position savedDot = editor.getDotCopy();
1040        try {
1041            lockWrite();
1042        }
1043        catch (InterruptedException JavaDoc e) {
1044            Log.error(e);
1045            return;
1046        }
1047        try {
1048            CompoundEdit JavaDoc compoundEdit = editor.beginCompoundEdit();
1049            // Remove all existing "Cc:" headers (there can be more than one).
1050
removeHeaders(editor, "cc:");
1051            // Move dot to line after "To:" header.
1052
for (Line line = getFirstLine(); line != null; line = line.next()) {
1053                if (line.getText().toLowerCase().startsWith("to:")) {
1054                    // Found first line of "To:" header.
1055
for (Line next = line.next(); next != null; next = next.next()) {
1056                        if (next.length() == 0 || next.charAt(0) == ' ' ||
1057                            next.charAt(0) == '\t') {
1058                            // Continuation line.
1059
continue;
1060                        } else {
1061                            // Found next header. Put dot here.
1062
editor.addUndo(SimpleEdit.MOVE);
1063                            editor.getDot().moveTo(next, 0);
1064                            break;
1065                        }
1066                    }
1067                    break;
1068                }
1069            }
1070            // Insert new "Cc:" header.
1071
editor.addUndo(SimpleEdit.INSERT_STRING);
1072            FastStringBuffer sb = new FastStringBuffer();
1073            sb.append(MailUtilities.constructAddressHeader("Cc: ", newList));
1074            sb.append(lineSeparator);
1075            insertString(editor.getDot(), sb.toString());
1076            // Restore dot to saved position if possible.
1077
Line dotLine = savedDot.getLine();
1078            if (contains(dotLine)) {
1079                int dotOffset = savedDot.getOffset();
1080                if (dotOffset > dotLine.length())
1081                    dotOffset = dotLine.length();
1082                editor.addUndo(SimpleEdit.MOVE);
1083                editor.getDot().moveTo(dotLine, dotOffset);
1084            }
1085            editor.addUndo(SimpleEdit.MOVE);
1086            editor.moveCaretToDotCol();
1087            editor.endCompoundEdit(compoundEdit);
1088            getFormatter().parseBuffer();
1089        }
1090        finally {
1091            unlockWrite();
1092        }
1093        repaint();
1094    }
1095
1096    // Remove all header lines for hdr (e.g "from:", "cc:", "bcc:"). We assume
1097
// the buffer is write-locked and beginCompoundEdit has been called. The
1098
// current editor gets passed in to manage undo.
1099
private void removeHeaders(Editor editor, String JavaDoc hdr)
1100    {
1101        // Make sure hdr is all lower case and ends with a colon.
1102
hdr = hdr.toLowerCase();
1103        if (!hdr.endsWith(":"))
1104            hdr = hdr.concat(":");
1105        while (true) {
1106            // Find appropriate line in message headers.
1107
Line beginLine = null;
1108            // Always start at top of buffer.
1109
for (Line line = getFirstLine(); line != null; line = line.next()) {
1110                String JavaDoc text = line.getText();
1111                if (text.equals(HEADER_SEPARATOR))
1112                    return;
1113                if (text.toLowerCase().startsWith(hdr)) {
1114                    beginLine = line;
1115                    break;
1116                }
1117            }
1118            // Not found.
1119
if (beginLine == null)
1120                return;
1121            // We want to delete the region up to the start of the next header
1122
// line.
1123
Line endLine = null;
1124            for (Line line = beginLine.next(); line != null; line = line.next()) {
1125                String JavaDoc text = line.getText();
1126                if (text.length() == 0)
1127                    continue;
1128                if (text.equals(HEADER_SEPARATOR)) {
1129                    endLine = line;
1130                    break;
1131                }
1132                char c = line.getText().charAt(0);
1133                if (c != ' ' && c != '\t') {
1134                    endLine = line;
1135                    break;
1136                }
1137            }
1138            // endLine should never be null here, since at worst we should
1139
// always hit the header separator line. But the user might have
1140
// deleted the header separator...
1141
if (endLine == null)
1142                return;
1143            // Delete the region from beginLine to endLine.
1144
Region r = new Region(this, new Position(beginLine, 0),
1145                new Position(endLine, 0));
1146            editor.addUndo(SimpleEdit.MOVE);
1147            editor.getDot().moveTo(r.getBegin());
1148            editor.addUndoDeleteRegion(r);
1149            r.delete();
1150        }
1151    }
1152
1153    public List JavaDoc getAddressees()
1154    {
1155        ArrayList JavaDoc list = new ArrayList JavaDoc();
1156        appendAddressesFromString(list, getTo());
1157        appendAddressesFromString(list, getCc());
1158        appendAddressesFromString(list, getBcc());
1159        for (int i = 0; i < list.size(); i++)
1160            Log.debug("|" + (String JavaDoc) list.get(i) + "|");
1161        return list;
1162    }
1163
1164    private static void appendAddressesFromString(List JavaDoc list, String JavaDoc s)
1165    {
1166        if (s == null)
1167            return;
1168        s = s.trim();
1169        int length = s.length();
1170        if (length == 0)
1171            return;
1172        FastStringBuffer sb = new FastStringBuffer(64);
1173        boolean inQuote = false;
1174        for (int i = 0; i < length; i++) {
1175            char c = s.charAt(i);
1176            switch (c) {
1177                case '"':
1178                    inQuote = !inQuote;
1179                    sb.append(c);
1180                    break;
1181                case ',':
1182                    if (inQuote) {
1183                        // A comma inside a quoted string is just an ordinary
1184
// character.
1185
sb.append(c);
1186                    } else {
1187                        // Otherwise a comma marks the end of the address.
1188
String JavaDoc address = sb.toString().trim();
1189                        if (address.length() > 0)
1190                            list.add(address);
1191                        sb.setLength(0);
1192                    }
1193                    break;
1194                default:
1195                    sb.append(c);
1196                    break;
1197            }
1198        }
1199        if (sb.length() > 0) {
1200            String JavaDoc address = sb.toString().trim();
1201            if (address.length() > 0)
1202                list.add(address);
1203        }
1204    }
1205
1206    public String JavaDoc getSubject()
1207    {
1208        return getHeaderValue("subject");
1209    }
1210
1211    // Combines multiple occurrences of "To:", "Cc:", "Bcc:".
1212
private String JavaDoc getHeaderValue(String JavaDoc headerName)
1213    {
1214        boolean combine = false;
1215        String JavaDoc key = headerName.toLowerCase() + ':';
1216        if (key.equals("to:") || key.equals("cc:") || key.equals("bcc:"))
1217            combine = true;
1218        FastStringBuffer sb = null;
1219        for (Line line = getFirstLine(); line != null; line = line.next()) {
1220            String JavaDoc text = line.getText();
1221            if (text.equals(HEADER_SEPARATOR))
1222                break;
1223            if (text.toLowerCase().startsWith(key)) {
1224                if (sb == null) {
1225                    sb = new FastStringBuffer();
1226                } else {
1227                    // Make sure there's a proper separator when we're
1228
// combining address headers.
1229
sb.append(", ");
1230                }
1231                sb.append(text.substring(key.length()).trim());
1232                Line continuation = line.next();
1233                while (continuation != null && continuation.length() > 0) {
1234                    char c = continuation.charAt(0);
1235                    if (c == ' ' || c == '\t') {
1236                        // This is in fact a continuation line.
1237
sb.append(continuation.getText());
1238                        line = continuation;
1239                        continuation = continuation.next();
1240                        continue;
1241                    } else
1242                        break;
1243                }
1244                if (!combine)
1245                    break;
1246            }
1247        }
1248        return sb != null ? sb.toString() : null;
1249    }
1250
1251    public Position getInitialDotPos()
1252    {
1253        if (reply) {
1254            for (Line line = getFirstLine(); line != null; line = line.next()) {
1255                if (line.getText().equals(HEADER_SEPARATOR)) {
1256                    line = line.next();
1257                    if (line != null)
1258                        return new Position(line, 0);
1259                }
1260            }
1261        } else {
1262            for (Line line = getFirstLine(); line != null; line = line.next()) {
1263                if (line.getText().startsWith("To: "))
1264                    return new Position(line, line.length());
1265            }
1266        }
1267        // Under normal circumstances we shouldn't get here, but anything can
1268
// happen with a postponed message...
1269
return new Position(getFirstLine(), 0);
1270    }
1271
1272    private List JavaDoc parseAttachments()
1273    {
1274        ArrayList JavaDoc attachments = null;
1275        for (Line line = getFirstLine(); line != null; line = line.next()) {
1276            if (line.getText().equals(HEADER_SEPARATOR))
1277                break;
1278            if (line.getText().toLowerCase().startsWith("attachment:")) {
1279                String JavaDoc filename = line.getText().substring(11).trim();
1280                if (filename.length() > 0) {
1281                    if (attachments == null)
1282                        attachments = new ArrayList JavaDoc();
1283                    attachments.add(filename);
1284                }
1285            }
1286        }
1287        return attachments;
1288    }
1289
1290    private String JavaDoc generateMimeHeaders(String JavaDoc separator)
1291    {
1292        FastStringBuffer sb = new FastStringBuffer();
1293        sb.append("MIME-Version: 1.0");
1294        sb.append(separator);
1295        sb.append("Content-Type: multipart/mixed; boundary=\"");
1296        sb.append(getBoundary());
1297        sb.append('"');
1298        sb.append(separator);
1299        return sb.toString();
1300    }
1301
1302    private String JavaDoc getBoundary()
1303    {
1304        if (boundary == null) {
1305            FastStringBuffer sb = new FastStringBuffer(16);
1306            Random JavaDoc random = new Random JavaDoc();
1307            char[] chars = getBoundaryChars();
1308            for (int i = 0; i < 16; i++) {
1309                int index = random.nextInt(chars.length);
1310                sb.append(chars[index]);
1311            }
1312            boundary = sb.toString();
1313        }
1314        return boundary;
1315    }
1316
1317    private char[] getBoundaryChars()
1318    {
1319        return Base64Encoder.getBase64Chars();
1320    }
1321
1322    private String JavaDoc getContentTypeForFile(File file)
1323    {
1324        boolean isBinary = false;
1325        try {
1326            BufferedInputStream JavaDoc inputStream =
1327                new BufferedInputStream JavaDoc(file.getInputStream());
1328            int c;
1329            while ((c = inputStream.read()) >= 0) {
1330                if (c >= ' ' && c < 127)
1331                    continue;
1332                if (c == '\r')
1333                    continue;
1334                if (c == '\n')
1335                    continue;
1336                if (c == '\t')
1337                    continue;
1338                if (c == '\f')
1339                    continue;
1340                // If none of the above, it's not text.
1341
isBinary = true;
1342                break;
1343            }
1344            inputStream.close();
1345        }
1346        catch (IOException JavaDoc e) {
1347            Log.error(e);
1348            isBinary = true;
1349        }
1350        String JavaDoc extension = Utilities.getExtension(file);
1351        if (extension != null)
1352            extension = extension.toLowerCase();
1353        if (isBinary) {
1354            if (extension != null) {
1355                // Check for known image types.
1356
extension = extension.toLowerCase();
1357                if (extension.equals(".jpeg") || extension.equals(".jpg"))
1358                    return "image/jpeg";
1359                if (extension.equals(".gif"))
1360                    return "image/gif";
1361                if (extension.equals(".png"))
1362                    return "image/png";
1363            }
1364            // No extension or not a known type.
1365
return "application/octet-stream";
1366        } else {
1367            if (extension != null) {
1368                if (extension.equals(".html") || extension.equals(".htm"))
1369                    return "text/html";
1370                if (extension.equals(".xml"))
1371                    return "text/xml";
1372            }
1373            // No extension or not a known type.
1374
return "text/plain";
1375        }
1376    }
1377
1378    public File getCurrentDirectory()
1379    {
1380        return Directories.getUserHomeDirectory();
1381    }
1382
1383    public File getCompletionDirectory()
1384    {
1385        return Directories.getUserHomeDirectory();
1386    }
1387
1388    // For the buffer list.
1389
public Icon JavaDoc getIcon()
1390    {
1391        if (isModified())
1392            return Utilities.getIconFromFile("compose_modified.png");
1393        return Utilities.getIconFromFile("compose.png");
1394    }
1395
1396    public String JavaDoc getFileNameForDisplay()
1397    {
1398        return "";
1399    }
1400
1401    private static boolean requiresEncoding(Line line)
1402    {
1403        final String JavaDoc text = line.getText();
1404        if (text.length() > 990)
1405            return true;
1406        if (text.length() == 0)
1407            return false;
1408        for (int i = text.length()-1; i >= 0; i--) {
1409            char c = text.charAt(i);
1410            if (c < ' ' && c != '\t')
1411                return true;
1412            if (c >= 127)
1413                return true;
1414        }
1415        if (text.startsWith("From "))
1416            return true;
1417        if (text.charAt(0) == '.')
1418            return true;
1419        return false;
1420    }
1421
1422    private static final String JavaDoc getCharSetName(String JavaDoc characterEncoding)
1423    {
1424        if (characterEncoding.equals("ASCII"))
1425            return "us-ascii";
1426        if (characterEncoding.startsWith("ISO8859_"))
1427            return "iso-8859-" + characterEncoding.substring(8);
1428        // BUG! There are more cases!
1429
return characterEncoding;
1430    }
1431
1432    public boolean isHeaderLine(Line maybe)
1433    {
1434        for (Line line = getFirstLine(); line != null; line = line.next()) {
1435            if (line == maybe)
1436                return true;
1437            if (line.getText().equals(HEADER_SEPARATOR))
1438                return false;
1439        }
1440        return false;
1441    }
1442
1443    public void tab(Editor editor)
1444    {
1445        Line dotLine = editor.getDotLine();
1446        if (dotLine.getText().equals(HEADER_SEPARATOR)) {
1447            Line line = dotLine.next();
1448            if (line != null)
1449                editor.moveDotTo(line, 0);
1450            return;
1451        }
1452        for (Line line = getFirstLine(); line != null; line = line.next()) {
1453            if (line == dotLine) {
1454                break;
1455            } else if (line.getText().equals(HEADER_SEPARATOR)) {
1456                // We're in the body of the message. Just do the normal thing.
1457
editor.insertTab();
1458                return;
1459            }
1460        }
1461        // Reaching here, dot is in the header area. Advance to the end of the
1462
// next header.
1463
Line line = null;
1464        for (line = dotLine.next(); line != null; line = line.next()) {
1465            if (!isContinuationLine(line))
1466                break;
1467        }
1468        if (line == null)
1469            return;
1470        // Have we reached the header separator line?
1471
if (line.getText().equals(HEADER_SEPARATOR)) {
1472            line = line.next();
1473            if (line != null)
1474                editor.moveDotTo(line, 0);
1475            return;
1476        }
1477        // We're at start of next header. Put dot at end of it.
1478
while (true) {
1479            Line next = line.next();
1480            if (next == null)
1481                return;
1482            if (!isContinuationLine(next)) {
1483                // Next line is not a continuation line of current header.
1484
// Move dot to end of current line.
1485
editor.moveDotTo(line, line.length());
1486                return;
1487            }
1488            line = next;
1489        }
1490    }
1491
1492    public void backTab(Editor editor)
1493    {
1494        Line dotLine = editor.getDotLine();
1495        // Check to see whether dot is in the header area or in the body of
1496
// the message.
1497
for (Line line = getFirstLine(); line != null; line = line.next()) {
1498            if (line == dotLine) {
1499                break;
1500            } else if (line.getText().equals(HEADER_SEPARATOR)) {
1501                // Dot is in the body of the message. Go back to the end of
1502
// the last header line.
1503
line = line.previous();
1504                if (line != null)
1505                    editor.moveDotTo(line, line.length());
1506                return;
1507            }
1508        }
1509        // Reaching here, dot is in the header area. Find the first line of
1510
// the header dot is in.
1511
Line line = dotLine;
1512        while (isContinuationLine(line)) {
1513            line = line.previous();
1514            if (line == null)
1515                return; // Shouldn't happen.
1516
}
1517        // Now we're on the first line of the header dot was in. Put dot at
1518
// the end of the previous line.
1519
line = line.previous();
1520        if (line != null)
1521            editor.moveDotTo(line, line.length());
1522    }
1523
1524    private static final boolean isContinuationLine(Line line)
1525    {
1526        if (line.length() == 0)
1527            return false;
1528        char c = line.charAt(0);
1529        return c == ' ' || c == '\t';
1530    }
1531
1532    // Append message to sent messages file (if so configured).
1533
private void writeFcc(File messageFile)
1534    {
1535        final File sentMessagesFile = Mail.getSentMessagesFile();
1536        if (sentMessagesFile == null)
1537            return;
1538        try {
1539            BufferedReader JavaDoc reader =
1540                new BufferedReader JavaDoc(new InputStreamReader JavaDoc(messageFile.getInputStream()));
1541            BufferedWriter JavaDoc writer =
1542                new BufferedWriter JavaDoc(new FileWriter JavaDoc(sentMessagesFile.canonicalPath(), true));
1543            writer.write("From - ");
1544            SimpleDateFormat JavaDoc dateFormatter =
1545                new SimpleDateFormat JavaDoc ("EEE MMM d HH:mm:ss yyyy");
1546            Calendar JavaDoc cal = Calendar.getInstance();
1547            String JavaDoc dateString = dateFormatter.format(cal.getTime());
1548            writer.write(dateString);
1549            writer.write('\n');
1550            String JavaDoc s;
1551            while((s = reader.readLine()) != null) {
1552                writer.write(s);
1553                writer.write('\n');
1554            }
1555            writer.write('\n');
1556            writer.flush();
1557            writer.close();
1558            reader.close();
1559        }
1560        catch (IOException JavaDoc e) {
1561            Log.error(e);
1562        }
1563    }
1564
1565    public Expansion getExpansion(Position dot)
1566    {
1567        int endOfHeaders = -1;
1568        for (Line line = getFirstLine(); line != null; line = line.next()) {
1569            if (line.getText().equals(HEADER_SEPARATOR)) {
1570                endOfHeaders = line.lineNumber();
1571                break;
1572            }
1573        }
1574        if (dot.lineNumber() < endOfHeaders)
1575            return new MailAddressExpansion(dot);
1576        else
1577            return super.getExpansion(dot);
1578    }
1579
1580    private static final RE dateRE =
1581        new UncheckedRE("[A-Za-z]+, [0-9][0-9]? [A-Za-z]+ [0-9][0-9][0-9][0-9]");
1582    private static final RE timeRE =
1583        new UncheckedRE("[0-9:]+ [+-][0-9][0-9][0-9][0-9]");
1584
1585    private static String JavaDoc getAttribution(MessageBuffer messageBuffer)
1586    {
1587        if (messageBuffer == null)
1588            return null;
1589        Mailbox mailbox = messageBuffer.getMailbox();
1590        if (mailbox == null)
1591            return null;
1592        MailboxEntry entry = messageBuffer.getMailboxEntry();
1593        if (entry == null)
1594            return null;
1595        String JavaDoc template = preferences.getStringProperty(Property.ATTRIBUTION);
1596        if (template == null || template.length() == 0)
1597            return null;
1598        FastStringBuffer sb = new FastStringBuffer();
1599        final int limit = template.length();
1600        for (int i = 0; i < limit; i++) {
1601            char c = template.charAt(i);
1602            if (c == '%' && ++i < limit) {
1603                c = template.charAt(i);
1604                switch (c) {
1605                    case 'd': {
1606                        // Date/time in sender's time zone.
1607
Message message = messageBuffer.getMessage();
1608                        if (message == null)
1609                            return null;
1610                        String JavaDoc dateTime = message.getHeaderValue(Headers.DATE);
1611                        REMatch m1 = dateRE.getMatch(dateTime);
1612                        if (m1 != null) {
1613                            REMatch m2 =
1614                                timeRE.getMatch(dateTime.substring(m1.getEndIndex()));
1615                            if (m2 != null) {
1616                                sb.append(m1.toString());
1617                                sb.append(" at ");
1618                                sb.append(m2.toString());
1619                            }
1620                        }
1621                        break;
1622                    }
1623                    case 'n': {
1624                        // Author's real name (or address if missing).
1625
MailAddress[] from = entry.getFrom();
1626                        if (from == null || from.length == 0)
1627                            return null;
1628                        String JavaDoc personal = from[0].getPersonal();
1629                        if (personal != null && personal.length() > 0)
1630                            sb.append(personal);
1631                        else {
1632                            String JavaDoc addr = from[0].getAddress();
1633                            if (addr != null && addr.length() > 0)
1634                                sb.append(addr);
1635                            else
1636                                return null;
1637                        }
1638                        break;
1639                    }
1640                    default:
1641                        Log.error("invalid format sequence \"%" + c + '"' +
1642                            " in attribution");
1643                        return null;
1644                }
1645            } else
1646                sb.append(c);
1647        }
1648        return sb.toString();
1649    }
1650}
1651
Popular Tags