| EmailDocumentHandler.java |
1 /*
2 * EmailDocumentHandler.java
3 *
4 * Copyright (c) 1998-2004, The University of Sheffield.
5 *
6 * This file is part of GATE (see http://gate.ac.uk/), and is free
7 * software, licenced under the GNU Library General Public License,
8 * Version 2, June 1991 (in the distribution as file licence.html,
9 * and also available at http://gate.ac.uk/gate/licence.html).
10 *
11 * Cristian URSU, 3/Aug/2000
12 *
13 * $Id: EmailDocumentHandler.java,v 1.26 2004/07/21 17:10:06 akshay Exp $
14 */
15
16 package gate.email;
17
18 import java.io.*;
19 import java.util.*;
20
21 import junit.framework.Assert;
22
23 import gate.*;
24 import gate.event.StatusListener;
25
26 /**
27 * This class implements the behaviour of the Email reader
28 * It takes the Gate Document representing a list with e-mails and
29 * creates Gate annotations on it.
30 */
31 public class EmailDocumentHandler{
32
33 /** Debug flag */
34 private static final boolean DEBUG = false;
35
36 private String content = null;
37 private long documentSize = 0;
38
39 /**
40 * Constructor used in tests mostly
41 */
42 public EmailDocumentHandler() {
43 setUp();
44 }//EmailDocumentHandler
45
46 /**
47 * Constructor initialises some private fields
48 */
49 public EmailDocumentHandler( gate.Document aGateDocument,
50 Map aMarkupElementsMap,
51 Map anElement2StringMap
52 ) {
53
54 gateDocument = aGateDocument;
55
56 // gets AnnotationSet based on the new gate document
57 if (basicAS == null)
58 basicAS = gateDocument.getAnnotations(
59 GateConstants.ORIGINAL_MARKUPS_ANNOT_SET_NAME);
60
61 markupElementsMap = aMarkupElementsMap;
62 element2StringMap = anElement2StringMap;
63 setUp();
64 }// EmailDocumentHandler
65
66 /**
67 * Reads the Gate Document line by line and does the folowing things:
68 * <ul>
69 * <li> Each line is analized in order to detect where an e-mail starts.
70 * <li> If the line belongs to an e-mail header then creates the
71 * annotation if the markupElementsMap allows that.
72 * <li> Lines belonging to the e-mail body are placed under a Gate
73 * annotation called messageBody.
74 * </ul>
75 */
76 public void annotateMessages() throws IOException,
77 gate.util.InvalidOffsetException {
78 // obtain a BufferedReader form the Gate document...
79 BufferedReader gateDocumentReader = null;
80 // Get the string representing the content of the document
81 // It is used inside CreateAnnotation method
82 content = gateDocument.getContent().toString();
83 // Get the sieze of the Gate Document. For the same purpose.
84 documentSize = gateDocument.getContent().size().longValue();
85
86 // gateDocumentReader = new BufferedReader(new InputStreamReader(
87 // gateDocument.getSourceUrl().openConnection().getInputStream()));
88 gateDocumentReader = new BufferedReader(new InputStreamReader(
89 new ByteArrayInputStream(content.getBytes())));
90
91 // for each line read from the gateDocumentReader do
92 // if the line begins an e-mail message then fire a status listener, mark
93 // that we are processing an e-mail, update the cursor and go to the next
94 // line.
95
96 // if we are inside an e-mail, test if the line belongs to the message
97 // header
98 // if so, create a header field annotation.
99
100 // if we are inside a a body and this is the first line from the body,
101 // create the message body annotation.
102 // Otherwise just update the cursor and go to the next line
103
104 // if the line doesn't belong to an e-mail message then just update the
105 // cursor.
106 // next line
107
108 String line = null;
109 String aFieldName = null;
110
111 long cursor = 0;
112 long endEmail = 0;
113 long startEmail = 0;
114 long endHeader = 0;
115 long startHeader = 0;
116 long endBody = 0;
117 long startBody = 0;
118 long endField = 0;
119 long startField = 0;
120
121 boolean insideAnEmail = false;
122 boolean insideHeader = false;
123 boolean insideBody = false;
124 boolean emailReadBefore = false;
125 boolean fieldReadBefore = false;
126
127 long nlSize = detectNLSize();
128
129 //Out.println("NL SIZE = " + nlSize);
130
131 // read each line from the reader
132 while ((line = gateDocumentReader.readLine()) != null){
133 // Here we test if the line delimitates two e-mail messages.
134 // Each e-mail message begins with a line like this:
135 // From P.Fairhurst Thu Apr 18 12:22:23 1996
136 // Method lineBeginsMessage() detects such lines.
137 if (lineBeginsMessage(line)){
138 // Inform the status listener to fire only
139 // if no. of elements processed.
140 // So far is a multiple of ELEMENTS_RATE
141 if ((++ emails % EMAILS_RATE) == 0)
142 fireStatusChangedEvent("Reading emails : " + emails);
143 // if there are e-mails read before, then the previous e-mail
144 // ends here.
145 if (true == emailReadBefore){
146 // Cursor points at the beggining of the line
147 // E-mail and Body ends before the \n char
148 // Email ends as cursor value indicates
149 endEmail = cursor - nlSize ;
150 // also the e-mail body ends when an e-mail ends
151 endBody = cursor - nlSize;
152 //Annotate an E-mail body (startBody, endEmail)
153 createAnnotation("Body",startBody,endBody,null);
154 //Annotate an E-mail message(startEmail, endEmail) Email starts
155 createAnnotation("Message",startEmail,endEmail,null);
156 }
157 // if no e-mail was read before, now there is at list one message
158 // read
159 emailReadBefore = true;
160 // E-mail starts imediately from the beginning of this line which
161 // sepatates 2 messages.
162 startEmail = cursor;
163 // E-mail header starts also from here
164 startHeader = cursor;
165 // The cursor is updated with the length of the line + the
166 // new line char
167 cursor += line.length() + nlSize;
168 // We are inside an e-mail
169 insideAnEmail = true;
170 // Next is the E-mail header
171 insideHeader = true;
172 // No field inside header has been read before
173 fieldReadBefore = false;
174 // Read the next line
175 continue;
176 }//if (lineBeginsMessage(line))
177 if (false == insideAnEmail){
178 // the cursor is update with the length of the line +
179 // the new line char
180 cursor += line.length() + nlSize;
181 // read the next line
182 continue;
183 }//if
184 // here we are inside an e-mail message (inside Header or Body)
185 if (true == insideHeader){
186 // E-mail spec sais that E-mail header is separated by E-mail body
187 // by a \n char
188 if (line.equals("")){
189 // this \n sepatates the header of an e-mail form its body
190 // If we are here it means that the header has ended.
191 insideHeader = false;
192 // e-mail header ends here
193 endHeader = cursor - nlSize;
194 // update the cursor with the length of \n
195 cursor += line.length() + nlSize;
196 // E-mail body starts from here
197 startBody = cursor;
198 // if fields were read before, it means that the e-mail has a header
199 if (true == fieldReadBefore){
200 endField = endHeader;
201 //Create a field annotation (fieldName, startField, endField)
202 createAnnotation(aFieldName, startField, endField, null);
203 //Create an e-mail header annotation
204 createAnnotation("Header",startHeader,endHeader,null);
205 }//if
206 // read the next line
207 continue;
208 }//if (line.equals(""))
209 // if line begins with a field then prepare to create an
210 // annotation with the name of the field
211 if (lineBeginsWithField(line)){
212 // if a field was read before, it means that the previous field ends
213 // here
214 if (true == fieldReadBefore){
215 // the previous field end here
216 endField = cursor - nlSize;
217 //Create a field annotation (fieldName, startField, endField)
218 createAnnotation(aFieldName, startField, endField, null);
219 }//if
220 fieldReadBefore = true;
221 aFieldName = getFieldName();
222 startField = cursor + aFieldName.length() + ":".length();
223 }//if
224 // in both cases the cursor is updated and read the next line
225 // the cursor is update with the length of the line +
226 // the new line char
227 cursor += line.length() + nlSize;
228 // read the next line
229 continue;
230 }//if (true == insideHeader)
231 // here we are inside the E-mail body
232 // the body will end when the e-mail will end.
233 // here we just update the cursor
234 cursor += line.length() + nlSize;
235 }//while
236 // it might be possible that the file to contain only one e-mail and
237 // if the file contains only one e-mail message then the variable
238 // emailReadBefore must be set on true value
239 if (true == emailReadBefore){
240 endBody = cursor - nlSize;
241 endEmail = cursor - nlSize;
242 //Annotate an E-mail body (startBody, endEmail)
243 createAnnotation("Body",startBody,endBody,null);
244 //Annotate an E-mail message(startEmail, endEmail) Email starts
245 createAnnotation("Message",startEmail,endEmail,null);
246 }
247 // if emailReadBefore is not set on true, that means that we didn't
248 // encounter any line like this:
249 // From P.Fairhurst Thu Apr 18 12:22:23 1996
250 }//annotateMessages
251
252 /**
253 * This method detects if the text file which contains e-mail messages
254 * is under MSDOS or UNIX format.
255 * Under MSDOS the size of NL is 2 (\n \r) and under UNIX (\n) the size is 1
256 * @return the size of the NL (1,2 or 0 = if no \n is found)
257 */
258 private int detectNLSize() {
259
260 // get a char array
261 char[] document = null;
262
263 // transform the gate Document into a char array
264 document = gateDocument.getContent().toString().toCharArray();
265
266 // search for the \n char
267 // when it is found test if is followed by the \r char
268 for (int i=0; i<document.length; i++){
269 if (document[i] == '\n'){
270
271 // we just found a \n char.
272 // here we test if is followed by a \r char or preceded by a \r char
273 if (
274 (((i+1) < document.length) && (document[i+1] == '\r'))
275 ||
276 (((i-1) >= 0) && (document[i-1] == '\r'))
277 ) return 2;
278 else return 1;
279 }
280 }
281 //if no \n char is found then the document is contained into a single text
282 // line.
283 return 0;
284
285 } // detectNLSize
286
287 /**
288 * This method creates a gate annotation given its name, start, end and
289 * feature map.
290 */
291 private void createAnnotation(String anAnnotationName, long anAnnotationStart,
292 long anAnnotationEnd, FeatureMap aFeatureMap)
293 throws gate.util.InvalidOffsetException{
294
295 /*
296 while (Character.isWhitespace(content.charAt((int) anAnnotationStart)))
297 anAnnotationStart ++;
298
299 // System.out.println(content.charAt((int) anAnnotationEnd));
300 while (Character.isWhitespace(content.charAt((int) anAnnotationEnd)))
301 anAnnotationEnd --;
302
303 anAnnotationEnd ++;
304 */
305 if (canCreateAnnotation(anAnnotationStart,anAnnotationEnd,documentSize)){
306 if (aFeatureMap == null)
307 aFeatureMap = Factory.newFeatureMap();
308 basicAS.add( new Long(anAnnotationStart),
309 new Long(anAnnotationEnd),
310 anAnnotationName.toLowerCase(),
311 aFeatureMap);
312 }// End if
313 }//createAnnotation
314 /**
315 * This method verifies if an Annotation can be created.
316 */
317 private boolean canCreateAnnotation(long start,
318 long end,
319 long gateDocumentSize){
320
321 if (start < 0 || end < 0 ) return false;
322 if (start > end ) return false;
323 if ((start > gateDocumentSize) || (end > gateDocumentSize)) return false;
324 return true;
325 }// canCreateAnnotation
326
327 /**
328 * Tests if the line begins an e-mail message
329 * @param aTextLine a line from the file containing the e-mail messages
330 * @return true if the line begins an e-mail message
331 * @return false if is doesn't
332 */
333 private boolean lineBeginsMessage(String aTextLine){
334 int score = 0;
335
336 // if first token is "From" and the rest contains Day, Zone, etc
337 // then this line begins a message
338 // create a new String Tokenizer with " " as separator
339 StringTokenizer tokenizer = new StringTokenizer(aTextLine," ");
340
341 // get the first token
342 String firstToken = null;
343 if (tokenizer.hasMoreTokens())
344 firstToken = tokenizer.nextToken();
345 else return false;
346
347 // trim it
348 firstToken.trim();
349
350 // check against "From" word
351 // if the first token is not From then the entire line can not begin
352 // a message.
353 if (!firstToken.equals("From"))
354 return false;
355
356 // else continue the analize
357 while (tokenizer.hasMoreTokens()){
358
359 // get the next token
360 String token = tokenizer.nextToken();
361 token.trim();
362
363 // see if it has a meaning(analize if is a Day, Month,Zone, Time, Year )
364 if (hasAMeaning(token))
365 score += 1;
366 }
367
368 // a score greather or equql with 5 means that this line begins a message
369 if (score >= 5) return true;
370 else return false;
371
372 } // lineBeginsMessage
373
374 /**
375 * Tests if the line begins with a field from the e-mail header
376 * If the answer is true then it also sets the member fieldName with the
377 * value of this e-mail header field.
378 * @param aTextLine a line from the file containing the e-mail text
379 * @return true if the line begins with a field from the e-mail header
380 * @return false if is doesn't
381 */
382 private boolean lineBeginsWithField(String aTextLine){
383 if (containsSemicolon(aTextLine)){
384 StringTokenizer tokenizer = new StringTokenizer(aTextLine,":");
385
386 // get the first token
387 String firstToken = null;
388
389 if (tokenizer.hasMoreTokens())
390 firstToken = tokenizer.nextToken();
391 else return false;
392
393 if (firstToken != null){
394 // trim it
395 firstToken.trim();
396 if (containsWhiteSpaces(firstToken)) return false;
397
398 // set the member field
399 fieldName = firstToken;
400 }
401 return true;
402 } else return false;
403
404 } // lineBeginsWithField
405
406 /**
407 * This method checks if a String contains white spaces.
408 */
409 private boolean containsWhiteSpaces(String aString) {
410 for (int i = 0; i<aString.length(); i++)
411 if (Character.isWhitespace(aString.charAt(i))) return true;
412 return false;
413 } // containsWhiteSpaces
414
415 /**
416 * This method checks if a String contains a semicolon char
417 */
418 private boolean containsSemicolon(String aString) {
419 for (int i = 0; i<aString.length(); i++)
420 if (aString.charAt(i) == ':') return true;
421 return false;
422 } // containsSemicolon
423
424 /**
425 * This method tests a token if is Day, Month, Zone, Time, Year
426 */
427 private boolean hasAMeaning(String aToken) {
428 // if token is a Day return true
429 if (day.contains(aToken)) return true;
430
431 // if token is a Month return true
432 if (month.contains(aToken)) return true;
433
434 // if token is a Zone then return true
435 if (zone.contains(aToken)) return true;
436
437 // test if is a day number or a year
438 Integer dayNumberOrYear = null;
439 try{
440 dayNumberOrYear = new Integer(aToken);
441 } catch (NumberFormatException e){
442 dayNumberOrYear = null;
443 }
444
445 // if the creation succeded, then test if is day or year
446 if (dayNumberOrYear != null) {
447 int number = dayNumberOrYear.intValue();
448
449 // if is a number between 1 and 31 then is a day
450 if ((number > 0) && (number < 32)) return true;
451
452 // if is a number between 1900 si 3000 then is a year ;))
453 if ((number > 1900) && (number < 3000)) return true;
454
455 // it might be the last two digits of 19xx
456 if ((number >= 0) && (number <= 99)) return true;
457 }
458 // test if is time: hh:mm:ss
459 if (isTime(aToken)) return true;
460
461 return false;
462 } // hasAMeaning
463
464 /**
465 * Tests a token if is in time format HH:MM:SS
466 */
467 private boolean isTime(String aToken) {
468 StringTokenizer st = new StringTokenizer(aToken,":");
469
470 // test each token if is hour, minute or second
471 String hourString = null;
472 if (st.hasMoreTokens())
473 hourString = st.nextToken();
474
475 // if there are no more tokens, it means that is not a time
476 if (hourString == null) return false;
477
478 // test if is a number between 0 and 23
479 Integer hourInteger = null;
480 try{
481 hourInteger = new Integer(hourString);
482 } catch (NumberFormatException e){
483 hourInteger = null;
484 }
485 if (hourInteger == null) return false;
486
487 // if is not null then it means is a number
488 // test if is in 0 - 23 range
489 // if is not in this range then is not an hour
490 int hour = hourInteger.intValue();
491 if ( (hour < 0) || (hour > 23) ) return false;
492
493 // we have the hour
494 // now repeat the test for minute and seconds
495
496 // minutes
497 String minutesString = null;
498 if (st.hasMoreTokens())
499 minutesString = st.nextToken();
500
501 // if there are no more tokens (minutesString == null) then return false
502 if (minutesString == null) return false;
503
504 // test if is a number between 0 and 59
505 Integer minutesInteger = null;
506 try {
507 minutesInteger = new Integer (minutesString);
508 } catch (NumberFormatException e){
509 minutesInteger = null;
510 }
511
512 if (minutesInteger == null) return false;
513
514 // if is not null then it means is a number
515 // test if is in 0 - 59 range
516 // if is not in this range then is not a minute
517 int minutes = minutesInteger.intValue();
518 if ( (minutes < 0) || (minutes > 59) ) return false;
519
520 // seconds
521 String secondsString = null;
522 if (st.hasMoreTokens())
523 secondsString = st.nextToken();
524
525 // if there are no more tokens (secondsString == null) then return false
526 if (secondsString == null) return false;
527
528 // test if is a number between 0 and 59
529 Integer secondsInteger = null;
530 try {
531 secondsInteger = new Integer (secondsString);
532 } catch (NumberFormatException e){
533 secondsInteger = null;
534 }
535 if (secondsInteger == null) return false;
536
537 // if is not null then it means is a number
538 // test if is in 0 - 59 range
539 // if is not in this range then is not a minute
540 int seconds = secondsInteger.intValue();
541 if ( (seconds < 0) || (seconds > 59) ) return false;
542
543 // if there are more tokens in st it means that we don't have this format:
544 // HH:MM:SS
545 if (st.hasMoreTokens()) return false;
546
547 // if we are here it means we have a time
548 return true;
549 }// isTime
550
551 /**
552 * Initialises the collections with data used by method lineBeginsMessage()
553 */
554 private void setUp(){
555 day = new HashSet();
556 day.add("Mon");
557 day.add("Tue");
558 day.add("Wed");
559 day.add("Thu");
560 day.add("Fri");
561 day.add("Sat");
562 day.add("Sun");
563
564 month = new HashSet();
565 month.add("Jan");
566 month.add("Feb");
567 month.add("Mar");
568 month.add("Apr");
569 month.add("May");
570 month.add("Jun");
571 month.add("Jul");
572 month.add("Aug");
573 month.add("Sep");
574 month.add("Oct");
575 month.add("Nov");
576 month.add("Dec");
577
578 zone = new HashSet();
579 zone.add("UT");
580 zone.add("GMT");
581 zone.add("EST");
582 zone.add("EDT");
583 zone.add("CST");
584 zone.add("CDT");
585 zone.add("MST");
586 zone.add("MDT");
587 zone.add("PST");
588 zone.add("PDT");
589 }//setUp
590
591 /**
592 * This method returns the value of the member fieldName.
593 * fieldName is set by the method lineBeginsWithField(String line).
594 * Each time the the line begins with a field name, that fiels will be stored
595 * in this member.
596 */
597 private String getFieldName() {
598 if (fieldName == null) return new String("");
599 else return fieldName;
600 } // getFieldName
601
602 // StatusReporter Implementation
603
604 /**
605 * This methos is called when a listener is registered with this class
606 */
607 public void addStatusListener(StatusListener listener){
608 myStatusListeners.add(listener);
609 }
610 /**
611 * This methos is called when a listener is removed
612 */
613 public void removeStatusListener(StatusListener listener){
614 myStatusListeners.remove(listener);
615 }
616
617 /**
618 * This methos is called whenever we need to inform the listener
619 * about an event.
620 */
621 protected void fireStatusChangedEvent(String text){
622 Iterator listenersIter = myStatusListeners.iterator();
623 while(listenersIter.hasNext())
624 ((StatusListener)listenersIter.next()).statusChanged(text);
625 }
626
627 private static final int EMAILS_RATE = 16;
628
629 // the content of the e-mail document, without any tag
630 // for internal use
631 private String tmpDocContent = null;
632
633 // a gate document
634 private gate.Document gateDocument = null;
635
636 // an annotation set used for creating annotation reffering the doc
637 private gate.AnnotationSet basicAS = null;
638
639 // this map marks the elements that we don't want to create annotations
640 private Map markupElementsMap = null;
641
642 // this map marks the elements after we want to insert some strings
643 private Map element2StringMap = null;
644
645 // listeners for status report
646 protected List myStatusListeners = new LinkedList();
647
648 // this reports the the number of emails that have beed processed so far
649 private int emails = 0;
650
651 // this is set by the method lineBeginsWithField(String line)
652 // each time the the line begins with a field name, that fiels will be stored
653 // in this member.
654 private String fieldName = null;
655
656 private Collection day = null;
657 private Collection month = null;
658 private Collection zone = null;
659
660
661 // TEST SECTION
662
663 /**
664 * Test containsSemicolon
665 */
666 private void testContainsSemicolon() {
667 String str1 = "X-Sender: oana@derwent";
668 String str2 = "X-Sender oana@derwent";
669 String str3 = ":X-Sender oana@derwent";
670 String str4 = "X-Sender oana@derwent:";
671
672 Assert.assertTrue((containsSemicolon(str1) == true));
673 Assert.assertTrue((containsSemicolon(str2)== false));
674 Assert.assertTrue((containsSemicolon(str3) == true));
675 Assert.assertTrue((containsSemicolon(str4) == true));
676 }// testContainsSemicolon
677
678 /**
679 * Test containsWhiteSpaces
680 */
681 private void testContainsWhiteSpaces(){
682 String str1 = "Content-Type: TEXT/PLAIN; charset=US-ASCII";
683 String str2 = "Content-Type:TEXT/PLAIN;charset=US-ASCII";
684 String str3 = " Content-Type:TEXT/PLAIN;charset=US-ASCII";
685 String str4 = "Content-Type:TEXT/PLAIN;charset=US-ASCII ";
686
687 Assert.assertTrue((containsWhiteSpaces(str1) == true));
688 Assert.assertTrue((containsWhiteSpaces(str2) == false));
689 Assert.assertTrue((containsWhiteSpaces(str3) == true));
690 Assert.assertTrue((containsWhiteSpaces(str4) == true));
691 }// testContainsWhiteSpaces
692
693 /**
694 * Test hasAMeaning
695 */
696 private void testHasAMeaning() {
697 String str1 = "12:05:22";
698 String str2 = "Sep";
699 String str3 = "Fri";
700 String str4 = "2000";
701 String str5 = "GMT";
702 String str6 = "Date: Wed, 13 Sep 2000 13:05:22 +0100 (BST)";
703 String str7 = "12:75:22";
704 String str8 = "September";
705 String str9 = "Friday";
706
707 Assert.assertTrue((hasAMeaning(str1) == true));
708 Assert.assertTrue((hasAMeaning(str2) == true));
709 Assert.assertTrue((hasAMeaning(str3) == true));
710 Assert.assertTrue((hasAMeaning(str4) == true));
711 Assert.assertTrue((hasAMeaning(str5) == true));
712 Assert.assertTrue((hasAMeaning(str6) == false));
713 Assert.assertTrue((hasAMeaning(str7) == false));
714 Assert.assertTrue((hasAMeaning(str8) == false));
715 Assert.assertTrue((hasAMeaning(str9) == false));
716 } // testHasAMeaning
717
718 /**
719 * Test isTime
720 */
721 private void testIsTime() {
722 String str1 = "13:05:22";
723 String str2 = "13/05/22";
724 String str3 = "24:05:22";
725
726 Assert.assertTrue((isTime(str1) == true));
727 Assert.assertTrue((isTime(str2) == false));
728 Assert.assertTrue((isTime(str3) == false));
729 }// testIsTime
730
731 /**
732 * Test lineBeginsMessage
733 */
734 private void testLineBeginsMessage(){
735 String str1 = "From oana@dcs.shef.ac.uk Wed Sep 13 13:05:23 2000";
736 String str2 = "Date: Wed, 13 Sep 2000 13:05:22 +0100 (BST)";
737 String str3 = "From oana@dcs.shef.ac.uk Sep 13 13:05:23 2000";
738
739 Assert.assertTrue((lineBeginsMessage(str1) == true));
740 Assert.assertTrue((lineBeginsMessage(str2) == false));
741 Assert.assertTrue((lineBeginsMessage(str3) == false));
742
743 }// testLineBeginsMessage
744
745 /**
746 * Test lineBeginsWithField
747 */
748 private void testLineBeginsWithField() {
749 String str1 = "Message-ID: <Pine.SOL.3.91.1000913130311.19537A-10@derwent>";
750 String str2 = "%:ContentType TEXT/PLAIN; charset=US-ASCII";
751
752 Assert.assertTrue((lineBeginsWithField(str1) == true));
753 Assert.assertTrue((lineBeginsWithField(str2) == true));
754 }// testLineBeginsWithField
755
756 /**
757 * Test final
758 */
759 public void testSelf(){
760 testContainsSemicolon();
761 testContainsWhiteSpaces();
762 testHasAMeaning();
763 testIsTime();
764 testLineBeginsMessage();
765 testLineBeginsWithField();
766 } // testSelf
767
768 } //EmailDocumentHandler
769