001: /*
002: * MailboxEntry.java
003: *
004: * Copyright (C) 2000-2002 Peter Graves
005: * $Id: MailboxEntry.java,v 1.2 2003/05/30 15:09:50 piso Exp $
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License
009: * as published by the Free Software Foundation; either version 2
010: * of the License, or (at your option) any later version.
011: *
012: * This program is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
015: * GNU General Public License for more details.
016: *
017: * You should have received a copy of the GNU General Public License
018: * along with this program; if not, write to the Free Software
019: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
020: */
021:
022: package org.armedbear.j.mail;
023:
024: import java.io.Serializable;
025: import java.text.SimpleDateFormat;
026: import java.util.ArrayList;
027: import java.util.Locale;
028: import org.armedbear.j.Debug;
029: import org.armedbear.j.Editor;
030: import org.armedbear.j.FastStringBuffer;
031: import org.armedbear.j.Log;
032: import org.armedbear.j.Property;
033: import org.armedbear.j.Utilities;
034:
035: public abstract class MailboxEntry implements Serializable {
036: // If DEBUG is true, formatDate() prints the time even if the entry is
037: // more than six months old.
038: private static final boolean DEBUG = Editor.preferences()
039: .getBooleanProperty("MailboxEntry.debug", false);
040:
041: protected static final boolean SHOW_MESSAGE_NUMBERS = Editor
042: .preferences().getBooleanProperty(
043: Property.SHOW_MESSAGE_NUMBERS);
044:
045: // Bit flags.
046: public static final int SEEN = 0x01;
047: public static final int RECENT = 0x02;
048: public static final int ANSWERED = 0x04;
049: public static final int DELETED = 0x08;
050: public static final int FLAGGED = 0x10;
051: public static final int TAGGED = 0x20; // Not persistent.
052:
053: protected static final String STRING_DEFAULT = " ";
054: protected static final String STRING_DELETED = "D";
055: protected static final String STRING_REPLIED = "r";
056: protected static final String STRING_NEW = "N";
057: protected static final String STRING_OLD = "O";
058: protected static final String STRING_FLAGGED = "!";
059:
060: protected int flags;
061: protected int messageNumber;
062: protected int size;
063: protected String subject;
064: protected RFC822Date date;
065: protected MailAddress[] from;
066: protected MailAddress[] replyTo;
067: protected MailAddress[] to;
068: protected MailAddress[] cc;
069: protected String messageId;
070: protected String inReplyTo;
071: protected String[] references;
072:
073: // The message number displayed to the user.
074: private transient int sequenceNumber;
075:
076: private transient boolean orphan;
077:
078: public final int getFlags() {
079: return flags;
080: }
081:
082: public final void setFlags(int flags) {
083: this .flags = flags;
084: }
085:
086: public final int getSize() {
087: return size;
088: }
089:
090: public final void setSize(int size) {
091: this .size = size;
092: }
093:
094: public final int getMessageNumber() {
095: return messageNumber;
096: }
097:
098: public final int getSequenceNumber() {
099: return sequenceNumber;
100: }
101:
102: public final void setSequenceNumber(int n) {
103: sequenceNumber = n;
104: }
105:
106: public final RFC822Date getDate() {
107: return date;
108: }
109:
110: public final MailAddress[] getFrom() {
111: return from;
112: }
113:
114: public final MailAddress[] getReplyTo() {
115: return replyTo;
116: }
117:
118: public final MailAddress[] getTo() {
119: return to;
120: }
121:
122: public final MailAddress[] getCc() {
123: return cc;
124: }
125:
126: public final String getSubject() {
127: if (subject != null && subject.length() > 0)
128: return subject;
129: return "(no subject)";
130: }
131:
132: public final String getBaseSubject() {
133: if (subject == null)
134: return null;
135: final int length = subject.length();
136: if (length == 0)
137: return null;
138: String s = subject.trim();
139: while (true) {
140: if (s.toLowerCase().startsWith("re:")) {
141: s = s.substring(3).trim();
142: continue;
143: }
144: if (s.length() > 0 && s.charAt(0) == '[') {
145: int end = s.indexOf(']', 1);
146: if (end >= 0) {
147: s = s.substring(end + 1).trim();
148: continue;
149: }
150: }
151: break;
152: }
153: while (s.toLowerCase().endsWith("(fwd)"))
154: s = s.substring(0, s.length() - 5).trim();
155:
156: // Some broken mailers (or MTAs) arbitrarily break the subject line
157: // after 74 characters. If this happens to be in the middle of a word,
158: // we'll end up with an extra LWSP char in the subject string when we
159: // unfold the header, meaning we won't get an exact match with the
160: // subject of the message being replied to, which is the whole point
161: // here. So we strip out all LWSP chars before returning the base
162: // subject.
163: FastStringBuffer sb = new FastStringBuffer();
164: for (int i = 0, limit = s.length(); i < limit; i++) {
165: char c = s.charAt(i);
166: if (c != ' ' && c != '\t')
167: sb.append(c);
168: }
169: return sb.toString();
170: }
171:
172: public final boolean subjectIsReply() {
173: if (subject != null && subject.toLowerCase().startsWith("re:"))
174: return true;
175: return false;
176: }
177:
178: public final String getMessageId() {
179: return messageId;
180: }
181:
182: public final String getInReplyTo() {
183: return inReplyTo;
184: }
185:
186: public final String[] getReferences() {
187: return references;
188: }
189:
190: public final void setOrphan(boolean b) {
191: orphan = b;
192: }
193:
194: public String getUidl() {
195: return null;
196: }
197:
198: public String formatSubject() {
199: if (subject == null)
200: return "";
201: return subject;
202: }
203:
204: public final boolean isDeleted() {
205: return (flags & DELETED) == DELETED;
206: }
207:
208: public final boolean isTagged() {
209: return (flags & TAGGED) == TAGGED;
210: }
211:
212: public final boolean isFlagged() {
213: return (flags & FLAGGED) == FLAGGED;
214: }
215:
216: public final boolean isAnswered() {
217: return (flags & ANSWERED) == ANSWERED;
218: }
219:
220: public final boolean isNew() {
221: return (flags & (SEEN | DELETED | RECENT)) == RECENT;
222: }
223:
224: public final boolean isRead() {
225: return (flags & SEEN) != 0;
226: }
227:
228: public final boolean isUnread() {
229: return (flags & (SEEN | DELETED)) == 0;
230: }
231:
232: public final void tag() {
233: flags |= TAGGED;
234: }
235:
236: public final void untag() {
237: flags &= ~TAGGED;
238: }
239:
240: public final void toggleTag() {
241: if ((flags & TAGGED) != 0)
242: flags &= ~TAGGED;
243: else
244: flags |= TAGGED;
245: }
246:
247: public final void flag() {
248: flags |= FLAGGED;
249: }
250:
251: public final void unflag() {
252: flags &= ~FLAGGED;
253: }
254:
255: public final void toggleFlag() {
256: if ((flags & FLAGGED) != 0)
257: flags &= ~FLAGGED;
258: else
259: flags |= FLAGGED;
260: }
261:
262: protected String formatSize() {
263: if (size < 1000)
264: return Utilities.rightJustify(size, 4);
265: if (size < 10000) {
266: int k = size / 1000;
267: int remainder = size % 1000;
268: int h = remainder / 100;
269: if (remainder % 100 > 49) {
270: ++h;
271: if (h == 10) {
272: ++k;
273: h = 0;
274: }
275: }
276: if (k < 10) {
277: FastStringBuffer sb = new FastStringBuffer();
278: sb.append(String.valueOf(k));
279: sb.append('.');
280: sb.append(String.valueOf(h));
281: sb.append('K');
282: return sb.toString();
283: }
284: // else fall through...
285: }
286: if (size < 1000000) {
287: int k = size / 1000;
288: if (size % 1000 > 499)
289: ++k;
290: if (k < 1000)
291: return Utilities.rightJustify(k, 3) + "K";
292: // else fall through...
293: }
294: int m = size / 1000000;
295: if (size % 1000000 > 499999)
296: ++m;
297: return Utilities.rightJustify(m, 3) + "M";
298: }
299:
300: protected char getToChar() {
301: if (isFlagged())
302: return '!';
303: if (isFromMe())
304: return 'F';
305: char c = ' ';
306: if (to != null) {
307: for (int i = to.length - 1; i >= 0; i--) {
308: MailAddress a = to[i];
309: if (a.matches(Mail.getUserMailAddress())) {
310: // Addressed to me.
311: if (to.length == 1)
312: c = '+';
313: else
314: return 'T';
315: }
316: }
317: }
318: if (c == '+') {
319: // Addressed to me alone.
320: if (cc != null && cc.length > 0) // Copied to others or to me.
321: return 'T';
322: return c;
323: }
324: if (cc != null) {
325: for (int i = cc.length - 1; i >= 0; i--) {
326: MailAddress a = cc[i];
327: if (a.matches(Mail.getUserMailAddress())) {
328: // Copied to me.
329: return 'C';
330: }
331: }
332: }
333: return ' ';
334: }
335:
336: private boolean isFromMe() {
337: if (from != null) {
338: MailAddress a = from[0];
339: if (a.matches(Mail.getUserMailAddress()))
340: return true;
341: }
342: return false;
343: }
344:
345: protected String formatFlags() {
346: if (isAnswered())
347: return STRING_REPLIED;
348: else if ((flags & (SEEN | RECENT)) == RECENT) // Might be deleted.
349: return STRING_NEW;
350: else if ((flags & SEEN) == 0) // Might be deleted.
351: return STRING_OLD;
352: else
353: return STRING_DEFAULT;
354: }
355:
356: private static final SimpleDateFormat dateFormat1 = new SimpleDateFormat(
357: "MMM dd HH:mm", Locale.US);
358:
359: private static final SimpleDateFormat dateFormat2 = new SimpleDateFormat(
360: "MMM dd yyyy", Locale.US);
361:
362: private static final String NULL_DATE = "null ";
363:
364: private static final long SIX_MONTHS = (long) 6 * 30 * 24 * 60 * 60
365: * 1000;
366:
367: protected String formatDate() {
368: if (date == null)
369: return NULL_DATE;
370: long millis = date.getTime();
371: if (millis == 0)
372: return NULL_DATE;
373: if (DEBUG || System.currentTimeMillis() - millis < SIX_MONTHS)
374: return dateFormat1.format(date.getDate());
375: return dateFormat2.format(date.getDate());
376: }
377:
378: protected String formatFrom(int fieldWidth) {
379: String s = null;
380: if (isFromMe() && to != null && to.length > 0) {
381: MailAddress a = to[0];
382: s = a.getPersonal();
383: if (s == null || s.length() == 0)
384: s = a.getAddress();
385: if (s != null)
386: s = "To: " + s;
387: } else if (from != null && from.length > 0) {
388: MailAddress a = from[0];
389: s = a.getPersonal();
390: if (s == null || s.length() == 0)
391: s = a.getAddress();
392: }
393: if (s == null)
394: s = "";
395: final int length = s.length();
396: if (length > fieldWidth)
397: s = s.substring(0, fieldWidth);
398: else if (length < fieldWidth)
399: s = s + Utilities.spaces(fieldWidth - length);
400: return s;
401: }
402:
403: public String toString() {
404: return toString(1);
405: }
406:
407: public String toString(int depth) {
408: FastStringBuffer sb = new FastStringBuffer(128);
409: if (SHOW_MESSAGE_NUMBERS) {
410: sb.append(Utilities.rightJustify(sequenceNumber, 5));
411: sb.append(' ');
412: }
413: sb.append(getToChar());
414: sb.append(' ');
415: sb.append(formatFlags());
416: sb.append(" ");
417: sb.append(formatDate());
418: sb.append(" ");
419: sb.append(formatFrom(20));
420: sb.append(" ");
421: sb.append(formatSize());
422: sb.append(" ");
423: if (orphan)
424: sb.append("- ");
425: else if (depth > 1)
426: sb.append(Utilities.spaces((depth - 1) * 2));
427: sb.append(formatSubject());
428: return sb.toString();
429: }
430:
431: protected static String parseInReplyTo(String s) {
432: if (s != null) {
433: int begin = s.indexOf('<');
434: if (begin >= 0) {
435: int end = s.indexOf('>', begin + 1);
436: if (end > begin)
437: return s.substring(begin, end + 1);
438: }
439: }
440: return null;
441: }
442:
443: protected static String[] parseReferences(String s) {
444: ArrayList list = null;
445: int begin = 0;
446: while (true) {
447: begin = s.indexOf('<', begin);
448: if (begin < 0)
449: break;
450: int end = s.indexOf('>', begin + 1);
451: if (end < 0)
452: break;
453: if (list == null)
454: list = new ArrayList();
455: list.add(s.substring(begin, ++end));
456: begin = end;
457: }
458: if (list == null)
459: return null;
460: String[] array = new String[list.size()];
461: return (String[]) list.toArray(array);
462: }
463: }
|