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