Index: src/main/java/org/apache/james/mime4j/MimeTokenStream.java
===================================================================
--- src/main/java/org/apache/james/mime4j/MimeTokenStream.java	(revision 564884)
+++ src/main/java/org/apache/james/mime4j/MimeTokenStream.java	(working copy)
@@ -167,7 +167,44 @@
             fieldChars.set(i);
         }
     }
+    
+    /**
+     * Enumerates events which can be monitored.
+     */
+    public final static class Event { 
 
+        /** Indicates that a body part ended prematurely. */
+        public static Event MIME_BODY_PREMATURE_END = new Event("Body part ended prematurely."); 
+        /** Indicates that unexpected end of headers detected.*/
+        public static Event HEADERS_PREMATURE_END = new Event("Unexpected end of headers detected.");
+        
+        private final String code;
+        
+        private Event(final String code) {
+            super();
+            this.code = code;
+        }
+        
+        public int hashCode() {
+            return code.hashCode();
+        }
+
+        public boolean equals(Object obj) {
+            if (this == obj)
+                return true;
+            if (obj == null)
+                return false;
+            if (getClass() != obj.getClass())
+                return false;
+            final Event other = (Event) obj;
+            return code.equals(other.code);
+        }
+        
+        public String toString() {
+            return code;
+        }
+    }
+
     abstract static class StateMachine {
         int state;
         abstract int next() throws IOException, MimeException;
@@ -207,15 +244,10 @@
             state = parseField() ? T_FIELD : T_END_HEADER;
         }
 
-        private int setParseBodyPartState() throws IOException {
+        private int setParseBodyPartState() throws IOException, MimeException {
             cursor.advanceToBoundary();
             if (cursor.isEnded()) {
-                if (log.isWarnEnabled()) {
-                    log.warn("Line " + cursor.getLineNumber() 
-                            + ": Body part ended prematurely. "
-                            + "Higher level boundary detected or "
-                            + "EOF reached.");
-                }
+                monitor(Event.MIME_BODY_PREMATURE_END);
             } else {
                 if (cursor.moreMimeParts()) {
                     final String boundary = body.getBoundary();
@@ -293,7 +325,7 @@
             return state;
         }
 
-        private void initHeaderParsing() throws IOException {
+        private void initHeaderParsing() throws IOException, MimeException {
             body = new BodyDescriptor(parent);
             startLineNumber = lineNumber = cursor.getLineNumber();
 
@@ -311,10 +343,8 @@
                 prev = curr == '\r' ? prev : curr;
             }
             
-            if (curr == -1 && log.isWarnEnabled()) {
-                log.warn("Line " + cursor.getLineNumber()  
-                        + ": Unexpected end of headers detected. "
-                        + "Boundary detected in header or EOF reached.");
+            if (curr == -1) {
+                monitor(Event.HEADERS_PREMATURE_END);
             }
         }
 
@@ -433,7 +463,7 @@
     public void setRaw(boolean raw) {
         this.raw = raw;
     }
-    
+
     /**
      * Finishes the parsing and stops reading lines.
      * NOTE: No more lines will be parsed but the parser
@@ -503,6 +533,73 @@
     }
 
     /**
+     * Monitors the given event.
+     * Subclasses may override to perform actions upon events.
+     * Base implementation logs at warn.
+     * @param event <code>Event</code>, not null
+     * @throws MimeException subclasses may elect to throw this exception upon
+     * invalid content
+     * @throws IOException subclasses may elect to throw this exception
+     */
+    protected void monitor(Event event) throws MimeException, IOException {
+        // TODO: consider adding a severity indication to Event
+        warn(event);
+    }
+    
+    /**
+     * Creates an indicative message suitable for display
+     * based on the given event and the current state of the system.
+     * @param event <code>Event</code>, not null
+     * @return message suitable for use as a message in an exception
+     * or for logging
+     */
+    protected String message(Event event) {
+        try {
+            final String result;
+            if (Event.HEADERS_PREMATURE_END.equals(event) && cursor != null) {
+                result = "Line " + cursor.getLineNumber()  
+                    + ": Unexpected end of headers detected. "
+                    + "Boundary detected in header or EOF reached.";
+            } else if (Event.MIME_BODY_PREMATURE_END.equals(event) && cursor != null) {
+                result = "Line " + cursor.getLineNumber() 
+                    + ": Body part ended prematurely. "
+                    + "Higher level boundary detected or "
+                    + "EOF reached.";
+            } else if (event == null) {
+                result = "Event is unexpectedly null.";
+            } else {
+                result = event.toString();
+            }
+            return result;
+        } catch (IOException e) {
+            log.debug("Cannot get line number for message", e);
+            return event.toString();
+        }
+    }
+    
+    /**
+     * Logs (at warn) an indicative message based on the given event 
+     * and the current state of the system.
+     * @param event <code>Event</code>, not null
+     */
+    protected void warn(Event event) {
+        if (log.isWarnEnabled()) {
+            log.warn(message(event));
+        }
+    }
+    
+    /**
+     * Logs (at debug) an indicative message based on the given event
+     * and the current state of the system.
+     * @param event <code>Event</code>, not null
+     */
+    protected void debug(Event event) {
+        if (log.isWarnEnabled()) {
+            log.warn(message(event));
+        }
+    }
+
+    /**
      * This method is valid, if {@link #getState()} returns either of
      * {@link #T_RAW_ENTITY}, {@link #T_PREAMBLE}, or {@link #T_EPILOGUE}.
      * It returns the raw entity, preamble, or epilogue contents.
