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